aboutsummaryrefslogtreecommitdiff
path: root/vendor/png/src/common.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/png/src/common.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/png/src/common.rs')
-rw-r--r--vendor/png/src/common.rs808
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"),
+ }
+ }
+}