Wire GPT resolution to input aspect ratio

This commit is contained in:
Alexander Tujicov
2026-09-09 18:39:52 +03:00
parent 9a2bf43333
commit d38b944da5
2 changed files with 88 additions and 9 deletions
+5 -2
View File
@@ -70,13 +70,14 @@ You must have a **Fal.ai API Key** to use this node.
* **num_inference_steps**: Number of inference steps for supported models.
* **seed**: Seed for reproducibility.
* **aspect_ratio**: Set target aspect ratio (e.g., 16:9, 1:1).
* **resolution**: Sets the maximum output edge for GPT Image models while preserving the first input image's aspect ratio: `0.5K` = 512px, `1K` = 1024px, `2K` = 2048px, and `4K` = 3840px.
* **quality**: GPT quality. GPT Image 2 supports `auto` through `high`; GPT Image 2.5 also supports `xhigh` and `max`.
* **enable_web_search**: Enables web search context for supported models.
* **thinking_level**: `off`, `minimal`, or `high` for supported models.
* **concurrent_runs**: Number of independent fal.ai queue jobs to submit in parallel from this node. This is separate from workflow-level concurrency, which automatically overlaps independent NanoSeed nodes. Each run requests its own `num_images`; outputs are returned in run order. Models with an explicit seed increment it for each additional run.
* **queue_timeout**: Maximum wait per queue job in seconds. Timed-out jobs receive a best-effort cancellation request.
* **gpt_background**: `auto`, `transparent`, or `opaque` for GPT Image models.
* **gpt_image_size**: GPT output-size preset, including 1080p, 1440p, and 3840x2160. Non-zero `width` and `height` override this preset.
* **gpt_image_size**: Optional GPT aspect-ratio override. `auto` follows the first input image; presets or non-zero `width` and `height` override the aspect ratio, while `resolution` still caps the longest edge.
* **gpt_output_format**: `png`, `jpeg`, or `webp` for GPT Image models.
* **gpt_output_compression**: Optional GPT Image 2.5 JPEG/WebP compression from 0-100. Leave at `-1` to use the API default.
@@ -88,7 +89,9 @@ Different models have different requirements implemented in the node:
* Supports all 10 NanoSeed reference-image sockets and up to **10 output images**.
* Supports `auto`, `low`, `medium`, `high`, `xhigh`, and `max` quality. Higher levels increase cost and latency.
* Supports automatic, transparent, or opaque backgrounds and PNG, JPEG, or WebP output.
* Includes 1080p, 1440p, and **3840x2160** presets. Custom non-zero `width` and `height` take priority.
* `resolution` automatically calculates dimensions from the first input image: 1K, 2K, or 4K sets a 1024px, 2048px, or 3840px maximum edge.
* Calculated dimensions are multiples of 16, and input aspect ratios beyond the API limit are clamped to 3:1.
* `gpt_image_size` or non-zero `width` and `height` can override the aspect ratio, but not the selected resolution cap.
* Supports the shared GPT `mask` socket.
2. **Flux 2 Edit (`flux_2_edit`)**:
+83 -7
View File
@@ -1,4 +1,5 @@
import asyncio
import math
import os
import re
import time
@@ -21,6 +22,15 @@ GPT_IMAGE_25_EDIT_URLS = {
"gpt_image_2_5_sunburst_edit": "https://fal.run/openai/gpt-image-2.5/sunburst/edit",
}
GPT_IMAGE_EDIT_MODELS = {"gpt_image_2_edit", *GPT_IMAGE_25_EDIT_URLS}
GPT_RESOLUTION_MAX_EDGE = {
"0.5K": 512,
"1K": 1024,
"2K": 2048,
"4K": 3840,
}
GPT_IMAGE_SIZE_OPTIONS = [
"auto",
"square_hd",
@@ -35,6 +45,12 @@ GPT_IMAGE_SIZE_OPTIONS = [
]
GPT_IMAGE_SIZE_DIMENSIONS = {
"square_hd": {"width": 1024, "height": 1024},
"square": {"width": 512, "height": 512},
"portrait_4_3": {"width": 768, "height": 1024},
"portrait_16_9": {"width": 576, "height": 1024},
"landscape_4_3": {"width": 1024, "height": 768},
"landscape_16_9": {"width": 1024, "height": 576},
"1920x1080": {"width": 1920, "height": 1080},
"2560x1440": {"width": 2560, "height": 1440},
"3840x2160": {"width": 3840, "height": 2160},
@@ -176,10 +192,54 @@ def _prepare_image_data_uris(images, resize_to=None):
return data_uris
def _resolve_gpt_image_size(width, height, image_size):
def _gpt_source_dimensions(image_tensor, width, height, image_size):
if width > 0 and height > 0:
return {"width": width, "height": height}
return GPT_IMAGE_SIZE_DIMENSIONS.get(image_size, image_size)
return width, height
preset = GPT_IMAGE_SIZE_DIMENSIONS.get(image_size)
if preset is not None:
return preset["width"], preset["height"]
shape = image_tensor.shape
if len(shape) == 4:
return int(shape[2]), int(shape[1])
if len(shape) == 3:
return int(shape[1]), int(shape[0])
raise ValueError("GPT image input must use ComfyUI IMAGE tensor dimensions.")
def _resolve_gpt_image_size(image_tensor, resolution, width, height, image_size):
max_edge = GPT_RESOLUTION_MAX_EDGE.get(resolution)
if max_edge is None:
raise ValueError(f"Unsupported GPT resolution: {resolution!r}")
source_width, source_height = _gpt_source_dimensions(
image_tensor,
width,
height,
image_size,
)
if source_width <= 0 or source_height <= 0:
raise ValueError("GPT image dimensions must be greater than zero.")
if source_width >= source_height:
aspect_ratio = min(source_width / source_height, 3.0)
output_width = max_edge
output_height = _round_to_multiple(output_width / aspect_ratio, 16)
output_height = max(
output_height,
math.ceil((output_width / 3.0) / 16) * 16,
)
else:
aspect_ratio = min(source_height / source_width, 3.0)
output_height = max_edge
output_width = _round_to_multiple(output_height / aspect_ratio, 16)
output_width = max(
output_width,
math.ceil((output_height / 3.0) / 16) * 16,
)
return {"width": output_width, "height": output_height}
def _safe_error_detail(detail):
@@ -467,11 +527,27 @@ class NanoSeedEdit:
images = [img for img in image_inputs if img is not None]
if not images:
raise ValueError("At least one image input must be connected.")
resolved_gpt_image_size = None
if model in GPT_IMAGE_EDIT_MODELS:
resolved_gpt_image_size = _resolve_gpt_image_size(
images[0],
resolution,
width,
height,
gpt_image_size,
)
custom_size = (width > 0 and height > 0)
resize_to = None
if custom_size and model not in ["nano_banana", "nano_banana_pro", "nano_banana_2"]:
resize_to = (width, height)
if resolved_gpt_image_size is not None:
resize_to = (
resolved_gpt_image_size["width"],
resolved_gpt_image_size["height"],
)
else:
resize_to = (width, height)
img_data_uris = await asyncio.to_thread(
_prepare_image_data_uris,
images,
@@ -530,7 +606,7 @@ class NanoSeedEdit:
"image_urls": img_data_uris,
"quality": quality,
"background": gpt_background,
"image_size": _resolve_gpt_image_size(width, height, gpt_image_size),
"image_size": resolved_gpt_image_size,
"num_images": min(num_images, 4),
"output_format": gpt_output_format,
"sync_mode": False,
@@ -546,7 +622,7 @@ class NanoSeedEdit:
"image_urls": img_data_uris,
"quality": quality,
"background": gpt_background,
"image_size": _resolve_gpt_image_size(width, height, gpt_image_size),
"image_size": resolved_gpt_image_size,
"num_images": num_images,
"output_format": gpt_output_format,
"sync_mode": False,