mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-31 21:48:53 -06:00
LR-HDR-EXPORT: HDR PNG export as 16-bit Rec. 2020 PQ (cICP, cLLI)
With HDR output on, a photo edited in HDR exports as a 16-bit PNG with the PQ curve in Rec. 2020 (SDR white at HDR_REFERENCE_WHITE_NITS), described by a `cICP` chunk (PNG third edition: BT.2020 primaries, PQ, RGB, full range) and a `cLLI` content light level chunk (the levels the HDR AVIF records). EXIF, XMP and print resolution are kept; no ICC profile next to cICP. Photos without an HDR edit, and HDR edits with HDR output off, export as before. `codecs::encode_png_pq` sits next to `encode_avif_pq` and takes the same input. Follow-up to #646, ported from #466. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0ba118de92
commit
cbc0e2d7db
@@ -602,8 +602,9 @@ pub struct ExportOptions {
|
||||
pub bit_depth: Option<u8>,
|
||||
/// HDR output for photos edited in HDR: JPEG as an ISO 21496-1 gain map JPEG (the SDR
|
||||
/// rendition plus a gain map, so the file looks right everywhere; quality as set, `limit_kb`
|
||||
/// not applied), AVIF as 10-bit Rec. 2020 PQ, 32-bit float TIFF with the highlights above SDR
|
||||
/// white kept. Other formats, and photos without an HDR edit, are SDR.
|
||||
/// not applied), AVIF as 10-bit Rec. 2020 PQ, PNG as 16-bit Rec. 2020 PQ (`cICP`), 32-bit float
|
||||
/// TIFF with the highlights above SDR white kept. Other formats, and photos without an HDR
|
||||
/// edit, are SDR.
|
||||
pub hdr: bool,
|
||||
}
|
||||
|
||||
@@ -995,6 +996,7 @@ impl ExportOptions {
|
||||
pub fn effective_depth(&self) -> OutputDepth {
|
||||
match (self.format, self.bit_depth) {
|
||||
(ExportFormat::Jpeg | ExportFormat::Webp | ExportFormat::Original | ExportFormat::Dng, _) => OutputDepth::U8,
|
||||
(ExportFormat::Png, _) if self.hdr => OutputDepth::F32Hdr,
|
||||
(ExportFormat::Png, Some(16 | 32)) => OutputDepth::U16,
|
||||
(ExportFormat::Png, _) => OutputDepth::U8,
|
||||
(ExportFormat::Tiff, Some(8)) => OutputDepth::U8,
|
||||
@@ -1018,15 +1020,20 @@ impl ExportOptions {
|
||||
}
|
||||
|
||||
/// Whether these options write HDR files (for photos edited in HDR): JPEG (gain map), AVIF
|
||||
/// (PQ) or 32-bit float TIFF with [`ExportOptions::hdr`].
|
||||
/// or PNG (PQ) or 32-bit float TIFF with [`ExportOptions::hdr`].
|
||||
pub fn hdr_output(&self) -> bool {
|
||||
self.hdr && matches!((self.format, self.bit_depth), (ExportFormat::Jpeg | ExportFormat::Avif, _) | (ExportFormat::Tiff, Some(32)))
|
||||
self.hdr
|
||||
&& matches!(
|
||||
(self.format, self.bit_depth),
|
||||
(ExportFormat::Jpeg | ExportFormat::Avif | ExportFormat::Png, _) | (ExportFormat::Tiff, Some(32))
|
||||
)
|
||||
}
|
||||
|
||||
/// The colour space the file is actually written in (AVIF: sRGB, or Rec. 2020 for HDR).
|
||||
/// The colour space the file is actually written in (AVIF: sRGB, or Rec. 2020 for HDR; HDR
|
||||
/// PNG: Rec. 2020).
|
||||
pub fn effective_space(&self) -> OutputSpace {
|
||||
match self.format {
|
||||
ExportFormat::Avif if self.hdr => OutputSpace::Rec2020,
|
||||
ExportFormat::Avif | ExportFormat::Png if self.hdr => OutputSpace::Rec2020,
|
||||
ExportFormat::Avif => OutputSpace::Srgb,
|
||||
_ => self.color_space,
|
||||
}
|
||||
@@ -1283,6 +1290,7 @@ pub fn encode_deep(img: &DeepImage, o: &ExportOptions, meta: Option<&Metadata>)
|
||||
DeepSamples::F32(v) => EncodeImage::new(w, h, 3, Samples::F32(v)),
|
||||
};
|
||||
let r = match (o.format, &img.samples) {
|
||||
(ExportFormat::Png, DeepSamples::F32(v)) if img.space == OutputSpace::Rec2020 => lightcraft_codecs::encode_png_pq(w, h, v, &meta),
|
||||
(ExportFormat::Png, _) => encode::encode_png(&e, &meta),
|
||||
(ExportFormat::Tiff, _) => encode::encode_tiff(&e, o.tiff_compression, &meta),
|
||||
(ExportFormat::Avif, DeepSamples::F32(v)) if img.space == OutputSpace::Rec2020 => {
|
||||
|
||||
Reference in New Issue
Block a user