diff --git a/src/chunk.rs b/src/chunk.rs index 34a088f5..b83ce54c 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -35,6 +35,8 @@ pub const gAMA: ChunkType = ChunkType(*b"gAMA"); pub const sRGB: ChunkType = ChunkType(*b"sRGB"); /// ICC profile chunk pub const iCCP: ChunkType = ChunkType(*b"iCCP"); +/// Coding-independent code points for video signal type identification chunk +pub const cICP: ChunkType = ChunkType(*b"cICP"); /// Mastering Display Color Volume chunk pub const mDCv: ChunkType = ChunkType(*b"mDCv"); /// Content Light Level Information chunk diff --git a/src/common.rs b/src/common.rs index 4c06e3b5..3d9dc13b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -470,6 +470,45 @@ impl SrgbRenderingIntent { } } +/// Coding-independent code points (cICP) specify the color space (primaries), +/// transfer function, matrix coefficients and scaling factor of the image using +/// the code points specified in [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273). +/// +/// See https://www.w3.org/TR/png-3/#cICP-chunk for more details. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CodingIndependentCodePoints { + /// Id number of the color primaries defined in + /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 2 - + /// Interpretation of colour primaries (ColourPrimaries) value". + pub color_primaries: u8, + + /// Id number of the transfer characteristics defined in + /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 3 - + /// Interpretation of transfer characteristics (TransferCharacteristics) + /// value". + pub transfer_function: u8, + + /// Id number of the matrix coefficients defined in + /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 4 - + /// Interpretation of matrix coefficients (MatrixCoefficients) value". + /// + /// This field is included to faithfully replicate the base + /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) specification, but matrix coefficients + /// will always be set to 0, because RGB is currently the only supported color mode in PNG. + pub matrix_coefficients: u8, + + /// Whether the image is + /// [a full range image](https://www.w3.org/TR/png-3/#dfn-full-range-image) + /// or + /// [a narrow range image](https://www.w3.org/TR/png-3/#dfn-narrow-range-image). + /// + /// This field is included to faithfully replicate the base + /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) specification, but it has limited + /// practical application to PNG images, because narrow-range images are [quite + /// rare](https://github.com/w3c/png/issues/312#issuecomment-2327349614) in practice. + pub is_video_full_range_image: bool, +} + /// Mastering Display Color Volume (mDCv) used at the point of content creation, /// as specified in [SMPTE-ST-2086](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8353899). /// @@ -557,6 +596,8 @@ pub struct Info<'a> { pub srgb: Option, /// The ICC profile for the image. pub icc_profile: Option>, + /// The coding-independent code points for video signal type identification of the image. + pub coding_independent_code_points: Option, /// The mastering display color volume for the image. pub mastering_display_color_volume: Option, /// The content light information for the image. @@ -593,6 +634,7 @@ impl Default for Info<'_> { source_chromaticities: None, srgb: None, icc_profile: None, + coding_independent_code_points: None, mastering_display_color_volume: None, content_light_level: None, exif_metadata: None, diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 68de12d7..760d5fdb 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -15,7 +15,7 @@ use crate::common::{ }; use crate::text_metadata::{ITXtChunk, TEXtChunk, TextDecodingError, ZTXtChunk}; use crate::traits::ReadBytesExt; -use crate::Limits; +use crate::{CodingIndependentCodePoints, Limits}; /// TODO check if these size are reasonable pub const CHUNK_BUFFER_SIZE: usize = 32 * 1024; @@ -959,6 +959,7 @@ impl StreamingDecoder { chunk::fcTL => self.parse_fctl(), chunk::cHRM => self.parse_chrm(), chunk::sRGB => self.parse_srgb(), + chunk::cICP => Ok(self.parse_cicp()), chunk::mDCv => Ok(self.parse_mdcv()), chunk::cLLi => Ok(self.parse_clli()), chunk::iCCP if !self.decode_options.ignore_iccp_chunk => self.parse_iccp(), @@ -1274,6 +1275,54 @@ impl StreamingDecoder { } } + // NOTE: This function cannot return `DecodingError` and handles parsing + // errors or spec violations as-if the chunk was missing. See + // https://github.com/image-rs/image-png/issues/525 for more discussion. + fn parse_cicp(&mut self) -> Decoded { + fn parse(mut buf: &[u8]) -> Result { + let color_primaries: u8 = buf.read_be()?; + let transfer_function: u8 = buf.read_be()?; + let matrix_coefficients: u8 = buf.read_be()?; + let is_video_full_range_image = { + let flag: u8 = buf.read_be()?; + match flag { + 0 => false, + 1 => true, + _ => { + return Err(std::io::ErrorKind::InvalidData.into()); + } + } + }; + + // RGB is currently the only supported color model in PNG, and as + // such Matrix Coefficients shall be set to 0. + if matrix_coefficients != 0 { + return Err(std::io::ErrorKind::InvalidData.into()); + } + + if !buf.is_empty() { + return Err(std::io::ErrorKind::InvalidData.into()); + } + + Ok(CodingIndependentCodePoints { + color_primaries, + transfer_function, + matrix_coefficients, + is_video_full_range_image, + }) + } + + // The spec requires that the cICP chunk MUST come before the PLTE and IDAT chunks. + // Additionally, we ignore a second, duplicated cICP chunk (if any). + let info = self.info.as_mut().unwrap(); + let is_before_plte_and_idat = !self.have_idat && info.palette.is_none(); + if is_before_plte_and_idat && info.coding_independent_code_points.is_none() { + info.coding_independent_code_points = parse(&self.current_chunk.raw_bytes[..]).ok(); + } + + Decoded::Nothing + } + // NOTE: This function cannot return `DecodingError` and handles parsing // errors or spec violations as-if the chunk was missing. See // https://github.com/image-rs/image-png/issues/525 for more discussion. @@ -1927,6 +1976,12 @@ mod tests { let reader = decoder.read_info().unwrap(); let info = reader.info(); + let cicp = info.coding_independent_code_points.unwrap(); + assert_eq!(cicp.color_primaries, 9); + assert_eq!(cicp.transfer_function, 16); + assert_eq!(cicp.matrix_coefficients, 0); + assert!(cicp.is_video_full_range_image); + let mdcv = info.mastering_display_color_volume.unwrap(); assert_relative_eq!(mdcv.chromaticities.red.0.into_value(), 0.680); assert_relative_eq!(mdcv.chromaticities.red.1.into_value(), 0.320);