Skip to content

Commit

Permalink
Add support for parsing cICP chunks.
Browse files Browse the repository at this point in the history
This commit is needed for parity with the existing PNG decoder
in Blink / Chromium - see https://crbug.com/376758571.
  • Loading branch information
anforowicz committed Nov 1, 2024
1 parent 6016c9b commit e88d94c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
///
Expand Down Expand Up @@ -557,6 +596,8 @@ pub struct Info<'a> {
pub srgb: Option<SrgbRenderingIntent>,
/// The ICC profile for the image.
pub icc_profile: Option<Cow<'a, [u8]>>,
/// The coding-independent code points for video signal type identification of the image.
pub coding_independent_code_points: Option<CodingIndependentCodePoints>,
/// The mastering display color volume for the image.
pub mastering_display_color_volume: Option<MasteringDisplayColorVolume>,
/// The content light information for the image.
Expand Down Expand Up @@ -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,
Expand Down
57 changes: 56 additions & 1 deletion src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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<CodingIndependentCodePoints, std::io::Error> {
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.
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit e88d94c

Please sign in to comment.