diff --git a/plugins.json b/plugins.json index c406b541..753a147b 100644 --- a/plugins.json +++ b/plugins.json @@ -306,11 +306,16 @@ "author": "Ewan Howell", "description": "Create Minecraft-styled title models!", "tags": ["Minecraft", "Title", "Logo"], - "version": "1.4.1", + "version": "1.5.0", "min_version": "4.8.0", "variant": "both", "creation_date": "2023-06-10", - "await_loading": true + "await_loading": true, + "contributes": { + "formats": ["minectaft_title"] + }, + "has_changelog": true, + "website": "https://ewanhowell.com/plugins/minecraft-title-generator/" }, "workspaces": { "title": "Workspaces", @@ -582,7 +587,7 @@ "version": "2.0.0", "min_version": "4.9.4", "variant": "both", - "tags": ["Format: Generic Model", "Mesh", "Tool"] + "tags": ["Format: Generic Model", "Mesh", "Tool"] }, "wasd_controls": { "title": "WASD Controls", diff --git a/plugins/minecraft_title_generator/changelog.json b/plugins/minecraft_title_generator/changelog.json new file mode 100644 index 00000000..9699206a --- /dev/null +++ b/plugins/minecraft_title_generator/changelog.json @@ -0,0 +1,23 @@ +{ + "1.5.0": { + "title": "1.5.0", + "date": "2024-05-10", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Made the position camera button a main button with a new icon", + "Render buttons now have tooltips" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed render controls disappearing for some people", + "Fixed render controls missing in Blockbench v4.10.0" + ] + } + ] + } +} \ No newline at end of file diff --git a/plugins/minecraft_title_generator/minecraft_title_generator.js b/plugins/minecraft_title_generator/minecraft_title_generator.js index 73c69e50..45c8e62b 100644 --- a/plugins/minecraft_title_generator/minecraft_title_generator.js +++ b/plugins/minecraft_title_generator/minecraft_title_generator.js @@ -48,7 +48,7 @@ rootIndex: 0 } let root = connection.roots[0] - let format, action, dialog, mode, panel, styles, preview, debug + let format, action, dialog, mode, styles, preview, debug, controls const id = "minecraft_title_generator" const name = "Minecraft Title Generator" const icon = "text_fields" @@ -96,93 +96,18 @@ author: "Ewan Howell", description, tags: ["Minecraft", "Title", "Logo"], - version: "1.4.1", + version: "1.5.0", min_version: "4.8.0", variant: "both", creation_date: "2023-06-10", await_loading: true, + contributes: { + formats: ["minectaft_title"] + }, + has_changelog: true, + website: "https://ewanhowell.com/plugins/minecraft-title-generator/", async onload() { styles = Blockbench.addCSS(` - body:not(.is_mobile) #work_screen:has(#panel_minecraft_title_render_panel:not(.hidden)) { - grid-template-columns: 0px auto 0 !important; - } - #panel_minecraft_title_render_panel > .panel_handle, #work_screen:has(#panel_minecraft_title_render_panel:not(.hidden)) > .resizer.vertical { - display: none !important; - } - #work_screen:has(#panel_minecraft_title_render_panel:not(.hidden)) > #center { - margin-bottom: calc(4px - var(--toolbar-height)); - } - #panel_minecraft_title_render_panel { - width: 0px !important; - height: 0px !important; - } - #minecraft-title-render-controls-container { - position: absolute; - left: 0; - right: 0; - bottom: 50px; - display: flex; - justify-content: center; - } - #minecraft-title-render-controls { - background-color: var(--color-ui); - z-index: 2; - padding: 10px; - box-shadow: 0 5px 10px #0006; - display: flex; - gap: 10px; - align-items: center; - flex-direction: column; - } - .minecraft-title-render-controls-row { - display: flex; - gap: 20px; - align-items: center; - } - #minecraft-title-render-button { - background-color: var(--color-close); - color: var(--color-light); - padding: 10px 30px 1px; - border-radius: 8px; - cursor: pointer; - transition: filter .15s; - } - #minecraft-title-render-button:hover { - filter: brightness(1.25) hue-rotate(5deg); - } - #minecraft-title-render-button.disabled { - background-color: var(--color-button); - cursor: not-allowed; - } - minecraft-title-render-button.disabled:hover { - filter: initial; - } - .minecraft-title-button { - cursor: pointer; - border-radius: 4px; - } - .minecraft-title-button.selected { - background-color: var(--color-accent); - color: var(--color-accent_text); - } - .minecraft-title-button.selected > svg { - fill: var(--color-accent_text); - } - #resolutions { - display: flex; - background-color: var(--color-button); - padding: 4px; - } - .resolution { - width: 32px; - height: 32px; - } - .resolution > i { - font-size: 32px; - } - .resolution > svg { - fill: var(--color-text); - } .minecraft-title-list { display: flex; max-height: 384px; @@ -403,576 +328,631 @@ Toolbars.outliner.add(action, 0) MenuBar.menus.edit.addAction(action, 4) Interface.Panels.outliner.menu.addAction(action, 0) - panel = new Panel("minecraft_title_render_panel", { - name: "Render Controls", - icon: "photo_camera", - condition: { - modes: ["minecraft_title_render"] - }, - component: { - methods: { - render() { - if (Preview.selected.camera instanceof THREE.OrthographicCamera) return Blockbench.showMessageBox({ - title: "Orthographic not supported", - message: "Orthographic perspectives are not supported for render mode.\n\nIf you wish to render with an orthographic perspective, use the built-in Blockbench screenshot options.", - buttons: ["Disable orthographic perspective", "dialog.close"], - width: 500 - }, async button => { - if (button === 0) Preview.selected.setProjectionMode(false) - }) - if (this.rendering) return - this.rendering = true - const args = this - const dialog = new Dialog({ - id: "minecraft_title_render_result", - title: "Rendering…", - buttons: [], - lines: [``], - component: { - data: { - rendering: true, - image: null, - dimensions: null, - size: null, - canvas: null, - tab: "normal", - resolutionWidth: 1920, - resolutionHeight: 1080, - aspectWidth: 16, - aspectHeight: 9, - lastChanged: "width", - linked: true, - minecraftMode: "1.20", - minecraftModes: { - "1.20": "1.20+ Title Texture", - "1.19": "1.19- Title Texture", - mojang: "Mojang Studios Texture" - }, - backgroundColour: "#78b8ff", - backgroundColour2: "#c7ecff", - backgroundColourEnabled: false, - backgroundColour2Enabled: false, - updating: false, - tabToUpdate: false, - padding: 0 + controls = document.createElement("div") + controls.id = "minecraft-title-render-controls-container" + document.getElementById("center").append(controls) + const controlsStyles = document.createElement("style") + controlsStyles.innerHTML = ` + #minecraft-title-render-controls { + position: absolute; + bottom: 50px; + left: 50%; + transform: translateX(-50%); + background-color: var(--color-ui); + flex-direction: column; + box-shadow: 0 5px 10px #0006; + align-items: center; + gap: 10px; + padding: 10px; + display: none; + z-index: 1; + } + + body[mode="minecraft_title_render"] { + #minecraft-title-render-controls { + display: flex; + } + + #center { + margin-bottom: calc(4px - var(--toolbar-height)); + } + } + + .minecraft-title-render-controls-row { + display: flex; + gap: 20px; + align-items: center; + } + + #minecraft-title-render-buttons { + display: flex; + gap: 10px; + justify-content: center; + + > div { + padding: 5px 30px; + cursor: pointer; + width: initial; + height: initial; + position: relative; + + &::before { + content: ""; + position: absolute; + inset: 0; + background-color: var(--color-error); + border-radius: 8px; + z-index: -1; + transition: filter .15s; + } + + &:first-child::before { + background-color: var(--color-accent); + } + + &:hover::before { + filter: brightness(1.25) hue-rotate(5deg); + } + + &.disabled { + cursor: not-allowed; + + &::before { + background-color: var(--color-button); + filter: initial !important; + } + } + + > i { + color: var(--color-light); + padding: 0; + } + + .tooltip { + transform: translate(calc(11px - 50%), 10px) + } + } + } + + #resolutions { + display: flex; + background-color: var(--color-button); + padding: 4px; + } + + .resolution { + width: 32px; + height: 32px; + cursor: pointer; + border-radius: 4px; + + > i { + font-size: 32px; + max-width: 32px; + } + + > svg { + fill: var(--color-text); + } + + &.selected { + background-color: var(--color-accent); + color: var(--color-accent_text); + + > svg { + fill: var(--color-accent_text); + } + } + } + ` + controls.append(controlsStyles) + const vue = document.createElement("div") + controls.append(vue) + new Vue({ + methods: { + render() { + if (Preview.selected.camera instanceof THREE.OrthographicCamera) return Blockbench.showMessageBox({ + title: "Orthographic not supported", + message: "Orthographic perspectives are not supported for render mode.\n\nIf you wish to render with an orthographic perspective, use the built-in Blockbench screenshot options.", + buttons: ["Disable orthographic perspective", "dialog.close"], + width: 500 + }, async button => { + if (button === 0) Preview.selected.setProjectionMode(false) + }) + if (this.rendering) return + this.rendering = true + const args = this + const dialog = new Dialog({ + id: "minecraft_title_render_result", + title: "Rendering…", + buttons: [], + lines: [``], + component: { + data: { + rendering: true, + image: null, + dimensions: null, + size: null, + canvas: null, + tab: "normal", + resolutionWidth: 1920, + resolutionHeight: 1080, + aspectWidth: 16, + aspectHeight: 9, + lastChanged: "width", + linked: true, + minecraftMode: "1.20", + minecraftModes: { + "1.20": "1.20+ Title Texture", + "1.19": "1.19- Title Texture", + mojang: "Mojang Studios Texture" }, - mounted() { - $(this.$refs.backgroundColour).spectrum(this.colourInput("backgroundColour")), - $(this.$refs.backgroundColour2).spectrum(this.colourInput("backgroundColour2")) + backgroundColour: "#78b8ff", + backgroundColour2: "#c7ecff", + backgroundColourEnabled: false, + backgroundColour2Enabled: false, + updating: false, + tabToUpdate: false, + padding: 0 + }, + mounted() { + $(this.$refs.backgroundColour).spectrum(this.colourInput("backgroundColour")), + $(this.$refs.backgroundColour2).spectrum(this.colourInput("backgroundColour2")) + }, + methods: { + async copy() { + const r = await fetch(this.canvas.toDataURL()) + navigator.clipboard.write([new ClipboardItem({ "image/png": await r.blob() })]) + Blockbench.showQuickMessage("Copied to clipboard") }, - methods: { - async copy() { - const r = await fetch(this.canvas.toDataURL()) - navigator.clipboard.write([new ClipboardItem({ "image/png": await r.blob() })]) - Blockbench.showQuickMessage("Copied to clipboard") - }, - save() { - Blockbench.export({ - extensions: ["png"], - type: tl("data.image"), - savetype: "image", - name: Project.name || "minecraft_title", - content: this.canvas.toDataURL() - }, () => Blockbench.showQuickMessage("Saved title")) - }, - async tabChange(tab) { - this.tab = tab - let canvas, ctx - if (tab === "normal") { - ({ canvas, ctx } = new CanvasFrame(this.image.width, this.image.height)) - ctx.drawImage(this.image, 0, 0) - } else if (tab === "square") { - const max = Math.max(this.image.width, this.image.height); - ({ canvas, ctx } = new CanvasFrame(max, max)) - ctx.drawImage(this.image, Math.floor(max / 2 - this.image.width / 2), Math.floor(max / 2 - this.image.height / 2)) - } else if (tab === "custom") { - ({ canvas, ctx } = new CanvasFrame(this.resolutionWidth, this.resolutionHeight)) - const aspect = this.image.width / this.image.height - const ratio = Math.min(canvas.width / this.image.width, canvas.height / this.image.height) - const w = Math.floor(this.image.width * ratio) - const h = Math.floor(this.image.height * ratio) - ctx.drawImage(this.image, Math.floor(canvas.width / 2 - w / 2), Math.floor(canvas.height / 2 - h / 2), w, h) - } else if (tab === "minecraft") { - const aspect = this.image.width / this.image.height - let w, h - if (this.image.width > 1024 || this.image.height > 1024) { - if (aspect > 1) { - w = 1024 - h = Math.floor(1024 / aspect) - } else { - h = 1024 - w = Math.floor(1024 * aspect) - } + save() { + Blockbench.export({ + extensions: ["png"], + type: tl("data.image"), + savetype: "image", + name: Project.name || "minecraft_title", + content: this.canvas.toDataURL() + }, () => Blockbench.showQuickMessage("Saved title")) + }, + async tabChange(tab) { + this.tab = tab + let canvas, ctx + if (tab === "normal") { + ({ canvas, ctx } = new CanvasFrame(this.image.width, this.image.height)) + ctx.drawImage(this.image, 0, 0) + } else if (tab === "square") { + const max = Math.max(this.image.width, this.image.height); + ({ canvas, ctx } = new CanvasFrame(max, max)) + ctx.drawImage(this.image, Math.floor(max / 2 - this.image.width / 2), Math.floor(max / 2 - this.image.height / 2)) + } else if (tab === "custom") { + ({ canvas, ctx } = new CanvasFrame(this.resolutionWidth, this.resolutionHeight)) + const aspect = this.image.width / this.image.height + const ratio = Math.min(canvas.width / this.image.width, canvas.height / this.image.height) + const w = Math.floor(this.image.width * ratio) + const h = Math.floor(this.image.height * ratio) + ctx.drawImage(this.image, Math.floor(canvas.width / 2 - w / 2), Math.floor(canvas.height / 2 - h / 2), w, h) + } else if (tab === "minecraft") { + const aspect = this.image.width / this.image.height + let w, h + if (this.image.width > 1024 || this.image.height > 1024) { + if (aspect > 1) { + w = 1024 + h = Math.floor(1024 / aspect) } else { - w = this.image.width - h = this.image.height - } - const capped = new CanvasFrame(w, h) - if (this.image.width < 64 || this.image.height < 64) capped.ctx.imageSmoothingEnabled = false - capped.ctx.drawImage(this.image, 0, 0, w, h) - capped.autoCrop() - if (this.minecraftMode === "1.20") { - ({ canvas, ctx } = new CanvasFrame(1024, 256)) - const scaleFactor = Math.min(1024 / capped.width, 176 / capped.height) - const newWidth = capped.width * scaleFactor - const newHeight = capped.height * scaleFactor - const x = (1024 - newWidth) / 2 - const y = (176 - newHeight) / 2 - if (newWidth > capped.width) ctx.imageSmoothingEnabled = false - ctx.drawImage(capped.canvas, x, y, newWidth, newHeight) - } else if (this.minecraftMode === "1.19") { - const base = await loadImage("data:image/png;base64,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") - let preCanvas - if (capped.width / capped.height < 137 / 22) { - preCanvas = new CanvasFrame(Math.floor((capped.height / 22) * 137), capped.height) - preCanvas.ctx.drawImage(capped.canvas, Math.floor((preCanvas.width - capped.width) / 2), 0) - } else if (capped.width / capped.height > 137 / 22) { - preCanvas = new CanvasFrame(capped.width, Math.floor((capped.width / 137) * 22)) - preCanvas.ctx.drawImage(capped.canvas, 0, Math.floor((preCanvas.height - capped.height) / 2)) - } else preCanvas = capped; - ({ canvas, ctx } = new CanvasFrame(Math.floor((preCanvas.width / 137) * 128), Math.floor((preCanvas.width / 137) * 128), true, 1024)) - ctx.imageSmoothingEnabled = false - ctx.drawImage(base, 0, 0, canvas.width, canvas.height) - const width = Math.floor((preCanvas.width / 274) * 155) - ctx.drawImage(preCanvas.canvas, 0, 0, width, preCanvas.height, 0, 0, width, preCanvas.height) - ctx.drawImage(preCanvas.canvas, width, 0, preCanvas.width - width, preCanvas.height, 0, Math.floor(preCanvas.height / 44 * 45), preCanvas.width - width, preCanvas.height) - } else if (this.minecraftMode === "mojang") { - let preCanvas - if (capped.width < capped.height * 4) { - preCanvas = new CanvasFrame(capped.height * 4 + 8, capped.height + 2) - preCanvas.ctx.drawImage(capped.canvas, Math.floor((capped.height * 4 - capped.width) / 2) + 4, 1) - } else if (capped.width > capped.height * 4) { - preCanvas = new CanvasFrame(capped.width + 8, Math.floor(capped.width / 4) + 2) - preCanvas.ctx.drawImage(capped.canvas, 4, Math.floor((preCanvas.height - capped.height) / 2)) - } else { - preCanvas = new CanvasFrame(capped.width + 8, capped.height + 4) - preCanvas.ctx.drawImage(capped.canvas, 4, 1) - } - ({ canvas, ctx } = new CanvasFrame(Math.floor(preCanvas.width / 2), Math.floor(preCanvas.width / 2))) - ctx.drawImage(preCanvas.canvas, 0, 0) - ctx.drawImage(preCanvas.canvas, Math.floor(-preCanvas.width / 2), Math.floor(preCanvas.width / 4)) + h = 1024 + w = Math.floor(1024 * aspect) } + } else { + w = this.image.width + h = this.image.height } - const padding = tab === "minecraft" ? 0 : this.padding - this.canvas.width = canvas.width + padding * 2 - this.canvas.height = canvas.height + padding * 2 - if (this.tab !== "minecraft" && this.backgroundColourEnabled) { - if (this.backgroundColour2Enabled) { - const gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height) - gradient.addColorStop(0, this.backgroundColour) - gradient.addColorStop(1, this.backgroundColour2) - this.ctx.fillStyle = gradient + const capped = new CanvasFrame(w, h) + if (this.image.width < 64 || this.image.height < 64) capped.ctx.imageSmoothingEnabled = false + capped.ctx.drawImage(this.image, 0, 0, w, h) + capped.autoCrop() + if (this.minecraftMode === "1.20") { + ({ canvas, ctx } = new CanvasFrame(1024, 256)) + const scaleFactor = Math.min(1024 / capped.width, 176 / capped.height) + const newWidth = capped.width * scaleFactor + const newHeight = capped.height * scaleFactor + const x = (1024 - newWidth) / 2 + const y = (176 - newHeight) / 2 + if (newWidth > capped.width) ctx.imageSmoothingEnabled = false + ctx.drawImage(capped.canvas, x, y, newWidth, newHeight) + } else if (this.minecraftMode === "1.19") { + const base = await loadImage("data:image/png;base64,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") + let preCanvas + if (capped.width / capped.height < 137 / 22) { + preCanvas = new CanvasFrame(Math.floor((capped.height / 22) * 137), capped.height) + preCanvas.ctx.drawImage(capped.canvas, Math.floor((preCanvas.width - capped.width) / 2), 0) + } else if (capped.width / capped.height > 137 / 22) { + preCanvas = new CanvasFrame(capped.width, Math.floor((capped.width / 137) * 22)) + preCanvas.ctx.drawImage(capped.canvas, 0, Math.floor((preCanvas.height - capped.height) / 2)) + } else preCanvas = capped; + ({ canvas, ctx } = new CanvasFrame(Math.floor((preCanvas.width / 137) * 128), Math.floor((preCanvas.width / 137) * 128), true, 1024)) + ctx.imageSmoothingEnabled = false + ctx.drawImage(base, 0, 0, canvas.width, canvas.height) + const width = Math.floor((preCanvas.width / 274) * 155) + ctx.drawImage(preCanvas.canvas, 0, 0, width, preCanvas.height, 0, 0, width, preCanvas.height) + ctx.drawImage(preCanvas.canvas, width, 0, preCanvas.width - width, preCanvas.height, 0, Math.floor(preCanvas.height / 44 * 45), preCanvas.width - width, preCanvas.height) + } else if (this.minecraftMode === "mojang") { + let preCanvas + if (capped.width < capped.height * 4) { + preCanvas = new CanvasFrame(capped.height * 4 + 8, capped.height + 2) + preCanvas.ctx.drawImage(capped.canvas, Math.floor((capped.height * 4 - capped.width) / 2) + 4, 1) + } else if (capped.width > capped.height * 4) { + preCanvas = new CanvasFrame(capped.width + 8, Math.floor(capped.width / 4) + 2) + preCanvas.ctx.drawImage(capped.canvas, 4, Math.floor((preCanvas.height - capped.height) / 2)) } else { - this.ctx.fillStyle = this.backgroundColour + preCanvas = new CanvasFrame(capped.width + 8, capped.height + 4) + preCanvas.ctx.drawImage(capped.canvas, 4, 1) } - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) + ({ canvas, ctx } = new CanvasFrame(Math.floor(preCanvas.width / 2), Math.floor(preCanvas.width / 2))) + ctx.drawImage(preCanvas.canvas, 0, 0) + ctx.drawImage(preCanvas.canvas, Math.floor(-preCanvas.width / 2), Math.floor(preCanvas.width / 4)) } - this.ctx.drawImage(canvas, padding, padding) - const size = this.canvas.toDataURL().slice(22).length * 0.75 - this.dimensions = `${this.canvas.width} x ${this.canvas.height}` - this.size = `${size > 1048576 ? `${Math.roundTo(size / 1048576, 2)} MB` : `${Math.round(size / 1024)} KB`}` - }, - changeResolution(changed) { - this.lastChanged = changed - if (this.linked) { - if (changed === "width") { - this.resolutionHeight = Math.max(1, Math.floor((this.resolutionWidth * this.aspectHeight) / this.aspectWidth)) - } else { - this.resolutionWidth = Math.max(1, Math.floor((this.resolutionHeight * this.aspectWidth) / this.aspectHeight)) - } - if (this.resolutionWidth > 4096 || this.resolutionHeight > 4096) { - const aspect = this.resolutionWidth / this.resolutionHeight - if (aspect > 1) { - this.resolutionWidth = 4096 - this.resolutionHeight = Math.floor(4096 / aspect) - } else { - this.resolutionHeight = 4096 - this.resolutionWidth = Math.floor(4096 * aspect) - } - } + } + const padding = tab === "minecraft" ? 0 : this.padding + this.canvas.width = canvas.width + padding * 2 + this.canvas.height = canvas.height + padding * 2 + if (this.tab !== "minecraft" && this.backgroundColourEnabled) { + if (this.backgroundColour2Enabled) { + const gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height) + gradient.addColorStop(0, this.backgroundColour) + gradient.addColorStop(1, this.backgroundColour2) + this.ctx.fillStyle = gradient } else { - this.resolutionWidth = Math.max(1, parseInt(this.resolutionWidth)) - this.resolutionHeight = Math.max(1, parseInt(this.resolutionHeight)) - const [aW, aH] = getAspectRatio(this.resolutionWidth, this.resolutionHeight) - this.aspectWidth = aW - this.aspectHeight = aH + this.ctx.fillStyle = this.backgroundColour } - this.update("custom") - }, - changeAspect() { - this.aspectWidth = Math.max(1, parseInt(this.aspectWidth)) - this.aspectHeight = Math.max(1, parseInt(this.aspectHeight)) - const [w, h] = getFromAspect(this.aspectWidth, this.aspectHeight, this.resolutionWidth, this.resolutionHeight, this.lastChanged === "width") - this.resolutionWidth = w - this.resolutionHeight = h - this.update("custom") - }, - colourInput: v => ({ - preferredFormat: "hex", - color: dialog.component.data[v], - showAlpha: false, - showInput: true, - move: c => dialog.content_vue.updateColour(v, c), - change: c => dialog.content_vue.updateColour(v, c), - hide: c => dialog.content_vue.updateColour(v, c) - }), - updateColour(v, c) { - this[v] = c.toHexString() - this.update(this.tab) - }, - async update(tab) { - if (this.updating) { - this.tabToUpdate = tab - return + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) + } + this.ctx.drawImage(canvas, padding, padding) + const size = this.canvas.toDataURL().slice(22).length * 0.75 + this.dimensions = `${this.canvas.width} x ${this.canvas.height}` + this.size = `${size > 1048576 ? `${Math.roundTo(size / 1048576, 2)} MB` : `${Math.round(size / 1024)} KB`}` + }, + changeResolution(changed) { + this.lastChanged = changed + if (this.linked) { + if (changed === "width") { + this.resolutionHeight = Math.max(1, Math.floor((this.resolutionWidth * this.aspectHeight) / this.aspectWidth)) + } else { + this.resolutionWidth = Math.max(1, Math.floor((this.resolutionHeight * this.aspectWidth) / this.aspectHeight)) } - this.updating = true - await this.tabChange(tab) - this.updating = false - if (this.tabToUpdate) { - this.update(this.tabToUpdate) - this.tabToUpdate = false + if (this.resolutionWidth > 4096 || this.resolutionHeight > 4096) { + const aspect = this.resolutionWidth / this.resolutionHeight + if (aspect > 1) { + this.resolutionWidth = 4096 + this.resolutionHeight = Math.floor(4096 / aspect) + } else { + this.resolutionHeight = 4096 + this.resolutionWidth = Math.floor(4096 * aspect) + } } + } else { + this.resolutionWidth = Math.max(1, parseInt(this.resolutionWidth)) + this.resolutionHeight = Math.max(1, parseInt(this.resolutionHeight)) + const [aW, aH] = getAspectRatio(this.resolutionWidth, this.resolutionHeight) + this.aspectWidth = aW + this.aspectHeight = aH } + this.update("custom") }, - template: ` -
-

Rendering…

-
- -
-
-
Resolution:
- -
- -
-
Link width and height
- -
-
-
-
Aspect Ratio:
- -
:
- + changeAspect() { + this.aspectWidth = Math.max(1, parseInt(this.aspectWidth)) + this.aspectHeight = Math.max(1, parseInt(this.aspectHeight)) + const [w, h] = getFromAspect(this.aspectWidth, this.aspectHeight, this.resolutionWidth, this.resolutionHeight, this.lastChanged === "width") + this.resolutionWidth = w + this.resolutionHeight = h + this.update("custom") + }, + colourInput: v => ({ + preferredFormat: "hex", + color: dialog.component.data[v], + showAlpha: false, + showInput: true, + move: c => dialog.content_vue.updateColour(v, c), + change: c => dialog.content_vue.updateColour(v, c), + hide: c => dialog.content_vue.updateColour(v, c) + }), + updateColour(v, c) { + this[v] = c.toHexString() + this.update(this.tab) + }, + async update(tab) { + if (this.updating) { + this.tabToUpdate = tab + return + } + this.updating = true + await this.tabChange(tab) + this.updating = false + if (this.tabToUpdate) { + this.update(this.tabToUpdate) + this.tabToUpdate = false + } + } + }, + template: ` +
+

Rendering…

+
+
    +
  • Normal
  • +
  • Square
  • +
  • Custom
  • +
  • Minecraft
  • +
+
+
+
Resolution:
+ +
+ +
+
Link width and height
+
-
-
-
Background Colour:
- -
- -
-
+
+
Aspect Ratio:
+ +
:
+
-
-
-
Second Background Colour:
- -
- -
+
+
+
+
Background Colour:
+ +
+
-
-
-
Padding:
- +
+
+
+
Second Background Colour:
+ +
+
-
-
Texture mode:
- -
-
- -
-
-
{{ dimensions }}
-
{{ size }}
-
-
- - +
+
+
+
Padding:
+
+
+
Texture mode:
+ +
+
+ +
+
+
{{ dimensions }}
+
{{ size }}
+
+
+ + +
- ` - }, - onOpen() { - setTimeout(() => Canvas.withoutGizmos(() => { - let minX = Infinity - let maxX = -Infinity - let minY = Infinity - let maxY = -Infinity - const direction = Preview.selected.controls.target.clone().sub(Preview.selected.camera.position).normalize() - Canvas.scene.traverseVisible(cube => { - if (cube.type === "cube") { - for (let i = 0; i < 72; i += 3) { - const vertex = new THREE.Vector3(...cube.geometry.attributes.position.array.slice(i, i + 3)) - if (direction.dot(vertex.clone().sub(Preview.selected.camera.position).normalize()) <= 0) continue - const vec = vertex.applyMatrix4(cube.matrixWorld).project(Preview.selected.camera) - const x = (vec.x + 1) / 2 - const y = (-vec.y + 1) / 2 - minX = Math.min(minX, x) - maxX = Math.max(maxX, x) - minY = Math.min(minY, y) - maxY = Math.max(maxY, y) - } +
+ ` + }, + onOpen() { + setTimeout(() => Canvas.withoutGizmos(() => { + let minX = Infinity + let maxX = -Infinity + let minY = Infinity + let maxY = -Infinity + const direction = Preview.selected.controls.target.clone().sub(Preview.selected.camera.position).normalize() + Canvas.scene.traverseVisible(cube => { + if (cube.type === "cube") { + for (let i = 0; i < 72; i += 3) { + const vertex = new THREE.Vector3(...cube.geometry.attributes.position.array.slice(i, i + 3)) + if (direction.dot(vertex.clone().sub(Preview.selected.camera.position).normalize()) <= 0) continue + const vec = vertex.applyMatrix4(cube.matrixWorld).project(Preview.selected.camera) + const x = (vec.x + 1) / 2 + const y = (-vec.y + 1) / 2 + minX = Math.min(minX, x) + maxX = Math.max(maxX, x) + minY = Math.min(minY, y) + maxY = Math.max(maxY, y) } - }) - if (minX === Infinity) { - dialog.close() - Blockbench.showQuickMessage("Nothing in frame") - args.rendering = false - return - } - minX = Math.max(minX, 0) - maxX = Math.min(maxX, 1) - minY = Math.max(minY, 0) - maxY = Math.min(maxY, 1) - if (minX === maxX || minY === maxY) { - dialog.close() - Blockbench.showQuickMessage("Nothing in frame") - args.rendering = false - return - } - const aspect = (maxX - minX) / ((maxY - minY) * Preview.selected.height / Preview.selected.width) - let outWidth, outHeight - const resolution = args.antialias ? Math.min(args.resolution * 2, 4096) : args.resolution - if (aspect > 1) { - outWidth = resolution - outHeight = resolution / aspect - } else { - outWidth = resolution * aspect - outHeight = resolution } - preview.resize(outWidth, outHeight) - preview.controls.target.copy(Preview.selected.controls.target) - preview.camera.position.copy(Preview.selected.camera.position) - const fullWidth = outWidth / (maxX - minX) - const fullHeight = outHeight / (maxY - minY) - preview.camera.setViewOffset(fullWidth, fullHeight, minX * fullWidth, minY * fullHeight, outWidth, outHeight) - preview.render() - const img = new CanvasFrame(preview.canvas) - img.autoCrop() - const imageData = img.ctx.getImageData(0, 0, img.width, img.height) - const data = imageData.data - const length = data.length - const width = img.width - const height = img.height - const width1 = width - 1 - const height1 = height - 1 - for (let i = length - 4; i >= 0; i -= 4) { - const x = i / 4 % img.width - const y = Math.floor(i / (4 * height)) - if (data[i + 3] === 0) { - if ( - (x === 0 || data[i - 1] !== 0) && - (x === width1 || data[i + 7] !== 0) && - (y === 0 || data[i - width * 4 + 3] !== 0) && - (y === height1 || data[i + width * 4 + 3] !== 0) - ) { - let count = 0 - let sr, sg, sb, sa - sr = sg = sb = sa = 0 - if (x !== 0) { - count++ - sr += data[i - 4] - sg += data[i - 3] - sb += data[i - 2] - sa += data[i - 1] - } - if (x !== width1) { - count++ - sr += data[i + 4] - sg += data[i + 5] - sb += data[i + 6] - sa += data[i + 7] - } - if (y !== 0) { - count++ - sr += data[i - width * 4] - sg += data[i - width * 4 + 1] - sb += data[i - width * 4 + 2] - sa += data[i - width * 4 + 3] - } - if (y !== height1) { - count++ - sr += data[i + width * 4] - sg += data[i + width * 4 + 1] - sb += data[i + width * 4 + 2] - sa += data[i + width * 4 + 3] - } - data[i] = sr / count - data[i + 1] = sg / count - data[i + 2] = sb / count - data[i + 3] = sa / count - } else { - const store = [] - const queue = [i] - while (store.length < 6 && queue.length !== 0) { - const j = queue.shift() - store.push(j) - const x = j / 4 % img.width - if (x !== 0 && data[j - 1] === 0 && !store.includes(j - 4)) queue.push(j - 4) - if (x !== width1 && data[j + 7] === 0 && !store.includes(j + 4)) queue.push(j + 4) - if (Math.floor(j / (4 * height)) !== 0 && data[j - width * 4 + 3] === 0 && !store.includes(j - width * 4)) queue.push(j - width * 4) - } - if (store.length >= 6) continue - for (const j of store) { - const x = j / 4 % img.width - const y = Math.floor(j / (4 * height)) - let count = 0 - let sr, sg, sb, sa - sr = sg = sb = sa = 0 - if (x !== 0 && data[j - 1] !== 0) { - count++ - sr += data[j - 4] - sg += data[j - 3] - sb += data[j - 2] - sa += data[j - 1] - } - if (x !== width1 && data[j + 7] !== 0) { - count++ - sr += data[j + 4] - sg += data[j + 5] - sb += data[j + 6] - sa += data[j + 7] - } - if (y !== 0 && data[j - width * 4 + 3] !== 0) { - count++ - sr += data[j - width * 4] - sg += data[j - width * 4 + 1] - sb += data[j - width * 4 + 2] - sa += data[j - width * 4 + 3] - } - if (y !== height1 && data[j + width * 4 + 3] !== 0) { - count++ - sr += data[j + width * 4] - sg += data[j + width * 4 + 1] - sb += data[j + width * 4 + 2] - sa += data[j + width * 4 + 3] - } - data[j] = sr / count - data[j + 1] = sg / count - data[j + 2] = sb / count - data[j + 3] = sa / count - } - } - } else if ( - (x === 0 || data[i - 1] === 0) && - (x === width1 || data[i + 7] === 0) && - (y === 0 || data[i - width * 4 + 3] === 0) && - (y === height1 || data[i + width * 4 + 3] === 0) + }) + if (minX === Infinity) { + dialog.close() + Blockbench.showQuickMessage("Nothing in frame") + args.rendering = false + return + } + minX = Math.max(minX, 0) + maxX = Math.min(maxX, 1) + minY = Math.max(minY, 0) + maxY = Math.min(maxY, 1) + if (minX === maxX || minY === maxY) { + dialog.close() + Blockbench.showQuickMessage("Nothing in frame") + args.rendering = false + return + } + const aspect = (maxX - minX) / ((maxY - minY) * Preview.selected.height / Preview.selected.width) + let outWidth, outHeight + const resolution = args.antialias ? Math.min(args.resolution * 2, 4096) : args.resolution + if (aspect > 1) { + outWidth = resolution + outHeight = resolution / aspect + } else { + outWidth = resolution * aspect + outHeight = resolution + } + preview.resize(outWidth, outHeight) + preview.controls.target.copy(Preview.selected.controls.target) + preview.camera.position.copy(Preview.selected.camera.position) + const fullWidth = outWidth / (maxX - minX) + const fullHeight = outHeight / (maxY - minY) + preview.camera.setViewOffset(fullWidth, fullHeight, minX * fullWidth, minY * fullHeight, outWidth, outHeight) + preview.render() + const img = new CanvasFrame(preview.canvas) + img.autoCrop() + const imageData = img.ctx.getImageData(0, 0, img.width, img.height) + const data = imageData.data + const length = data.length + const width = img.width + const height = img.height + const width1 = width - 1 + const height1 = height - 1 + for (let i = length - 4; i >= 0; i -= 4) { + const x = i / 4 % img.width + const y = Math.floor(i / (4 * height)) + if (data[i + 3] === 0) { + if ( + (x === 0 || data[i - 1] !== 0) && + (x === width1 || data[i + 7] !== 0) && + (y === 0 || data[i - width * 4 + 3] !== 0) && + (y === height1 || data[i + width * 4 + 3] !== 0) ) { - data[i + 3] = 0 + let count = 0 + let sr, sg, sb, sa + sr = sg = sb = sa = 0 + if (x !== 0) { + count++ + sr += data[i - 4] + sg += data[i - 3] + sb += data[i - 2] + sa += data[i - 1] + } + if (x !== width1) { + count++ + sr += data[i + 4] + sg += data[i + 5] + sb += data[i + 6] + sa += data[i + 7] + } + if (y !== 0) { + count++ + sr += data[i - width * 4] + sg += data[i - width * 4 + 1] + sb += data[i - width * 4 + 2] + sa += data[i - width * 4 + 3] + } + if (y !== height1) { + count++ + sr += data[i + width * 4] + sg += data[i + width * 4 + 1] + sb += data[i + width * 4 + 2] + sa += data[i + width * 4 + 3] + } + data[i] = sr / count + data[i + 1] = sg / count + data[i + 2] = sb / count + data[i + 3] = sa / count } else { const store = [] const queue = [i] @@ -980,119 +960,181 @@ const j = queue.shift() store.push(j) const x = j / 4 % img.width - if (x !== 0 && data[j - 1] !== 0 && !store.includes(j - 4)) queue.push(j - 4) - if (x !== width1 && data[j + 7] !== 0 && !store.includes(j + 4)) queue.push(j + 4) - if (Math.floor(j / (4 * height)) !== 0 && data[j - width * 4 + 3] !== 0 && !store.includes(j - width * 4)) queue.push(j - width * 4) + if (x !== 0 && data[j - 1] === 0 && !store.includes(j - 4)) queue.push(j - 4) + if (x !== width1 && data[j + 7] === 0 && !store.includes(j + 4)) queue.push(j + 4) + if (Math.floor(j / (4 * height)) !== 0 && data[j - width * 4 + 3] === 0 && !store.includes(j - width * 4)) queue.push(j - width * 4) } if (store.length >= 6) continue for (const j of store) { - data[j + 3] = 0 + const x = j / 4 % img.width + const y = Math.floor(j / (4 * height)) + let count = 0 + let sr, sg, sb, sa + sr = sg = sb = sa = 0 + if (x !== 0 && data[j - 1] !== 0) { + count++ + sr += data[j - 4] + sg += data[j - 3] + sb += data[j - 2] + sa += data[j - 1] + } + if (x !== width1 && data[j + 7] !== 0) { + count++ + sr += data[j + 4] + sg += data[j + 5] + sb += data[j + 6] + sa += data[j + 7] + } + if (y !== 0 && data[j - width * 4 + 3] !== 0) { + count++ + sr += data[j - width * 4] + sg += data[j - width * 4 + 1] + sb += data[j - width * 4 + 2] + sa += data[j - width * 4 + 3] + } + if (y !== height1 && data[j + width * 4 + 3] !== 0) { + count++ + sr += data[j + width * 4] + sg += data[j + width * 4 + 1] + sb += data[j + width * 4 + 2] + sa += data[j + width * 4 + 3] + } + data[j] = sr / count + data[j + 1] = sg / count + data[j + 2] = sb / count + data[j + 3] = sa / count } - } - } - img.ctx.putImageData(imageData, 0, 0) - img.autoCrop() - let out - if (args.antialias) { - const canvas = new CanvasFrame(img.width, img.height) - canvas.ctx.filter = "blur(0.75px)" - canvas.ctx.drawImage(img.canvas, 0, 0) - out = new CanvasFrame(Math.floor(img.width / 2), Math.floor(img.height / 2)) - out.ctx.drawImage(canvas.canvas, 0, 0, out.width, out.height) + } + } else if ( + (x === 0 || data[i - 1] === 0) && + (x === width1 || data[i + 7] === 0) && + (y === 0 || data[i - width * 4 + 3] === 0) && + (y === height1 || data[i + width * 4 + 3] === 0) + ) { + data[i + 3] = 0 } else { - out = img + const store = [] + const queue = [i] + while (store.length < 6 && queue.length !== 0) { + const j = queue.shift() + store.push(j) + const x = j / 4 % img.width + if (x !== 0 && data[j - 1] !== 0 && !store.includes(j - 4)) queue.push(j - 4) + if (x !== width1 && data[j + 7] !== 0 && !store.includes(j + 4)) queue.push(j + 4) + if (Math.floor(j / (4 * height)) !== 0 && data[j - width * 4 + 3] !== 0 && !store.includes(j - width * 4)) queue.push(j - width * 4) + } + if (store.length >= 6) continue + for (const j of store) { + data[j + 3] = 0 + } } - args.rendering = false - this.content_vue.rendering = false - this.content_vue.image = out.canvas - const size = out.canvas.toDataURL().slice(22).length * 0.75 - this.content_vue.dimensions = `${out.canvas.width} x ${out.canvas.height}` - this.content_vue.size = `${size > 1048576 ? `${Math.roundTo(size / 1048576, 2)} MB` : `${Math.round(size / 1024)} KB`}` - dialog.object.style.width = `${Math.max(450, out.canvas.width)}px` - dialog.object.querySelector(".dialog_title").textContent = "Minecraft Title Render" - dialog.object.style.left = `${Math.clamp((window.innerWidth - dialog.object.clientWidth) / 2, 0, 2000)}px` - dialog.object.addEventListener("keydown", e => { - if (e.key === "Enter") return e.stopPropagation() - }) - setTimeout(() => { - this.content_vue.canvas = dialog.object.querySelector("#minecraft-title-output") - this.content_vue.canvas.width = this.content_vue.image.width - this.content_vue.canvas.height = this.content_vue.image.height - this.content_vue.ctx = this.content_vue.canvas.getContext("2d") - this.content_vue.ctx.drawImage(this.content_vue.image, 0, 0) - }, 0) - }), 10) - } - }) - dialog.show() - }, - position: loadRenderAngle, - custom() { - new Dialog({ - id: "custom_resolution", - title: "Custom Resolution", - form: { - resolution: { - label: "Resolution", - type: "number", - value: this.resolution, - min: 1, - max: 4096 - }, - info: { - type: "info", - text: "The resolution determines the width or height of the render, depending on which one is larger. The chosen resolution may not exactly match the render size" - }, - info2: { - type: "info", - text: "If antialiasing is enabled, the render size is capped at 2048 pixels" } - }, - onConfirm: result => this.resolution = this.antialias ? Math.min(result.resolution, 2048) : result.resolution - }).show() - } - }, - data: { - resolution: 1024, - antialias: true, - rendering: false + img.ctx.putImageData(imageData, 0, 0) + img.autoCrop() + let out + if (args.antialias) { + const canvas = new CanvasFrame(img.width, img.height) + canvas.ctx.filter = "blur(0.75px)" + canvas.ctx.drawImage(img.canvas, 0, 0) + out = new CanvasFrame(Math.floor(img.width / 2), Math.floor(img.height / 2)) + out.ctx.drawImage(canvas.canvas, 0, 0, out.width, out.height) + } else { + out = img + } + args.rendering = false + this.content_vue.rendering = false + this.content_vue.image = out.canvas + const size = out.canvas.toDataURL().slice(22).length * 0.75 + this.content_vue.dimensions = `${out.canvas.width} x ${out.canvas.height}` + this.content_vue.size = `${size > 1048576 ? `${Math.roundTo(size / 1048576, 2)} MB` : `${Math.round(size / 1024)} KB`}` + dialog.object.style.width = `${Math.max(450, out.canvas.width)}px` + dialog.object.querySelector(".dialog_title").textContent = "Minecraft Title Render" + dialog.object.style.left = `${Math.clamp((window.innerWidth - dialog.object.clientWidth) / 2, 0, 2000)}px` + dialog.object.addEventListener("keydown", e => { + if (e.key === "Enter") return e.stopPropagation() + }) + setTimeout(() => { + this.content_vue.canvas = dialog.object.querySelector("#minecraft-title-output") + this.content_vue.canvas.width = this.content_vue.image.width + this.content_vue.canvas.height = this.content_vue.image.height + this.content_vue.ctx = this.content_vue.canvas.getContext("2d") + this.content_vue.ctx.drawImage(this.content_vue.image, 0, 0) + }, 0) + }), 10) + } + }) + dialog.show() }, - template: ` -
-
-
- photo_camera + position: loadRenderAngle, + custom() { + new Dialog({ + id: "custom_resolution", + title: "Custom Resolution", + form: { + resolution: { + label: "Resolution", + type: "number", + value: this.resolution, + min: 1, + max: 4096 + }, + info: { + type: "info", + text: "The resolution determines the width or height of the render, depending on which one is larger. The chosen resolution may not exactly match the render size" + }, + info2: { + type: "info", + text: "If antialiasing is enabled, the render size is capped at 2048 pixels" + } + }, + onConfirm: result => this.resolution = this.antialias ? Math.min(result.resolution, 2048) : result.resolution + }).show() + } + }, + data: { + resolution: 1024, + antialias: true, + rendering: false + }, + template: ` +
+
+
+
Position camera
Position the camera at the correct angle for rendering a Minecraft Title
+ text_rotation_angleup +
+
+
Render Minecraft Title
Render the Minecraft Title as an image
+ photo_camera +
+
+
+
+
+
-
- auto_mode -
-
- sd -
-
- hd -
-
- -
-
- 2k -
-
- 4k -
-
- tag -
-
-
Antialiasing:
- +
+ +
+
+ 1k +
+
+ 2k +
+
+ 4k +
+
+ tag
+
Antialiasing:
+
- ` - } - }) +
+ ` + }).$mount(vue) preview = new Preview({ id: "minecraft_title_preview", antialias: false, @@ -3083,12 +3125,12 @@ format.delete() action.delete() mode.delete() - panel.delete() styles.delete() preview.delete() debug.delete() dialog.close() debugDialog.close() + controls.remove() } })