PNG EXR Alpha

examples/png-exr-alpha/

Float-image PNG save trap — float_buffer=True Image.save() writes RGBA16 and unpremultiplies as if associated-alpha, clamping straight-authored dark values at low alpha to white (closed-form err 0.98 at RGB 0.02 / a=1/255).

Rendered headless by the example itself. Select it to enlarge.

witnesses float→PNG IHDR is RGBA16 and matches q16(min(1, c/q16(a))); OpenEXR round-trips within 1e-5; byte→PNG is straight RGBA8; EXR color_mode=RGB drops alpha to opaque.

tags images rendering alpha

runs on 4.5 LTS · 5.1 · 5.2 LTS

blender --background --python examples/png-exr-alpha/png_exr_alpha.py --

falsify it Breaks the contract on purpose and must exit 5

blender --background --python examples/png-exr-alpha/png_exr_alpha.py -- --opaque-alpha

A runnable example that witnesses the float-image PNG save trap: a float_buffer=True image saved via Image.save() to PNG is written as 16-bit RGBA and is unpremultiplied as if the buffer were associated-alpha before storage. The normal pixels API authors straight RGBA, so any channel where c > a blows past 1.0 and clamps to white — closed-form RGB error reaches 0.98 for authored (0.02, 0.02, 0.02) at alpha 1/255. The same buffer saved as OpenEXR round-trips at float precision. A byte image (float_buffer=False) writes 8-bit PNG with straight alpha and only pays ordinary quantization.

Found while probing the ROADMAP "image save-format" candidate against live Blender 5.1 — the hazard is not classic 8-bit premul quantization (float PNG is 16-bit); it is the false associated-alpha unpremultiply on a straight buffer. Documented here so the contract cannot quietly drift.

What it witnesses:

What each check catches on failure:

Version divergence: none probed on the save/reload contracts above — they assert on Blender 5.1.1 locally. Blender 4.5 LTS is exercised by the blender-smoke CI job (4.5 is not installed on this authoring host; do not treat a 4.4 run as a 4.5 substitute). The only version gate in the file is the EEVEE engine id for the optional render (BLENDER_EEVEE_NEXT on 4.x, BLENDER_EEVEE on 5.x).

Render: two thin-bezel monitors on machined stands, sharing a walnut-topped console — the left screen bakes the closed-form PNG mangling (dark rows flash to white at low-alpha columns), the right screen shows the authored straight buffer (EXR-clean); brass desk plates in front read FLOAT → PNG and FLOAT → EXR. If the contract failed, both screens would read the same. The render path gates framing through examples/gallery_framing.py (exit 10) before writing the still.

Run#

# Cheap correctness check (no render) — the CI check:
blender --background --python png_exr_alpha.py --

# Falsifier: opaque authored alpha. Must exit non-zero (float→PNG error floor).
blender --background --python png_exr_alpha.py -- --opaque-alpha

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
blender --background --python png_exr_alpha.py -- --output alpha.png
blender --background --python png_exr_alpha.py -- --output alpha.png --engine cycles

Exit codes#

Per-script sequential checks. 9 is a valid check code; there is no rule against it. 10 is also the shared framing helper.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3float_buffer=True image reports is_float=False
4Float→PNG IHDR is not RGBA16
5Float→PNG RGB error below floor (--opaque-alpha lands here)
6Float→PNG disagrees with closed-form false-unpremul model
7Float→EXR round-trip above tolerance
8float_buffer=False image reports is_float=True
9Byte→PNG IHDR is not RGBA8
10Gallery framing violation (--output only; gallery_framing)
12EXR color_mode=RGB alpha-drop contract
13--output produced no file
14Closed-form palette no longer stresses unpremul (checked first)
15Byte→PNG disagrees with straight-alpha 8-bit model
16Byte→PNG stress cell looks false-unpremul-mangled

The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron, the needs-5.1 PR label, or manual dispatch). Smoke does not pass --output. Its catalog falsifier is --opaque-alpha (expects exit 5).

Source

examples/png-exr-alpha/png_exr_alpha.py View the script on GitHub →
Two thin-bezel monitors on a walnut console with brass desk plates reading FLOAT to PNG and FLOAT to EXR; the PNG screen washes its dark rows to white while the EXR screen keeps every bar.