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