112 lines
3.8 KiB
Python
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")
|