png/common.rs
1//! Common types shared between the encoder and decoder
2use crate::text_metadata::{ITXtChunk, TEXtChunk, ZTXtChunk};
3#[allow(unused_imports)] // used by doc comments only
4use crate::Filter;
5use crate::{chunk, encoder};
6use io::Write;
7use std::{borrow::Cow, convert::TryFrom, fmt, io};
8
9/// Describes how a pixel is encoded.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum ColorType {
13 /// 1 grayscale sample.
14 Grayscale = 0,
15 /// 1 red sample, 1 green sample, 1 blue sample.
16 Rgb = 2,
17 /// 1 sample for the palette index.
18 Indexed = 3,
19 /// 1 grayscale sample, then 1 alpha sample.
20 GrayscaleAlpha = 4,
21 /// 1 red sample, 1 green sample, 1 blue sample, and finally, 1 alpha sample.
22 Rgba = 6,
23}
24
25impl ColorType {
26 /// Returns the number of samples used per pixel encoded in this way.
27 pub fn samples(self) -> usize {
28 self.samples_u8().into()
29 }
30
31 pub(crate) fn samples_u8(self) -> u8 {
32 use self::ColorType::*;
33 match self {
34 Grayscale | Indexed => 1,
35 Rgb => 3,
36 GrayscaleAlpha => 2,
37 Rgba => 4,
38 }
39 }
40
41 /// u8 -> Self. Temporary solution until Rust provides a canonical one.
42 pub fn from_u8(n: u8) -> Option<ColorType> {
43 match n {
44 0 => Some(ColorType::Grayscale),
45 2 => Some(ColorType::Rgb),
46 3 => Some(ColorType::Indexed),
47 4 => Some(ColorType::GrayscaleAlpha),
48 6 => Some(ColorType::Rgba),
49 _ => None,
50 }
51 }
52
53 pub(crate) fn checked_raw_row_length(self, depth: BitDepth, width: u32) -> Option<usize> {
54 // No overflow can occur in 64 bits, we multiply 32-bit with 5 more bits.
55 let bits = u64::from(width) * u64::from(self.samples_u8()) * u64::from(depth.into_u8());
56 TryFrom::try_from(1 + (bits + 7) / 8).ok()
57 }
58
59 pub(crate) fn raw_row_length_from_width(self, depth: BitDepth, width: u32) -> usize {
60 let samples = width as usize * self.samples();
61 1 + match depth {
62 BitDepth::Sixteen => samples * 2,
63 BitDepth::Eight => samples,
64 subbyte => {
65 let samples_per_byte = 8 / subbyte as usize;
66 let whole = samples / samples_per_byte;
67 let fract = usize::from(samples % samples_per_byte > 0);
68 whole + fract
69 }
70 }
71 }
72
73 pub(crate) fn is_combination_invalid(self, bit_depth: BitDepth) -> bool {
74 // Section 11.2.2 of the PNG standard disallows several combinations
75 // of bit depth and color type
76 ((bit_depth == BitDepth::One || bit_depth == BitDepth::Two || bit_depth == BitDepth::Four)
77 && (self == ColorType::Rgb
78 || self == ColorType::GrayscaleAlpha
79 || self == ColorType::Rgba))
80 || (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed)
81 }
82
83 pub(crate) fn bits_per_pixel(&self, bit_depth: BitDepth) -> usize {
84 self.samples() * bit_depth as usize
85 }
86
87 pub(crate) fn bytes_per_pixel(&self, bit_depth: BitDepth) -> usize {
88 // If adjusting this for expansion or other transformation passes, remember to keep the old
89 // implementation for bpp_in_prediction, which is internal to the png specification.
90 self.samples() * ((bit_depth as usize + 7) >> 3)
91 }
92}
93
94/// Bit depth of the PNG file.
95/// Specifies the number of bits per sample.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[repr(u8)]
98pub enum BitDepth {
99 One = 1,
100 Two = 2,
101 Four = 4,
102 Eight = 8,
103 Sixteen = 16,
104}
105
106/// Internal count of bytes per pixel.
107/// This is used for filtering which never uses sub-byte units. This essentially reduces the number
108/// of possible byte chunk lengths to a very small set of values appropriate to be defined as an
109/// enum.
110#[derive(Debug, Clone, Copy)]
111#[repr(u8)]
112pub(crate) enum BytesPerPixel {
113 One = 1,
114 Two = 2,
115 Three = 3,
116 Four = 4,
117 Six = 6,
118 Eight = 8,
119}
120
121impl BitDepth {
122 /// u8 -> Self. Temporary solution until Rust provides a canonical one.
123 pub fn from_u8(n: u8) -> Option<BitDepth> {
124 match n {
125 1 => Some(BitDepth::One),
126 2 => Some(BitDepth::Two),
127 4 => Some(BitDepth::Four),
128 8 => Some(BitDepth::Eight),
129 16 => Some(BitDepth::Sixteen),
130 _ => None,
131 }
132 }
133
134 pub(crate) fn into_u8(self) -> u8 {
135 self as u8
136 }
137}
138
139/// Pixel dimensions information
140#[derive(Clone, Copy, Debug)]
141pub struct PixelDimensions {
142 /// Pixels per unit, X axis
143 pub xppu: u32,
144 /// Pixels per unit, Y axis
145 pub yppu: u32,
146 /// Either *Meter* or *Unspecified*
147 pub unit: Unit,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151#[repr(u8)]
152/// Physical unit of the pixel dimensions
153pub enum Unit {
154 Unspecified = 0,
155 Meter = 1,
156}
157
158impl Unit {
159 /// u8 -> Self. Temporary solution until Rust provides a canonical one.
160 pub fn from_u8(n: u8) -> Option<Unit> {
161 match n {
162 0 => Some(Unit::Unspecified),
163 1 => Some(Unit::Meter),
164 _ => None,
165 }
166 }
167}
168
169/// How to reset buffer of an animated png (APNG) at the end of a frame.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171#[repr(u8)]
172pub enum DisposeOp {
173 /// Leave the buffer unchanged.
174 None = 0,
175 /// Clear buffer with the background color.
176 Background = 1,
177 /// Reset the buffer to the state before the current frame.
178 Previous = 2,
179}
180
181impl DisposeOp {
182 /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
183 pub fn from_u8(n: u8) -> Option<DisposeOp> {
184 match n {
185 0 => Some(DisposeOp::None),
186 1 => Some(DisposeOp::Background),
187 2 => Some(DisposeOp::Previous),
188 _ => None,
189 }
190 }
191}
192
193impl fmt::Display for DisposeOp {
194 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195 let name = match *self {
196 DisposeOp::None => "DISPOSE_OP_NONE",
197 DisposeOp::Background => "DISPOSE_OP_BACKGROUND",
198 DisposeOp::Previous => "DISPOSE_OP_PREVIOUS",
199 };
200 write!(f, "{}", name)
201 }
202}
203
204/// How pixels are written into the buffer.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206#[repr(u8)]
207pub enum BlendOp {
208 /// Pixels overwrite the value at their position.
209 Source = 0,
210 /// The new pixels are blended into the current state based on alpha.
211 Over = 1,
212}
213
214impl BlendOp {
215 /// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
216 pub fn from_u8(n: u8) -> Option<BlendOp> {
217 match n {
218 0 => Some(BlendOp::Source),
219 1 => Some(BlendOp::Over),
220 _ => None,
221 }
222 }
223}
224
225impl fmt::Display for BlendOp {
226 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227 let name = match *self {
228 BlendOp::Source => "BLEND_OP_SOURCE",
229 BlendOp::Over => "BLEND_OP_OVER",
230 };
231 write!(f, "{}", name)
232 }
233}
234
235/// Frame control information
236#[derive(Clone, Copy, Debug)]
237pub struct FrameControl {
238 /// Sequence number of the animation chunk, starting from 0
239 pub sequence_number: u32,
240 /// Width of the following frame
241 pub width: u32,
242 /// Height of the following frame
243 pub height: u32,
244 /// X position at which to render the following frame
245 pub x_offset: u32,
246 /// Y position at which to render the following frame
247 pub y_offset: u32,
248 /// Frame delay fraction numerator
249 pub delay_num: u16,
250 /// Frame delay fraction denominator
251 pub delay_den: u16,
252 /// Type of frame area disposal to be done after rendering this frame
253 pub dispose_op: DisposeOp,
254 /// Type of frame area rendering for this frame
255 pub blend_op: BlendOp,
256}
257
258impl Default for FrameControl {
259 fn default() -> FrameControl {
260 FrameControl {
261 sequence_number: 0,
262 width: 0,
263 height: 0,
264 x_offset: 0,
265 y_offset: 0,
266 delay_num: 1,
267 delay_den: 30,
268 dispose_op: DisposeOp::None,
269 blend_op: BlendOp::Source,
270 }
271 }
272}
273
274impl FrameControl {
275 pub fn set_seq_num(&mut self, s: u32) {
276 self.sequence_number = s;
277 }
278
279 pub fn inc_seq_num(&mut self, i: u32) {
280 self.sequence_number += i;
281 }
282
283 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
284 let mut data = [0u8; 26];
285 data[..4].copy_from_slice(&self.sequence_number.to_be_bytes());
286 data[4..8].copy_from_slice(&self.width.to_be_bytes());
287 data[8..12].copy_from_slice(&self.height.to_be_bytes());
288 data[12..16].copy_from_slice(&self.x_offset.to_be_bytes());
289 data[16..20].copy_from_slice(&self.y_offset.to_be_bytes());
290 data[20..22].copy_from_slice(&self.delay_num.to_be_bytes());
291 data[22..24].copy_from_slice(&self.delay_den.to_be_bytes());
292 data[24] = self.dispose_op as u8;
293 data[25] = self.blend_op as u8;
294
295 encoder::write_chunk(w, chunk::fcTL, &data)
296 }
297}
298
299/// Animation control information
300#[derive(Clone, Copy, Debug)]
301pub struct AnimationControl {
302 /// Number of frames
303 pub num_frames: u32,
304 /// Number of times to loop this APNG. 0 indicates infinite looping.
305 pub num_plays: u32,
306}
307
308impl AnimationControl {
309 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
310 let mut data = [0; 8];
311 data[..4].copy_from_slice(&self.num_frames.to_be_bytes());
312 data[4..].copy_from_slice(&self.num_plays.to_be_bytes());
313 encoder::write_chunk(w, chunk::acTL, &data)
314 }
315}
316
317/// The type and strength of applied compression.
318///
319/// This is a simple, high-level interface that will automatically choose
320/// the appropriate DEFLATE compression mode and PNG filter.
321///
322/// If you need more control over the encoding parameters,
323/// you can set the [DeflateCompression] and [Filter] manually.
324#[derive(Debug, Clone, Copy)]
325#[non_exhaustive]
326pub enum Compression {
327 /// No compression whatsoever. Fastest, but results in large files.
328 NoCompression,
329 /// Extremely fast but light compression.
330 ///
331 /// Note: When used in streaming mode, this compression level can actually result in files
332 /// *larger* than would be produced by `NoCompression` on incompressible data because
333 /// it doesn't do any buffering of the output stream to detect whether the data is being compressed or not.
334 Fastest,
335 /// Extremely fast compression with a decent compression ratio.
336 ///
337 /// Significantly outperforms libpng and other popular encoders by using a [specialized DEFLATE
338 /// implementation tuned for PNG](https://crates.io/crates/fdeflate), while still providing
339 /// better compression ratio than the fastest modes of other encoders.
340 ///
341 /// Like `Compression::Fast` this can currently produce files larger than `NoCompression` in
342 /// streaming mode when given incompressible data. This may change in the future.
343 Fast,
344 /// Balances encoding speed and compression ratio
345 Balanced,
346 /// Spend much more time to produce a slightly smaller file than with `Balanced`.
347 High,
348}
349
350impl Default for Compression {
351 fn default() -> Self {
352 Self::Balanced
353 }
354}
355
356/// Advanced compression settings with more customization options than [Compression].
357///
358/// Note that this setting only affects DEFLATE compression.
359/// Another setting that influences the compression ratio and lets you choose
360/// between encoding speed and compression ratio is the [Filter].
361///
362/// ### Stability guarantees
363///
364/// The implementation details of DEFLATE compression may evolve over time,
365/// even without a semver-breaking change to the version of `png` crate.
366///
367/// If a certain compression setting is superseded by other options,
368/// it may be marked deprecated and remapped to a different option.
369/// You will see a deprecation notice when compiling code relying on such options.
370#[non_exhaustive]
371#[derive(Debug, Clone, Copy)]
372pub enum DeflateCompression {
373 /// Do not compress the data at all.
374 ///
375 /// Useful for incompressible images, or when speed is paramount and you don't care about size
376 /// at all.
377 ///
378 /// This mode also disables filters, forcing [Filter::NoFilter].
379 NoCompression,
380
381 /// Excellent for creating lightly compressed PNG images very quickly.
382 ///
383 /// Uses the [fdeflate](https://crates.io/crates/fdeflate) crate under the hood to achieve
384 /// speeds far exceeding what libpng is capable of while still providing a decent compression
385 /// ratio.
386 ///
387 /// Note: When used in streaming mode, this compression level can actually result in files
388 /// *larger* than would be produced by `NoCompression` because it doesn't do any buffering of
389 /// the output stream to detect whether the data is being compressed or not.
390 FdeflateUltraFast,
391
392 /// Compression level between 1 and 9, where higher values mean better compression at the cost of
393 /// speed.
394 ///
395 /// This is currently implemented via [flate2](https://crates.io/crates/flate2) crate
396 /// by passing through the [compression level](flate2::Compression::new).
397 ///
398 /// The implementation details and the exact meaning of each level may change in the future,
399 /// including in semver-compatible releases.
400 Level(u8),
401 // Other variants can be added in the future
402}
403
404impl Default for DeflateCompression {
405 fn default() -> Self {
406 Self::from_simple(Compression::Balanced)
407 }
408}
409
410impl DeflateCompression {
411 pub(crate) fn from_simple(value: Compression) -> Self {
412 match value {
413 Compression::NoCompression => Self::NoCompression,
414 Compression::Fastest => Self::FdeflateUltraFast,
415 Compression::Fast => Self::FdeflateUltraFast,
416 Compression::Balanced => Self::Level(flate2::Compression::default().level() as u8),
417 Compression::High => Self::Level(flate2::Compression::best().level() as u8),
418 }
419 }
420}
421
422/// An unsigned integer scaled version of a floating point value,
423/// equivalent to an integer quotient with fixed denominator (100_000)).
424#[derive(Clone, Copy, Debug, PartialEq, Eq)]
425pub struct ScaledFloat(u32);
426
427impl ScaledFloat {
428 const SCALING: f32 = 100_000.0;
429
430 /// Gets whether the value is within the clamped range of this type.
431 pub fn in_range(value: f32) -> bool {
432 value >= 0.0 && (value * Self::SCALING).floor() <= u32::MAX as f32
433 }
434
435 /// Gets whether the value can be exactly converted in round-trip.
436 #[allow(clippy::float_cmp)] // Stupid tool, the exact float compare is _the entire point_.
437 pub fn exact(value: f32) -> bool {
438 let there = Self::forward(value);
439 let back = Self::reverse(there);
440 value == back
441 }
442
443 fn forward(value: f32) -> u32 {
444 (value.max(0.0) * Self::SCALING).floor() as u32
445 }
446
447 fn reverse(encoded: u32) -> f32 {
448 encoded as f32 / Self::SCALING
449 }
450
451 /// Slightly inaccurate scaling and quantization.
452 /// Clamps the value into the representable range if it is negative or too large.
453 pub fn new(value: f32) -> Self {
454 Self(Self::forward(value))
455 }
456
457 /// Fully accurate construction from a value scaled as per specification.
458 pub fn from_scaled(val: u32) -> Self {
459 Self(val)
460 }
461
462 /// Get the accurate encoded value.
463 pub fn into_scaled(self) -> u32 {
464 self.0
465 }
466
467 /// Get the unscaled value as a floating point.
468 pub fn into_value(self) -> f32 {
469 Self::reverse(self.0)
470 }
471
472 pub(crate) fn encode_gama<W: Write>(self, w: &mut W) -> encoder::Result<()> {
473 encoder::write_chunk(w, chunk::gAMA, &self.into_scaled().to_be_bytes())
474 }
475}
476
477/// Chromaticities of the color space primaries
478#[derive(Clone, Copy, Debug, PartialEq, Eq)]
479pub struct SourceChromaticities {
480 pub white: (ScaledFloat, ScaledFloat),
481 pub red: (ScaledFloat, ScaledFloat),
482 pub green: (ScaledFloat, ScaledFloat),
483 pub blue: (ScaledFloat, ScaledFloat),
484}
485
486impl SourceChromaticities {
487 pub fn new(white: (f32, f32), red: (f32, f32), green: (f32, f32), blue: (f32, f32)) -> Self {
488 SourceChromaticities {
489 white: (ScaledFloat::new(white.0), ScaledFloat::new(white.1)),
490 red: (ScaledFloat::new(red.0), ScaledFloat::new(red.1)),
491 green: (ScaledFloat::new(green.0), ScaledFloat::new(green.1)),
492 blue: (ScaledFloat::new(blue.0), ScaledFloat::new(blue.1)),
493 }
494 }
495
496 #[rustfmt::skip]
497 pub fn to_be_bytes(self) -> [u8; 32] {
498 let white_x = self.white.0.into_scaled().to_be_bytes();
499 let white_y = self.white.1.into_scaled().to_be_bytes();
500 let red_x = self.red.0.into_scaled().to_be_bytes();
501 let red_y = self.red.1.into_scaled().to_be_bytes();
502 let green_x = self.green.0.into_scaled().to_be_bytes();
503 let green_y = self.green.1.into_scaled().to_be_bytes();
504 let blue_x = self.blue.0.into_scaled().to_be_bytes();
505 let blue_y = self.blue.1.into_scaled().to_be_bytes();
506 [
507 white_x[0], white_x[1], white_x[2], white_x[3],
508 white_y[0], white_y[1], white_y[2], white_y[3],
509 red_x[0], red_x[1], red_x[2], red_x[3],
510 red_y[0], red_y[1], red_y[2], red_y[3],
511 green_x[0], green_x[1], green_x[2], green_x[3],
512 green_y[0], green_y[1], green_y[2], green_y[3],
513 blue_x[0], blue_x[1], blue_x[2], blue_x[3],
514 blue_y[0], blue_y[1], blue_y[2], blue_y[3],
515 ]
516 }
517
518 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
519 encoder::write_chunk(w, chunk::cHRM, &self.to_be_bytes())
520 }
521}
522
523/// The rendering intent for an sRGB image.
524///
525/// Presence of this data also indicates that the image conforms to the sRGB color space.
526#[repr(u8)]
527#[derive(Clone, Copy, Debug, PartialEq, Eq)]
528pub enum SrgbRenderingIntent {
529 /// For images preferring good adaptation to the output device gamut at the expense of colorimetric accuracy, such as photographs.
530 Perceptual = 0,
531 /// For images requiring colour appearance matching (relative to the output device white point), such as logos.
532 RelativeColorimetric = 1,
533 /// For images preferring preservation of saturation at the expense of hue and lightness, such as charts and graphs.
534 Saturation = 2,
535 /// For images requiring preservation of absolute colorimetry, such as previews of images destined for a different output device (proofs).
536 AbsoluteColorimetric = 3,
537}
538
539impl SrgbRenderingIntent {
540 pub(crate) fn into_raw(self) -> u8 {
541 self as u8
542 }
543
544 pub(crate) fn from_raw(raw: u8) -> Option<Self> {
545 match raw {
546 0 => Some(SrgbRenderingIntent::Perceptual),
547 1 => Some(SrgbRenderingIntent::RelativeColorimetric),
548 2 => Some(SrgbRenderingIntent::Saturation),
549 3 => Some(SrgbRenderingIntent::AbsoluteColorimetric),
550 _ => None,
551 }
552 }
553
554 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
555 encoder::write_chunk(w, chunk::sRGB, &[self.into_raw()])
556 }
557}
558
559/// Coding-independent code points (cICP) specify the color space (primaries),
560/// transfer function, matrix coefficients and scaling factor of the image using
561/// the code points specified in [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273).
562///
563/// See https://www.w3.org/TR/png-3/#cICP-chunk for more details.
564#[derive(Clone, Copy, Debug, PartialEq, Eq)]
565pub struct CodingIndependentCodePoints {
566 /// Id number of the color primaries defined in
567 /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 2 -
568 /// Interpretation of colour primaries (ColourPrimaries) value".
569 pub color_primaries: u8,
570
571 /// Id number of the transfer characteristics defined in
572 /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 3 -
573 /// Interpretation of transfer characteristics (TransferCharacteristics)
574 /// value".
575 pub transfer_function: u8,
576
577 /// Id number of the matrix coefficients defined in
578 /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) in "Table 4 -
579 /// Interpretation of matrix coefficients (MatrixCoefficients) value".
580 ///
581 /// This field is included to faithfully replicate the base
582 /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) specification, but matrix coefficients
583 /// will always be set to 0, because RGB is currently the only supported color mode in PNG.
584 pub matrix_coefficients: u8,
585
586 /// Whether the image is
587 /// [a full range image](https://www.w3.org/TR/png-3/#dfn-full-range-image)
588 /// or
589 /// [a narrow range image](https://www.w3.org/TR/png-3/#dfn-narrow-range-image).
590 ///
591 /// This field is included to faithfully replicate the base
592 /// [ITU-T-H.273](https://www.itu.int/rec/T-REC-H.273) specification, but it has limited
593 /// practical application to PNG images, because narrow-range images are [quite
594 /// rare](https://github.com/w3c/png/issues/312#issuecomment-2327349614) in practice.
595 pub is_video_full_range_image: bool,
596}
597
598/// Mastering Display Color Volume (mDCV) used at the point of content creation,
599/// as specified in [SMPTE-ST-2086](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8353899).
600///
601/// See https://www.w3.org/TR/png-3/#mDCV-chunk for more details.
602#[derive(Clone, Copy, Debug, PartialEq, Eq)]
603pub struct MasteringDisplayColorVolume {
604 /// Mastering display chromaticities.
605 pub chromaticities: SourceChromaticities,
606
607 /// Mastering display maximum luminance.
608 ///
609 /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
610 /// is set to `10000000` then it indicates 1000 cd/m^2.
611 pub max_luminance: u32,
612
613 /// Mastering display minimum luminance.
614 ///
615 /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
616 /// is set to `10000000` then it indicates 1000 cd/m^2.
617 pub min_luminance: u32,
618}
619
620/// Content light level information of HDR content.
621///
622/// See https://www.w3.org/TR/png-3/#cLLI-chunk for more details.
623#[derive(Clone, Copy, Debug, PartialEq, Eq)]
624pub struct ContentLightLevelInfo {
625 /// Maximum Content Light Level indicates the maximum light level of any
626 /// single pixel (in cd/m^2, also known as nits) of the entire playback
627 /// sequence.
628 ///
629 /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
630 /// is set to `10000000` then it indicates 1000 cd/m^2.
631 ///
632 /// A value of zero means that the value is unknown or not currently calculable.
633 pub max_content_light_level: u32,
634
635 /// Maximum Frame Average Light Level indicates the maximum value of the
636 /// frame average light level (in cd/m^2, also known as nits) of the entire
637 /// playback sequence. It is calculated by first averaging the decoded
638 /// luminance values of all the pixels in each frame, and then using the
639 /// value for the frame with the highest value.
640 ///
641 /// The value is expressed in units of 0.0001 cd/m^2 - for example if this field
642 /// is set to `10000000` then it indicates 1000 cd/m^2.
643 ///
644 /// A value of zero means that the value is unknown or not currently calculable.
645 pub max_frame_average_light_level: u32,
646}
647
648/// PNG info struct
649#[derive(Clone, Debug)]
650#[non_exhaustive]
651pub struct Info<'a> {
652 pub width: u32,
653 pub height: u32,
654 pub bit_depth: BitDepth,
655 /// How colors are stored in the image.
656 pub color_type: ColorType,
657 pub interlaced: bool,
658 /// The image's `sBIT` chunk, if present; contains significant bits of the sample.
659 pub sbit: Option<Cow<'a, [u8]>>,
660 /// The image's `tRNS` chunk, if present; contains the alpha channel of the image's palette, 1 byte per entry.
661 pub trns: Option<Cow<'a, [u8]>>,
662 pub pixel_dims: Option<PixelDimensions>,
663 /// 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).
664 pub palette: Option<Cow<'a, [u8]>>,
665 /// The contents of the image's gAMA chunk, if present.
666 /// Prefer `source_gamma` to also get the derived replacement gamma from sRGB chunks.
667 pub gama_chunk: Option<ScaledFloat>,
668 /// The contents of the image's `cHRM` chunk, if present.
669 /// Prefer `source_chromaticities` to also get the derived replacements from sRGB chunks.
670 pub chrm_chunk: Option<SourceChromaticities>,
671 /// The contents of the image's `bKGD` chunk, if present.
672 pub bkgd: Option<Cow<'a, [u8]>>,
673
674 pub frame_control: Option<FrameControl>,
675 pub animation_control: Option<AnimationControl>,
676 /// Gamma of the source system.
677 /// Set by both `gAMA` as well as to a replacement by `sRGB` chunk.
678 pub source_gamma: Option<ScaledFloat>,
679 /// Chromaticities of the source system.
680 /// Set by both `cHRM` as well as to a replacement by `sRGB` chunk.
681 pub source_chromaticities: Option<SourceChromaticities>,
682 /// The rendering intent of an SRGB image.
683 ///
684 /// Presence of this value also indicates that the image conforms to the SRGB color space.
685 pub srgb: Option<SrgbRenderingIntent>,
686 /// The ICC profile for the image.
687 pub icc_profile: Option<Cow<'a, [u8]>>,
688 /// The coding-independent code points for video signal type identification of the image.
689 pub coding_independent_code_points: Option<CodingIndependentCodePoints>,
690 /// The mastering display color volume for the image.
691 pub mastering_display_color_volume: Option<MasteringDisplayColorVolume>,
692 /// The content light information for the image.
693 pub content_light_level: Option<ContentLightLevelInfo>,
694 /// The EXIF metadata for the image.
695 pub exif_metadata: Option<Cow<'a, [u8]>>,
696 /// tEXt field
697 pub uncompressed_latin1_text: Vec<TEXtChunk>,
698 /// zTXt field
699 pub compressed_latin1_text: Vec<ZTXtChunk>,
700 /// iTXt field
701 pub utf8_text: Vec<ITXtChunk>,
702}
703
704impl Default for Info<'_> {
705 fn default() -> Info<'static> {
706 Info {
707 width: 0,
708 height: 0,
709 bit_depth: BitDepth::Eight,
710 color_type: ColorType::Grayscale,
711 interlaced: false,
712 palette: None,
713 sbit: None,
714 trns: None,
715 gama_chunk: None,
716 chrm_chunk: None,
717 bkgd: None,
718 pixel_dims: None,
719 frame_control: None,
720 animation_control: None,
721 source_gamma: None,
722 source_chromaticities: None,
723 srgb: None,
724 icc_profile: None,
725 coding_independent_code_points: None,
726 mastering_display_color_volume: None,
727 content_light_level: None,
728 exif_metadata: None,
729 uncompressed_latin1_text: Vec::new(),
730 compressed_latin1_text: Vec::new(),
731 utf8_text: Vec::new(),
732 }
733 }
734}
735
736impl Info<'_> {
737 /// A utility constructor for a default info with width and height.
738 pub fn with_size(width: u32, height: u32) -> Self {
739 Info {
740 width,
741 height,
742 ..Default::default()
743 }
744 }
745
746 /// Size of the image, width then height.
747 pub fn size(&self) -> (u32, u32) {
748 (self.width, self.height)
749 }
750
751 /// Returns true if the image is an APNG image.
752 pub fn is_animated(&self) -> bool {
753 self.frame_control.is_some() && self.animation_control.is_some()
754 }
755
756 /// Returns the frame control information of the image.
757 pub fn animation_control(&self) -> Option<&AnimationControl> {
758 self.animation_control.as_ref()
759 }
760
761 /// Returns the frame control information of the current frame
762 pub fn frame_control(&self) -> Option<&FrameControl> {
763 self.frame_control.as_ref()
764 }
765
766 /// Returns the number of bits per pixel.
767 pub fn bits_per_pixel(&self) -> usize {
768 self.color_type.bits_per_pixel(self.bit_depth)
769 }
770
771 /// Returns the number of bytes per pixel.
772 pub fn bytes_per_pixel(&self) -> usize {
773 // If adjusting this for expansion or other transformation passes, remember to keep the old
774 // implementation for bpp_in_prediction, which is internal to the png specification.
775 self.color_type.bytes_per_pixel(self.bit_depth)
776 }
777
778 /// Return the number of bytes for this pixel used in prediction.
779 ///
780 /// Some filters use prediction, over the raw bytes of a scanline. Where a previous pixel is
781 /// require for such forms the specification instead references previous bytes. That is, for
782 /// a gray pixel of bit depth 2, the pixel used in prediction is actually 4 pixels prior. This
783 /// has the consequence that the number of possible values is rather small. To make this fact
784 /// more obvious in the type system and the optimizer we use an explicit enum here.
785 pub(crate) fn bpp_in_prediction(&self) -> BytesPerPixel {
786 BytesPerPixel::from_usize(self.bytes_per_pixel())
787 }
788
789 /// Returns the number of bytes needed for one deinterlaced image.
790 pub fn raw_bytes(&self) -> usize {
791 self.height as usize * self.raw_row_length()
792 }
793
794 /// Returns the number of bytes needed for one deinterlaced row.
795 pub fn raw_row_length(&self) -> usize {
796 self.raw_row_length_from_width(self.width)
797 }
798
799 pub(crate) fn checked_raw_row_length(&self) -> Option<usize> {
800 self.color_type
801 .checked_raw_row_length(self.bit_depth, self.width)
802 }
803
804 /// Returns the number of bytes needed for one deinterlaced row of width `width`.
805 pub fn raw_row_length_from_width(&self, width: u32) -> usize {
806 self.color_type
807 .raw_row_length_from_width(self.bit_depth, width)
808 }
809
810 /// Gamma dependent on sRGB chunk
811 pub fn gamma(&self) -> Option<ScaledFloat> {
812 if self.srgb.is_some() {
813 Some(crate::srgb::substitute_gamma())
814 } else {
815 self.gama_chunk
816 }
817 }
818
819 /// Chromaticities dependent on sRGB chunk
820 pub fn chromaticities(&self) -> Option<SourceChromaticities> {
821 if self.srgb.is_some() {
822 Some(crate::srgb::substitute_chromaticities())
823 } else {
824 self.chrm_chunk
825 }
826 }
827
828 /// Mark the image data as conforming to the SRGB color space with the specified rendering intent.
829 ///
830 /// Any ICC profiles will be ignored.
831 ///
832 /// Source gamma and chromaticities will be written only if they're set to fallback
833 /// values specified in [11.3.2.5](https://www.w3.org/TR/png-3/#sRGB-gAMA-cHRM).
834 pub(crate) fn set_source_srgb(&mut self, rendering_intent: SrgbRenderingIntent) {
835 self.srgb = Some(rendering_intent);
836 self.icc_profile = None;
837 }
838}
839
840impl BytesPerPixel {
841 pub(crate) fn from_usize(bpp: usize) -> Self {
842 match bpp {
843 1 => BytesPerPixel::One,
844 2 => BytesPerPixel::Two,
845 3 => BytesPerPixel::Three,
846 4 => BytesPerPixel::Four,
847 6 => BytesPerPixel::Six, // Only rgb×16bit
848 8 => BytesPerPixel::Eight, // Only rgba×16bit
849 _ => unreachable!("Not a possible byte rounded pixel width"),
850 }
851 }
852
853 pub(crate) fn into_usize(self) -> usize {
854 self as usize
855 }
856}
857
858bitflags::bitflags! {
859 /// Output transformations
860 ///
861 /// Many flags from libpng are not yet supported. A PR discussing/adding them would be nice.
862 ///
863 #[doc = "
864 ```c
865 /// Discard the alpha channel
866 const STRIP_ALPHA = 0x0002; // read only
867 /// Expand 1; 2 and 4-bit samples to bytes
868 const PACKING = 0x0004; // read and write
869 /// Change order of packed pixels to LSB first
870 const PACKSWAP = 0x0008; // read and write
871 /// Invert monochrome images
872 const INVERT_MONO = 0x0020; // read and write
873 /// Normalize pixels to the sBIT depth
874 const SHIFT = 0x0040; // read and write
875 /// Flip RGB to BGR; RGBA to BGRA
876 const BGR = 0x0080; // read and write
877 /// Flip RGBA to ARGB or GA to AG
878 const SWAP_ALPHA = 0x0100; // read and write
879 /// Byte-swap 16-bit samples
880 const SWAP_ENDIAN = 0x0200; // read and write
881 /// Change alpha from opacity to transparency
882 const INVERT_ALPHA = 0x0400; // read and write
883 const STRIP_FILLER = 0x0800; // write only
884 const STRIP_FILLER_BEFORE = 0x0800; // write only
885 const STRIP_FILLER_AFTER = 0x1000; // write only
886 const GRAY_TO_RGB = 0x2000; // read only
887 const EXPAND_16 = 0x4000; // read only
888 /// Similar to STRIP_16 but in libpng considering gamma?
889 /// Not entirely sure the documentation says it is more
890 /// accurate but doesn't say precisely how.
891 const SCALE_16 = 0x8000; // read only
892 ```
893 "]
894 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
895 pub struct Transformations: u32 {
896 /// No transformation
897 const IDENTITY = 0x00000; // read and write */
898 /// Strip 16-bit samples to 8 bits
899 const STRIP_16 = 0x00001; // read only */
900 /// Expand paletted images to RGB; expand grayscale images of
901 /// less than 8-bit depth to 8-bit depth; and expand tRNS chunks
902 /// to alpha channels.
903 const EXPAND = 0x00010; // read only */
904 /// Expand paletted images to include an alpha channel. Implies `EXPAND`.
905 const ALPHA = 0x10000; // read only */
906 }
907}
908
909impl Transformations {
910 /// Transform every input to 8bit grayscale or color.
911 ///
912 /// This sets `EXPAND` and `STRIP_16` which is similar to the default transformation used by
913 /// this library prior to `0.17`.
914 pub fn normalize_to_color8() -> Transformations {
915 Transformations::EXPAND | Transformations::STRIP_16
916 }
917}
918
919/// Instantiate the default transformations, the identity transform.
920impl Default for Transformations {
921 fn default() -> Transformations {
922 Transformations::IDENTITY
923 }
924}
925
926#[derive(Debug)]
927pub struct ParameterError {
928 inner: ParameterErrorKind,
929}
930
931#[derive(Debug)]
932pub(crate) enum ParameterErrorKind {
933 /// A provided buffer must be have the exact size to hold the image data. Where the buffer can
934 /// be allocated by the caller, they must ensure that it has a minimum size as hinted previously.
935 /// Even though the size is calculated from image data, this does counts as a parameter error
936 /// because they must react to a value produced by this library, which can have been subjected
937 /// to limits.
938 ImageBufferSize { expected: usize, actual: usize },
939 /// A bit like return `None` from an iterator.
940 /// We use it to differentiate between failing to seek to the next image in a sequence and the
941 /// absence of a next image. This is an error of the caller because they should have checked
942 /// the number of images by inspecting the header data returned when opening the image. This
943 /// library will perform the checks necessary to ensure that data was accurate or error with a
944 /// format error otherwise.
945 PolledAfterEndOfImage,
946 /// Attempt to continue decoding after a fatal, non-resumable error was reported (e.g. after
947 /// [`DecodingError::Format`]). The only case when it is possible to resume after an error
948 /// is an `UnexpectedEof` scenario - see [`DecodingError::IoError`].
949 PolledAfterFatalError,
950}
951
952impl From<ParameterErrorKind> for ParameterError {
953 fn from(inner: ParameterErrorKind) -> Self {
954 ParameterError { inner }
955 }
956}
957
958impl fmt::Display for ParameterError {
959 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
960 use ParameterErrorKind::*;
961 match self.inner {
962 ImageBufferSize { expected, actual } => {
963 write!(fmt, "wrong data size, expected {} got {}", expected, actual)
964 }
965 PolledAfterEndOfImage => write!(fmt, "End of image has been reached"),
966 PolledAfterFatalError => {
967 write!(fmt, "A fatal decoding error has been encounted earlier")
968 }
969 }
970 }
971}