fix: fall back to source image dimensions for missing width/height

When metadata extraction succeeds but no recognized latent source
provides dimensions (e.g. img2img via VAEEncode), width/height now fall
back to the source image size from the loaded pixels instead of the
synthetic 1024x1024 starter preset. The starter preset for metadata-free
images keeps its fixed size, and explicit overrides still win.
This commit is contained in:
Will Miao
2026-09-23 20:31:59 +08:00
parent c202654d49
commit 755e1a5bca
3 changed files with 56 additions and 2 deletions
+4 -1
View File
@@ -75,7 +75,8 @@ prompt; UI-workflow-only subgraph definitions are not expanded or executed.
Extraction errors do not stop this node. If an API prompt uses unsupported
samplers, the node first tries the image's saved generation parameters. Any
remaining unavailable or invalid extracted fields use the SDXL starter defaults;
remaining unavailable or invalid extracted fields use the SDXL starter defaults
(width/height fall back to the source image dimensions instead);
valid extracted fields are preserved. `readable_report` starts with **❌ ERROR**
and explains each recovery or substitution. This also applies to existing nodes
saved with `missing_settings=strict`; that legacy option no longer blocks
@@ -147,6 +148,8 @@ when uniquely indexed; otherwise choose an SDXL checkpoint manually or supply
original workflow for those cases.
- Width/height come from a recognized latent source or fall back to source-image
dimensions; resized/upscaled images can therefore need dimension overrides.
Only the synthetic starter preset for metadata-free images uses a fixed
1024×1024 regardless of the source image size.
- VAE, text encoder choice, CLIP skip, ControlNet and architecture-specific
conditioning still need the appropriate nodes. No embedded code is executed
and no external metadata service is contacted.
+14 -1
View File
@@ -253,8 +253,21 @@ class LoadImageMetadataLM:
if "model" in extracted.issues and "model_name" not in overrides:
values.pop("checkpoint_name", None)
values.pop("unet_name", None)
# Extraction without a recognized latent source (e.g. img2img) leaves
# width/height unset; the source image dimensions are the best
# estimate then. The synthetic starter preset keeps its fixed size.
image_fallback = not no_metadata and "source" not in extracted.issues
try:
image_height, image_width = int(pixels.shape[1]), int(pixels.shape[2])
except (AttributeError, IndexError, TypeError, ValueError):
image_fallback = False
for key, default in EMPTY_IMAGE_DEFAULTS.items():
if key not in values:
if key in values:
continue
if image_fallback and key in ("width", "height"):
values[key] = image_width if key == "width" else image_height
notes.append(f"WARNING Missing {key}; using source image dimension {values[key]}.")
else:
values[key] = default
notes.append(f"ERROR: Missing {key}; using default {default!r}.")
# Validate independently so one invalid value cannot erase the other
+38
View File
@@ -96,6 +96,44 @@ def test_no_metadata_can_be_inspected_with_defaults(runtime):
assert "No model resolved" in result[15]
def test_graph_without_recognized_latent_falls_back_to_image_size(runtime):
info = PngImagePlugin.PngInfo()
graph = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "base.safetensors"}},
"2": {"class_type": "CLIPTextEncode", "inputs": {"text": "pos", "clip": ["1", 1]}},
"3": {"class_type": "CLIPTextEncode", "inputs": {"text": "neg", "clip": ["1", 1]}},
"5": {"class_type": "KSampler", "inputs": {
"model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0],
"latent_image": ["9", 0], "seed": 1, "steps": 20, "cfg": 7,
"sampler_name": "euler", "scheduler": "normal", "denoise": 1,
}},
"9": {"class_type": "VAEEncode", "inputs": {"pixels": ["10", 0], "vae": ["1", 2]}},
}
info.add_text("prompt", json.dumps(graph))
Image.new("RGB", (16, 24)).save(runtime[0], pnginfo=info)
# The mocked loader returns pixels with shape (1, 24, 16, 3): H=24, W=16.
result = LoadImageMetadataLM().load_metadata("input.png")
assert result[12:14] == (16, 24)
assert "using source image dimension" in result[15]
assert "❌ ERROR" not in result[16]
def test_parameters_without_size_fall_back_to_image_size(runtime):
info = PngImagePlugin.PngInfo()
info.add_text("parameters", PARAMETERS.replace(", Size: 768x1024", ""))
Image.new("RGB", (16, 24)).save(runtime[0], pnginfo=info)
result = LoadImageMetadataLM().load_metadata("input.png")
assert result[12:14] == (16, 24)
def test_size_override_wins_over_image_size_fallback(runtime):
info = PngImagePlugin.PngInfo()
info.add_text("parameters", PARAMETERS.replace(", Size: 768x1024", ""))
Image.new("RGB", (16, 24)).save(runtime[0], pnginfo=info)
result = LoadImageMetadataLM().load_metadata("input.png", overrides_json='{"width": 512, "height": 640}')
assert result[12:14] == (512, 640)
@pytest.mark.parametrize("override", [{"seed": -1}, {"steps": 2.5}, {"cfg": float("nan")}, {"sampler_name": "made_up"}, {"positive": ["1", 0]}, {"unknown": 1}])
def test_invalid_override_rejected(runtime, override):
with pytest.raises((MetadataError, ValueError)):