diff options
Diffstat (limited to 'vendor/png/src/common.rs')
-rw-r--r-- | vendor/png/src/common.rs | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/vendor/png/src/common.rs b/vendor/png/src/common.rs new file mode 100644 index 0000000..6e5dbff --- /dev/null +++ b/vendor/png/src/common.rs @@ -0,0 +1,808 @@ +//! Common types shared between the encoder and decoder +use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk}; +use crate::{chunk, encoder}; +use io::Write; +use std::{borrow::Cow, convert::TryFrom, fmt, io}; + +/// Describes how a pixel is encoded. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ColorType { + /// 1 grayscale sample. + Grayscale = 0, + /// 1 red sample, 1 green sample, 1 blue sample. + Rgb = 2, + /// 1 sample for the palette index. + Indexed = 3, + /// 1 grayscale sample, then 1 alpha sample. + GrayscaleAlpha = 4, + /// 1 red sample, 1 green sample, 1 blue sample, and finally, 1 alpha sample. + Rgba = 6, +} + +impl ColorType { + /// Returns the number of samples used per pixel encoded in this way. + pub fn samples(self) -> usize { + self.samples_u8().into() + } + + pub(crate) fn samples_u8(self) -> u8 { + use self::ColorType::*; + match self { + Grayscale | Indexed => 1, + Rgb => 3, + GrayscaleAlpha => 2, + Rgba => 4, + } + } + + /// u8 -> Self. Temporary solution until Rust provides a canonical one. + pub fn from_u8(n: u8) -> Option<ColorType> { + match n { + 0 => Some(ColorType::Grayscale), + 2 => Some(ColorType::Rgb), + 3 => Some(ColorType::Indexed), + 4 => Some(ColorType::GrayscaleAlpha), + 6 => Some(ColorType::Rgba), + _ => None, + } + } + + pub(crate) fn checked_raw_row_length(self, depth: BitDepth, width: u32) -> Option<usize> { + // No overflow can occur in 64 bits, we multiply 32-bit with 5 more bits. + let bits = u64::from(width) * u64::from(self.samples_u8()) * u64::from(depth.into_u8()); + TryFrom::try_from(1 + (bits + 7) / 8).ok() + } + + pub(crate) fn raw_row_length_from_width(self, depth: BitDepth, width: u32) -> usize { + let samples = width as usize * self.samples(); + 1 + match depth { + BitDepth::Sixteen => samples * 2, + BitDepth::Eight => samples, + subbyte => { + let samples_per_byte = 8 / subbyte as usize; + let whole = samples / samples_per_byte; + let fract = usize::from(samples % samples_per_byte > 0); + whole + fract + } + } + } + + pub(crate) fn is_combination_invalid(self, bit_depth: BitDepth) -> bool { + // Section 11.2.2 of the PNG standard disallows several combinations + // of bit depth and color type + ((bit_depth == BitDepth::One || bit_depth == BitDepth::Two || bit_depth == BitDepth::Four) + && (self == ColorType::Rgb + || self == ColorType::GrayscaleAlpha + || self == ColorType::Rgba)) + || (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed) + } +} + +/// Bit depth of the PNG file. +/// Specifies the number of bits per sample. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum BitDepth { + One = 1, + Two = 2, + Four = 4, + Eight = 8, + Sixteen = 16, +} + +/// Internal count of bytes per pixel. +/// This is used for filtering which never uses sub-byte units. This essentially reduces the number +/// of possible byte chunk lengths to a very small set of values appropriate to be defined as an +/// enum. +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub(crate) enum BytesPerPixel { + One = 1, + Two = 2, + Three = 3, + Four = 4, + Six = 6, + Eight = 8, +} + +impl BitDepth { + /// u8 -> Self. Temporary solution until Rust provides a canonical one. + pub fn from_u8(n: u8) -> Option<BitDepth> { + match n { + 1 => Some(BitDepth::One), + 2 => Some(BitDepth::Two), + 4 => Some(BitDepth::Four), + 8 => Some(BitDepth::Eight), + 16 => Some(BitDepth::Sixteen), + _ => None, + } + } + + pub(crate) fn into_u8(self) -> u8 { + self as u8 + } +} + +/// Pixel dimensions information +#[derive(Clone, Copy, Debug)] +pub struct PixelDimensions { + /// Pixels per unit, X axis + pub xppu: u32, + /// Pixels per unit, Y axis + pub yppu: u32, + /// Either *Meter* or *Unspecified* + pub unit: Unit, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +/// Physical unit of the pixel dimensions +pub enum Unit { + Unspecified = 0, + Meter = 1, +} + +impl Unit { + /// u8 -> Self. Temporary solution until Rust provides a canonical one. + pub fn from_u8(n: u8) -> Option<Unit> { + match n { + 0 => Some(Unit::Unspecified), + 1 => Some(Unit::Meter), + _ => None, + } + } +} + +/// How to reset buffer of an animated png (APNG) at the end of a frame. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum DisposeOp { + /// Leave the buffer unchanged. + None = 0, + /// Clear buffer with the background color. + Background = 1, + /// Reset the buffer to the state before the current frame. + Previous = 2, +} + +impl DisposeOp { + /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now. + pub fn from_u8(n: u8) -> Option<DisposeOp> { + match n { + 0 => Some(DisposeOp::None), + 1 => Some(DisposeOp::Background), + 2 => Some(DisposeOp::Previous), + _ => None, + } + } +} + +impl fmt::Display for DisposeOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = match *self { + DisposeOp::None => "DISPOSE_OP_NONE", + DisposeOp::Background => "DISPOSE_OP_BACKGROUND", + DisposeOp::Previous => "DISPOSE_OP_PREVIOUS", + }; + write!(f, "{}", name) + } +} + +/// How pixels are written into the buffer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum BlendOp { + /// Pixels overwrite the value at their position. + Source = 0, + /// The new pixels are blended into the current state based on alpha. + Over = 1, +} + +impl BlendOp { + /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now. + pub fn from_u8(n: u8) -> Option<BlendOp> { + match n { + 0 => Some(BlendOp::Source), + 1 => Some(BlendOp::Over), + _ => None, + } + } +} + +impl fmt::Display for BlendOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = match *self { + BlendOp::Source => "BLEND_OP_SOURCE", + BlendOp::Over => "BLEND_OP_OVER", + }; + write!(f, "{}", name) + } +} + +/// Frame control information +#[derive(Clone, Copy, Debug)] +pub struct FrameControl { + /// Sequence number of the animation chunk, starting from 0 + pub sequence_number: u32, + /// Width of the following frame + pub width: u32, + /// Height of the following frame + pub height: u32, + /// X position at which to render the following frame + pub x_offset: u32, + /// Y position at which to render the following frame + pub y_offset: u32, + /// Frame delay fraction numerator + pub delay_num: u16, + /// Frame delay fraction denominator + pub delay_den: u16, + /// Type of frame area disposal to be done after rendering this frame + pub dispose_op: DisposeOp, + /// Type of frame area rendering for this frame + pub blend_op: BlendOp, +} + +impl Default for FrameControl { + fn default() -> FrameControl { + FrameControl { + sequence_number: 0, + width: 0, + height: 0, + x_offset: 0, + y_offset: 0, + delay_num: 1, + delay_den: 30, + dispose_op: DisposeOp::None, + blend_op: BlendOp::Source, + } + } +} + +impl FrameControl { + pub fn set_seq_num(&mut self, s: u32) { + self.sequence_number = s; + } + + pub fn inc_seq_num(&mut self, i: u32) { + self.sequence_number += i; + } + + pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> { + let mut data = [0u8; 26]; + data[..4].copy_from_slice(&self.sequence_number.to_be_bytes()); + data[4..8].copy_from_slice(&self.width.to_be_bytes()); + data[8..12].copy_from_slice(&self.height.to_be_bytes()); + data[12..16].copy_from_slice(&self.x_offset.to_be_bytes()); + data[16..20].copy_from_slice(&self.y_offset.to_be_bytes()); + data[20..22].copy_from_slice(&self.delay_num.to_be_bytes()); + data[22..24].copy_from_slice(&self.delay_den.to_be_bytes()); + data[24] = self.dispose_op as u8; + data[25] = self.blend_op as u8; + + encoder::write_chunk(w, chunk::fcTL, &data) + } +} + +/// Animation control information +#[derive(Clone, Copy, Debug)] +pub struct AnimationControl { + /// Number of frames + pub num_frames: u32, + /// Number of times to loop this APNG. 0 indicates infinite looping. + pub num_plays: u32, +} + +impl AnimationControl { + pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> { + let mut data = [0; 8]; + data[..4].copy_from_slice(&self.num_frames.to_be_bytes()); + data[4..].copy_from_slice(&self.num_plays.to_be_bytes()); + encoder::write_chunk(w, chunk::acTL, &data) + } +} + +/// The type and strength of applied compression. +#[derive(Debug, Clone, Copy)] +pub enum Compression { + /// Default level + Default, + /// Fast minimal compression + Fast, + /// Higher compression level + /// + /// Best in this context isn't actually the highest possible level + /// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2` + /// library. + Best, + #[deprecated( + since = "0.17.6", + note = "use one of the other compression levels instead, such as 'fast'" + )] + Huffman, + #[deprecated( + since = "0.17.6", + note = "use one of the other compression levels instead, such as 'fast'" + )] + Rle, +} + +impl Default for Compression { + fn default() -> Self { + Self::Default + } +} + +/// An unsigned integer scaled version of a floating point value, +/// equivalent to an integer quotient with fixed denominator (100_000)). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ScaledFloat(u32); + +impl ScaledFloat { + const SCALING: f32 = 100_000.0; + + /// Gets whether the value is within the clamped range of this type. + pub fn in_range(value: f32) -> bool { + value >= 0.0 && (value * Self::SCALING).floor() <= std::u32::MAX as f32 + } + + /// Gets whether the value can be exactly converted in round-trip. + #[allow(clippy::float_cmp)] // Stupid tool, the exact float compare is _the entire point_. + pub fn exact(value: f32) -> bool { + let there = Self::forward(value); + let back = Self::reverse(there); + value == back + } + + fn forward(value: f32) -> u32 { + (value.max(0.0) * Self::SCALING).floor() as u32 + } + + fn reverse(encoded: u32) -> f32 { + encoded as f32 / Self::SCALING + } + + /// Slightly inaccurate scaling and quantization. + /// Clamps the value into the representable range if it is negative or too large. + pub fn new(value: f32) -> Self { + Self(Self::forward(value)) + } + + /// Fully accurate construction from a value scaled as per specification. + pub fn from_scaled(val: u32) -> Self { + Self(val) + } + + /// Get the accurate encoded value. + pub fn into_scaled(self) -> u32 { + self.0 + } + + /// Get the unscaled value as a floating point. + pub fn into_value(self) -> f32 { + Self::reverse(self.0) + } + + pub(crate) fn encode_gama<W: Write>(self, w: &mut W) -> encoder::Result<()> { + encoder::write_chunk(w, chunk::gAMA, &self.into_scaled().to_be_bytes()) + } +} + +/// Chromaticities of the color space primaries +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SourceChromaticities { + pub white: (ScaledFloat, ScaledFloat), + pub red: (ScaledFloat, ScaledFloat), + pub green: (ScaledFloat, ScaledFloat), + pub blue: (ScaledFloat, ScaledFloat), +} + +impl SourceChromaticities { + pub fn new(white: (f32, f32), red: (f32, f32), green: (f32, f32), blue: (f32, f32)) -> Self { + SourceChromaticities { + white: (ScaledFloat::new(white.0), ScaledFloat::new(white.1)), + red: (ScaledFloat::new(red.0), ScaledFloat::new(red.1)), + green: (ScaledFloat::new(green.0), ScaledFloat::new(green.1)), + blue: (ScaledFloat::new(blue.0), ScaledFloat::new(blue.1)), + } + } + + #[rustfmt::skip] + pub fn to_be_bytes(self) -> [u8; 32] { + let white_x = self.white.0.into_scaled().to_be_bytes(); + let white_y = self.white.1.into_scaled().to_be_bytes(); + let red_x = self.red.0.into_scaled().to_be_bytes(); + let red_y = self.red.1.into_scaled().to_be_bytes(); + let green_x = self.green.0.into_scaled().to_be_bytes(); + let green_y = self.green.1.into_scaled().to_be_bytes(); + let blue_x = self.blue.0.into_scaled().to_be_bytes(); + let blue_y = self.blue.1.into_scaled().to_be_bytes(); + [ + white_x[0], white_x[1], white_x[2], white_x[3], + white_y[0], white_y[1], white_y[2], white_y[3], + red_x[0], red_x[1], red_x[2], red_x[3], + red_y[0], red_y[1], red_y[2], red_y[3], + green_x[0], green_x[1], green_x[2], green_x[3], + green_y[0], green_y[1], green_y[2], green_y[3], + blue_x[0], blue_x[1], blue_x[2], blue_x[3], + blue_y[0], blue_y[1], blue_y[2], blue_y[3], + ] + } + + pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> { + encoder::write_chunk(w, chunk::cHRM, &self.to_be_bytes()) + } +} + +/// The rendering intent for an sRGB image. +/// +/// Presence of this data also indicates that the image conforms to the sRGB color space. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SrgbRenderingIntent { + /// For images preferring good adaptation to the output device gamut at the expense of colorimetric accuracy, such as photographs. + Perceptual = 0, + /// For images requiring colour appearance matching (relative to the output device white point), such as logos. + RelativeColorimetric = 1, + /// For images preferring preservation of saturation at the expense of hue and lightness, such as charts and graphs. + Saturation = 2, + /// For images requiring preservation of absolute colorimetry, such as previews of images destined for a different output device (proofs). + AbsoluteColorimetric = 3, +} + +impl SrgbRenderingIntent { + pub(crate) fn into_raw(self) -> u8 { + self as u8 + } + + pub(crate) fn from_raw(raw: u8) -> Option<Self> { + match raw { + 0 => Some(SrgbRenderingIntent::Perceptual), + 1 => Some(SrgbRenderingIntent::RelativeColorimetric), + 2 => Some(SrgbRenderingIntent::Saturation), + 3 => Some(SrgbRenderingIntent::AbsoluteColorimetric), + _ => None, + } + } + + pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> { + encoder::write_chunk(w, chunk::sRGB, &[self.into_raw()]) + } +} + +/// PNG info struct +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct Info<'a> { + pub width: u32, + pub height: u32, + pub bit_depth: BitDepth, + /// How colors are stored in the image. + pub color_type: ColorType, + pub interlaced: bool, + /// The image's `tRNS` chunk, if present; contains the alpha channel of the image's palette, 1 byte per entry. + pub trns: Option<Cow<'a, [u8]>>, + pub pixel_dims: Option<PixelDimensions>, + /// The image's `PLTE` chunk, if present; contains the RGB channels (in that order) of the image's palettes, 3 bytes per entry (1 per channel). + pub palette: Option<Cow<'a, [u8]>>, + /// The contents of the image's gAMA chunk, if present. + /// Prefer `source_gamma` to also get the derived replacement gamma from sRGB chunks. + pub gama_chunk: Option<ScaledFloat>, + /// The contents of the image's `cHRM` chunk, if present. + /// Prefer `source_chromaticities` to also get the derived replacements from sRGB chunks. + pub chrm_chunk: Option<SourceChromaticities>, + + pub frame_control: Option<FrameControl>, + pub animation_control: Option<AnimationControl>, + pub compression: Compression, + /// Gamma of the source system. + /// Set by both `gAMA` as well as to a replacement by `sRGB` chunk. + pub source_gamma: Option<ScaledFloat>, + /// Chromaticities of the source system. + /// Set by both `cHRM` as well as to a replacement by `sRGB` chunk. + pub source_chromaticities: Option<SourceChromaticities>, + /// The rendering intent of an SRGB image. + /// + /// Presence of this value also indicates that the image conforms to the SRGB color space. + pub srgb: Option<SrgbRenderingIntent>, + /// The ICC profile for the image. + pub icc_profile: Option<Cow<'a, [u8]>>, + /// tEXt field + pub uncompressed_latin1_text: Vec<TEXtChunk>, + /// zTXt field + pub compressed_latin1_text: Vec<ZTXtChunk>, + /// iTXt field + pub utf8_text: Vec<ITXtChunk>, +} + +impl Default for Info<'_> { + fn default() -> Info<'static> { + Info { + width: 0, + height: 0, + bit_depth: BitDepth::Eight, + color_type: ColorType::Grayscale, + interlaced: false, + palette: None, + trns: None, + gama_chunk: None, + chrm_chunk: None, + pixel_dims: None, + frame_control: None, + animation_control: None, + // Default to `deflate::Compression::Fast` and `filter::FilterType::Sub` + // to maintain backward compatible output. + compression: Compression::Fast, + source_gamma: None, + source_chromaticities: None, + srgb: None, + icc_profile: None, + uncompressed_latin1_text: Vec::new(), + compressed_latin1_text: Vec::new(), + utf8_text: Vec::new(), + } + } +} + +impl Info<'_> { + /// A utility constructor for a default info with width and height. + pub fn with_size(width: u32, height: u32) -> Self { + Info { + width, + height, + ..Default::default() + } + } + + /// Size of the image, width then height. + pub fn size(&self) -> (u32, u32) { + (self.width, self.height) + } + + /// Returns true if the image is an APNG image. + pub fn is_animated(&self) -> bool { + self.frame_control.is_some() && self.animation_control.is_some() + } + + /// Returns the frame control information of the image. + pub fn animation_control(&self) -> Option<&AnimationControl> { + self.animation_control.as_ref() + } + + /// Returns the frame control information of the current frame + pub fn frame_control(&self) -> Option<&FrameControl> { + self.frame_control.as_ref() + } + + /// Returns the number of bits per pixel. + pub fn bits_per_pixel(&self) -> usize { + self.color_type.samples() * self.bit_depth as usize + } + + /// Returns the number of bytes per pixel. + pub fn bytes_per_pixel(&self) -> usize { + // If adjusting this for expansion or other transformation passes, remember to keep the old + // implementation for bpp_in_prediction, which is internal to the png specification. + self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3) + } + + /// Return the number of bytes for this pixel used in prediction. + /// + /// Some filters use prediction, over the raw bytes of a scanline. Where a previous pixel is + /// require for such forms the specification instead references previous bytes. That is, for + /// a gray pixel of bit depth 2, the pixel used in prediction is actually 4 pixels prior. This + /// has the consequence that the number of possible values is rather small. To make this fact + /// more obvious in the type system and the optimizer we use an explicit enum here. + pub(crate) fn bpp_in_prediction(&self) -> BytesPerPixel { + match self.bytes_per_pixel() { + 1 => BytesPerPixel::One, + 2 => BytesPerPixel::Two, + 3 => BytesPerPixel::Three, + 4 => BytesPerPixel::Four, + 6 => BytesPerPixel::Six, // Only rgb×16bit + 8 => BytesPerPixel::Eight, // Only rgba×16bit + _ => unreachable!("Not a possible byte rounded pixel width"), + } + } + + /// Returns the number of bytes needed for one deinterlaced image. + pub fn raw_bytes(&self) -> usize { + self.height as usize * self.raw_row_length() + } + + /// Returns the number of bytes needed for one deinterlaced row. + pub fn raw_row_length(&self) -> usize { + self.raw_row_length_from_width(self.width) + } + + pub(crate) fn checked_raw_row_length(&self) -> Option<usize> { + self.color_type + .checked_raw_row_length(self.bit_depth, self.width) + } + + /// Returns the number of bytes needed for one deinterlaced row of width `width`. + pub fn raw_row_length_from_width(&self, width: u32) -> usize { + self.color_type + .raw_row_length_from_width(self.bit_depth, width) + } + + /// Encode this header to the writer. + /// + /// Note that this does _not_ include the PNG signature, it starts with the IHDR chunk and then + /// includes other chunks that were added to the header. + pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> { + // Encode the IHDR chunk + let mut data = [0; 13]; + data[..4].copy_from_slice(&self.width.to_be_bytes()); + data[4..8].copy_from_slice(&self.height.to_be_bytes()); + data[8] = self.bit_depth as u8; + data[9] = self.color_type as u8; + data[12] = self.interlaced as u8; + encoder::write_chunk(&mut w, chunk::IHDR, &data)?; + // Encode the pHYs chunk + if let Some(pd) = self.pixel_dims { + let mut phys_data = [0; 9]; + phys_data[0..4].copy_from_slice(&pd.xppu.to_be_bytes()); + phys_data[4..8].copy_from_slice(&pd.yppu.to_be_bytes()); + match pd.unit { + Unit::Meter => phys_data[8] = 1, + Unit::Unspecified => phys_data[8] = 0, + } + encoder::write_chunk(&mut w, chunk::pHYs, &phys_data)?; + } + + if let Some(p) = &self.palette { + encoder::write_chunk(&mut w, chunk::PLTE, p)?; + }; + + if let Some(t) = &self.trns { + encoder::write_chunk(&mut w, chunk::tRNS, t)?; + } + + // If specified, the sRGB information overrides the source gamma and chromaticities. + if let Some(srgb) = &self.srgb { + let gamma = crate::srgb::substitute_gamma(); + let chromaticities = crate::srgb::substitute_chromaticities(); + srgb.encode(&mut w)?; + gamma.encode_gama(&mut w)?; + chromaticities.encode(&mut w)?; + } else { + if let Some(gma) = self.source_gamma { + gma.encode_gama(&mut w)? + } + if let Some(chrms) = self.source_chromaticities { + chrms.encode(&mut w)?; + } + } + if let Some(actl) = self.animation_control { + actl.encode(&mut w)?; + } + + for text_chunk in &self.uncompressed_latin1_text { + text_chunk.encode(&mut w)?; + } + + for text_chunk in &self.compressed_latin1_text { + text_chunk.encode(&mut w)?; + } + + for text_chunk in &self.utf8_text { + text_chunk.encode(&mut w)?; + } + + Ok(()) + } +} + +impl BytesPerPixel { + pub(crate) fn into_usize(self) -> usize { + self as usize + } +} + +bitflags! { + /// Output transformations + /// + /// Many flags from libpng are not yet supported. A PR discussing/adding them would be nice. + /// + #[doc = " + ```c + /// Discard the alpha channel + const STRIP_ALPHA = 0x0002; // read only + /// Expand 1; 2 and 4-bit samples to bytes + const PACKING = 0x0004; // read and write + /// Change order of packed pixels to LSB first + const PACKSWAP = 0x0008; // read and write + /// Invert monochrome images + const INVERT_MONO = 0x0020; // read and write + /// Normalize pixels to the sBIT depth + const SHIFT = 0x0040; // read and write + /// Flip RGB to BGR; RGBA to BGRA + const BGR = 0x0080; // read and write + /// Flip RGBA to ARGB or GA to AG + const SWAP_ALPHA = 0x0100; // read and write + /// Byte-swap 16-bit samples + const SWAP_ENDIAN = 0x0200; // read and write + /// Change alpha from opacity to transparency + const INVERT_ALPHA = 0x0400; // read and write + const STRIP_FILLER = 0x0800; // write only + const STRIP_FILLER_BEFORE = 0x0800; // write only + const STRIP_FILLER_AFTER = 0x1000; // write only + const GRAY_TO_RGB = 0x2000; // read only + const EXPAND_16 = 0x4000; // read only + /// Similar to STRIP_16 but in libpng considering gamma? + /// Not entirely sure the documentation says it is more + /// accurate but doesn't say precisely how. + const SCALE_16 = 0x8000; // read only + ``` + "] + pub struct Transformations: u32 { + /// No transformation + const IDENTITY = 0x00000; // read and write */ + /// Strip 16-bit samples to 8 bits + const STRIP_16 = 0x00001; // read only */ + /// Expand paletted images to RGB; expand grayscale images of + /// less than 8-bit depth to 8-bit depth; and expand tRNS chunks + /// to alpha channels. + const EXPAND = 0x00010; // read only */ + /// Expand paletted images to include an alpha channel. Implies `EXPAND`. + const ALPHA = 0x10000; // read only */ + } +} + +impl Transformations { + /// Transform every input to 8bit grayscale or color. + /// + /// This sets `EXPAND` and `STRIP_16` which is similar to the default transformation used by + /// this library prior to `0.17`. + pub fn normalize_to_color8() -> Transformations { + Transformations::EXPAND | Transformations::STRIP_16 + } +} + +/// Instantiate the default transformations, the identity transform. +impl Default for Transformations { + fn default() -> Transformations { + Transformations::IDENTITY + } +} + +#[derive(Debug)] +pub struct ParameterError { + inner: ParameterErrorKind, +} + +#[derive(Debug)] +pub(crate) enum ParameterErrorKind { + /// A provided buffer must be have the exact size to hold the image data. Where the buffer can + /// be allocated by the caller, they must ensure that it has a minimum size as hinted previously. + /// Even though the size is calculated from image data, this does counts as a parameter error + /// because they must react to a value produced by this library, which can have been subjected + /// to limits. + ImageBufferSize { expected: usize, actual: usize }, + /// A bit like return `None` from an iterator. + /// We use it to differentiate between failing to seek to the next image in a sequence and the + /// absence of a next image. This is an error of the caller because they should have checked + /// the number of images by inspecting the header data returned when opening the image. This + /// library will perform the checks necessary to ensure that data was accurate or error with a + /// format error otherwise. + PolledAfterEndOfImage, +} + +impl From<ParameterErrorKind> for ParameterError { + fn from(inner: ParameterErrorKind) -> Self { + ParameterError { inner } + } +} + +impl fmt::Display for ParameterError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use ParameterErrorKind::*; + match self.inner { + ImageBufferSize { expected, actual } => { + write!(fmt, "wrong data size, expected {} got {}", expected, actual) + } + PolledAfterEndOfImage => write!(fmt, "End of image has been reached"), + } + } +} |