Skip to content

Commit

Permalink
Add support for parsing cICP chunks. (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
anforowicz authored Nov 1, 2024
1 parent 6016c9b commit e912074
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 e912074

Please sign in to comment.