pnglitcher/
common.rs

1//! Common types shared between the encoder and decoder
2use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk};
3use crate::{chunk, encoder};
4use io::Write;
5use std::{borrow::Cow, fmt, io};
6
7/// Describes how a pixel is encoded.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum ColorType {
11    /// 1 grayscale sample.
12    Grayscale = 0,
13    /// 1 red sample, 1 green sample, 1 blue sample.
14    Rgb = 2,
15    /// 1 sample for the palette index.
16    Indexed = 3,
17    /// 1 grayscale sample, then 1 alpha sample.
18    GrayscaleAlpha = 4,
19    /// 1 red sample, 1 green sample, 1 blue sample, and finally, 1 alpha sample.
20    Rgba = 6,
21}
22
23impl ColorType {
24    /// Returns the number of samples used per pixel encoded in this way.
25    pub fn samples(self) -> usize {
26        self.samples_u8().into()
27    }
28
29    pub(crate) fn samples_u8(self) -> u8 {
30        use self::ColorType::*;
31        match self {
32            Grayscale | Indexed => 1,
33            Rgb => 3,
34            GrayscaleAlpha => 2,
35            Rgba => 4,
36        }
37    }
38
39    /// u8 -> Self. Temporary solution until Rust provides a canonical one.
40    pub fn from_u8(n: u8) -> Option<ColorType> {
41        match n {
42            0 => Some(ColorType::Grayscale),
43            2 => Some(ColorType::Rgb),
44            3 => Some(ColorType::Indexed),
45            4 => Some(ColorType::GrayscaleAlpha),
46            6 => Some(ColorType::Rgba),
47            _ => None,
48        }
49    }
50
51    pub(crate) fn raw_row_length_from_width(self, depth: BitDepth, width: u32) -> usize {
52        let samples = width as usize * self.samples();
53        1 + match depth {
54            BitDepth::Sixteen => samples * 2,
55            BitDepth::Eight => samples,
56            subbyte => {
57                let samples_per_byte = 8 / subbyte as usize;
58                let whole = samples / samples_per_byte;
59                let fract = usize::from(samples % samples_per_byte > 0);
60                whole + fract
61            }
62        }
63    }
64
65    pub(crate) fn is_combination_invalid(self, bit_depth: BitDepth) -> bool {
66        // Section 11.2.2 of the PNG standard disallows several combinations
67        // of bit depth and color type
68        ((bit_depth == BitDepth::One || bit_depth == BitDepth::Two || bit_depth == BitDepth::Four)
69            && (self == ColorType::Rgb
70                || self == ColorType::GrayscaleAlpha
71                || self == ColorType::Rgba))
72            || (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed)
73    }
74}
75
76/// Bit depth of the PNG file.
77/// Specifies the number of bits per sample.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[repr(u8)]
80pub enum BitDepth {
81    One = 1,
82    Two = 2,
83    Four = 4,
84    Eight = 8,
85    Sixteen = 16,
86}
87
88/// Internal count of bytes per pixel.
89/// This is used for filtering which never uses sub-byte units. This essentially reduces the number
90/// of possible byte chunk lengths to a very small set of values appropriate to be defined as an
91/// enum.
92#[derive(Debug, Clone, Copy)]
93#[repr(u8)]
94pub(crate) enum BytesPerPixel {
95    One = 1,
96    Two = 2,
97    Three = 3,
98    Four = 4,
99    Six = 6,
100    Eight = 8,
101}
102
103impl BitDepth {
104    /// u8 -> Self. Temporary solution until Rust provides a canonical one.
105    pub fn from_u8(n: u8) -> Option<BitDepth> {
106        match n {
107            1 => Some(BitDepth::One),
108            2 => Some(BitDepth::Two),
109            4 => Some(BitDepth::Four),
110            8 => Some(BitDepth::Eight),
111            16 => Some(BitDepth::Sixteen),
112            _ => None,
113        }
114    }
115}
116
117/// Pixel dimensions information
118#[derive(Clone, Copy, Debug)]
119pub struct PixelDimensions {
120    /// Pixels per unit, X axis
121    pub xppu: u32,
122    /// Pixels per unit, Y axis
123    pub yppu: u32,
124    /// Either *Meter* or *Unspecified*
125    pub unit: Unit,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[repr(u8)]
130/// Physical unit of the pixel dimensions
131pub enum Unit {
132    Unspecified = 0,
133    Meter = 1,
134}
135
136impl Unit {
137    /// u8 -> Self. Temporary solution until Rust provides a canonical one.
138    pub fn from_u8(n: u8) -> Option<Unit> {
139        match n {
140            0 => Some(Unit::Unspecified),
141            1 => Some(Unit::Meter),
142            _ => None,
143        }
144    }
145}
146
147/// How to reset buffer of an animated png (APNG) at the end of a frame.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149#[repr(u8)]
150pub enum DisposeOp {
151    /// Leave the buffer unchanged.
152    None = 0,
153    /// Clear buffer with the background color.
154    Background = 1,
155    /// Reset the buffer to the state before the current frame.
156    Previous = 2,
157}
158
159impl DisposeOp {
160    /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
161    pub fn from_u8(n: u8) -> Option<DisposeOp> {
162        match n {
163            0 => Some(DisposeOp::None),
164            1 => Some(DisposeOp::Background),
165            2 => Some(DisposeOp::Previous),
166            _ => None,
167        }
168    }
169}
170
171impl fmt::Display for DisposeOp {
172    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173        let name = match *self {
174            DisposeOp::None => "DISPOSE_OP_NONE",
175            DisposeOp::Background => "DISPOSE_OP_BACKGROUND",
176            DisposeOp::Previous => "DISPOSE_OP_PREVIOUS",
177        };
178        write!(f, "{}", name)
179    }
180}
181
182/// How pixels are written into the buffer.
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[repr(u8)]
185pub enum BlendOp {
186    /// Pixels overwrite the value at their position.
187    Source = 0,
188    /// The new pixels are blended into the current state based on alpha.
189    Over = 1,
190}
191
192impl BlendOp {
193    /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
194    pub fn from_u8(n: u8) -> Option<BlendOp> {
195        match n {
196            0 => Some(BlendOp::Source),
197            1 => Some(BlendOp::Over),
198            _ => None,
199        }
200    }
201}
202
203impl fmt::Display for BlendOp {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        let name = match *self {
206            BlendOp::Source => "BLEND_OP_SOURCE",
207            BlendOp::Over => "BLEND_OP_OVER",
208        };
209        write!(f, "{}", name)
210    }
211}
212
213/// Frame control information
214#[derive(Clone, Copy, Debug)]
215pub struct FrameControl {
216    /// Sequence number of the animation chunk, starting from 0
217    pub sequence_number: u32,
218    /// Width of the following frame
219    pub width: u32,
220    /// Height of the following frame
221    pub height: u32,
222    /// X position at which to render the following frame
223    pub x_offset: u32,
224    /// Y position at which to render the following frame
225    pub y_offset: u32,
226    /// Frame delay fraction numerator
227    pub delay_num: u16,
228    /// Frame delay fraction denominator
229    pub delay_den: u16,
230    /// Type of frame area disposal to be done after rendering this frame
231    pub dispose_op: DisposeOp,
232    /// Type of frame area rendering for this frame
233    pub blend_op: BlendOp,
234}
235
236impl Default for FrameControl {
237    fn default() -> FrameControl {
238        FrameControl {
239            sequence_number: 0,
240            width: 0,
241            height: 0,
242            x_offset: 0,
243            y_offset: 0,
244            delay_num: 1,
245            delay_den: 30,
246            dispose_op: DisposeOp::None,
247            blend_op: BlendOp::Source,
248        }
249    }
250}
251
252impl FrameControl {
253    pub fn set_seq_num(&mut self, s: u32) {
254        self.sequence_number = s;
255    }
256
257    pub fn inc_seq_num(&mut self, i: u32) {
258        self.sequence_number += i;
259    }
260
261    pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
262        let mut data = [0u8; 26];
263        data[..4].copy_from_slice(&self.sequence_number.to_be_bytes());
264        data[4..8].copy_from_slice(&self.width.to_be_bytes());
265        data[8..12].copy_from_slice(&self.height.to_be_bytes());
266        data[12..16].copy_from_slice(&self.x_offset.to_be_bytes());
267        data[16..20].copy_from_slice(&self.y_offset.to_be_bytes());
268        data[20..22].copy_from_slice(&self.delay_num.to_be_bytes());
269        data[22..24].copy_from_slice(&self.delay_den.to_be_bytes());
270        data[24] = self.dispose_op as u8;
271        data[25] = self.blend_op as u8;
272
273        encoder::write_chunk(w, chunk::fcTL, &data)
274    }
275}
276
277/// Animation control information
278#[derive(Clone, Copy, Debug)]
279pub struct AnimationControl {
280    /// Number of frames
281    pub num_frames: u32,
282    /// Number of times to loop this APNG.  0 indicates infinite looping.
283    pub num_plays: u32,
284}
285
286impl AnimationControl {
287    pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
288        let mut data = [0; 8];
289        data[..4].copy_from_slice(&self.num_frames.to_be_bytes());
290        data[4..].copy_from_slice(&self.num_plays.to_be_bytes());
291        encoder::write_chunk(w, chunk::acTL, &data)
292    }
293}
294
295/// The type and strength of applied compression.
296#[derive(Debug, Clone, Copy)]
297pub enum Compression {
298    /// Default level
299    Default,
300    /// Fast minimal compression
301    Fast,
302    /// Higher compression level
303    ///
304    /// Best in this context isn't actually the highest possible level
305    /// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2`
306    /// library.
307    Best,
308    Huffman,
309    Rle,
310}
311
312/// An unsigned integer scaled version of a floating point value,
313/// equivalent to an integer quotient with fixed denominator (100_000)).
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
315pub struct ScaledFloat(u32);
316
317impl ScaledFloat {
318    const SCALING: f32 = 100_000.0;
319
320    /// Gets whether the value is within the clamped range of this type.
321    pub fn in_range(value: f32) -> bool {
322        value >= 0.0 && (value * Self::SCALING).floor() <= std::u32::MAX as f32
323    }
324
325    /// Gets whether the value can be exactly converted in round-trip.
326    #[allow(clippy::float_cmp)] // Stupid tool, the exact float compare is _the entire point_.
327    pub fn exact(value: f32) -> bool {
328        let there = Self::forward(value);
329        let back = Self::reverse(there);
330        value == back
331    }
332
333    fn forward(value: f32) -> u32 {
334        (value.max(0.0) * Self::SCALING).floor() as u32
335    }
336
337    fn reverse(encoded: u32) -> f32 {
338        encoded as f32 / Self::SCALING
339    }
340
341    /// Slightly inaccurate scaling and quantization.
342    /// Clamps the value into the representable range if it is negative or too large.
343    pub fn new(value: f32) -> Self {
344        Self {
345            0: Self::forward(value),
346        }
347    }
348
349    /// Fully accurate construction from a value scaled as per specification.
350    pub fn from_scaled(val: u32) -> Self {
351        Self { 0: val }
352    }
353
354    /// Get the accurate encoded value.
355    pub fn into_scaled(self) -> u32 {
356        self.0
357    }
358
359    /// Get the unscaled value as a floating point.
360    pub fn into_value(self) -> f32 {
361        Self::reverse(self.0) as f32
362    }
363
364    pub(crate) fn encode_gama<W: Write>(self, w: &mut W) -> encoder::Result<()> {
365        encoder::write_chunk(w, chunk::gAMA, &self.into_scaled().to_be_bytes())
366    }
367}
368
369/// Chromaticities of the color space primaries
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub struct SourceChromaticities {
372    pub white: (ScaledFloat, ScaledFloat),
373    pub red: (ScaledFloat, ScaledFloat),
374    pub green: (ScaledFloat, ScaledFloat),
375    pub blue: (ScaledFloat, ScaledFloat),
376}
377
378impl SourceChromaticities {
379    pub fn new(white: (f32, f32), red: (f32, f32), green: (f32, f32), blue: (f32, f32)) -> Self {
380        SourceChromaticities {
381            white: (ScaledFloat::new(white.0), ScaledFloat::new(white.1)),
382            red: (ScaledFloat::new(red.0), ScaledFloat::new(red.1)),
383            green: (ScaledFloat::new(green.0), ScaledFloat::new(green.1)),
384            blue: (ScaledFloat::new(blue.0), ScaledFloat::new(blue.1)),
385        }
386    }
387
388    #[rustfmt::skip]
389    pub fn to_be_bytes(self) -> [u8; 32] {
390        let white_x = self.white.0.into_scaled().to_be_bytes();
391        let white_y = self.white.1.into_scaled().to_be_bytes();
392        let red_x   = self.red.0.into_scaled().to_be_bytes();
393        let red_y   = self.red.1.into_scaled().to_be_bytes();
394        let green_x = self.green.0.into_scaled().to_be_bytes();
395        let green_y = self.green.1.into_scaled().to_be_bytes();
396        let blue_x  = self.blue.0.into_scaled().to_be_bytes();
397        let blue_y  = self.blue.1.into_scaled().to_be_bytes();
398        [
399            white_x[0], white_x[1], white_x[2], white_x[3],
400            white_y[0], white_y[1], white_y[2], white_y[3],
401            red_x[0],   red_x[1],   red_x[2],   red_x[3],
402            red_y[0],   red_y[1],   red_y[2],   red_y[3],
403            green_x[0], green_x[1], green_x[2], green_x[3],
404            green_y[0], green_y[1], green_y[2], green_y[3],
405            blue_x[0],  blue_x[1],  blue_x[2],  blue_x[3],
406            blue_y[0],  blue_y[1],  blue_y[2],  blue_y[3],
407        ]
408    }
409
410    pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
411        encoder::write_chunk(w, chunk::cHRM, &self.to_be_bytes())
412    }
413}
414
415/// The rendering intent for an sRGB image.
416///
417/// Presence of this data also indicates that the image conforms to the sRGB color space.
418#[repr(u8)]
419#[derive(Clone, Copy, Debug, PartialEq, Eq)]
420pub enum SrgbRenderingIntent {
421    /// For images preferring good adaptation to the output device gamut at the expense of colorimetric accuracy, such as photographs.
422    Perceptual = 0,
423    /// For images requiring colour appearance matching (relative to the output device white point), such as logos.
424    RelativeColorimetric = 1,
425    /// For images preferring preservation of saturation at the expense of hue and lightness, such as charts and graphs.
426    Saturation = 2,
427    /// For images requiring preservation of absolute colorimetry, such as previews of images destined for a different output device (proofs).
428    AbsoluteColorimetric = 3,
429}
430
431impl SrgbRenderingIntent {
432    pub(crate) fn into_raw(self) -> u8 {
433        self as u8
434    }
435
436    pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
437        encoder::write_chunk(w, chunk::sRGB, &[self.into_raw()])
438    }
439}
440
441/// PNG info struct
442#[derive(Clone, Debug)]
443#[non_exhaustive]
444pub struct Info<'a> {
445    pub width: u32,
446    pub height: u32,
447    pub bit_depth: BitDepth,
448    /// How colors are stored in the image.
449    pub color_type: ColorType,
450    pub interlaced: bool,
451    /// The image's `tRNS` chunk, if present; contains the alpha channel of the image's palette, 1 byte per entry.
452    pub trns: Option<Cow<'a, [u8]>>,
453    pub pixel_dims: Option<PixelDimensions>,
454    /// Gamma of the source system.
455    pub source_gamma: Option<ScaledFloat>,
456    /// 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).
457    pub palette: Option<Cow<'a, [u8]>>,
458    pub frame_control: Option<FrameControl>,
459    pub animation_control: Option<AnimationControl>,
460    pub compression: Compression,
461    /// Chromaticities of the source system.
462    pub source_chromaticities: Option<SourceChromaticities>,
463    /// The rendering intent of an SRGB image.
464    ///
465    /// Presence of this value also indicates that the image conforms to the SRGB color space.
466    pub srgb: Option<SrgbRenderingIntent>,
467    /// The ICC profile for the image.
468    pub icc_profile: Option<Cow<'a, [u8]>>,
469    /// tEXt field
470    pub uncompressed_latin1_text: Vec<TEXtChunk>,
471    /// zTXt field
472    pub compressed_latin1_text: Vec<ZTXtChunk>,
473    /// iTXt field
474    pub utf8_text: Vec<ITXtChunk>,
475}
476
477impl Default for Info<'_> {
478    fn default() -> Info<'static> {
479        Info {
480            width: 0,
481            height: 0,
482            bit_depth: BitDepth::Eight,
483            color_type: ColorType::Grayscale,
484            interlaced: false,
485            palette: None,
486            trns: None,
487            pixel_dims: None,
488            source_gamma: None,
489            frame_control: None,
490            animation_control: None,
491            // Default to `deflate::Compression::Fast` and `filter::FilterType::Sub`
492            // to maintain backward compatible output.
493            compression: Compression::Fast,
494            source_chromaticities: None,
495            srgb: None,
496            icc_profile: None,
497            uncompressed_latin1_text: Vec::new(),
498            compressed_latin1_text: Vec::new(),
499            utf8_text: Vec::new(),
500        }
501    }
502}
503
504impl Info<'_> {
505    /// A utility constructor for a default info with width and height.
506    pub fn with_size(width: u32, height: u32) -> Self {
507        Info {
508            width,
509            height,
510            ..Default::default()
511        }
512    }
513
514    /// Size of the image, width then height.
515    pub fn size(&self) -> (u32, u32) {
516        (self.width, self.height)
517    }
518
519    /// Returns true if the image is an APNG image.
520    pub fn is_animated(&self) -> bool {
521        self.frame_control.is_some() && self.animation_control.is_some()
522    }
523
524    /// Returns the frame control information of the image.
525    pub fn animation_control(&self) -> Option<&AnimationControl> {
526        self.animation_control.as_ref()
527    }
528
529    /// Returns the frame control information of the current frame
530    pub fn frame_control(&self) -> Option<&FrameControl> {
531        self.frame_control.as_ref()
532    }
533
534    /// Returns the number of bits per pixel.
535    pub fn bits_per_pixel(&self) -> usize {
536        self.color_type.samples() * self.bit_depth as usize
537    }
538
539    /// Returns the number of bytes per pixel.
540    pub fn bytes_per_pixel(&self) -> usize {
541        // If adjusting this for expansion or other transformation passes, remember to keep the old
542        // implementation for bpp_in_prediction, which is internal to the png specification.
543        self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3)
544    }
545
546    /// Return the number of bytes for this pixel used in prediction.
547    ///
548    /// Some filters use prediction, over the raw bytes of a scanline. Where a previous pixel is
549    /// require for such forms the specification instead references previous bytes. That is, for
550    /// a gray pixel of bit depth 2, the pixel used in prediction is actually 4 pixels prior. This
551    /// has the consequence that the number of possible values is rather small. To make this fact
552    /// more obvious in the type system and the optimizer we use an explicit enum here.
553    pub(crate) fn bpp_in_prediction(&self) -> BytesPerPixel {
554        match self.bytes_per_pixel() {
555            1 => BytesPerPixel::One,
556            2 => BytesPerPixel::Two,
557            3 => BytesPerPixel::Three,
558            4 => BytesPerPixel::Four,
559            6 => BytesPerPixel::Six,   // Only rgb×16bit
560            8 => BytesPerPixel::Eight, // Only rgba×16bit
561            _ => unreachable!("Not a possible byte rounded pixel width"),
562        }
563    }
564
565    /// Returns the number of bytes needed for one deinterlaced image.
566    pub fn raw_bytes(&self) -> usize {
567        self.height as usize * self.raw_row_length()
568    }
569
570    /// Returns the number of bytes needed for one deinterlaced row.
571    pub fn raw_row_length(&self) -> usize {
572        self.raw_row_length_from_width(self.width)
573    }
574
575    /// Returns the number of bytes needed for one deinterlaced row of width `width`.
576    pub fn raw_row_length_from_width(&self, width: u32) -> usize {
577        self.color_type
578            .raw_row_length_from_width(self.bit_depth, width)
579    }
580
581    /// Encode this header to the writer.
582    ///
583    /// Note that this does _not_ include the PNG signature, it starts with the IHDR chunk and then
584    /// includes other chunks that were added to the header.
585    pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> {
586        // Encode the IHDR chunk
587        let mut data = [0; 13];
588        data[..4].copy_from_slice(&self.width.to_be_bytes());
589        data[4..8].copy_from_slice(&self.height.to_be_bytes());
590        data[8] = self.bit_depth as u8;
591        data[9] = self.color_type as u8;
592        data[12] = self.interlaced as u8;
593        encoder::write_chunk(&mut w, chunk::IHDR, &data)?;
594
595        if let Some(p) = &self.palette {
596            encoder::write_chunk(&mut w, chunk::PLTE, p)?;
597        };
598
599        if let Some(t) = &self.trns {
600            encoder::write_chunk(&mut w, chunk::tRNS, t)?;
601        }
602
603        // If specified, the sRGB information overrides the source gamma and chromaticities.
604        if let Some(srgb) = &self.srgb {
605            let gamma = crate::srgb::substitute_gamma();
606            let chromaticities = crate::srgb::substitute_chromaticities();
607            srgb.encode(&mut w)?;
608            gamma.encode_gama(&mut w)?;
609            chromaticities.encode(&mut w)?;
610        } else {
611            if let Some(gma) = self.source_gamma {
612                gma.encode_gama(&mut w)?
613            }
614            if let Some(chrms) = self.source_chromaticities {
615                chrms.encode(&mut w)?;
616            }
617        }
618        if let Some(actl) = self.animation_control {
619            actl.encode(&mut w)?;
620        }
621
622        for text_chunk in &self.uncompressed_latin1_text {
623            text_chunk.encode(&mut w)?;
624        }
625
626        for text_chunk in &self.compressed_latin1_text {
627            text_chunk.encode(&mut w)?;
628        }
629
630        for text_chunk in &self.utf8_text {
631            text_chunk.encode(&mut w)?;
632        }
633
634        Ok(())
635    }
636}
637
638impl BytesPerPixel {
639    pub(crate) fn into_usize(self) -> usize {
640        self as usize
641    }
642}
643
644bitflags! {
645    /// Output transformations
646    ///
647    /// Many flags from libpng are not yet supported. A PR discussing/adding them would be nice.
648    ///
649    #[doc = "
650    ```c
651    /// Discard the alpha channel
652    const STRIP_ALPHA         = 0x0002; // read only
653    /// Expand 1; 2 and 4-bit samples to bytes
654    const PACKING             = 0x0004; // read and write
655    /// Change order of packed pixels to LSB first
656    const PACKSWAP            = 0x0008; // read and write
657    /// Invert monochrome images
658    const INVERT_MONO         = 0x0020; // read and write
659    /// Normalize pixels to the sBIT depth
660    const SHIFT               = 0x0040; // read and write
661    /// Flip RGB to BGR; RGBA to BGRA
662    const BGR                 = 0x0080; // read and write
663    /// Flip RGBA to ARGB or GA to AG
664    const SWAP_ALPHA          = 0x0100; // read and write
665    /// Byte-swap 16-bit samples
666    const SWAP_ENDIAN         = 0x0200; // read and write
667    /// Change alpha from opacity to transparency
668    const INVERT_ALPHA        = 0x0400; // read and write
669    const STRIP_FILLER        = 0x0800; // write only
670    const STRIP_FILLER_BEFORE = 0x0800; // write only
671    const STRIP_FILLER_AFTER  = 0x1000; // write only
672    const GRAY_TO_RGB         = 0x2000; // read only
673    const EXPAND_16           = 0x4000; // read only
674    /// Similar to STRIP_16 but in libpng considering gamma?
675    /// Not entirely sure the documentation says it is more
676    /// accurate but doesn't say precisely how.
677    const SCALE_16            = 0x8000; // read only
678    ```
679    "]
680    pub struct Transformations: u32 {
681        /// No transformation
682        const IDENTITY            = 0x0000; // read and write */
683        /// Strip 16-bit samples to 8 bits
684        const STRIP_16            = 0x0001; // read only */
685        /// Expand paletted images to RGB; expand grayscale images of
686        /// less than 8-bit depth to 8-bit depth; and expand tRNS chunks
687        /// to alpha channels.
688        const EXPAND              = 0x0010; // read only */
689    }
690}
691
692impl Transformations {
693    /// Transform every input to 8bit grayscale or color.
694    ///
695    /// This sets `EXPAND` and `STRIP_16` which is similar to the default transformation used by
696    /// this library prior to `0.17`.
697    pub fn normalize_to_color8() -> Transformations {
698        Transformations::EXPAND | Transformations::STRIP_16
699    }
700}
701
702/// Instantiate the default transformations, the identity transform.
703impl Default for Transformations {
704    fn default() -> Transformations {
705        Transformations::IDENTITY
706    }
707}
708
709#[derive(Debug)]
710pub struct ParameterError {
711    inner: ParameterErrorKind,
712}
713
714#[derive(Debug)]
715pub(crate) enum ParameterErrorKind {
716    /// A provided buffer must be have the exact size to hold the image data. Where the buffer can
717    /// be allocated by the caller, they must ensure that it has a minimum size as hinted previously.
718    /// Even though the size is calculated from image data, this does counts as a parameter error
719    /// because they must react to a value produced by this library, which can have been subjected
720    /// to limits.
721    ImageBufferSize { expected: usize, actual: usize },
722}
723
724impl From<ParameterErrorKind> for ParameterError {
725    fn from(inner: ParameterErrorKind) -> Self {
726        ParameterError { inner }
727    }
728}
729
730impl fmt::Display for ParameterError {
731    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
732        use ParameterErrorKind::*;
733        match self.inner {
734            ImageBufferSize { expected, actual } => {
735                write!(fmt, "wrong data size, expected {} got {}", expected, actual)
736            }
737        }
738    }
739}