1use super::filter::{unfilter, FilterType};
12use crate::error::{CodecError, CodecResult};
13use bytes::Bytes;
14use flate2::read::ZlibDecoder;
15use std::io::Read;
16
17const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ColorType {
23 Grayscale = 0,
25 Rgb = 2,
27 Palette = 3,
29 GrayscaleAlpha = 4,
31 Rgba = 6,
33}
34
35impl ColorType {
36 pub fn from_u8(value: u8) -> CodecResult<Self> {
42 match value {
43 0 => Ok(Self::Grayscale),
44 2 => Ok(Self::Rgb),
45 3 => Ok(Self::Palette),
46 4 => Ok(Self::GrayscaleAlpha),
47 6 => Ok(Self::Rgba),
48 _ => Err(CodecError::InvalidData(format!(
49 "Invalid color type: {value}"
50 ))),
51 }
52 }
53
54 #[must_use]
56 pub const fn samples_per_pixel(self) -> usize {
57 match self {
58 Self::Grayscale => 1,
59 Self::Rgb => 3,
60 Self::Palette => 1,
61 Self::GrayscaleAlpha => 2,
62 Self::Rgba => 4,
63 }
64 }
65
66 #[must_use]
68 pub const fn has_alpha(self) -> bool {
69 matches!(self, Self::GrayscaleAlpha | Self::Rgba)
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct ImageHeader {
76 pub width: u32,
78 pub height: u32,
80 pub bit_depth: u8,
82 pub color_type: ColorType,
84 pub compression: u8,
86 pub filter_method: u8,
88 pub interlace: u8,
90}
91
92impl ImageHeader {
93 pub fn parse(data: &[u8]) -> CodecResult<Self> {
99 if data.len() < 13 {
100 return Err(CodecError::InvalidData("IHDR too short".into()));
101 }
102
103 let width = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
104 let height = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
105 let bit_depth = data[8];
106 let color_type = ColorType::from_u8(data[9])?;
107 let compression = data[10];
108 let filter_method = data[11];
109 let interlace = data[12];
110
111 if width == 0 || height == 0 {
112 return Err(CodecError::InvalidData("Invalid dimensions".into()));
113 }
114
115 if compression != 0 {
116 return Err(CodecError::UnsupportedFeature(format!(
117 "Unsupported compression method: {compression}"
118 )));
119 }
120
121 if filter_method != 0 {
122 return Err(CodecError::UnsupportedFeature(format!(
123 "Unsupported filter method: {filter_method}"
124 )));
125 }
126
127 if interlace > 1 {
128 return Err(CodecError::InvalidData(format!(
129 "Invalid interlace method: {interlace}"
130 )));
131 }
132
133 let valid_depths = match color_type {
135 ColorType::Grayscale => &[1, 2, 4, 8, 16][..],
136 ColorType::Rgb => &[8, 16][..],
137 ColorType::Palette => &[1, 2, 4, 8][..],
138 ColorType::GrayscaleAlpha => &[8, 16][..],
139 ColorType::Rgba => &[8, 16][..],
140 };
141
142 if !valid_depths.contains(&bit_depth) {
143 return Err(CodecError::InvalidData(format!(
144 "Invalid bit depth {bit_depth} for color type {color_type:?}"
145 )));
146 }
147
148 Ok(Self {
149 width,
150 height,
151 bit_depth,
152 color_type,
153 compression,
154 filter_method,
155 interlace,
156 })
157 }
158
159 #[must_use]
161 pub fn bytes_per_pixel(&self) -> usize {
162 let bits_per_pixel = self.color_type.samples_per_pixel() * self.bit_depth as usize;
163 (bits_per_pixel + 7) / 8
164 }
165
166 #[must_use]
168 pub fn scanline_length(&self) -> usize {
169 let bits_per_scanline =
170 self.width as usize * self.color_type.samples_per_pixel() * self.bit_depth as usize;
171 (bits_per_scanline + 7) / 8
172 }
173}
174
175#[derive(Debug)]
177struct Chunk {
178 chunk_type: [u8; 4],
180 data: Vec<u8>,
182}
183
184impl Chunk {
185 fn read(data: &[u8], offset: &mut usize) -> CodecResult<Self> {
187 if *offset + 12 > data.len() {
188 return Err(CodecError::InvalidData("Incomplete chunk".into()));
189 }
190
191 let length = u32::from_be_bytes([
192 data[*offset],
193 data[*offset + 1],
194 data[*offset + 2],
195 data[*offset + 3],
196 ]) as usize;
197 *offset += 4;
198
199 let chunk_type = [
200 data[*offset],
201 data[*offset + 1],
202 data[*offset + 2],
203 data[*offset + 3],
204 ];
205 *offset += 4;
206
207 if *offset + length + 4 > data.len() {
208 return Err(CodecError::InvalidData("Incomplete chunk data".into()));
209 }
210
211 let chunk_data = data[*offset..*offset + length].to_vec();
212 *offset += length;
213
214 let expected_crc = u32::from_be_bytes([
215 data[*offset],
216 data[*offset + 1],
217 data[*offset + 2],
218 data[*offset + 3],
219 ]);
220 *offset += 4;
221
222 let actual_crc = crc32(&chunk_type, &chunk_data);
224 if actual_crc != expected_crc {
225 return Err(CodecError::InvalidData(format!(
226 "CRC mismatch for chunk {:?}",
227 std::str::from_utf8(&chunk_type).unwrap_or("???")
228 )));
229 }
230
231 Ok(Self {
232 chunk_type,
233 data: chunk_data,
234 })
235 }
236
237 fn type_str(&self) -> &str {
239 std::str::from_utf8(&self.chunk_type).unwrap_or("????")
240 }
241}
242
243fn crc32(chunk_type: &[u8; 4], data: &[u8]) -> u32 {
245 let mut crc = !0u32;
246
247 for &byte in chunk_type.iter().chain(data.iter()) {
248 crc ^= u32::from(byte);
249 for _ in 0..8 {
250 crc = if crc & 1 != 0 {
251 0xedb8_8320 ^ (crc >> 1)
252 } else {
253 crc >> 1
254 };
255 }
256 }
257
258 !crc
259}
260
261struct Adam7Pass {
263 x_start: u32,
264 y_start: u32,
265 x_step: u32,
266 y_step: u32,
267}
268
269const ADAM7_PASSES: [Adam7Pass; 7] = [
270 Adam7Pass {
271 x_start: 0,
272 y_start: 0,
273 x_step: 8,
274 y_step: 8,
275 },
276 Adam7Pass {
277 x_start: 4,
278 y_start: 0,
279 x_step: 8,
280 y_step: 8,
281 },
282 Adam7Pass {
283 x_start: 0,
284 y_start: 4,
285 x_step: 4,
286 y_step: 8,
287 },
288 Adam7Pass {
289 x_start: 2,
290 y_start: 0,
291 x_step: 4,
292 y_step: 4,
293 },
294 Adam7Pass {
295 x_start: 0,
296 y_start: 2,
297 x_step: 2,
298 y_step: 4,
299 },
300 Adam7Pass {
301 x_start: 1,
302 y_start: 0,
303 x_step: 2,
304 y_step: 2,
305 },
306 Adam7Pass {
307 x_start: 0,
308 y_start: 1,
309 x_step: 1,
310 y_step: 2,
311 },
312];
313
314pub struct PngDecoder {
316 header: ImageHeader,
317 palette: Option<Vec<u8>>,
318 transparency: Option<Vec<u8>>,
319 #[allow(dead_code)]
320 gamma: Option<f64>,
321 image_data: Vec<u8>,
322}
323
324impl PngDecoder {
325 pub fn new(data: &[u8]) -> CodecResult<Self> {
331 if data.len() < 8 {
332 return Err(CodecError::InvalidData("PNG data too short".into()));
333 }
334
335 if &data[0..8] != PNG_SIGNATURE {
337 return Err(CodecError::InvalidData("Invalid PNG signature".into()));
338 }
339
340 let mut offset = 8;
341 let mut header: Option<ImageHeader> = None;
342 let mut palette: Option<Vec<u8>> = None;
343 let mut transparency: Option<Vec<u8>> = None;
344 let mut gamma: Option<f64> = None;
345 let mut idat_chunks = Vec::new();
346
347 while offset < data.len() {
349 let chunk = Chunk::read(data, &mut offset)?;
350
351 match chunk.type_str() {
352 "IHDR" => {
353 if header.is_some() {
354 return Err(CodecError::InvalidData("Multiple IHDR chunks".into()));
355 }
356 header = Some(ImageHeader::parse(&chunk.data)?);
357 }
358 "PLTE" => {
359 if palette.is_some() {
360 return Err(CodecError::InvalidData("Multiple PLTE chunks".into()));
361 }
362 if chunk.data.len() % 3 != 0 || chunk.data.is_empty() {
363 return Err(CodecError::InvalidData("Invalid PLTE chunk".into()));
364 }
365 palette = Some(chunk.data);
366 }
367 "IDAT" => {
368 idat_chunks.push(chunk.data);
369 }
370 "IEND" => {
371 break;
372 }
373 "tRNS" => {
374 transparency = Some(chunk.data);
375 }
376 "gAMA" => {
377 if chunk.data.len() == 4 {
378 let gamma_int = u32::from_be_bytes([
379 chunk.data[0],
380 chunk.data[1],
381 chunk.data[2],
382 chunk.data[3],
383 ]);
384 gamma = Some(f64::from(gamma_int) / 100_000.0);
385 }
386 }
387 _ => {
388 }
390 }
391 }
392
393 let header = header.ok_or_else(|| CodecError::InvalidData("Missing IHDR chunk".into()))?;
394
395 if header.color_type == ColorType::Palette && palette.is_none() {
396 return Err(CodecError::InvalidData(
397 "Palette color type requires PLTE chunk".into(),
398 ));
399 }
400
401 let compressed_data: Vec<u8> = idat_chunks.into_iter().flatten().collect();
403 let mut decoder = ZlibDecoder::new(&compressed_data[..]);
404 let mut image_data = Vec::new();
405 decoder
406 .read_to_end(&mut image_data)
407 .map_err(|e| CodecError::DecoderError(format!("DEFLATE decompression failed: {e}")))?;
408
409 Ok(Self {
410 header,
411 palette,
412 transparency,
413 gamma,
414 image_data,
415 })
416 }
417
418 #[must_use]
420 pub const fn width(&self) -> u32 {
421 self.header.width
422 }
423
424 #[must_use]
426 pub const fn height(&self) -> u32 {
427 self.header.height
428 }
429
430 #[must_use]
432 pub const fn color_type(&self) -> ColorType {
433 self.header.color_type
434 }
435
436 #[must_use]
438 pub const fn bit_depth(&self) -> u8 {
439 self.header.bit_depth
440 }
441
442 #[must_use]
444 pub const fn is_interlaced(&self) -> bool {
445 self.header.interlace == 1
446 }
447
448 pub fn decode(&self) -> CodecResult<DecodedImage> {
454 let raw_data = if self.is_interlaced() {
455 self.decode_interlaced()?
456 } else {
457 self.decode_sequential()?
458 };
459
460 let rgba_data = self.convert_to_rgba(&raw_data)?;
461
462 Ok(DecodedImage {
463 width: self.header.width,
464 height: self.header.height,
465 data: Bytes::from(rgba_data),
466 })
467 }
468
469 fn decode_sequential(&self) -> CodecResult<Vec<u8>> {
471 let scanline_len = self.header.scanline_length();
472 let bytes_per_pixel = self.header.bytes_per_pixel();
473 let expected_len = (scanline_len + 1) * self.header.height as usize;
474
475 if self.image_data.len() < expected_len {
476 return Err(CodecError::InvalidData("Insufficient image data".into()));
477 }
478
479 let mut output = Vec::with_capacity(scanline_len * self.header.height as usize);
480 let mut prev_scanline: Option<Vec<u8>> = None;
481
482 for y in 0..self.header.height as usize {
483 let offset = y * (scanline_len + 1);
484 let filter_type = FilterType::from_u8(self.image_data[offset])?;
485 let filtered = &self.image_data[offset + 1..offset + 1 + scanline_len];
486
487 let scanline = unfilter(
488 filter_type,
489 filtered,
490 prev_scanline.as_deref(),
491 bytes_per_pixel,
492 )?;
493
494 output.extend_from_slice(&scanline);
495 prev_scanline = Some(scanline);
496 }
497
498 Ok(output)
499 }
500
501 #[allow(clippy::similar_names)]
503 fn decode_interlaced(&self) -> CodecResult<Vec<u8>> {
504 let bytes_per_pixel = self.header.bytes_per_pixel();
505 let bits_per_pixel =
506 self.header.color_type.samples_per_pixel() * self.header.bit_depth as usize;
507 let bytes_per_sample = (self.header.bit_depth as usize + 7) / 8;
508
509 let total_pixels =
510 self.header.width as usize * self.header.height as usize * bytes_per_pixel;
511 let mut output = vec![0u8; total_pixels];
512
513 let mut data_offset = 0;
514
515 for pass in &ADAM7_PASSES {
516 let pass_width =
517 (self.header.width.saturating_sub(pass.x_start) + pass.x_step - 1) / pass.x_step;
518 let pass_height =
519 (self.header.height.saturating_sub(pass.y_start) + pass.y_step - 1) / pass.y_step;
520
521 if pass_width == 0 || pass_height == 0 {
522 continue;
523 }
524
525 let scanline_bits = pass_width as usize * bits_per_pixel;
526 let scanline_len = (scanline_bits + 7) / 8;
527
528 let mut prev_scanline: Option<Vec<u8>> = None;
529
530 for py in 0..pass_height {
531 if data_offset >= self.image_data.len() {
532 return Err(CodecError::InvalidData(
533 "Insufficient data for interlaced image".into(),
534 ));
535 }
536
537 let filter_type = FilterType::from_u8(self.image_data[data_offset])?;
538 data_offset += 1;
539
540 if data_offset + scanline_len > self.image_data.len() {
541 return Err(CodecError::InvalidData(
542 "Insufficient data for scanline".into(),
543 ));
544 }
545
546 let filtered = &self.image_data[data_offset..data_offset + scanline_len];
547 data_offset += scanline_len;
548
549 let scanline = unfilter(
550 filter_type,
551 filtered,
552 prev_scanline.as_deref(),
553 bytes_per_pixel,
554 )?;
555
556 let y = (pass.y_start + py * pass.y_step) as usize;
558 for px in 0..pass_width as usize {
559 let x = pass.x_start as usize + px * pass.x_step as usize;
560 let src_offset = px * bytes_per_pixel;
561 let dst_offset = (y * self.header.width as usize + x) * bytes_per_pixel;
562
563 if src_offset + bytes_per_pixel <= scanline.len()
564 && dst_offset + bytes_per_pixel <= output.len()
565 {
566 output[dst_offset..dst_offset + bytes_per_pixel]
567 .copy_from_slice(&scanline[src_offset..src_offset + bytes_per_pixel]);
568 }
569 }
570
571 prev_scanline = Some(scanline);
572 }
573 }
574
575 Ok(output)
576 }
577
578 #[allow(clippy::too_many_lines)]
580 fn convert_to_rgba(&self, raw_data: &[u8]) -> CodecResult<Vec<u8>> {
581 let pixel_count = (self.header.width * self.header.height) as usize;
582 let mut rgba = vec![255u8; pixel_count * 4];
583
584 match self.header.color_type {
585 ColorType::Grayscale => {
586 if self.header.bit_depth == 8 {
587 for i in 0..pixel_count {
588 let gray = raw_data[i];
589 rgba[i * 4] = gray;
590 rgba[i * 4 + 1] = gray;
591 rgba[i * 4 + 2] = gray;
592 rgba[i * 4 + 3] = 255;
593 }
594 } else if self.header.bit_depth == 16 {
595 for i in 0..pixel_count {
596 let gray = raw_data[i * 2];
597 rgba[i * 4] = gray;
598 rgba[i * 4 + 1] = gray;
599 rgba[i * 4 + 2] = gray;
600 rgba[i * 4 + 3] = 255;
601 }
602 } else {
603 self.expand_grayscale(raw_data, &mut rgba)?;
605 }
606 }
607 ColorType::Rgb => {
608 if self.header.bit_depth == 8 {
609 for i in 0..pixel_count {
610 rgba[i * 4] = raw_data[i * 3];
611 rgba[i * 4 + 1] = raw_data[i * 3 + 1];
612 rgba[i * 4 + 2] = raw_data[i * 3 + 2];
613 rgba[i * 4 + 3] = 255;
614 }
615 } else if self.header.bit_depth == 16 {
616 for i in 0..pixel_count {
617 rgba[i * 4] = raw_data[i * 6];
618 rgba[i * 4 + 1] = raw_data[i * 6 + 2];
619 rgba[i * 4 + 2] = raw_data[i * 6 + 4];
620 rgba[i * 4 + 3] = 255;
621 }
622 }
623 }
624 ColorType::Palette => {
625 let palette = self
626 .palette
627 .as_ref()
628 .ok_or_else(|| CodecError::InvalidData("Missing palette".into()))?;
629 self.expand_palette(raw_data, palette, &mut rgba)?;
630 }
631 ColorType::GrayscaleAlpha => {
632 if self.header.bit_depth == 8 {
633 for i in 0..pixel_count {
634 let gray = raw_data[i * 2];
635 let alpha = raw_data[i * 2 + 1];
636 rgba[i * 4] = gray;
637 rgba[i * 4 + 1] = gray;
638 rgba[i * 4 + 2] = gray;
639 rgba[i * 4 + 3] = alpha;
640 }
641 } else if self.header.bit_depth == 16 {
642 for i in 0..pixel_count {
643 let gray = raw_data[i * 4];
644 let alpha = raw_data[i * 4 + 2];
645 rgba[i * 4] = gray;
646 rgba[i * 4 + 1] = gray;
647 rgba[i * 4 + 2] = gray;
648 rgba[i * 4 + 3] = alpha;
649 }
650 }
651 }
652 ColorType::Rgba => {
653 if self.header.bit_depth == 8 {
654 rgba.copy_from_slice(raw_data);
655 } else if self.header.bit_depth == 16 {
656 for i in 0..pixel_count {
657 rgba[i * 4] = raw_data[i * 8];
658 rgba[i * 4 + 1] = raw_data[i * 8 + 2];
659 rgba[i * 4 + 2] = raw_data[i * 8 + 4];
660 rgba[i * 4 + 3] = raw_data[i * 8 + 6];
661 }
662 }
663 }
664 }
665
666 if let Some(trns) = &self.transparency {
668 self.apply_transparency(&mut rgba, trns)?;
669 }
670
671 Ok(rgba)
672 }
673
674 fn expand_grayscale(&self, raw_data: &[u8], rgba: &mut [u8]) -> CodecResult<()> {
676 let pixel_count = (self.header.width * self.header.height) as usize;
677 let scale = 255 / ((1 << self.header.bit_depth) - 1);
678
679 let mut bit_pos = 0;
680 for i in 0..pixel_count {
681 let byte_pos = bit_pos / 8;
682 let shift = 8 - (bit_pos % 8) - self.header.bit_depth as usize;
683 let mask = (1 << self.header.bit_depth) - 1;
684
685 if byte_pos >= raw_data.len() {
686 return Err(CodecError::InvalidData(
687 "Insufficient grayscale data".into(),
688 ));
689 }
690
691 let value = (raw_data[byte_pos] >> shift) & mask;
692 let gray = value * scale;
693
694 rgba[i * 4] = gray;
695 rgba[i * 4 + 1] = gray;
696 rgba[i * 4 + 2] = gray;
697 rgba[i * 4 + 3] = 255;
698
699 bit_pos += self.header.bit_depth as usize;
700 }
701
702 Ok(())
703 }
704
705 fn expand_palette(&self, raw_data: &[u8], palette: &[u8], rgba: &mut [u8]) -> CodecResult<()> {
707 let pixel_count = (self.header.width * self.header.height) as usize;
708 let mut bit_pos = 0;
709
710 for i in 0..pixel_count {
711 let byte_pos = bit_pos / 8;
712 let shift = 8 - (bit_pos % 8) - self.header.bit_depth as usize;
713 let mask = (1 << self.header.bit_depth) - 1;
714
715 if byte_pos >= raw_data.len() {
716 return Err(CodecError::InvalidData("Insufficient palette data".into()));
717 }
718
719 let index = ((raw_data[byte_pos] >> shift) & mask) as usize;
720 let palette_offset = index * 3;
721
722 if palette_offset + 3 > palette.len() {
723 return Err(CodecError::InvalidData(format!(
724 "Invalid palette index: {index}"
725 )));
726 }
727
728 rgba[i * 4] = palette[palette_offset];
729 rgba[i * 4 + 1] = palette[palette_offset + 1];
730 rgba[i * 4 + 2] = palette[palette_offset + 2];
731 rgba[i * 4 + 3] = 255;
732
733 bit_pos += self.header.bit_depth as usize;
734 }
735
736 Ok(())
737 }
738
739 fn apply_transparency(&self, rgba: &mut [u8], trns: &[u8]) -> CodecResult<()> {
741 match self.header.color_type {
742 ColorType::Grayscale => {
743 if trns.len() < 2 {
744 return Ok(());
745 }
746 let transparent_gray = u16::from_be_bytes([trns[0], trns[1]]);
747 let transparent_gray_8 = if self.header.bit_depth == 16 {
748 (transparent_gray >> 8) as u8
749 } else {
750 transparent_gray as u8
751 };
752
753 for i in 0..rgba.len() / 4 {
754 if rgba[i * 4] == transparent_gray_8 {
755 rgba[i * 4 + 3] = 0;
756 }
757 }
758 }
759 ColorType::Palette => {
760 for i in 0..rgba.len() / 4 {
761 if i < trns.len() {
762 rgba[i * 4 + 3] = trns[i];
763 }
764 }
765 }
766 ColorType::Rgb => {
767 if trns.len() < 6 {
768 return Ok(());
769 }
770 let tr = u16::from_be_bytes([trns[0], trns[1]]);
771 let tg = u16::from_be_bytes([trns[2], trns[3]]);
772 let tb = u16::from_be_bytes([trns[4], trns[5]]);
773
774 for i in 0..rgba.len() / 4 {
775 let r = u16::from(rgba[i * 4]);
776 let g = u16::from(rgba[i * 4 + 1]);
777 let b = u16::from(rgba[i * 4 + 2]);
778
779 if r == tr && g == tg && b == tb {
780 rgba[i * 4 + 3] = 0;
781 }
782 }
783 }
784 _ => {}
785 }
786
787 Ok(())
788 }
789}
790
791#[derive(Debug, Clone)]
793pub struct DecodedImage {
794 pub width: u32,
796 pub height: u32,
798 pub data: Bytes,
800}
801
802#[derive(Debug, Clone, Copy)]
804pub struct Chromaticity {
805 pub white_x: f64,
807 pub white_y: f64,
809 pub red_x: f64,
811 pub red_y: f64,
813 pub green_x: f64,
815 pub green_y: f64,
817 pub blue_x: f64,
819 pub blue_y: f64,
821}
822
823#[derive(Debug, Clone, Copy)]
825pub struct PhysicalDimensions {
826 pub x: u32,
828 pub y: u32,
830 pub unit: u8,
832}
833
834#[derive(Debug, Clone)]
836pub struct SignificantBits {
837 pub bits: Vec<u8>,
839}
840
841#[derive(Debug, Clone)]
843pub struct TextChunk {
844 pub keyword: String,
846 pub text: String,
848}
849
850#[derive(Debug, Clone, Default)]
852pub struct PngMetadata {
853 pub gamma: Option<f64>,
855 pub chromaticity: Option<Chromaticity>,
857 pub physical_dimensions: Option<PhysicalDimensions>,
859 pub significant_bits: Option<SignificantBits>,
861 pub text_chunks: Vec<TextChunk>,
863 pub background_color: Option<(u16, u16, u16)>,
865 pub suggested_palette: Option<Vec<u8>>,
867}
868
869impl Chromaticity {
870 pub fn parse(data: &[u8]) -> CodecResult<Self> {
876 if data.len() < 32 {
877 return Err(CodecError::InvalidData("cHRM chunk too short".into()));
878 }
879
880 let white_x = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
881 let white_y = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
882 let red_x = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
883 let red_y = u32::from_be_bytes([data[12], data[13], data[14], data[15]]);
884 let green_x = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
885 let green_y = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
886 let blue_x = u32::from_be_bytes([data[24], data[25], data[26], data[27]]);
887 let blue_y = u32::from_be_bytes([data[28], data[29], data[30], data[31]]);
888
889 Ok(Self {
890 white_x: f64::from(white_x) / 100_000.0,
891 white_y: f64::from(white_y) / 100_000.0,
892 red_x: f64::from(red_x) / 100_000.0,
893 red_y: f64::from(red_y) / 100_000.0,
894 green_x: f64::from(green_x) / 100_000.0,
895 green_y: f64::from(green_y) / 100_000.0,
896 blue_x: f64::from(blue_x) / 100_000.0,
897 blue_y: f64::from(blue_y) / 100_000.0,
898 })
899 }
900
901 #[must_use]
903 pub fn srgb() -> Self {
904 Self {
905 white_x: 0.3127,
906 white_y: 0.329,
907 red_x: 0.64,
908 red_y: 0.33,
909 green_x: 0.3,
910 green_y: 0.6,
911 blue_x: 0.15,
912 blue_y: 0.06,
913 }
914 }
915}
916
917impl PhysicalDimensions {
918 pub fn parse(data: &[u8]) -> CodecResult<Self> {
924 if data.len() < 9 {
925 return Err(CodecError::InvalidData("pHYs chunk too short".into()));
926 }
927
928 let x = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
929 let y = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
930 let unit = data[8];
931
932 Ok(Self { x, y, unit })
933 }
934
935 #[must_use]
937 pub fn dpi(&self) -> Option<(f64, f64)> {
938 if self.unit == 1 {
939 const METERS_PER_INCH: f64 = 0.0254;
940 Some((
941 f64::from(self.x) * METERS_PER_INCH,
942 f64::from(self.y) * METERS_PER_INCH,
943 ))
944 } else {
945 None
946 }
947 }
948}
949
950impl SignificantBits {
951 pub fn parse(data: &[u8]) -> CodecResult<Self> {
957 if data.is_empty() {
958 return Err(CodecError::InvalidData("sBIT chunk is empty".into()));
959 }
960
961 Ok(Self {
962 bits: data.to_vec(),
963 })
964 }
965}
966
967impl TextChunk {
968 pub fn parse(data: &[u8]) -> CodecResult<Self> {
974 let null_pos = data
976 .iter()
977 .position(|&b| b == 0)
978 .ok_or_else(|| CodecError::InvalidData("tEXt chunk missing null separator".into()))?;
979
980 let keyword = String::from_utf8_lossy(&data[..null_pos]).into_owned();
981 let text = String::from_utf8_lossy(&data[null_pos + 1..]).into_owned();
982
983 Ok(Self { keyword, text })
984 }
985}
986
987pub struct PngDecoderExtended {
989 pub decoder: PngDecoder,
991 pub metadata: PngMetadata,
993}
994
995impl PngDecoderExtended {
996 #[allow(clippy::too_many_lines)]
1002 pub fn new(data: &[u8]) -> CodecResult<Self> {
1003 if data.len() < 8 {
1004 return Err(CodecError::InvalidData("PNG data too short".into()));
1005 }
1006
1007 if &data[0..8] != PNG_SIGNATURE {
1008 return Err(CodecError::InvalidData("Invalid PNG signature".into()));
1009 }
1010
1011 let mut offset = 8;
1012 let mut header: Option<ImageHeader> = None;
1013 let mut palette: Option<Vec<u8>> = None;
1014 let mut transparency: Option<Vec<u8>> = None;
1015 let mut metadata = PngMetadata::default();
1016 let mut idat_chunks = Vec::new();
1017
1018 while offset < data.len() {
1019 let chunk = Chunk::read(data, &mut offset)?;
1020
1021 match chunk.type_str() {
1022 "IHDR" => {
1023 if header.is_some() {
1024 return Err(CodecError::InvalidData("Multiple IHDR chunks".into()));
1025 }
1026 header = Some(ImageHeader::parse(&chunk.data)?);
1027 }
1028 "PLTE" => {
1029 if palette.is_some() {
1030 return Err(CodecError::InvalidData("Multiple PLTE chunks".into()));
1031 }
1032 if chunk.data.len() % 3 != 0 || chunk.data.is_empty() {
1033 return Err(CodecError::InvalidData("Invalid PLTE chunk".into()));
1034 }
1035 palette = Some(chunk.data);
1036 }
1037 "IDAT" => {
1038 idat_chunks.push(chunk.data);
1039 }
1040 "IEND" => {
1041 break;
1042 }
1043 "tRNS" => {
1044 transparency = Some(chunk.data);
1045 }
1046 "gAMA" => {
1047 if chunk.data.len() == 4 {
1048 let gamma_int = u32::from_be_bytes([
1049 chunk.data[0],
1050 chunk.data[1],
1051 chunk.data[2],
1052 chunk.data[3],
1053 ]);
1054 metadata.gamma = Some(f64::from(gamma_int) / 100_000.0);
1055 }
1056 }
1057 "cHRM" => {
1058 metadata.chromaticity = Some(Chromaticity::parse(&chunk.data)?);
1059 }
1060 "pHYs" => {
1061 metadata.physical_dimensions = Some(PhysicalDimensions::parse(&chunk.data)?);
1062 }
1063 "sBIT" => {
1064 metadata.significant_bits = Some(SignificantBits::parse(&chunk.data)?);
1065 }
1066 "tEXt" => {
1067 if let Ok(text_chunk) = TextChunk::parse(&chunk.data) {
1068 metadata.text_chunks.push(text_chunk);
1069 }
1070 }
1071 "bKGD" => {
1072 if chunk.data.len() >= 6 {
1073 let r = u16::from_be_bytes([chunk.data[0], chunk.data[1]]);
1074 let g = u16::from_be_bytes([chunk.data[2], chunk.data[3]]);
1075 let b = u16::from_be_bytes([chunk.data[4], chunk.data[5]]);
1076 metadata.background_color = Some((r, g, b));
1077 }
1078 }
1079 "sPLT" => {
1080 metadata.suggested_palette = Some(chunk.data);
1081 }
1082 _ => {
1083 }
1085 }
1086 }
1087
1088 let header = header.ok_or_else(|| CodecError::InvalidData("Missing IHDR chunk".into()))?;
1089
1090 if header.color_type == ColorType::Palette && palette.is_none() {
1091 return Err(CodecError::InvalidData(
1092 "Palette color type requires PLTE chunk".into(),
1093 ));
1094 }
1095
1096 let compressed_data: Vec<u8> = idat_chunks.into_iter().flatten().collect();
1097 let mut zlib_decoder = ZlibDecoder::new(&compressed_data[..]);
1098 let mut image_data = Vec::new();
1099 zlib_decoder
1100 .read_to_end(&mut image_data)
1101 .map_err(|e| CodecError::DecoderError(format!("DEFLATE decompression failed: {e}")))?;
1102
1103 let decoder = PngDecoder {
1104 header,
1105 palette,
1106 transparency,
1107 gamma: metadata.gamma,
1108 image_data,
1109 };
1110
1111 Ok(Self { decoder, metadata })
1112 }
1113
1114 pub fn decode(&self) -> CodecResult<DecodedImage> {
1120 self.decoder.decode()
1121 }
1122}