1From 6016c9bee0fbd6244ebe49ed19a25094898c8001 Mon Sep 17 00:00:00 2001
2From: Lukasz Anforowicz <[email protected]>
3Date: Wed, 30 Oct 2024 20:45:18 -0700
4Subject: [PATCH 301/302] Add support for parsing `mDCv` and `cLLi` chunks.
5 (#528)
6
7---
8 Cargo.toml                 |   1 +
9 src/chunk.rs               |   4 ++
10 src/common.rs              |  56 +++++++++++++++++++
11 src/decoder/stream.rs      | 108 ++++++++++++++++++++++++++++++++++++-
12 tests/bugfixes/cicp_pq.png | Bin 0 -> 191 bytes
13 tests/results.txt          |   1 +
14 tests/results_alpha.txt    |   1 +
15 tests/results_identity.txt |   1 +
16 8 files changed, 170 insertions(+), 2 deletions(-)
17 create mode 100644 tests/bugfixes/cicp_pq.png
18
19diff --git a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/chunk.rs b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/chunk.rs
20index 3908313..34a088f 100644
21--- a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/chunk.rs
22+++ b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/chunk.rs
23@@ -35,6 +35,10 @@ pub const gAMA: ChunkType = ChunkType(*b"gAMA");
24 pub const sRGB: ChunkType = ChunkType(*b"sRGB");
25 /// ICC profile chunk
26 pub const iCCP: ChunkType = ChunkType(*b"iCCP");
27+/// Mastering Display Color Volume chunk
28+pub const mDCv: ChunkType = ChunkType(*b"mDCv");
29+/// Content Light Level Information chunk
30+pub const cLLi: ChunkType = ChunkType(*b"cLLi");
31 /// EXIF metadata chunk
32 pub const eXIf: ChunkType = ChunkType(*b"eXIf");
33 /// Latin-1 uncompressed textual data
34diff --git a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/common.rs b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/common.rs
35index 259a2b1..4c06e3b 100644
36--- a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/common.rs
37+++ b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/common.rs
38@@ -470,6 +470,56 @@ impl SrgbRenderingIntent {
39     }
40 }
41
42+/// Mastering Display Color Volume (mDCv) used at the point of content creation,
43+/// as specified in [SMPTE-ST-2086](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8353899).
44+///
45+/// See https://www.w3.org/TR/png-3/#mDCv-chunk for more details.
46+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47+pub struct MasteringDisplayColorVolume {
48+    /// Mastering display chromaticities.
49+    pub chromaticities: SourceChromaticities,
50+
51+    /// Mastering display maximum luminance.
52+    ///
53+    /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
54+    /// is set to `10000000` then it indicates 1000 cd/m^2.
55+    pub max_luminance: u32,
56+
57+    /// Mastering display minimum luminance.
58+    ///
59+    /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
60+    /// is set to `10000000` then it indicates 1000 cd/m^2.
61+    pub min_luminance: u32,
62+}
63+
64+/// Content light level information of HDR content.
65+///
66+/// See https://www.w3.org/TR/png-3/#cLLi-chunk for more details.
67+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68+pub struct ContentLightLevelInfo {
69+    /// Maximum Content Light Level indicates the maximum light level of any
70+    /// single pixel (in cd/m^2, also known as nits) of the entire playback
71+    /// sequence.
72+    ///
73+    /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
74+    /// is set to `10000000` then it indicates 1000 cd/m^2.
75+    ///
76+    /// A value of zero means that the value is unknown or not currently calculable.
77+    pub max_content_light_level: u32,
78+
79+    /// Maximum Frame Average Light Level indicates the maximum value of the
80+    /// frame average light level (in cd/m^2, also known as nits) of the entire
81+    /// playback sequence. It is calculated by first averaging the decoded
82+    /// luminance values of all the pixels in each frame, and then using the
83+    /// value for the frame with the highest value.
84+    ///
85+    /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
86+    /// is set to `10000000` then it indicates 1000 cd/m^2.
87+    ///
88+    /// A value of zero means that the value is unknown or not currently calculable.
89+    pub max_frame_average_light_level: u32,
90+}
91+
92 /// PNG info struct
93 #[derive(Clone, Debug)]
94 #[non_exhaustive]
95@@ -507,6 +557,10 @@ pub struct Info<'a> {
96     pub srgb: Option<SrgbRenderingIntent>,
97     /// The ICC profile for the image.
98     pub icc_profile: Option<Cow<'a, [u8]>>,
99+    /// The mastering display color volume for the image.
100+    pub mastering_display_color_volume: Option<MasteringDisplayColorVolume>,
101+    /// The content light information for the image.
102+    pub content_light_level: Option<ContentLightLevelInfo>,
103     /// The EXIF metadata for the image.
104     pub exif_metadata: Option<Cow<'a, [u8]>>,
105     /// tEXt field
106@@ -539,6 +593,8 @@ impl Default for Info<'_> {
107             source_chromaticities: None,
108             srgb: None,
109             icc_profile: None,
110+            mastering_display_color_volume: None,
111+            content_light_level: None,
112             exif_metadata: None,
113             uncompressed_latin1_text: Vec::new(),
114             compressed_latin1_text: Vec::new(),
115diff --git a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
116index 5c21a5a..68de12d 100644
117--- a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
118+++ b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
119@@ -9,8 +9,9 @@ use crc32fast::Hasher as Crc32;
120 use super::zlib::ZlibStream;
121 use crate::chunk::{self, ChunkType, IDAT, IEND, IHDR};
122 use crate::common::{
123-    AnimationControl, BitDepth, BlendOp, ColorType, DisposeOp, FrameControl, Info, ParameterError,
124-    ParameterErrorKind, PixelDimensions, ScaledFloat, SourceChromaticities, Unit,
125+    AnimationControl, BitDepth, BlendOp, ColorType, ContentLightLevelInfo, DisposeOp, FrameControl,
126+    Info, MasteringDisplayColorVolume, ParameterError, ParameterErrorKind, PixelDimensions,
127+    ScaledFloat, SourceChromaticities, Unit,
128 };
129 use crate::text_metadata::{ITXtChunk, TEXtChunk, TextDecodingError, ZTXtChunk};
130 use crate::traits::ReadBytesExt;
131@@ -958,6 +959,8 @@ impl StreamingDecoder {
132             chunk::fcTL => self.parse_fctl(),
133             chunk::cHRM => self.parse_chrm(),
134             chunk::sRGB => self.parse_srgb(),
135+            chunk::mDCv => Ok(self.parse_mdcv()),
136+            chunk::cLLi => Ok(self.parse_clli()),
137             chunk::iCCP if !self.decode_options.ignore_iccp_chunk => self.parse_iccp(),
138             chunk::tEXt if !self.decode_options.ignore_text_chunk => self.parse_text(),
139             chunk::zTXt if !self.decode_options.ignore_text_chunk => self.parse_ztxt(),
140@@ -1271,6 +1274,82 @@ impl StreamingDecoder {
141         }
142     }
143
144+    // NOTE: This function cannot return `DecodingError` and handles parsing
145+    // errors or spec violations as-if the chunk was missing.  See
146+    // https://github.com/image-rs/image-png/issues/525 for more discussion.
147+    fn parse_mdcv(&mut self) -> Decoded {
148+        fn parse(mut buf: &[u8]) -> Result<MasteringDisplayColorVolume, std::io::Error> {
149+            let red_x: u16 = buf.read_be()?;
150+            let red_y: u16 = buf.read_be()?;
151+            let green_x: u16 = buf.read_be()?;
152+            let green_y: u16 = buf.read_be()?;
153+            let blue_x: u16 = buf.read_be()?;
154+            let blue_y: u16 = buf.read_be()?;
155+            let white_x: u16 = buf.read_be()?;
156+            let white_y: u16 = buf.read_be()?;
157+            fn scale(chunk: u16) -> ScaledFloat {
158+                // `ScaledFloat::SCALING` is hardcoded to 100_000, which works
159+                // well for the `cHRM` chunk where the spec says that "a value
160+                // of 0.3127 would be stored as the integer 31270".  In the
161+                // `mDCv` chunk the spec says that "0.708, 0.292)" is stored as
162+                // "{ 35400, 14600 }", using a scaling factor of 50_000, so we
163+                // multiply by 2 before converting.
164+                ScaledFloat::from_scaled((chunk as u32) * 2)
165+            }
166+            let chromaticities = SourceChromaticities {
167+                white: (scale(white_x), scale(white_y)),
168+                red: (scale(red_x), scale(red_y)),
169+                green: (scale(green_x), scale(green_y)),
170+                blue: (scale(blue_x), scale(blue_y)),
171+            };
172+            let max_luminance: u32 = buf.read_be()?;
173+            let min_luminance: u32 = buf.read_be()?;
174+            if !buf.is_empty() {
175+                return Err(std::io::ErrorKind::InvalidData.into());
176+            }
177+            Ok(MasteringDisplayColorVolume {
178+                chromaticities,
179+                max_luminance,
180+                min_luminance,
181+            })
182+        }
183+
184+        // The spec requires that the mDCv chunk MUST come before the PLTE and IDAT chunks.
185+        // Additionally, we ignore a second, duplicated mDCv chunk (if any).
186+        let info = self.info.as_mut().unwrap();
187+        let is_before_plte_and_idat = !self.have_idat && info.palette.is_none();
188+        if is_before_plte_and_idat && info.mastering_display_color_volume.is_none() {
189+            info.mastering_display_color_volume = parse(&self.current_chunk.raw_bytes[..]).ok();
190+        }
191+
192+        Decoded::Nothing
193+    }
194+
195+    // NOTE: This function cannot return `DecodingError` and handles parsing
196+    // errors or spec violations as-if the chunk was missing.  See
197+    // https://github.com/image-rs/image-png/issues/525 for more discussion.
198+    fn parse_clli(&mut self) -> Decoded {
199+        fn parse(mut buf: &[u8]) -> Result<ContentLightLevelInfo, std::io::Error> {
200+            let max_content_light_level: u32 = buf.read_be()?;
201+            let max_frame_average_light_level: u32 = buf.read_be()?;
202+            if !buf.is_empty() {
203+                return Err(std::io::ErrorKind::InvalidData.into());
204+            }
205+            Ok(ContentLightLevelInfo {
206+                max_content_light_level,
207+                max_frame_average_light_level,
208+            })
209+        }
210+
211+        // We ignore a second, duplicated cLLi chunk (if any).
212+        let info = self.info.as_mut().unwrap();
213+        if info.content_light_level.is_none() {
214+            info.content_light_level = parse(&self.current_chunk.raw_bytes[..]).ok();
215+        }
216+
217+        Decoded::Nothing
218+    }
219+
220     fn parse_iccp(&mut self) -> Result<Decoded, DecodingError> {
221         if self.have_idat {
222             Err(DecodingError::Format(
223--
2242.47.0.199.ga7371fff76-goog
225
226