ComfyUI/tests-unit/comfy_extras_test/compositor_blend_fixture_ge...

112 lines
3.8 KiB
Python

"""Regenerate ``compositor_blend_golden.json``.
The golden file is the *shared contract* for layer blending. Every
implementation of these 26 modes must reproduce it within ``tolerance``:
* ``comfy_extras/compositor_blend.py`` - numpy, server-side compositing
* ``layerBlend.frag`` - GLSL, the live preview in the layer editor
* any future CPU reference in the frontend
Run from the repository root::
python tests-unit/comfy_extras_test/compositor_blend_fixture_gen.py
and review the diff. A change to this file is a change to user-visible
blending behaviour in every implementation, so it should never be
regenerated just to make a test pass.
"""
import json
import os
import sys
import numpy as np
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from comfy_extras.compositor_blend import CHANNEL_BLEND, HSL_BLEND, blend_pixel # noqa: E402
GOLDEN_PATH = os.path.join(os.path.dirname(__file__), "compositor_blend_golden.json")
# Scalar grid for the per-channel modes: both endpoints, the midpoint, values
# just inside each endpoint, and values inside the 1e-6 epsilon guards.
SCALARS = [0.0, 1e-7, 0.001, 0.25, 0.5, 0.75, 0.999, 1.0 - 1e-7, 1.0]
# Colour pairs for the HSL modes, which read all three channels at once.
COLORS = [
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[0.5, 0.5, 0.5],
[1.0, 0.0, 0.0],
[0.0, 0.0, 1.0],
[0.2, 0.4, 0.6],
[0.9, 0.1, 0.35],
[1e-7, 1e-7, 1e-7],
[1e-7, 0.0, 0.0],
[0.05, 0.05, 0.05],
]
def _round(value) -> float:
return round(float(value), 7)
def build() -> dict:
channel = {}
for mode in CHANNEL_BLEND:
rows = []
for i in SCALARS:
for l in SCALARS:
out = blend_pixel(mode, np.float32([i] * 3), np.float32([l] * 3))
rows.append([_round(i), _round(l), _round(np.asarray(out).reshape(3)[0])])
channel[mode] = rows
hsl = {}
for mode in HSL_BLEND:
rows = []
for i in COLORS:
for l in COLORS:
out = blend_pixel(mode, np.float32(i), np.float32(l))
rows.append([
[_round(v) for v in i],
[_round(v) for v in l],
[_round(v) for v in np.asarray(out).reshape(3)],
])
hsl[mode] = rows
return {
"_comment": (
"Golden blend values shared by comfy_extras/compositor_blend.py and "
"layerBlend.frag. Inputs are unpremultiplied colours already in the "
"blend space; outputs are unclamped (the compositor clamps once, at "
"the end). 'channel' rows are [i, l, out] applied per channel; 'hsl' "
"rows are [rgb_backdrop, rgb_layer, rgb_out]. Regenerate with "
"tests-unit/comfy_extras_test/compositor_blend_fixture_gen.py."
),
"tolerance": 1e-4,
"channel": channel,
"hsl": hsl,
}
def dumps(data: dict) -> str:
"""One row per line, so a behaviour change shows up as a readable diff."""
lines = ["{", f' "_comment": {json.dumps(data["_comment"])},', f' "tolerance": {data["tolerance"]},']
for section in ("channel", "hsl"):
lines.append(f' "{section}": {{')
modes = sorted(data[section])
for m_index, mode in enumerate(modes):
lines.append(f' "{mode}": [')
rows = data[section][mode]
for r_index, row in enumerate(rows):
comma = "" if r_index == len(rows) - 1 else ","
lines.append(f" {json.dumps(row)}{comma}")
lines.append(" ]" + ("" if m_index == len(modes) - 1 else ","))
lines.append(" }" + ("," if section == "channel" else ""))
lines.append("}")
return "\n".join(lines) + "\n"
if __name__ == "__main__":
with open(GOLDEN_PATH, "w") as handle:
handle.write(dumps(build()))
sys.stdout.write(f"wrote {GOLDEN_PATH}\n")