Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parsing cICP chunks. #529

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading