From b09a14c7eb50d0fa91a68d95bbf75c82e2e9d4fc Mon Sep 17 00:00:00 2001 From: Enrique Calot Date: Wed, 8 Apr 2026 13:30:29 +1000 Subject: [PATCH] Remove resize dither buttons and show frontend palette grid --- js/resize_dither.js | 196 +++++++++++++------------------------------- 1 file changed, 57 insertions(+), 139 deletions(-) diff --git a/js/resize_dither.js b/js/resize_dither.js index bd69b13..f98a426 100644 --- a/js/resize_dither.js +++ b/js/resize_dither.js @@ -3,7 +3,7 @@ * * Responsibilities: * - Render before/after previews directly inside the node. - * - Provide clickable controls for dither mode and output sizing. + * - Render a palette swatch grid from the frontend-computed active palette. * - Compute local preview with the same nearest + dither logic as backend. * * Event contract: @@ -17,19 +17,19 @@ const NODE_HEADER_HEIGHT = 28; const NODE_PANEL_HEIGHT = 130; const NODE_PADDING = 6; const PREVIEW_GAP = 6; -const BUTTON_GAP = 6; -const BUTTON_ROW_HEIGHT = 24; const MAX_DIMENSION = 8192; const NODE_WIDGET_ROW_HEIGHT = 24; const NODE_MIN_TOP_RESERVED = 74; +const PALETTE_GRID_GAP = 1; +const PALETTE_GRID_TOP_INSET = 20; +const PALETTE_GRID_BOTTOM_INSET = 4; +const PALETTE_GRID_MAX_COLUMNS = 32; const TEXT_COLOR_DEFAULT = "#e6e6e6"; const BG_PANEL_COLOR = "#2d2d2d"; const BG_CONTROL_COLOR = "#2a2a2a"; -const BUTTON_BG_COLOR = "#3d3d3d"; -const BUTTON_ACTIVE_BG_COLOR = "#8e5321"; -const BUTTON_BORDER_COLOR = "#9a9a9a"; const PREVIEW_PLACEHOLDER_TEXT_COLOR = "#ebebeb"; +const PALETTE_SWATCH_BORDER_COLOR = "rgba(0,0,0,0.35)"; const DITHER_MODES = ["positional", "floyd"]; const PALETTE_MODES = ["vga_256", "optimized_x_colors"]; @@ -122,9 +122,6 @@ const normalizePalette = (rawPalette) => { return null; }; -const isPointInRect = (x, y, rect) => - !!rect && x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; - app.registerExtension({ name: "resize and dither", @@ -145,7 +142,6 @@ app.registerExtension({ localPreviewCanvas: null, localPreviewCtx: null, hasUserResizedNode: false, - controlLayout: null, vgaPalette: [...FALLBACK_PALETTE], paletteFloat: null, paletteInt: null, @@ -429,56 +425,61 @@ app.registerExtension({ return area; }, - buildButtonRow: function (area, y, labels, ids) { - const width = (area.width - BUTTON_GAP * (labels.length - 1)) / labels.length; - return labels.map((label, i) => ({ - id: ids[i], - label, - x: area.x + i * (width + BUTTON_GAP), - y, - width, - height: BUTTON_ROW_HEIGHT, - })); - }, - - updateControlLayout: function () { - const controlsArea = this.getControlsArea(); - if (!controlsArea) { - this.controlLayout = null; + drawPaletteGrid: function (ctx, controlsArea) { + const palette = this.paletteInt?.length ? this.paletteInt : FALLBACK_PALETTE; + const colorCount = palette.length; + if (colorCount === 0) { return; } - const row1Y = controlsArea.y; - const row2Y = row1Y + BUTTON_ROW_HEIGHT + BUTTON_GAP; - const row3Y = row2Y + BUTTON_ROW_HEIGHT + BUTTON_GAP; - - this.controlLayout = { - modeButtons: this.buildButtonRow( - controlsArea, - row1Y, - ["Positional", "Floyd"], - ["mode:positional", "mode:floyd"], - ), - scaleButtons: this.buildButtonRow(controlsArea, row2Y, ["1x", "2x", "4x"], ["scale:1", "scale:2", "scale:4"]), - adjustButtons: this.buildButtonRow( - controlsArea, - row3Y, - ["W-", "W+", "H-", "H+", "Reset"], - ["w:-16", "w:+16", "h:-16", "h:+16", "reset"], - ), - }; - }, - - drawButton: function (ctx, button, isActive = false) { - ctx.save(); - ctx.fillStyle = isActive ? BUTTON_ACTIVE_BG_COLOR : BUTTON_BG_COLOR; - ctx.fillRect(button.x, button.y, button.width, button.height); - ctx.strokeStyle = BUTTON_BORDER_COLOR; - ctx.lineWidth = 1; - ctx.strokeRect(button.x + 0.5, button.y + 0.5, button.width - 1, button.height - 1); - drawStyledText(ctx, button.label, button.x + button.width / 2, button.y + button.height / 2, { + const labelY = controlsArea.y + PALETTE_GRID_TOP_INSET - 8; + drawStyledText(ctx, `Palette (${colorCount} colors)`, controlsArea.x + controlsArea.width / 2, labelY, { font: "12px Arial", + fillStyle: "rgba(235,235,235,0.95)", }); + + const gridX = controlsArea.x; + const gridY = controlsArea.y + PALETTE_GRID_TOP_INSET; + const gridWidth = controlsArea.width; + const gridHeight = controlsArea.height - PALETTE_GRID_TOP_INSET - PALETTE_GRID_BOTTOM_INSET; + if (gridWidth <= 0 || gridHeight <= 0) { + return; + } + + const minColumns = Math.min(4, colorCount); + const maxColumns = Math.min(PALETTE_GRID_MAX_COLUMNS, colorCount); + let bestColumns = minColumns; + let bestRows = Math.ceil(colorCount / bestColumns); + let bestScore = -1; + + for (let columns = minColumns; columns <= maxColumns; columns++) { + const rows = Math.ceil(colorCount / columns); + const cellWidth = (gridWidth - PALETTE_GRID_GAP * (columns - 1)) / columns; + const cellHeight = (gridHeight - PALETTE_GRID_GAP * (rows - 1)) / rows; + const score = Math.min(cellWidth, cellHeight); + if (score > bestScore) { + bestScore = score; + bestColumns = columns; + bestRows = rows; + } + } + + const cellWidth = (gridWidth - PALETTE_GRID_GAP * (bestColumns - 1)) / bestColumns; + const cellHeight = (gridHeight - PALETTE_GRID_GAP * (bestRows - 1)) / bestRows; + + ctx.save(); + for (let i = 0; i < colorCount; i++) { + const col = i % bestColumns; + const row = Math.floor(i / bestColumns); + const color = palette[i]; + const x = gridX + col * (cellWidth + PALETTE_GRID_GAP); + const y = gridY + row * (cellHeight + PALETTE_GRID_GAP); + + ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`; + ctx.fillRect(x, y, cellWidth, cellHeight); + ctx.strokeStyle = PALETTE_SWATCH_BORDER_COLOR; + ctx.strokeRect(x + 0.5, y + 0.5, Math.max(0, cellWidth - 1), Math.max(0, cellHeight - 1)); + } ctx.restore(); }, @@ -693,27 +694,6 @@ app.registerExtension({ this.setDirtyCanvas(true, true); }, - applyScalePreset: function (scale) { - const baseWidth = this.sourcePixelWidth > 0 ? this.sourcePixelWidth : this.getIntWidgetValue("width", 256); - const baseHeight = this.sourcePixelHeight > 0 ? this.sourcePixelHeight : this.getIntWidgetValue("height", 256); - - this.setWidgetValue("width", clamp(Math.round(baseWidth * scale), 1, MAX_DIMENSION)); - this.setWidgetValue("height", clamp(Math.round(baseHeight * scale), 1, MAX_DIMENSION)); - this.updateBackend(); - }, - - adjustDimension: function (axis, delta) { - const widgetName = axis === "w" ? "width" : "height"; - const current = this.getIntWidgetValue(widgetName, 0); - this.setWidgetValue(widgetName, clamp(current + delta, 0, MAX_DIMENSION)); - this.updateBackend(); - }, - - setMode: function (mode) { - this.setWidgetValue("dither_mode", mode); - this.updateBackend(); - }, - onDrawForeground: function (ctx) { if (this.flags?.collapsed) return; @@ -722,8 +702,6 @@ app.registerExtension({ const controlsArea = this.getControlsArea(); if (!topPreviewArea || !bottomPreviewArea || !controlsArea) return; - this.updateControlLayout(); - ctx.save(); ctx.fillStyle = BG_PANEL_COLOR; ctx.fillRect(topPreviewArea.x, topPreviewArea.y, topPreviewArea.width, topPreviewArea.height); @@ -792,17 +770,6 @@ app.registerExtension({ ctx.fillRect(controlsArea.x, controlsArea.y, controlsArea.width, controlsArea.height); ctx.restore(); - if (this.controlLayout) { - const mode = this.getModeValue(); - this.controlLayout.modeButtons.forEach((button) => { - const isActive = button.id === `mode:${mode}`; - this.drawButton(ctx, button, isActive); - }); - - this.controlLayout.scaleButtons.forEach((button) => this.drawButton(ctx, button, false)); - this.controlLayout.adjustButtons.forEach((button) => this.drawButton(ctx, button, false)); - } - const requestedWidth = this.getIntWidgetValue("width", 0); const requestedHeight = this.getIntWidgetValue("height", 0); const resolved = this.resolveTargetSize(requestedWidth, requestedHeight); @@ -818,52 +785,8 @@ app.registerExtension({ fillStyle: "rgba(235,235,235,0.95)", }, ); - }, - onMouseDown: function (event) { - if (event.button !== 0) return false; - if (!this.controlLayout) return false; - - const localPos = app.canvas.convertEventToCanvasOffset(event); - const mouseX = localPos[0] - this.pos[0]; - const mouseY = localPos[1] - this.pos[1]; - - const hit = (buttons) => buttons.find((button) => isPointInRect(mouseX, mouseY, button)); - - const modeButton = hit(this.controlLayout.modeButtons); - if (modeButton) { - const mode = modeButton.id.split(":")[1]; - this.setMode(mode); - return true; - } - - const scaleButton = hit(this.controlLayout.scaleButtons); - if (scaleButton) { - const scale = Number.parseFloat(scaleButton.id.split(":")[1]); - this.applyScalePreset(scale); - return true; - } - - const adjustButton = hit(this.controlLayout.adjustButtons); - if (adjustButton) { - if (adjustButton.id === "reset") { - const width = this.sourcePixelWidth > 0 ? this.sourcePixelWidth : 256; - const height = this.sourcePixelHeight > 0 ? this.sourcePixelHeight : 256; - this.setWidgetValue("width", width); - this.setWidgetValue("height", height); - this.setWidgetValue("dither_mode", "positional"); - this.setWidgetValue("palette_mode", "vga_256"); - this.setWidgetValue("palette_colors", 24); - this.updateBackend(); - return true; - } - - const [axis, deltaText] = adjustButton.id.split(":"); - this.adjustDimension(axis, toInt(deltaText, 0)); - return true; - } - - return false; + this.drawPaletteGrid(ctx, controlsArea); }, onResize: function () { @@ -919,11 +842,6 @@ api.addEventListener("game_dev.resize_dither.initialise", ({ detail }) => { ); } - const incomingPalette = normalizePalette(detail.vga_palette); - if (incomingPalette) { - node.setPalette(incomingPalette); - } - const onBeforeLoad = () => { if (revision !== node.bootstrapRevision) { return;