Files
BRADSEC 02f84ccb23 Fix invalid presets, validate preset path, sync JS resolution table (review 1, 2)
Qwen Image 1140x1472 (and mirror) and SD 1.5 512x682 were not divisible
by 8, so the generated latent did not match the reported width/height;
removed the stray Qwen pair (official list has 1104x1472) and corrected
682 to 680. The preset branch now runs _validate_dimensions before the
multiplier, so API-built workflows cannot pair a model with another
model's incompatible preset. Regenerated the hand-copied JS table from
nodes.py (it had drifted by 14 entries) and added tests that fail on
future drift or non-conforming presets. Bump to 2.1.6.
2026-07-03 10:56:23 +10:00

241 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "/scripts/app.js";
// Model-specific resolution presets. Mirror of MODEL_RESOLUTIONS in nodes.py:
// keep in sync (test_resolution_selector.py diffs the two tables).
const MODEL_RESOLUTIONS = {
"Flux": {
"square": [[512, 512], [768, 768], [1024, 1024], [1088, 1088], [1280, 1280], [1328, 1328], [1536, 1536], [1920, 1920], [2048, 2048]],
"portrait": [[688, 2048], [768, 1344], [832, 1216], [896, 1152], [928, 1664], [1024, 1536], [1024, 1792], [1024, 2048], [1056, 1584], [1088, 1920], [1104, 1472], [1152, 2048], [1200, 1792], [1360, 2048], [1456, 2048], [1536, 2048], [1616, 2048], [1632, 2048], [1712, 2048]],
"landscape": [[1280, 720], [1344, 768], [1216, 832], [1152, 896], [1472, 1104], [1536, 1024], [1584, 1056], [1664, 928], [1792, 1024], [1792, 1200], [1920, 1088], [2048, 688], [2048, 1024], [2048, 1152], [2048, 1360], [2048, 1456], [2048, 1536], [2048, 1616], [2048, 1632], [2048, 1712]],
},
"Qwen Image": {
"square": [[1024, 1024], [1080, 1080], [1280, 1280], [1328, 1328], [1536, 1536], [1920, 1920], [2048, 2048]],
"portrait": [[680, 2048], [928, 1664], [1024, 1536], [1024, 2048], [1056, 1584], [1080, 1920], [1104, 1472], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
"landscape": [[1280, 720], [1472, 1104], [1536, 1024], [1584, 1056], [1664, 928], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
},
"Z-Image": {
"square": [[512, 512], [768, 768], [1024, 1024], [1080, 1080], [1280, 1280], [1328, 1328], [1536, 1536], [1920, 1920], [2048, 2048]],
"portrait": [[680, 2048], [720, 1280], [768, 1024], [928, 1664], [1024, 2048], [1056, 1584], [1080, 1920], [1104, 1472], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
"landscape": [[1024, 768], [1280, 720], [1472, 1104], [1584, 1056], [1664, 928], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
},
"SD 1.5": {
"square": [[512, 512], [768, 768], [1024, 1024], [1080, 1080], [1280, 1280], [1536, 1536]],
"portrait": [[512, 768], [512, 680], [512, 1024], [680, 2048], [768, 1024], [768, 1344], [1024, 2048], [1080, 1920], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
"landscape": [[768, 512], [1024, 512], [1024, 768], [1280, 720], [1344, 768], [1536, 512], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
},
"SDXL": {
"square": [[1024, 1024], [1080, 1080], [1280, 1280], [1536, 1536], [1920, 1920], [2048, 2048]],
"portrait": [[640, 1536], [680, 2048], [768, 1344], [832, 1216], [896, 1152], [1024, 1536], [1024, 2048], [1080, 1920], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
"landscape": [[1152, 896], [1216, 832], [1280, 720], [1344, 768], [1536, 640], [1536, 1024], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
}
};
function gcd(a, b) {
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}
function calculateAspectRatio(width, height) {
// Calculate actual ratio as decimal
const actualRatio = width / height;
// Common aspect ratios [ratio_value, "width:height" string]
const commonRatios = [
[1.0, "1:1"], // Square
[1.25, "5:4"], // 1.25
[1.33, "4:3"], // 1.333...
[1.5, "3:2"], // 1.5
[1.6, "16:10"], // 1.6
[1.78, "16:9"], // 1.777...
[2.0, "2:1"], // 2.0
[2.35, "21:9"], // 2.333... (ultrawide)
[2.4, "12:5"], // 2.4
[3.0, "3:1"], // 3.0
// Portrait ratios
[0.75, "3:4"], // 0.75
[0.67, "2:3"], // 0.666...
[0.625, "5:8"], // 0.625
[0.56, "9:16"], // 0.5625
[0.5, "1:2"], // 0.5
[0.42, "5:12"], // 0.4166...
[0.33, "1:3"], // 0.333... (panoramic)
];
// Find closest common ratio by absolute difference
let closestRatio = commonRatios[0];
let minDiff = Math.abs(actualRatio - closestRatio[0]);
for (const ratio of commonRatios) {
const diff = Math.abs(actualRatio - ratio[0]);
if (diff < minDiff) {
minDiff = diff;
closestRatio = ratio;
}
}
return closestRatio[1];
}
function formatResolution(width, height) {
const aspectRatio = calculateAspectRatio(width, height);
let orientation;
if (width === height) {
orientation = "Square";
} else if (width < height) {
orientation = "Portrait";
} else {
orientation = "Landscape";
}
// Format with fixed width for better alignment
const resolutionStr = `${width}x${height}`;
const paddedResolution = resolutionStr.padEnd(13, ' '); // Pad to 13 chars for alignment
return `${paddedResolution}(${aspectRatio} ${orientation})`;
}
function getAllResolutions() {
const uniqueResolutions = new Map();
// Collect all unique width×height pairs
for (const modelName in MODEL_RESOLUTIONS) {
const modelData = MODEL_RESOLUTIONS[modelName];
for (const category of ["square", "portrait", "landscape"]) {
if (modelData[category]) {
for (const [w, h] of modelData[category]) {
const key = `${w}x${h}`;
if (!uniqueResolutions.has(key)) {
uniqueResolutions.set(key, { width: w, height: h, pixels: w * h });
}
}
}
}
}
// Sort by total pixels, then by width
const sorted = Array.from(uniqueResolutions.values())
.sort((a, b) => {
if (a.pixels !== b.pixels) return a.pixels - b.pixels;
return a.width - b.width;
});
return sorted.map(({ width, height }) => formatResolution(width, height));
}
function getResolutionsForModel(model) {
if (model === "All") {
return getAllResolutions();
}
const modelData = MODEL_RESOLUTIONS[model];
if (!modelData) return [];
const resolutions = [];
for (const category of ["square", "portrait", "landscape"]) {
if (modelData[category]) {
for (const [w, h] of modelData[category]) {
resolutions.push(formatResolution(w, h));
}
}
}
return resolutions;
}
function getDefaultResolution(model) {
// Model-specific native/optimal resolutions
const defaultResolutions = {
"Flux": [1024, 1024],
"Qwen Image": [1328, 1328],
"Z-Image": [1024, 1024],
"SD 1.5": [512, 512],
"SDXL": [1024, 1024],
"All": [1024, 1024]
};
if (defaultResolutions[model]) {
const [w, h] = defaultResolutions[model];
return formatResolution(w, h);
}
// Fallback
return formatResolution(1024, 1024);
}
app.registerExtension({
name: "ResolutionSelectorPlus.DynamicDropdown",
async nodeCreated(node) {
if (node.comfyClass !== "ResolutionSelectorPlus") return;
// Set wider default node width to prevent text cutoff
node.setSize([400, node.size[1]]);
const modelWidget = node.widgets.find(w => w.name === "model");
const resolutionWidget = node.widgets.find(w => w.name === "resolution");
if (!modelWidget || !resolutionWidget) {
console.error("ResolutionSelector: Required widgets not found");
return;
}
const origCallback = modelWidget.callback;
const updateResolutions = (modelValue) => {
const resolutions = getResolutionsForModel(modelValue);
if (resolutions.length === 0) {
console.warn(`ResolutionSelector: No resolutions found for model ${modelValue}`);
return;
}
resolutionWidget.options.values = resolutions;
// Set to model-specific default resolution
const defaultRes = getDefaultResolution(modelValue);
if (resolutions.includes(defaultRes)) {
resolutionWidget.value = defaultRes;
} else if (!resolutions.includes(resolutionWidget.value)) {
// Fallback to first resolution if default not found
resolutionWidget.value = resolutions[0];
}
node.setDirtyCanvas(true, true);
};
modelWidget.callback = function(value) {
if (origCallback) {
origCallback.apply(this, arguments);
}
updateResolutions(value);
};
setTimeout(() => {
updateResolutions(modelWidget.value);
}, 10);
},
async loadedGraphNode(node) {
if (node.comfyClass !== "ResolutionSelectorPlus") return;
const modelWidget = node.widgets.find(w => w.name === "model");
if (modelWidget) {
const resolutions = getResolutionsForModel(modelWidget.value);
const resolutionWidget = node.widgets.find(w => w.name === "resolution");
if (resolutionWidget && resolutions.length > 0) {
resolutionWidget.options.values = resolutions;
if (!resolutions.includes(resolutionWidget.value)) {
console.warn(`ResolutionSelector: saved resolution "${resolutionWidget.value}" is not valid for model ${modelWidget.value}; reset to ${resolutions[0]}`);
resolutionWidget.value = resolutions[0];
}
}
}
}
});