1use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk};
3use crate::{chunk, encoder};
4use io::Write;
5use std::{borrow::Cow, fmt, io};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum ColorType {
11 Grayscale = 0,
13 Rgb = 2,
15 Indexed = 3,
17 GrayscaleAlpha = 4,
19 Rgba = 6,
21}
22
23impl ColorType {
24 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 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 ((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#[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#[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 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#[derive(Clone, Copy, Debug)]
119pub struct PixelDimensions {
120 pub xppu: u32,
122 pub yppu: u32,
124 pub unit: Unit,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[repr(u8)]
130pub enum Unit {
132 Unspecified = 0,
133 Meter = 1,
134}
135
136impl Unit {
137 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149#[repr(u8)]
150pub enum DisposeOp {
151 None = 0,
153 Background = 1,
155 Previous = 2,
157}
158
159impl DisposeOp {
160 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[repr(u8)]
185pub enum BlendOp {
186 Source = 0,
188 Over = 1,
190}
191
192impl BlendOp {
193 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#[derive(Clone, Copy, Debug)]
215pub struct FrameControl {
216 pub sequence_number: u32,
218 pub width: u32,
220 pub height: u32,
222 pub x_offset: u32,
224 pub y_offset: u32,
226 pub delay_num: u16,
228 pub delay_den: u16,
230 pub dispose_op: DisposeOp,
232 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#[derive(Clone, Copy, Debug)]
279pub struct AnimationControl {
280 pub num_frames: u32,
282 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#[derive(Debug, Clone, Copy)]
297pub enum Compression {
298 Default,
300 Fast,
302 Best,
308 Huffman,
309 Rle,
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
315pub struct ScaledFloat(u32);
316
317impl ScaledFloat {
318 const SCALING: f32 = 100_000.0;
319
320 pub fn in_range(value: f32) -> bool {
322 value >= 0.0 && (value * Self::SCALING).floor() <= std::u32::MAX as f32
323 }
324
325 #[allow(clippy::float_cmp)] 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 pub fn new(value: f32) -> Self {
344 Self {
345 0: Self::forward(value),
346 }
347 }
348
349 pub fn from_scaled(val: u32) -> Self {
351 Self { 0: val }
352 }
353
354 pub fn into_scaled(self) -> u32 {
356 self.0
357 }
358
359 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#[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#[repr(u8)]
419#[derive(Clone, Copy, Debug, PartialEq, Eq)]
420pub enum SrgbRenderingIntent {
421 Perceptual = 0,
423 RelativeColorimetric = 1,
425 Saturation = 2,
427 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#[derive(Clone, Debug)]
443#[non_exhaustive]
444pub struct Info<'a> {
445 pub width: u32,
446 pub height: u32,
447 pub bit_depth: BitDepth,
448 pub color_type: ColorType,
450 pub interlaced: bool,
451 pub trns: Option<Cow<'a, [u8]>>,
453 pub pixel_dims: Option<PixelDimensions>,
454 pub source_gamma: Option<ScaledFloat>,
456 pub palette: Option<Cow<'a, [u8]>>,
458 pub frame_control: Option<FrameControl>,
459 pub animation_control: Option<AnimationControl>,
460 pub compression: Compression,
461 pub source_chromaticities: Option<SourceChromaticities>,
463 pub srgb: Option<SrgbRenderingIntent>,
467 pub icc_profile: Option<Cow<'a, [u8]>>,
469 pub uncompressed_latin1_text: Vec<TEXtChunk>,
471 pub compressed_latin1_text: Vec<ZTXtChunk>,
473 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 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 pub fn with_size(width: u32, height: u32) -> Self {
507 Info {
508 width,
509 height,
510 ..Default::default()
511 }
512 }
513
514 pub fn size(&self) -> (u32, u32) {
516 (self.width, self.height)
517 }
518
519 pub fn is_animated(&self) -> bool {
521 self.frame_control.is_some() && self.animation_control.is_some()
522 }
523
524 pub fn animation_control(&self) -> Option<&AnimationControl> {
526 self.animation_control.as_ref()
527 }
528
529 pub fn frame_control(&self) -> Option<&FrameControl> {
531 self.frame_control.as_ref()
532 }
533
534 pub fn bits_per_pixel(&self) -> usize {
536 self.color_type.samples() * self.bit_depth as usize
537 }
538
539 pub fn bytes_per_pixel(&self) -> usize {
541 self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3)
544 }
545
546 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, 8 => BytesPerPixel::Eight, _ => unreachable!("Not a possible byte rounded pixel width"),
562 }
563 }
564
565 pub fn raw_bytes(&self) -> usize {
567 self.height as usize * self.raw_row_length()
568 }
569
570 pub fn raw_row_length(&self) -> usize {
572 self.raw_row_length_from_width(self.width)
573 }
574
575 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 pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> {
586 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 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 #[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 const IDENTITY = 0x0000; const STRIP_16 = 0x0001; const EXPAND = 0x0010; }
690}
691
692impl Transformations {
693 pub fn normalize_to_color8() -> Transformations {
698 Transformations::EXPAND | Transformations::STRIP_16
699 }
700}
701
702impl 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 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}