diff --git a/README.md b/README.md index b4660fe..926adc5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ A ComfyUI custom node for selecting mathematically valid image resolutions. - Divisibility constraints (16 / 32 / 64) - Max megapixel limit - 1 MP = 1024 × 1024 = 1,048,576 pixels -- Dynamically filtered resolution dropdown +- **Real-time filtered resolution dropdown** (New!) + - Resolultions update instantly when parameters are changed. - Outputs: - Width (px) - Height (px) diff --git a/resolution_matrix.py b/resolution_matrix.py index 546063d..4d39d9f 100644 --- a/resolution_matrix.py +++ b/resolution_matrix.py @@ -46,7 +46,7 @@ class PixelForge: ["1 MP", "2 MP", "4 MP", "6 MP", "8 MP", "12 MP", "16 MP"], {"default": f"{cls._last_max_mp} MP"}, ), - "resolution": (resolutions,), + "resolution": ("STRING", {"default": "1024×1024"}), } } diff --git a/web/js/pixel_forge.js b/web/js/pixel_forge.js new file mode 100644 index 0000000..de3db6e --- /dev/null +++ b/web/js/pixel_forge.js @@ -0,0 +1,89 @@ +import { app } from "../../../scripts/app.js"; + +// PixelForge Resolution Matrix - Frontend Extension +app.registerExtension({ + name: "PixelForge.ResolutionMatrix", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "PixelForge") { + const ASPECT_RATIOS = { + "1:1": [1, 1], + "3:2": [3, 2], + "4:3": [4, 3], + "16:9": [16, 9], + "16:10": [16, 10], + }; + + const MP_BASE = 1024 * 1024; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + const aspectWidget = this.widgets.find(w => w.name === "aspect_ratio"); + const divisibleWidget = this.widgets.find(w => w.name === "divisible_by"); + const mpLimitWidget = this.widgets.find(w => w.name === "max_megapixels"); + const resolutionWidget = this.widgets.find(w => w.name === "resolution"); + resolutionWidget.type = "combo"; + resolutionWidget.options = { values: [] }; + + const updateResolutions = () => { + const aspect = aspectWidget.value; + const div = parseInt(divisibleWidget.value); + const maxMp = parseInt(mpLimitWidget.value.split(" ")[0]); + + const ratio = ASPECT_RATIOS[aspect]; + if (!ratio) return; + + const [ratio_w, ratio_h] = ratio; + const maxPixels = maxMp * MP_BASE; + + const resolutions = []; + let k = div; + + while (true) { + const w = ratio_w * k; + const h = ratio_h * k; + const total = w * h; + + if (total > maxPixels) break; + + resolutions.push(`${w}×${h}`); + k += div; + } + + if (resolutions.length === 0) { + resolutions.push("INVALID"); + } + + // Update the widget options + resolutionWidget.options.values = resolutions; + + // If current value is not in the new list, pick the closest one or the first one + if (!resolutions.includes(resolutionWidget.value)) { + resolutionWidget.value = resolutions[0]; + } + + if (this.setDirtyCanvas) { + this.setDirtyCanvas(true, true); + } + }; + + // Add callbacks to trigger update + const widgetsToWatch = [aspectWidget, divisibleWidget, mpLimitWidget]; + widgetsToWatch.forEach(widget => { + const callback = widget.callback; + widget.callback = function () { + const result = callback ? callback.apply(this, arguments) : undefined; + updateResolutions(); + return result; + }; + }); + + // Initial update + updateResolutions(); + + return r; + }; + } + } +});