// The fixed-length ancillary chunks: read into fields once their CRC has
// passed, and left out (counted in `ignored_chunks`) when a value is out of
// what the specification allows. The decoder does not interpret them: it
// does not apply gamma or a color space, and leaves which of cICP, iCCP,
// sRGB and cHRM wins to the caller.
///|
/// tRNS (11.3.1.1): which pixels are transparent.
pub(all) enum Transparency {
/// For a grayscale image: the gray level, masked to the bit depth, that is
/// fully transparent.
Gray(Int32)
/// For a truecolor image: the red, green and blue samples, each masked to
/// the bit depth, of the color that is fully transparent.
Rgb(Int32, Int32, Int32)
/// For an indexed-color image: the alpha of each palette entry from the
/// first, at most one for each; entries past the end are opaque.
Alpha(ArrayView[Byte])
} derive(Eq, @debug.Debug)
///|
/// `Transparency::equal` and `Transparency::not_equal`, the derived
/// comparison.
pub extend Transparency with Eq::{equal, not_equal}
///|
/// `Transparency::to_repr`, the derived debug representation.
pub extend Transparency with @debug.Debug::{to_repr}
///|
/// cHRM (11.3.2.1): the chromaticities of the white point and the primaries,
/// CIE 1931 x and y, each times 100,000.
pub struct Chromaticities {
white_x : UInt32
white_y : UInt32
red_x : UInt32
red_y : UInt32
green_x : UInt32
green_y : UInt32
blue_x : UInt32
blue_y : UInt32
} derive(Eq, @debug.Debug)
///|
/// `Chromaticities::equal` and `Chromaticities::not_equal`, the derived
/// comparison.
pub extend Chromaticities with Eq::{equal, not_equal}
///|
/// `Chromaticities::to_repr`, the derived debug representation.
pub extend Chromaticities with @debug.Debug::{to_repr}
///|
/// cICP (11.3.2.6): the image's color space as code points of ITU-T H.273.
pub struct CodingPoints {
/// Color primaries (1 is BT.709 and sRGB, 9 BT.2020, 12 Display P3).
color_primaries : Int32
/// Transfer characteristics (13 is sRGB's, 16 PQ, 18 HLG).
transfer_function : Int32
/// Matrix coefficients: always 0, since PNG's samples are RGB.
matrix_coefficients : Int32
/// Whether the samples span their full range rather than a narrow one.
full_range : Bool
} derive(Eq, @debug.Debug)
///|
/// `CodingPoints::equal` and `CodingPoints::not_equal`, the derived
/// comparison.
pub extend CodingPoints with Eq::{equal, not_equal}
///|
/// `CodingPoints::to_repr`, the derived debug representation.
pub extend CodingPoints with @debug.Debug::{to_repr}
///|
/// mDCV (11.3.2.7): the display the content was mastered on (SMPTE ST
/// 2086). Chromaticities are CIE 1931 x and y in units of 0.00002,
/// luminances in units of 0.0001 cd/m².
pub struct MasteringDisplay {
red_x : Int32
red_y : Int32
green_x : Int32
green_y : Int32
blue_x : Int32
blue_y : Int32
white_x : Int32
white_y : Int32
max_luminance : UInt32
min_luminance : UInt32
} derive(Eq, @debug.Debug)
///|
/// `MasteringDisplay::equal` and `MasteringDisplay::not_equal`, the derived
/// comparison.
pub extend MasteringDisplay with Eq::{equal, not_equal}
///|
/// `MasteringDisplay::to_repr`, the derived debug representation.
pub extend MasteringDisplay with @debug.Debug::{to_repr}
///|
/// cLLI (11.3.2.8): the content's light levels, in units of 0.0001 cd/m².
pub struct LightLevel {
/// The brightest pixel (MaxCLL).
max_content : UInt32
/// The brightest frame's average (MaxFALL).
max_frame_average : UInt32
} derive(Eq, @debug.Debug)
///|
/// `LightLevel::equal` and `LightLevel::not_equal`, the derived comparison.
pub extend LightLevel with Eq::{equal, not_equal}
///|
/// `LightLevel::to_repr`, the derived debug representation.
pub extend LightLevel with @debug.Debug::{to_repr}
///|
/// pHYs (11.3.4.1): the pixels' size, or their aspect ratio.
pub struct PhysicalSize {
/// Pixels per unit, across.
x : UInt32
/// Pixels per unit, down.
y : UInt32
/// Whether the unit is the metre; else only the ratio is known.
per_meter : Bool
} derive(Eq, @debug.Debug)
///|
/// `PhysicalSize::equal` and `PhysicalSize::not_equal`, the derived
/// comparison.
pub extend PhysicalSize with Eq::{equal, not_equal}
///|
/// `PhysicalSize::to_repr`, the derived debug representation.
pub extend PhysicalSize with @debug.Debug::{to_repr}
///|
/// bKGD (11.3.4.2): a background color to show the image against.
pub(all) enum Background {
/// A gray level, for grayscale images with or without alpha.
Gray(Int32)
/// Red, green and blue samples, for truecolor images with or without
/// alpha.
Rgb(Int32, Int32, Int32)
/// A palette index, for indexed-color images.
Index(Int32)
} derive(Eq, @debug.Debug)
///|
/// `Background::equal` and `Background::not_equal`, the derived comparison.
pub extend Background with Eq::{equal, not_equal}
///|
/// `Background::to_repr`, the derived debug representation.
pub extend Background with @debug.Debug::{to_repr}
///|
/// The longest fixed-length chunk the decoder reads into fields: tRNS of an
/// indexed-color image, one byte for each of 256 entries.
const FIELD_BYTES : Int32 = 256
///|
/// Whether the chunk of `index` in `@rules.known` is one the decoder reads
/// into fields: cHRM, cICP, gAMA, mDCV, cLLI, sBIT, sRGB, bKGD, tRNS and
/// pHYs.
fn is_field(index : Int32) -> Bool {
index
is (@rules.CHRM
| @rules.CICP
| @rules.GAMA
| @rules.MDCV
| @rules.CLLI
| @rules.SBIT
| @rules.SRGB
| @rules.BKGD
| @rules.TRNS
| @rules.PHYS)
}
///|
/// What the fixed-length ancillary chunks said, once valid.
priv struct Fields {
mut trns : Transparency?
alpha : FixedArray[Byte]
sbit : FixedArray[Byte]
mut sbit_len : Int32
mut gamma : UInt32?
mut chromaticities : Chromaticities?
mut srgb : Int32?
mut cicp : CodingPoints?
mut mdcv : MasteringDisplay?
mut clli : LightLevel?
mut phys : PhysicalSize?
mut bkgd : Background?
}
///|
fn Fields::new() -> Fields {
{
trns: None,
alpha: FixedArray::make(256, b'\x00'),
sbit: FixedArray::make(4, b'\x00'),
sbit_len: 0,
gamma: None,
chromaticities: None,
srgb: None,
cicp: None,
mdcv: None,
clli: None,
phys: None,
bkgd: None,
}
}
///|
fn Fields::reset(self : Fields) -> Unit {
self.trns = None
self.sbit_len = 0
self.gamma = None
self.chromaticities = None
self.srgb = None
self.cicp = None
self.mdcv = None
self.clli = None
self.phys = None
self.bkgd = None
}
///|
fn u16(data : FixedArray[Byte], at : Int32) -> Int32 {
(data[at].to_int() << 8) | data[at + 1].to_int()
}
///|
fn u32(data : FixedArray[Byte], at : Int32) -> UInt32 {
(data[at].to_uint() << 24) |
(data[at + 1].to_uint() << 16) |
(data[at + 2].to_uint() << 8) |
data[at + 3].to_uint()
}
///|
/// Reads the chunk of `index`, whose `len` bytes are in `data`, into the
/// fields, for an image of `color_type` and `bit_depth` with `entries`
/// palette entries. Returns false, changing nothing, when the chunk's
/// length or values are not what the specification allows; the rules are
/// libpng's, but for tRNS's samples, whose bits above the bit depth are
/// left out where libpng keeps them and warns: libpng drops them too when
/// it expands rows, and WPT's trns-high-bits-set.png expects browsers to.
fn Fields::read(
self : Fields,
index : Int32,
data : FixedArray[Byte],
len : Int32,
color_type : Int32,
bit_depth : Int32,
entries : Int32,
) -> Bool {
let sample_max = if bit_depth == 16 { 0xFFFF } else { (1 << bit_depth) - 1 }
match index {
// cHRM: eight values, each at most 2^31 - 1.
@rules.CHRM => {
guard len == 32 else { return false }
let v = FixedArray::makei(8, i => u32(data, 4 * i))
guard v.iter().all(x => x <= 0x7FFFFFFF) else { return false }
self.chromaticities = Some({
white_x: v[0],
white_y: v[1],
red_x: v[2],
red_y: v[3],
green_x: v[4],
green_y: v[5],
blue_x: v[6],
blue_y: v[7],
})
}
// cICP: matrix coefficients 0, the range flag 0 or 1.
@rules.CICP => {
guard len == 4 && data[2] == b'\x00' && data[3] <= b'\x01' else {
return false
}
self.cicp = Some({
color_primaries: data[0].to_int(),
transfer_function: data[1].to_int(),
matrix_coefficients: 0,
full_range: data[3] == b'\x01',
})
}
// gAMA: from 1 to 2^31 - 1.
@rules.GAMA => {
guard len == 4 else { return false }
let gamma = u32(data, 0)
guard gamma > 0 && gamma <= 0x7FFFFFFF else { return false }
self.gamma = Some(gamma)
}
@rules.MDCV => {
guard len == 24 else { return false }
self.mdcv = Some({
red_x: u16(data, 0),
red_y: u16(data, 2),
green_x: u16(data, 4),
green_y: u16(data, 6),
blue_x: u16(data, 8),
blue_y: u16(data, 10),
white_x: u16(data, 12),
white_y: u16(data, 14),
max_luminance: u32(data, 16),
min_luminance: u32(data, 20),
})
}
@rules.CLLI => {
guard len == 8 else { return false }
self.clli = Some({
max_content: u32(data, 0),
max_frame_average: u32(data, 4),
})
}
// sBIT: one value for each channel (three for indexed-color), each from
// 1 to the samples' depth (8 for indexed-color).
@rules.SBIT => {
let channels = if color_type == 3 {
3
} else {
@rules.channels(color_type)
}
let depth = if color_type == 3 { 8 } else { bit_depth }
guard len == channels else { return false }
for i in 0..= 1 && data[i].to_int() <= depth else {
return false
}
}
data.blit_to(self.sbit, len~)
self.sbit_len = len
}
// sRGB: a rendering intent from 0 to 3.
@rules.SRGB => {
guard len == 1 && data[0] <= b'\x03' else { return false }
self.srgb = Some(data[0].to_int())
}
// bKGD: an index into the palette, or samples within the bit depth.
@rules.BKGD =>
match color_type {
3 => {
guard len == 1 && data[0].to_int() < entries else { return false }
self.bkgd = Some(Index(data[0].to_int()))
}
0 | 4 => {
guard len == 2 && u16(data, 0) <= sample_max else { return false }
self.bkgd = Some(Gray(u16(data, 0)))
}
_ => {
guard len == 6 else { return false }
let (r, g, b) = (u16(data, 0), u16(data, 2), u16(data, 4))
guard r <= sample_max && g <= sample_max && b <= sample_max else {
return false
}
self.bkgd = Some(Rgb(r, g, b))
}
}
// tRNS: for grayscale and truecolor one level or color; for
// indexed-color from 1 to the palette's entries; none with alpha.
@rules.TRNS =>
match color_type {
0 => {
guard len == 2 else { return false }
self.trns = Some(Gray(u16(data, 0) & sample_max))
}
2 => {
guard len == 6 else { return false }
self.trns = Some(
Rgb(
u16(data, 0) & sample_max,
u16(data, 2) & sample_max,
u16(data, 4) & sample_max,
),
)
}
3 => {
guard len >= 1 && len <= entries else { return false }
data.blit_to(self.alpha, len~)
self.trns = Some(Alpha(self.alpha[:len]))
}
_ => return false
}
// pHYs: a unit of 0 (none) or 1 (the metre).
_ => {
guard len == 9 && data[8] <= b'\x01' else { return false }
self.phys = Some({
x: u32(data, 0),
y: u32(data, 4),
per_meter: data[8] == b'\x01',
})
}
}
true
}
///|
/// tRNS: which pixels are transparent, or `None` without a valid tRNS.
/// Valid from `Frame` until `reset`.
pub fn Decoder::transparency(self : Decoder) -> Transparency? {
self.fields.trns
}
///|
/// sBIT: the significant bits of each channel's samples, in the order of
/// the channels (red, green and blue for indexed-color), or empty without a
/// valid sBIT. Valid from `Frame` until `reset`.
pub fn Decoder::significant_bits(self : Decoder) -> ArrayView[Byte] {
self.fields.sbit[:self.fields.sbit_len]
}
///|
/// gAMA: the image's gamma times 100,000 (45,455 for 1/2.2), or `None`.
/// Valid from `Frame` until `reset`.
pub fn Decoder::gamma(self : Decoder) -> UInt32? {
self.fields.gamma
}
///|
/// cHRM: the chromaticities of the white point and the primaries, or
/// `None`. Valid from `Frame` until `reset`.
pub fn Decoder::chromaticities(self : Decoder) -> Chromaticities? {
self.fields.chromaticities
}
///|
/// sRGB: the rendering intent, from 0 (perceptual) to 3 (absolute
/// colorimetric), or `None` without a valid sRGB. Valid from `Frame` until
/// `reset`.
pub fn Decoder::srgb_intent(self : Decoder) -> Int32? {
self.fields.srgb
}
///|
/// cICP: the color space's code points, or `None`. Valid from `Frame`
/// until `reset`.
pub fn Decoder::coding_points(self : Decoder) -> CodingPoints? {
self.fields.cicp
}
///|
/// mDCV: the display the content was mastered on, or `None`. Valid from
/// `Frame` until `reset`.
pub fn Decoder::mastering_display(self : Decoder) -> MasteringDisplay? {
self.fields.mdcv
}
///|
/// cLLI: the content's light levels, or `None`. Valid from `Frame` until
/// `reset`.
pub fn Decoder::light_level(self : Decoder) -> LightLevel? {
self.fields.clli
}
///|
/// pHYs: the pixels' size or aspect ratio, or `None`. Valid from `Frame`
/// until `reset`.
pub fn Decoder::physical_size(self : Decoder) -> PhysicalSize? {
self.fields.phys
}
///|
/// bKGD: the background color, or `None`. Valid from `Frame` until `reset`.
pub fn Decoder::background(self : Decoder) -> Background? {
self.fields.bkgd
}
///|
/// The type of the chunk whose block the last `Metadata` wrote, as its four
/// letters. The block is the chunk's data, but for the inflated ones:
/// iCCP's is the profile's name, a NUL and the profile; zTXt's is the
/// keyword, a NUL and the text, as tEXt's is; iTXt's is the chunk's data
/// with the text inflated and the compression flag and method set to 0.
/// Valid after `Metadata`, until the next step.
pub fn Decoder::metadata_type(self : Decoder) -> String {
@rules.type_name(self.metadata_chunk)
}