Skip to content

imageio: write peak luminance metadata for PQ JPEG XL, HEIF and PNG exports - #22666

Merged
TurboGit merged 3 commits into
darktable-org:masterfrom
nicknezis:pq-peak-metadata
Oct 18, 2026
Merged

TurboGit merged 3 commits into
darktable-org:masterfrom
nicknezis:pq-peak-metadata

Conversation

@nicknezis

@nicknezis nicknezis commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

PQ exports to JPEG XL had no intensity target, so libjxl recorded 10000 nits and viewers tone mapped every image as if it reached the top of the PQ range. The JPEG XL writer now measures the brightest sample and stores it as the intensity target. The measurement is shared: the AVIF and HEIF writers each had their own copy of the PQ EOTF and the MaxCLL/MaxFALL loop, which now live in one helper, dt_imageio_pq_light_level(), built on dt_hdr_pq_decode(). The levels they write are unchanged. HLG output is untouched, since HLG is relative.

An earlier revision also wrote mastering display metadata (HEIF mdcv, PNG mDCV). It was dropped after review: its primaries and white point only repeated the CICP signaling every writer already emits, and its peak was the MaxCLL already in clli/cLLI.

Referenced issue

Fixes #22663

Checklist

  • I have read CONTRIBUTING.md and the coding style.
  • I have not merged master into the topic branch.
  • The pull request is one logical change, and every commit compiles on its own.
  • I ran the relevant tests: unit tests, src/tests/integration/ where the pixelpipe is touched, or darktable-cli as a headless smoke test.
  • New user-visible strings use _(), new preferences are registered in data/darktableconfig.xml.in. (none added)
  • A RELEASE_NOTES.md entry was added (only needed if fixing an issue in a release). Do not reference GitHub issues.

Test instructions

Export any image with a PQ Rec2020 or PQ P3 output profile to JPEG XL, HEIF and AVIF:

darktable-cli in.pfm out.jxl  --icc-type PQ_REC2020 --core -d imageio
darktable-cli in.pfm out.avif --icc-type PQ_REC2020 --core -d imageio
jxlinfo out.jxl | grep intensity_target   # was 10000 nits, now the image peak

What I ran: darktable-cli on a synthetic linear ramp peaking at 4x reference white (812 nits), with PQ Rec2020, PQ P3 and HLG Rec2020. jxlinfo reads back an intensity target of 812 nits for both PQ variants and none for HLG. AVIF and HEIF still report MaxCLL 812 and MaxFALL 406 after the refactor, the same values as before it. Built with libjxl 0.7.0, libheif 1.17.6, libavif 1.0.4, no warnings, each commit built on its own.

Not verified: how macOS, Chrome or other viewers render the intensity target; writing the HEIF file itself, since the test machine's libheif has no HEVC encoder (the clli values are from the debug log).

AI assistance

Written with Claude Code (an AI coding agent), which made the code change and ran the darktable-cli and metadata checks listed above. [Nick: add what you checked yourself.]

@kmilos

kmilos commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Output CICP should already be written for HEIF and PNG (as with AVIF and JXL), IMHO one can (or should be able to) skip writing the chromaticities and white point explicitly... I.e. one should aim to keep those writers as similar as possible wrt HDR workflow, perhaps factor as much common code out as possible...

The AVIF and HEIF writers each carried their own copy of the PQ EOTF
and the same MaxCLL/MaxFALL loop. Move the loop into
dt_imageio_pq_light_level(), built on dt_hdr_pq_decode() from
common/hdr-transfer.h, so every writer that needs the PQ content peak
measures it the same way.

NaN samples decode to black and Inf to the PQ maximum, as the old
isfinite() check guaranteed, so the written levels do not change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw9xh7zRjZCUynr6FAuohP
PQ exports left intensity_target at 0, so libjxl recorded its default
of 10000 nits and viewers tone mapped every image as if it reached the
top of the PQ range. Measure the content peak with the helper the AVIF
and HEIF writers use for MaxCLL and store it as the intensity target.
HLG is relative and keeps libjxl's default.

Fixes darktable-org#22663

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw9xh7zRjZCUynr6FAuohP
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw9xh7zRjZCUynr6FAuohP
@nicknezis

Copy link
Copy Markdown
Contributor Author

Output CICP should already be written for HEIF and PNG (as with AVIF and JXL), IMHO one can (or should be able to) skip writing the chromaticities and white point explicitly... I.e. one should aim to keep those writers as similar as possible wrt HDR workflow, perhaps factor as much common code out as possible...

Thanks, agreed. CICP is already written by all four writers, and the mdcv/mDCV I added carried nothing new: its primaries and white point repeated CICP, and its peak was the same MaxCLL already in clli/cLLI. I've dropped both, so HEIF and PNG are back to CICP + content light level, the same as AVIF.

I've also factored the measurement out. AVIF and HEIF each had their own PQ EOTF and MaxCLL/MaxFALL loop; that now lives in dt_imageio_pq_light_level() (built on dt_hdr_pq_decode()), and the JXL intensity target uses the same helper. The clli values AVIF and HEIF write are unchanged (812/406 nits on my test ramp).

PNG keeps its own measurement because it only sees the quantized 8/16-bit output, not the float buffer. I can route it through the shared decode too if you'd prefer.

The PR is now 3 commits: the shared helper, the JXL intensity target, and the release note.

@TurboGit TurboGit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

@TurboGit
TurboGit merged commit 0a8de93 into darktable-org:master Oct 18, 2026
5 checks passed
@TurboGit TurboGit added this to the 5.8 milestone Oct 18, 2026
@TurboGit TurboGit added feature: enhancement current features to improve priority: low core features work as expected, only secondary/optional features don't scope: codebase making darktable source code easier to manage labels Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: enhancement current features to improve priority: low core features work as expected, only secondary/optional features don't scope: codebase making darktable source code easier to manage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write peak luminance metadata (JXL intensity_target, MDCV) for PQ exports

4 participants