1use std::io::{self, Write};
18
19use crate::container;
20use crate::error::PngError;
21use crate::interlace;
22use crate::meta::{Bkgd, Location, Metadata, Text, TextKind, Trns};
23use crate::{Image16, ImageRef};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ColorMode {
27 Auto,
29 Gray1,
30 Gray2,
31 Gray4,
32 Gray8,
33 Gray16,
34 GrayAlpha8,
35 GrayAlpha16,
36 Indexed1,
39 Indexed2,
40 Indexed4,
41 Indexed8,
42 Rgb8,
43 Rgb16,
44 Rgba8,
45 Rgba16,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum Filter {
50 None,
51 Sub,
52 Up,
53 Average,
54 Paeth,
55 Adaptive,
58 Choice(FilterSet),
62}
63
64impl Filter {
65 #[inline]
68 fn allowed(self) -> FilterSet {
69 match self {
70 Filter::None => FilterSet::NONE,
71 Filter::Sub => FilterSet::SUB,
72 Filter::Up => FilterSet::UP,
73 Filter::Average => FilterSet::AVERAGE,
74 Filter::Paeth => FilterSet::PAETH,
75 Filter::Adaptive => FilterSet::ALL,
76 Filter::Choice(set) => set,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct FilterSet(u8);
92
93impl FilterSet {
94 pub const NONE: Self = Self(1 << 0);
95 pub const SUB: Self = Self(1 << 1);
96 pub const UP: Self = Self(1 << 2);
97 pub const AVERAGE: Self = Self(1 << 3);
98 pub const PAETH: Self = Self(1 << 4);
99 pub const ALL: Self = Self(0b1_1111);
101
102 #[inline]
104 pub const fn contains(self, filter_type: u8) -> bool {
105 self.0 & (1 << filter_type) != 0
106 }
107
108 #[inline]
111 const fn single(self) -> Option<u8> {
112 if self.0.is_power_of_two() {
113 Some(self.0.trailing_zeros() as u8)
114 } else {
115 None
116 }
117 }
118}
119
120impl std::ops::BitOr for FilterSet {
121 type Output = Self;
122 #[inline]
123 fn bitor(self, rhs: Self) -> Self {
124 Self(self.0 | rhs.0)
125 }
126}
127
128#[derive(Debug, Clone)]
139pub struct EncodeOptions {
140 pub color: ColorMode,
141 pub compression: u8,
143 pub filter: Filter,
144 pub interlace: bool,
145}
146
147impl Default for EncodeOptions {
148 fn default() -> Self {
149 Self {
150 color: ColorMode::Auto,
151 compression: 4,
156 filter: Filter::Adaptive,
157 interlace: false,
158 }
159 }
160}
161
162struct Mode {
163 color_type: u8,
164 bit_depth: u8,
165 palette: Option<(Vec<[u8; 3]>, Vec<u8>)>,
167 palette_index: Option<ColorMap>,
171 indices: Option<Vec<u8>>,
176}
177
178impl Mode {
179 fn new(color_type: u8, bit_depth: u8, palette: Option<(Vec<[u8; 3]>, Vec<u8>)>) -> Self {
180 let palette_index = palette
181 .as_ref()
182 .map(|(rgb, alpha)| ColorMap::from_palette(rgb, alpha));
183 Self {
184 color_type,
185 bit_depth,
186 palette,
187 palette_index,
188 indices: None,
189 }
190 }
191
192 fn indexed(bit_depth: u8, scan: Scan) -> Self {
195 let palette = palette_from_map(&scan.map);
196 Self {
197 color_type: 3,
198 bit_depth,
199 palette: Some(palette),
200 palette_index: Some(scan.map),
201 indices: Some(scan.indices),
202 }
203 }
204}
205
206#[derive(Clone, Copy, Default)]
211struct Slot {
212 key: u32,
213 index: u8,
214 occupied: bool,
215}
216
217struct ColorMap {
223 slots: Box<[Slot]>,
224 len: u16,
225}
226
227impl ColorMap {
228 const BITS: u32 = 9;
229 const SLOTS: usize = 1 << Self::BITS; const MASK: usize = Self::SLOTS - 1;
231
232 fn new() -> Self {
233 Self {
234 slots: vec![Slot::default(); Self::SLOTS].into_boxed_slice(),
235 len: 0,
236 }
237 }
238
239 #[inline]
241 fn slot_for(key: u32) -> usize {
242 (key.wrapping_mul(0x9E37_79B1) >> (32 - Self::BITS)) as usize
243 }
244
245 #[inline]
249 fn get_or_insert(&mut self, key: u32, max: usize) -> Option<u8> {
250 let mut s = Self::slot_for(key);
251 loop {
252 if !self.slots[s].occupied {
253 if self.len as usize >= max {
254 return None;
255 }
256 let index = self.len as u8;
257 self.slots[s] = Slot {
258 key,
259 index,
260 occupied: true,
261 };
262 self.len += 1;
263 return Some(index);
264 }
265 if self.slots[s].key == key {
266 return Some(self.slots[s].index);
267 }
268 s = (s + 1) & Self::MASK;
269 }
270 }
271
272 fn from_palette(rgb: &[[u8; 3]], alpha: &[u8]) -> Self {
276 let mut map = Self::new();
277 for (i, (c, &a)) in rgb.iter().zip(alpha).enumerate() {
278 let key = u32::from_be_bytes([c[0], c[1], c[2], a]);
279 let mut s = Self::slot_for(key);
280 loop {
281 if !map.slots[s].occupied {
282 map.slots[s] = Slot {
283 key,
284 index: i as u8,
285 occupied: true,
286 };
287 map.len += 1;
288 break;
289 }
290 if map.slots[s].key == key {
291 map.slots[s].index = i as u8;
292 break;
293 }
294 s = (s + 1) & Self::MASK;
295 }
296 }
297 map
298 }
299
300 #[inline]
301 fn get(&self, key: u32) -> Option<u8> {
302 let mut s = Self::slot_for(key);
303 loop {
304 if !self.slots[s].occupied {
305 return None;
306 }
307 if self.slots[s].key == key {
308 return Some(self.slots[s].index);
309 }
310 s = (s + 1) & Self::MASK;
311 }
312 }
313}
314
315struct Scan {
317 opaque: bool,
319 palette_ok: bool,
321 map: ColorMap,
323 indices: Vec<u8>,
325}
326
327fn scan_pixels(data: &[u8], max: usize) -> Scan {
332 let mut opaque = true;
333 let mut map = ColorMap::new();
334 let mut indices = Vec::new();
335 let mut palette_ok = true;
336 for p in data.chunks_exact(4) {
337 opaque &= p[3] == 255;
338 if palette_ok {
339 match map.get_or_insert(u32::from_be_bytes([p[0], p[1], p[2], p[3]]), max) {
340 Some(index) => indices.push(index),
341 None => {
342 palette_ok = false;
343 indices = Vec::new();
344 }
345 }
346 }
347 }
348 Scan {
349 opaque,
350 palette_ok,
351 map,
352 indices,
353 }
354}
355
356fn palette_from_map(map: &ColorMap) -> (Vec<[u8; 3]>, Vec<u8>) {
358 let n = map.len as usize;
359 let mut rgb = vec![[0u8; 3]; n];
360 let mut alpha = vec![255u8; n];
361 for slot in map.slots.iter().filter(|s| s.occupied) {
362 let b = slot.key.to_be_bytes();
363 rgb[slot.index as usize] = [b[0], b[1], b[2]];
364 alpha[slot.index as usize] = b[3];
365 }
366 (rgb, alpha)
367}
368
369fn validate_options(options: &EncodeOptions) -> Result<(), PngError> {
373 if options.compression > 12 {
374 return Err(PngError::InvalidOptions("compression level must be 0..=12"));
375 }
376 Ok(())
377}
378
379pub fn encode(
380 image: ImageRef,
381 options: &EncodeOptions,
382 meta: &Metadata,
383) -> Result<Vec<u8>, PngError> {
384 validate_image(image)?;
385 validate_options(options)?;
386
387 if stored_rgba8_applies(options, meta) {
388 let mut out = Vec::with_capacity(stored_rgba8_png_len(image));
391 if write_stored_rgba8(image, &mut out)? {
392 return Ok(out);
393 }
394 }
395 encode_core(image, options, meta, compress)
396}
397
398#[cfg(feature = "parallel")]
401pub fn encode_parallel(
402 image: ImageRef,
403 options: &EncodeOptions,
404 meta: &Metadata,
405 threads: std::num::NonZeroUsize,
406) -> Result<Vec<u8>, PngError> {
407 if options.compression == 0 {
408 return encode(image, options, meta);
409 }
410 validate_image(image)?;
411 validate_options(options)?;
412 encode_core(image, options, meta, |raw, level| {
413 crate::parallel::compress_banded(raw, level, threads)
414 })
415}
416
417pub fn encode_to<W: Write>(
421 image: ImageRef,
422 options: &EncodeOptions,
423 meta: &Metadata,
424 out: &mut W,
425) -> Result<(), PngError> {
426 validate_image(image)?;
427 validate_options(options)?;
428
429 if stored_rgba8_applies(options, meta) && write_stored_rgba8(image, out)? {
430 return Ok(());
431 }
432 let bytes = encode_core(image, options, meta, compress)?;
433 out.write_all(&bytes)?;
434 Ok(())
435}
436
437fn encode_core(
441 image: ImageRef,
442 options: &EncodeOptions,
443 meta: &Metadata,
444 compress: impl FnOnce(&[u8], u8) -> Vec<u8>,
445) -> Result<Vec<u8>, PngError> {
446 let mode = resolve_mode(image, options.color)?;
447 let mode = apply_explicit_palette(image, mode, meta)?;
448 let raw = build_raw_stream(image, &mode, options)?;
449 let zlib = compress(&raw, options.compression);
450
451 Ok(assemble_png(
452 image.width,
453 image.height,
454 &mode,
455 options,
456 meta,
457 &zlib,
458 ))
459}
460
461fn validate_image(image: ImageRef) -> Result<(), PngError> {
462 if image.width == 0 || image.height == 0 {
463 return Err(PngError::InvalidOptions("image dimensions must be nonzero"));
464 }
465 let expected = rgba_byte_len(image.width, image.height)
468 .ok_or(PngError::InvalidOptions("image dimensions overflow usize"))?;
469 if image.data.len() != expected {
470 return Err(PngError::InvalidOptions(
471 "data length != width * height * 4",
472 ));
473 }
474 Ok(())
475}
476
477#[inline]
480fn rgba_byte_len(width: u32, height: u32) -> Option<usize> {
481 (width as usize)
482 .checked_mul(height as usize)
483 .and_then(|n| n.checked_mul(4))
484}
485
486#[inline]
495fn stored_rgba8_applies(options: &EncodeOptions, meta: &Metadata) -> bool {
496 options.color == ColorMode::Rgba8
497 && options.filter == Filter::None
498 && options.compression == 0
499 && !options.interlace
500 && *meta == Metadata::default()
501}
502
503#[inline]
507fn stored_zlib_len(raw_len: usize) -> usize {
508 const BLOCK: usize = 0xffff;
509 let n_blocks = raw_len.div_ceil(BLOCK).max(1);
510 2 + n_blocks * 5 + raw_len + 4
511}
512
513#[inline]
516fn stored_raw_len(image: ImageRef) -> Option<usize> {
517 let stride = (image.width as usize).checked_mul(4)?.checked_add(1)?;
518 (image.height as usize).checked_mul(stride)
519}
520
521fn stored_rgba8_png_len(image: ImageRef) -> usize {
526 let Some(raw_len) = stored_raw_len(image) else {
527 return 0;
528 };
529 8 + 25 + 12 + stored_zlib_len(raw_len) + 12
531}
532
533fn write_stored_rgba8<W: Write>(image: ImageRef, out: &mut W) -> io::Result<bool> {
544 const BLOCK: usize = 0xffff;
545 let row_len = image.width as usize * 4;
546 let stride = 1 + row_len; let Some(raw_len) = stored_raw_len(image) else {
548 return Ok(false); };
550 let zlib_len = stored_zlib_len(raw_len);
551 if zlib_len > i32::MAX as usize {
552 return Ok(false);
553 }
554
555 container::write_signature(out)?;
556 let mut ihdr = [0u8; 13];
557 ihdr[0..4].copy_from_slice(&image.width.to_be_bytes());
558 ihdr[4..8].copy_from_slice(&image.height.to_be_bytes());
559 ihdr[8] = 8; ihdr[9] = 6; container::write_chunk(out, b"IHDR", &ihdr)?;
563
564 let mut idat = container::IdatStreamer::new(out, zlib_len as u32)?;
565 idat.write(&[0x78, 0x01])?; let mut adler = crate::backend::Adler::new();
568 let mut produced = 0usize; let mut row = 0usize; let mut col = 0usize; while produced < raw_len {
572 let n = (raw_len - produced).min(BLOCK);
573 let final_block = produced + n == raw_len;
574 let mut hdr = [0u8; 5];
575 hdr[0] = final_block as u8;
576 hdr[1..3].copy_from_slice(&(n as u16).to_le_bytes());
577 hdr[3..5].copy_from_slice(&(!(n as u16)).to_le_bytes());
578 idat.write(&hdr)?;
579
580 let mut left = n;
581 while left > 0 {
582 if col == 0 {
583 idat.write(&[0u8])?; adler.write(&[0u8]);
585 col = 1;
586 left -= 1;
587 } else {
588 let in_row = col - 1;
589 let take = (row_len - in_row).min(left);
590 let base = row * row_len + in_row;
591 let seg = &image.data[base..base + take];
592 idat.write(seg)?;
593 adler.write(seg);
594 left -= take;
595 col += take;
596 if col == stride {
597 col = 0;
598 row += 1;
599 }
600 }
601 }
602 produced += n;
603 }
604
605 idat.write(&adler.finish().to_be_bytes())?;
606 idat.finish()?;
607 container::write_chunk(out, b"IEND", &[])?;
608 Ok(true)
609}
610
611pub fn encode16(
617 image: &Image16,
618 options: &EncodeOptions,
619 meta: &Metadata,
620) -> Result<Vec<u8>, PngError> {
621 if image.width == 0 || image.height == 0 {
622 return Err(PngError::InvalidOptions("image dimensions must be nonzero"));
623 }
624 let expected = rgba_byte_len(image.width, image.height)
625 .ok_or(PngError::InvalidOptions("image dimensions overflow usize"))?;
626 if image.data.len() != expected {
627 return Err(PngError::InvalidOptions(
628 "data length != width * height * 4",
629 ));
630 }
631 validate_options(options)?;
632
633 let mode = resolve_mode16(image, options.color)?;
634 let raw = build_raw_stream16(image, &mode, options);
635 let zlib = compress(&raw, options.compression);
636
637 Ok(assemble_png(
638 image.width,
639 image.height,
640 &mode,
641 options,
642 meta,
643 &zlib,
644 ))
645}
646
647fn apply_explicit_palette(image: ImageRef, mode: Mode, meta: &Metadata) -> Result<Mode, PngError> {
653 let Some(palette) = &meta.palette else {
654 return Ok(mode);
655 };
656 if mode.color_type != 3 {
657 return Ok(mode);
658 }
659 let n = palette.entries.len();
660 if n == 0 || n > 256 || n > (1usize << mode.bit_depth) {
661 return Err(PngError::InvalidOptions(
662 "explicit palette size exceeds bit depth",
663 ));
664 }
665 let alpha: Vec<u8> = match &meta.transparency {
666 Some(Trns::Palette(a)) => (0..n).map(|i| a.get(i).copied().unwrap_or(255)).collect(),
667 _ => vec![255; n],
668 };
669 let new = Mode::new(3, mode.bit_depth, Some((palette.entries.clone(), alpha)));
670 let index = new.palette_index.as_ref().unwrap();
671 for p in image.data.chunks_exact(4) {
672 if index
673 .get(u32::from_be_bytes([p[0], p[1], p[2], p[3]]))
674 .is_none()
675 {
676 return Err(PngError::Unrepresentable(
677 "pixel color not present in explicit palette",
678 ));
679 }
680 }
681 Ok(new)
682}
683
684#[inline]
691fn gray_depth_ok(v: u8, depth: u8) -> bool {
692 match depth {
693 1 => v == 0 || v == 255,
694 2 => v.is_multiple_of(85),
695 4 => v.is_multiple_of(17),
696 _ => true,
697 }
698}
699
700fn resolve_mode(image: ImageRef, color: ColorMode) -> Result<Mode, PngError> {
701 use ColorMode::*;
702 let px = image.data.chunks_exact(4);
703
704 let mode = match color {
705 Auto => {
706 let gray = image
710 .data
711 .chunks_exact(4)
712 .all(|p| p[0] == p[1] && p[1] == p[2]);
713 if gray && image.data.chunks_exact(4).all(|p| p[3] == 255) {
714 let depth = [1u8, 2, 4]
715 .into_iter()
716 .find(|&d| image.data.chunks_exact(4).all(|p| gray_depth_ok(p[0], d)))
717 .unwrap_or(8);
718 return resolve_mode(
719 image,
720 match depth {
721 1 => Gray1,
722 2 => Gray2,
723 4 => Gray4,
724 _ => Gray8,
725 },
726 );
727 }
728 let scan = scan_pixels(image.data, 256);
731 if scan.palette_ok {
732 let depth = match scan.map.len {
733 0..=2 => 1,
734 3..=4 => 2,
735 5..=16 => 4,
736 _ => 8,
737 };
738 return Ok(Mode::indexed(depth, scan));
739 }
740 if gray {
741 return resolve_mode(image, GrayAlpha8);
742 }
743 if scan.opaque {
744 return resolve_mode(image, Rgb8);
745 }
746 return resolve_mode(image, Rgba8);
747 }
748 Gray1 | Gray2 | Gray4 | Gray8 | Gray16 => {
749 let depth = match color {
750 Gray1 => 1,
751 Gray2 => 2,
752 Gray4 => 4,
753 Gray8 => 8,
754 _ => 16,
755 };
756 for p in px {
757 if p[0] != p[1] || p[1] != p[2] {
758 return Err(PngError::Unrepresentable("grayscale requires r == g == b"));
759 }
760 if p[3] != 255 {
761 return Err(PngError::Unrepresentable("grayscale requires opaque alpha"));
762 }
763 if !gray_depth_ok(p[0], depth) {
764 return Err(PngError::Unrepresentable(
765 "gray value not representable at this bit depth",
766 ));
767 }
768 }
769 Mode::new(0, depth, None)
770 }
771 GrayAlpha8 | GrayAlpha16 => {
772 for p in px {
773 if p[0] != p[1] || p[1] != p[2] {
774 return Err(PngError::Unrepresentable("grayscale requires r == g == b"));
775 }
776 }
777 Mode::new(4, if color == GrayAlpha8 { 8 } else { 16 }, None)
778 }
779 Indexed1 | Indexed2 | Indexed4 | Indexed8 => {
780 let depth = match color {
781 Indexed1 => 1,
782 Indexed2 => 2,
783 Indexed4 => 4,
784 _ => 8,
785 };
786 let scan = scan_pixels(image.data, 1 << depth);
787 if !scan.palette_ok {
788 return Err(PngError::Unrepresentable(
789 "too many unique colors for the palette depth",
790 ));
791 }
792 Mode::indexed(depth, scan)
793 }
794 Rgb8 | Rgb16 => {
795 for p in px {
796 if p[3] != 255 {
797 return Err(PngError::Unrepresentable("RGB requires opaque alpha"));
798 }
799 }
800 Mode::new(2, if color == Rgb8 { 8 } else { 16 }, None)
801 }
802 Rgba8 => Mode::new(6, 8, None),
803 Rgba16 => Mode::new(6, 16, None),
804 };
805 Ok(mode)
806}
807
808fn resolve_mode16(image: &Image16, color: ColorMode) -> Result<Mode, PngError> {
812 use ColorMode::*;
813 let px = || image.data.chunks_exact(4);
814 let mode =
815 match color {
816 Auto => {
817 let opaque = px().all(|p| p[3] == 65535);
818 let gray = px().all(|p| p[0] == p[1] && p[1] == p[2]);
819 match (gray, opaque) {
820 (true, true) => Mode::new(0, 16, None),
821 (true, false) => Mode::new(4, 16, None),
822 (false, true) => Mode::new(2, 16, None),
823 (false, false) => Mode::new(6, 16, None),
824 }
825 }
826 Gray16 => {
827 for p in px() {
828 if p[0] != p[1] || p[1] != p[2] {
829 return Err(PngError::Unrepresentable("grayscale requires r == g == b"));
830 }
831 if p[3] != 65535 {
832 return Err(PngError::Unrepresentable("grayscale requires opaque alpha"));
833 }
834 }
835 Mode::new(0, 16, None)
836 }
837 GrayAlpha16 => {
838 for p in px() {
839 if p[0] != p[1] || p[1] != p[2] {
840 return Err(PngError::Unrepresentable("grayscale requires r == g == b"));
841 }
842 }
843 Mode::new(4, 16, None)
844 }
845 Rgb16 => {
846 for p in px() {
847 if p[3] != 65535 {
848 return Err(PngError::Unrepresentable("RGB requires opaque alpha"));
849 }
850 }
851 Mode::new(2, 16, None)
852 }
853 Rgba16 => Mode::new(6, 16, None),
854 _ => return Err(PngError::InvalidOptions(
855 "encode16 supports only 16-bit color modes (Gray16/GrayAlpha16/Rgb16/Rgba16/Auto)",
856 )),
857 };
858 Ok(mode)
859}
860
861fn channels(color_type: u8) -> usize {
866 match color_type {
867 2 => 3,
868 4 => 2,
869 6 => 4,
870 _ => 1,
871 }
872}
873
874fn filter_bpp(color_type: u8, bit_depth: u8) -> usize {
875 if bit_depth < 8 {
876 1
877 } else {
878 channels(color_type) * (bit_depth as usize / 8)
879 }
880}
881
882fn row_bytes(color_type: u8, bit_depth: u8, width: usize) -> usize {
883 (channels(color_type) * bit_depth as usize * width).div_ceil(8)
884}
885
886fn convert_row(pixels: impl Iterator<Item = [u8; 4]>, mode: &Mode, out: &mut Vec<u8>) {
890 match (mode.color_type, mode.bit_depth) {
891 (0, 16) => {
892 for p in pixels {
893 out.extend_from_slice(&[p[0], p[0]]);
894 }
895 }
896 (0, 8) => out.extend(pixels.map(|p| p[0])),
897 (0, depth) => pack_bits(pixels.map(|p| p[0] >> (8 - depth)), depth, out),
898 (2, 8) => {
899 for p in pixels {
900 out.extend_from_slice(&p[..3]);
901 }
902 }
903 (2, _) => {
904 for p in pixels {
905 out.extend_from_slice(&[p[0], p[0], p[1], p[1], p[2], p[2]]);
906 }
907 }
908 (3, depth) => {
909 let index = mode
910 .palette_index
911 .as_ref()
912 .expect("indexed mode has palette");
913 let index_of = move |p: [u8; 4]| {
914 index
915 .get(u32::from_be_bytes(p))
916 .expect("pixel color in palette")
917 };
918 if depth == 8 {
919 out.extend(pixels.map(index_of));
920 } else {
921 pack_bits(pixels.map(index_of), depth, out);
922 }
923 }
924 (4, 8) => {
925 for p in pixels {
926 out.extend_from_slice(&[p[0], p[3]]);
927 }
928 }
929 (4, _) => {
930 for p in pixels {
931 out.extend_from_slice(&[p[0], p[0], p[3], p[3]]);
932 }
933 }
934 (6, 8) => {
935 for p in pixels {
936 out.extend_from_slice(&p);
937 }
938 }
939 _ => {
940 for p in pixels {
941 out.extend_from_slice(&[p[0], p[0], p[1], p[1], p[2], p[2], p[3], p[3]]);
942 }
943 }
944 }
945}
946
947fn pack_bits(samples: impl Iterator<Item = u8>, depth: u8, out: &mut Vec<u8>) {
949 let mut acc = 0u8;
950 let mut filled = 0u8;
951 for s in samples {
952 acc |= s << (8 - depth - filled);
953 filled += depth;
954 if filled == 8 {
955 out.push(acc);
956 acc = 0;
957 filled = 0;
958 }
959 }
960 if filled > 0 {
961 out.push(acc);
962 }
963}
964
965fn build_raw_stream(
967 image: ImageRef,
968 mode: &Mode,
969 options: &EncodeOptions,
970) -> Result<Vec<u8>, PngError> {
971 let width = image.width as usize;
972 let bpp = filter_bpp(mode.color_type, mode.bit_depth);
973
974 let passes: Vec<(usize, usize, usize)> = if options.interlace {
975 interlace::pass_dimensions(image.width, image.height)
976 .iter()
977 .enumerate()
978 .filter(|(_, &(w, h))| w > 0 && h > 0)
979 .map(|(i, &(w, h))| (i, w as usize, h as usize))
980 .collect()
981 } else {
982 vec![(usize::MAX, width, image.height as usize)]
983 };
984
985 let mut out = Vec::new();
986 for &(pass, pw, ph) in &passes {
987 let rb = row_bytes(mode.color_type, mode.bit_depth, pw);
988
989 if pass == usize::MAX && mode.color_type == 6 && mode.bit_depth == 8 {
992 filter_pass(&mut out, image.data, rb, ph, bpp, options);
993 continue;
994 }
995
996 let mut raw = Vec::with_capacity(rb * ph);
1000 if pass == usize::MAX {
1001 if let Some(indices) = mode.indices.as_ref() {
1002 if mode.bit_depth == 8 {
1006 raw.extend_from_slice(indices);
1007 } else {
1008 for i in 0..ph {
1009 pack_bits(
1010 indices[i * width..(i + 1) * width].iter().copied(),
1011 mode.bit_depth,
1012 &mut raw,
1013 );
1014 }
1015 }
1016 } else {
1017 for i in 0..ph {
1018 let base = i * width * 4;
1019 let pixels = image.data[base..base + width * 4]
1020 .chunks_exact(4)
1021 .map(|p| [p[0], p[1], p[2], p[3]]);
1022 convert_row(pixels, mode, &mut raw);
1023 }
1024 }
1025 } else {
1026 let y_start = interlace::Y_START[pass] as usize;
1027 let y_delta = interlace::Y_DELTA[pass] as usize;
1028 let x0 = interlace::X_START[pass] as usize;
1029 let dx = interlace::X_DELTA[pass] as usize;
1030 for i in 0..ph {
1031 let y = y_start + i * y_delta;
1032 let pixels = (0..pw).map(move |k| {
1033 let base = (y * width + x0 + k * dx) * 4;
1034 let p = &image.data[base..base + 4];
1035 [p[0], p[1], p[2], p[3]]
1036 });
1037 convert_row(pixels, mode, &mut raw);
1038 }
1039 }
1040 debug_assert_eq!(raw.len(), rb * ph);
1041
1042 filter_pass(&mut out, &raw, rb, ph, bpp, options);
1043 }
1044 Ok(out)
1045}
1046
1047fn filter_pass(
1051 out: &mut Vec<u8>,
1052 raw: &[u8],
1053 rb: usize,
1054 ph: usize,
1055 bpp: usize,
1056 options: &EncodeOptions,
1057) {
1058 let set = options.filter.allowed();
1059 let base = out.len();
1060 let added = (1 + rb) * ph;
1063 out.reserve(added);
1064 #[allow(clippy::uninit_vec)]
1067 unsafe {
1068 out.set_len(base + added)
1069 };
1070 out[base..]
1071 .chunks_mut(1 + rb)
1072 .enumerate()
1073 .for_each(|(y, dst)| {
1074 let row = &raw[y * rb..(y + 1) * rb];
1075 let prev = if y == 0 {
1076 &[][..]
1077 } else {
1078 &raw[(y - 1) * rb..y * rb]
1079 };
1080 let filter = best_filter(set, row, prev, bpp);
1081 dst[0] = filter;
1082 apply_filter(filter, row, prev, bpp, &mut dst[1..]);
1083 });
1084}
1085
1086fn convert_row16(pixels: impl Iterator<Item = [u16; 4]>, color_type: u8, out: &mut Vec<u8>) {
1089 match color_type {
1090 0 => {
1091 for p in pixels {
1092 out.extend_from_slice(&p[0].to_be_bytes());
1093 }
1094 }
1095 2 => {
1096 for p in pixels {
1097 out.extend_from_slice(&p[0].to_be_bytes());
1098 out.extend_from_slice(&p[1].to_be_bytes());
1099 out.extend_from_slice(&p[2].to_be_bytes());
1100 }
1101 }
1102 4 => {
1103 for p in pixels {
1104 out.extend_from_slice(&p[0].to_be_bytes());
1105 out.extend_from_slice(&p[3].to_be_bytes());
1106 }
1107 }
1108 _ => {
1109 for p in pixels {
1110 for c in p {
1111 out.extend_from_slice(&c.to_be_bytes());
1112 }
1113 }
1114 }
1115 }
1116}
1117
1118fn build_raw_stream16(image: &Image16, mode: &Mode, options: &EncodeOptions) -> Vec<u8> {
1122 let width = image.width as usize;
1123 let bpp = filter_bpp(mode.color_type, mode.bit_depth);
1124
1125 let passes: Vec<(usize, usize, usize)> = if options.interlace {
1126 interlace::pass_dimensions(image.width, image.height)
1127 .iter()
1128 .enumerate()
1129 .filter(|(_, &(w, h))| w > 0 && h > 0)
1130 .map(|(i, &(w, h))| (i, w as usize, h as usize))
1131 .collect()
1132 } else {
1133 vec![(usize::MAX, width, image.height as usize)]
1134 };
1135
1136 let mut out = Vec::new();
1137 for &(pass, pw, ph) in &passes {
1138 let rb = row_bytes(mode.color_type, mode.bit_depth, pw);
1139
1140 let mut raw = Vec::with_capacity(rb * ph);
1141 if pass == usize::MAX {
1142 for i in 0..ph {
1143 let base = i * width * 4;
1144 let pixels = image.data[base..base + width * 4]
1145 .chunks_exact(4)
1146 .map(|p| [p[0], p[1], p[2], p[3]]);
1147 convert_row16(pixels, mode.color_type, &mut raw);
1148 }
1149 } else {
1150 let y_start = interlace::Y_START[pass] as usize;
1151 let y_delta = interlace::Y_DELTA[pass] as usize;
1152 let x0 = interlace::X_START[pass] as usize;
1153 let dx = interlace::X_DELTA[pass] as usize;
1154 for i in 0..ph {
1155 let y = y_start + i * y_delta;
1156 let pixels = (0..pw).map(move |k| {
1157 let base = (y * width + x0 + k * dx) * 4;
1158 let p = &image.data[base..base + 4];
1159 [p[0], p[1], p[2], p[3]]
1160 });
1161 convert_row16(pixels, mode.color_type, &mut raw);
1162 }
1163 }
1164 debug_assert_eq!(raw.len(), rb * ph);
1165
1166 filter_pass(&mut out, &raw, rb, ph, bpp, options);
1167 }
1168 out
1169}
1170
1171#[inline(always)]
1172fn paeth(a: u8, b: u8, c: u8) -> u8 {
1173 let (ia, ib, ic) = (a as i16, b as i16, c as i16);
1174 let pa = (ib - ic).abs();
1175 let pb = (ia - ic).abs();
1176 let pc = (ia + ib - 2 * ic).abs();
1177 if pa <= pb && pa <= pc {
1178 a
1179 } else if pb <= pc {
1180 b
1181 } else {
1182 c
1183 }
1184}
1185
1186#[inline]
1187fn filtered_byte(filter: u8, x: u8, a: u8, b: u8, c: u8) -> u8 {
1188 match filter {
1189 0 => x,
1190 1 => x.wrapping_sub(a),
1191 2 => x.wrapping_sub(b),
1192 3 => x.wrapping_sub(((a as u16 + b as u16) >> 1) as u8),
1193 _ => x.wrapping_sub(paeth(a, b, c)),
1194 }
1195}
1196
1197fn best_filter(set: FilterSet, row: &[u8], prev: &[u8], bpp: usize) -> u8 {
1208 if let Some(only) = set.single() {
1209 return only;
1210 }
1211 let mut best = 0u8;
1212 let mut best_sum = u64::MAX;
1213 for filter in 0..=4u8 {
1214 if !set.contains(filter) {
1215 continue;
1216 }
1217 let sum = filter_sad(filter, row, prev, bpp, best_sum);
1218 if sum < best_sum {
1219 best_sum = sum;
1220 best = filter;
1221 }
1222 }
1223 best
1224}
1225
1226#[inline]
1229fn filter_sad(filter: u8, row: &[u8], prev: &[u8], bpp: usize, limit: u64) -> u64 {
1230 #[cfg(target_arch = "aarch64")]
1231 {
1232 neon::filter_sad(filter, row, prev, bpp, limit)
1233 }
1234 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
1235 {
1236 wasm_simd::filter_sad(filter, row, prev, bpp, limit)
1237 }
1238 #[cfg(not(any(
1239 target_arch = "aarch64",
1240 all(target_arch = "wasm32", target_feature = "simd128")
1241 )))]
1242 {
1243 filter_sad_scalar(filter, row, prev, bpp, limit)
1244 }
1245}
1246
1247fn filter_sad_scalar(filter: u8, row: &[u8], prev: &[u8], bpp: usize, limit: u64) -> u64 {
1250 let mut sum = 0u64;
1251 for i in 0..row.len() {
1252 let a = if i >= bpp { row[i - bpp] } else { 0 };
1253 let b = prev.get(i).copied().unwrap_or(0);
1254 let c = if i >= bpp {
1255 prev.get(i - bpp).copied().unwrap_or(0)
1256 } else {
1257 0
1258 };
1259 let v = filtered_byte(filter, row[i], a, b, c);
1260 sum += (v as i8).unsigned_abs() as u64;
1261 if sum >= limit {
1262 break;
1263 }
1264 }
1265 sum
1266}
1267
1268fn apply_filter(filter: u8, row: &[u8], prev: &[u8], bpp: usize, out: &mut [u8]) {
1269 if filter == 0 || (filter == 2 && prev.is_empty()) {
1272 out.copy_from_slice(row);
1273 return;
1274 }
1275 #[cfg(target_arch = "aarch64")]
1276 {
1277 neon::apply_filter(filter, row, prev, bpp, out);
1278 }
1279 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
1280 {
1281 wasm_simd::apply_filter(filter, row, prev, bpp, out);
1282 }
1283 #[cfg(not(any(
1284 target_arch = "aarch64",
1285 all(target_arch = "wasm32", target_feature = "simd128")
1286 )))]
1287 apply_filter_scalar(filter, row, prev, bpp, out);
1288}
1289
1290fn apply_filter_scalar(filter: u8, row: &[u8], prev: &[u8], bpp: usize, out: &mut [u8]) {
1293 for i in 0..row.len() {
1294 let a = if i >= bpp { row[i - bpp] } else { 0 };
1295 let b = prev.get(i).copied().unwrap_or(0);
1296 let c = if i >= bpp {
1297 prev.get(i - bpp).copied().unwrap_or(0)
1298 } else {
1299 0
1300 };
1301 out[i] = filtered_byte(filter, row[i], a, b, c);
1302 }
1303}
1304
1305#[cfg(target_arch = "aarch64")]
1312mod neon {
1313 use core::arch::aarch64::*;
1314
1315 #[inline(always)]
1318 unsafe fn filt_chunk(
1319 filter: u8,
1320 x: uint8x16_t,
1321 a: uint8x16_t,
1322 b: uint8x16_t,
1323 c: uint8x16_t,
1324 ) -> uint8x16_t {
1325 match filter {
1326 0 => x,
1330 1 => vsubq_u8(x, a),
1331 2 => vsubq_u8(x, b),
1332 3 => vsubq_u8(x, vhaddq_u8(a, b)),
1334 _ => vsubq_u8(x, paeth_vec(a, b, c)),
1335 }
1336 }
1337
1338 #[inline(always)]
1341 unsafe fn paeth_vec(a: uint8x16_t, b: uint8x16_t, c: uint8x16_t) -> uint8x16_t {
1342 let lo = paeth_half(vget_low_u8(a), vget_low_u8(b), vget_low_u8(c));
1343 let hi = paeth_half(vget_high_u8(a), vget_high_u8(b), vget_high_u8(c));
1344 vcombine_u8(lo, hi)
1345 }
1346
1347 #[inline(always)]
1348 unsafe fn paeth_half(a8: uint8x8_t, b8: uint8x8_t, c8: uint8x8_t) -> uint8x8_t {
1349 let a = vreinterpretq_s16_u16(vmovl_u8(a8));
1350 let b = vreinterpretq_s16_u16(vmovl_u8(b8));
1351 let c = vreinterpretq_s16_u16(vmovl_u8(c8));
1352 let pa = vabsq_s16(vsubq_s16(b, c));
1353 let pb = vabsq_s16(vsubq_s16(a, c));
1354 let pc = vabsq_s16(vsubq_s16(vaddq_s16(a, b), vaddq_s16(c, c)));
1355 let use_a = vandq_u16(vcleq_s16(pa, pb), vcleq_s16(pa, pc));
1357 let use_b = vcleq_s16(pb, pc);
1358 let pred = vbslq_s16(use_a, a, vbslq_s16(use_b, b, c));
1359 vmovn_u16(vreinterpretq_u16_s16(pred))
1360 }
1361
1362 #[inline(always)]
1365 unsafe fn neighbors(
1366 row: &[u8],
1367 prev: &[u8],
1368 bpp: usize,
1369 i: usize,
1370 ) -> (uint8x16_t, uint8x16_t, uint8x16_t, uint8x16_t) {
1371 let x = vld1q_u8(row.as_ptr().add(i));
1372 let a = vld1q_u8(row.as_ptr().add(i - bpp));
1373 let (b, c) = if prev.is_empty() {
1374 (vdupq_n_u8(0), vdupq_n_u8(0))
1375 } else {
1376 (
1377 vld1q_u8(prev.as_ptr().add(i)),
1378 vld1q_u8(prev.as_ptr().add(i - bpp)),
1379 )
1380 };
1381 (x, a, b, c)
1382 }
1383
1384 pub fn filter_sad(filter: u8, row: &[u8], prev: &[u8], bpp: usize, limit: u64) -> u64 {
1385 let n = row.len();
1386 let head = bpp.min(n);
1388 let mut sum =
1389 super::filter_sad_scalar(filter, &row[..head], prev_head(prev, head), bpp, limit);
1390 if sum >= limit {
1391 return sum;
1392 }
1393 unsafe {
1395 let mut acc = vdupq_n_u32(0);
1396 let mut i = head;
1397 let mut since_drain = 0u32;
1398 while i + 16 <= n {
1399 let (x, a, b, c) = neighbors(row, prev, bpp, i);
1400 let v = filt_chunk(filter, x, a, b, c);
1401 let abs = vreinterpretq_u8_s8(vabsq_s8(vreinterpretq_s8_u8(v)));
1403 acc = vpadalq_u16(acc, vpaddlq_u8(abs));
1404 i += 16;
1405 since_drain += 1;
1409 if since_drain == 4096 {
1410 sum += vaddvq_u32(acc) as u64;
1411 acc = vdupq_n_u32(0);
1412 since_drain = 0;
1413 if sum >= limit {
1414 return sum;
1415 }
1416 }
1417 }
1418 sum += vaddvq_u32(acc) as u64;
1419 for j in i..n {
1421 let a = row[j - bpp];
1422 let b = if prev.is_empty() { 0 } else { prev[j] };
1423 let c = if prev.is_empty() { 0 } else { prev[j - bpp] };
1424 let fv = super::filtered_byte(filter, row[j], a, b, c);
1425 sum += (fv as i8).unsigned_abs() as u64;
1426 if sum >= limit {
1427 break;
1428 }
1429 }
1430 }
1431 sum
1432 }
1433
1434 pub fn apply_filter(filter: u8, row: &[u8], prev: &[u8], bpp: usize, out: &mut [u8]) {
1435 let n = row.len();
1436 let head = bpp.min(n);
1437 super::apply_filter_scalar(
1438 filter,
1439 &row[..head],
1440 prev_head(prev, head),
1441 bpp,
1442 &mut out[..head],
1443 );
1444 unsafe {
1446 let mut i = head;
1447 while i + 16 <= n {
1448 let (x, a, b, c) = neighbors(row, prev, bpp, i);
1449 vst1q_u8(out.as_mut_ptr().add(i), filt_chunk(filter, x, a, b, c));
1450 i += 16;
1451 }
1452 for j in i..n {
1453 let a = row[j - bpp];
1454 let b = if prev.is_empty() { 0 } else { prev[j] };
1455 let c = if prev.is_empty() { 0 } else { prev[j - bpp] };
1456 out[j] = super::filtered_byte(filter, row[j], a, b, c);
1457 }
1458 }
1459 }
1460
1461 #[inline]
1464 fn prev_head(prev: &[u8], head: usize) -> &[u8] {
1465 if prev.is_empty() {
1466 prev
1467 } else {
1468 &prev[..head]
1469 }
1470 }
1471}
1472
1473#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
1481mod wasm_simd {
1482 use core::arch::wasm32::*;
1483
1484 #[inline(always)]
1486 fn filt_chunk(filter: u8, x: v128, a: v128, b: v128, c: v128) -> v128 {
1487 match filter {
1488 0 => x,
1492 1 => i8x16_sub(x, a),
1493 2 => i8x16_sub(x, b),
1494 3 => i8x16_sub(x, i8x16_add(v128_and(a, b), u8x16_shr(v128_xor(a, b), 1))),
1497 _ => i8x16_sub(x, paeth_vec(a, b, c)),
1498 }
1499 }
1500
1501 #[inline(always)]
1506 fn paeth_vec(a: v128, b: v128, c: v128) -> v128 {
1507 let lo = paeth_half(
1508 i16x8_extend_low_u8x16(a),
1509 i16x8_extend_low_u8x16(b),
1510 i16x8_extend_low_u8x16(c),
1511 );
1512 let hi = paeth_half(
1513 i16x8_extend_high_u8x16(a),
1514 i16x8_extend_high_u8x16(b),
1515 i16x8_extend_high_u8x16(c),
1516 );
1517 u8x16_narrow_i16x8(lo, hi)
1518 }
1519
1520 #[inline(always)]
1521 fn paeth_half(a: v128, b: v128, c: v128) -> v128 {
1522 let pa = i16x8_abs(i16x8_sub(b, c));
1523 let pb = i16x8_abs(i16x8_sub(a, c));
1524 let pc = i16x8_abs(i16x8_sub(i16x8_add(a, b), i16x8_add(c, c)));
1525 let use_a = v128_and(i16x8_le(pa, pb), i16x8_le(pa, pc));
1527 let use_b = i16x8_le(pb, pc);
1528 v128_bitselect(a, v128_bitselect(b, c, use_b), use_a)
1529 }
1530
1531 #[inline(always)]
1535 unsafe fn neighbors(row: &[u8], prev: &[u8], bpp: usize, i: usize) -> (v128, v128, v128, v128) {
1536 let x = v128_load(row.as_ptr().add(i) as *const v128);
1537 let a = v128_load(row.as_ptr().add(i - bpp) as *const v128);
1538 let (b, c) = if prev.is_empty() {
1539 (u8x16_splat(0), u8x16_splat(0))
1540 } else {
1541 (
1542 v128_load(prev.as_ptr().add(i) as *const v128),
1543 v128_load(prev.as_ptr().add(i - bpp) as *const v128),
1544 )
1545 };
1546 (x, a, b, c)
1547 }
1548
1549 #[inline(always)]
1551 fn hsum_u32x4(acc: v128) -> u64 {
1552 u32x4_extract_lane::<0>(acc) as u64
1553 + u32x4_extract_lane::<1>(acc) as u64
1554 + u32x4_extract_lane::<2>(acc) as u64
1555 + u32x4_extract_lane::<3>(acc) as u64
1556 }
1557
1558 pub fn filter_sad(filter: u8, row: &[u8], prev: &[u8], bpp: usize, limit: u64) -> u64 {
1559 let n = row.len();
1560 let head = bpp.min(n);
1562 let mut sum =
1563 super::filter_sad_scalar(filter, &row[..head], prev_head(prev, head), bpp, limit);
1564 if sum >= limit {
1565 return sum;
1566 }
1567 unsafe {
1569 let mut acc = u8x16_splat(0); let mut i = head;
1571 let mut since_drain = 0u32;
1572 while i + 16 <= n {
1573 let (x, a, b, c) = neighbors(row, prev, bpp, i);
1574 let v = filt_chunk(filter, x, a, b, c);
1575 let abs = i8x16_abs(v);
1578 let sum16 = i16x8_add(i16x8_extend_low_u8x16(abs), i16x8_extend_high_u8x16(abs));
1582 acc = i32x4_add(acc, i32x4_extadd_pairwise_i16x8(sum16));
1583 i += 16;
1584 since_drain += 1;
1588 if since_drain == 4096 {
1589 sum += hsum_u32x4(acc);
1590 acc = u8x16_splat(0);
1591 since_drain = 0;
1592 if sum >= limit {
1593 return sum;
1594 }
1595 }
1596 }
1597 sum += hsum_u32x4(acc);
1598 for j in i..n {
1600 let a = row[j - bpp];
1601 let b = if prev.is_empty() { 0 } else { prev[j] };
1602 let c = if prev.is_empty() { 0 } else { prev[j - bpp] };
1603 let fv = super::filtered_byte(filter, row[j], a, b, c);
1604 sum += (fv as i8).unsigned_abs() as u64;
1605 if sum >= limit {
1606 break;
1607 }
1608 }
1609 }
1610 sum
1611 }
1612
1613 pub fn apply_filter(filter: u8, row: &[u8], prev: &[u8], bpp: usize, out: &mut [u8]) {
1614 let n = row.len();
1615 let head = bpp.min(n);
1616 super::apply_filter_scalar(
1617 filter,
1618 &row[..head],
1619 prev_head(prev, head),
1620 bpp,
1621 &mut out[..head],
1622 );
1623 unsafe {
1625 let mut i = head;
1626 while i + 16 <= n {
1627 let (x, a, b, c) = neighbors(row, prev, bpp, i);
1628 v128_store(
1629 out.as_mut_ptr().add(i) as *mut v128,
1630 filt_chunk(filter, x, a, b, c),
1631 );
1632 i += 16;
1633 }
1634 for j in i..n {
1635 let a = row[j - bpp];
1636 let b = if prev.is_empty() { 0 } else { prev[j] };
1637 let c = if prev.is_empty() { 0 } else { prev[j - bpp] };
1638 out[j] = super::filtered_byte(filter, row[j], a, b, c);
1639 }
1640 }
1641 }
1642
1643 #[inline]
1646 fn prev_head(prev: &[u8], head: usize) -> &[u8] {
1647 if prev.is_empty() {
1648 prev
1649 } else {
1650 &prev[..head]
1651 }
1652 }
1653}
1654
1655fn compress(raw: &[u8], level: u8) -> Vec<u8> {
1660 if level == 0 {
1661 return stored_zlib(raw);
1664 }
1665 crate::backend::compress(raw, level)
1666}
1667
1668fn stored_zlib(raw: &[u8]) -> Vec<u8> {
1674 const BLOCK: usize = 0xffff;
1675
1676 if raw.is_empty() {
1677 return vec![0x78, 0x01, 1, 0, 0, 0xff, 0xff, 0, 0, 0, 1];
1679 }
1680
1681 let n_blocks = raw.len().div_ceil(BLOCK);
1682 let out_len = 2 + n_blocks * 5 + raw.len() + 4;
1683 let mut out = vec![0u8; out_len];
1684 out[0] = 0x78;
1685 out[1] = 0x01;
1686
1687 let mut pos = 2;
1688 let mut off = 0;
1689 while off < raw.len() {
1690 let n = (raw.len() - off).min(BLOCK);
1691 let final_block = off + n == raw.len();
1692 out[pos] = final_block as u8;
1693 out[pos + 1..pos + 3].copy_from_slice(&(n as u16).to_le_bytes());
1694 out[pos + 3..pos + 5].copy_from_slice(&(!(n as u16)).to_le_bytes());
1695 out[pos + 5..pos + 5 + n].copy_from_slice(&raw[off..off + n]);
1696 pos += 5 + n;
1697 off += n;
1698 }
1699
1700 let mut adler = crate::backend::Adler::new();
1701 adler.write(raw);
1702 out[out_len - 4..].copy_from_slice(&adler.finish().to_be_bytes());
1703 out
1704}
1705
1706fn write_chunk(out: &mut Vec<u8>, ty: &[u8; 4], payload: &[u8]) {
1713 container::write_chunk(out, ty, payload).expect("Vec write is infallible");
1714}
1715
1716fn assemble_png(
1717 width: u32,
1718 height: u32,
1719 mode: &Mode,
1720 options: &EncodeOptions,
1721 meta: &Metadata,
1722 zlib: &[u8],
1723) -> Vec<u8> {
1724 let mut out = Vec::with_capacity(zlib.len() + 256);
1725 container::write_signature(&mut out).expect("Vec write is infallible");
1726
1727 let mut ihdr = [0u8; 13];
1728 ihdr[0..4].copy_from_slice(&width.to_be_bytes());
1729 ihdr[4..8].copy_from_slice(&height.to_be_bytes());
1730 ihdr[8] = mode.bit_depth;
1731 ihdr[9] = mode.color_type;
1732 ihdr[12] = options.interlace as u8;
1733 write_chunk(&mut out, b"IHDR", &ihdr);
1734
1735 write_meta_before_plte(&mut out, mode.color_type, meta);
1736 write_unknown(&mut out, meta, Location::AfterIhdr);
1737
1738 if let Some((rgb, alpha)) = &mode.palette {
1739 let plte: Vec<u8> = rgb.iter().flatten().copied().collect();
1740 write_chunk(&mut out, b"PLTE", &plte);
1741 if alpha.iter().any(|&a| a != 255) {
1742 let last = alpha.iter().rposition(|&a| a != 255).unwrap();
1743 write_chunk(&mut out, b"tRNS", &alpha[..=last]);
1744 }
1745 }
1746
1747 write_meta_after_plte(&mut out, mode.color_type, meta);
1748 write_unknown(&mut out, meta, Location::AfterPlte);
1749
1750 for part in zlib.chunks(i32::MAX as usize) {
1752 write_chunk(&mut out, b"IDAT", part);
1753 }
1754
1755 write_unknown(&mut out, meta, Location::AfterIdat);
1756 write_chunk(&mut out, b"IEND", &[]);
1757 out
1758}
1759
1760fn write_meta_before_plte(out: &mut Vec<u8>, color_type: u8, meta: &Metadata) {
1766 if let Some(c) = &meta.chrm {
1767 let mut p = [0u8; 32];
1768 for (i, v) in [
1769 c.white_x, c.white_y, c.red_x, c.red_y, c.green_x, c.green_y, c.blue_x, c.blue_y,
1770 ]
1771 .iter()
1772 .enumerate()
1773 {
1774 p[i * 4..i * 4 + 4].copy_from_slice(&v.to_be_bytes());
1775 }
1776 write_chunk(out, b"cHRM", &p);
1777 }
1778 if let Some(g) = meta.gama {
1779 write_chunk(out, b"gAMA", &g.to_be_bytes());
1780 }
1781 if let Some(intent) = meta.srgb {
1782 write_chunk(out, b"sRGB", &[intent]);
1783 }
1784 if let Some(iccp) = &meta.iccp {
1785 let mut p = Vec::with_capacity(iccp.name.len() + 2 + iccp.profile.len());
1786 p.extend_from_slice(&iccp.name);
1787 p.push(0); p.push(0); p.extend_from_slice(&compress(&iccp.profile, 6));
1790 write_chunk(out, b"iCCP", &p);
1791 }
1792 if let Some(s) = &meta.sbit {
1793 let p: &[u8] = match color_type {
1794 0 => &[s.grayscale],
1795 2 | 3 => &[s.red, s.green, s.blue],
1796 4 => &[s.grayscale, s.alpha],
1797 _ => &[s.red, s.green, s.blue, s.alpha],
1798 };
1799 write_chunk(out, b"sBIT", p);
1800 }
1801}
1802
1803fn write_meta_after_plte(out: &mut Vec<u8>, color_type: u8, meta: &Metadata) {
1805 match (&meta.transparency, color_type) {
1807 (Some(Trns::Gray(v)), 0) => write_chunk(out, b"tRNS", &v.to_be_bytes()),
1808 (Some(Trns::Rgb(r, g, b)), 2) => {
1809 let mut p = [0u8; 6];
1810 p[0..2].copy_from_slice(&r.to_be_bytes());
1811 p[2..4].copy_from_slice(&g.to_be_bytes());
1812 p[4..6].copy_from_slice(&b.to_be_bytes());
1813 write_chunk(out, b"tRNS", &p);
1814 }
1815 _ => {}
1816 }
1817 if let Some(b) = &meta.bkgd {
1818 let p: Vec<u8> = match b {
1819 Bkgd::Gray(v) => v.to_be_bytes().to_vec(),
1820 Bkgd::Rgb(r, g, bl) => [r.to_be_bytes(), g.to_be_bytes(), bl.to_be_bytes()].concat(),
1821 Bkgd::Palette(i) => vec![*i],
1822 };
1823 write_chunk(out, b"bKGD", &p);
1824 }
1825 if let Some(h) = &meta.hist {
1826 let p: Vec<u8> = h.iter().flat_map(|v| v.to_be_bytes()).collect();
1827 write_chunk(out, b"hIST", &p);
1828 }
1829 if let Some(ph) = &meta.phys {
1830 let mut p = [0u8; 9];
1831 p[0..4].copy_from_slice(&ph.ppu_x.to_be_bytes());
1832 p[4..8].copy_from_slice(&ph.ppu_y.to_be_bytes());
1833 p[8] = ph.unit;
1834 write_chunk(out, b"pHYs", &p);
1835 }
1836 for s in &meta.splt {
1837 let mut p = Vec::with_capacity(s.name.len() + 2 + s.entries.len() * 10);
1838 p.extend_from_slice(&s.name);
1839 p.push(0);
1840 p.push(s.sample_depth);
1841 for e in &s.entries {
1842 if s.sample_depth == 16 {
1843 p.extend_from_slice(&e.red.to_be_bytes());
1844 p.extend_from_slice(&e.green.to_be_bytes());
1845 p.extend_from_slice(&e.blue.to_be_bytes());
1846 p.extend_from_slice(&e.alpha.to_be_bytes());
1847 } else {
1848 p.extend_from_slice(&[e.red as u8, e.green as u8, e.blue as u8, e.alpha as u8]);
1849 }
1850 p.extend_from_slice(&e.frequency.to_be_bytes());
1851 }
1852 write_chunk(out, b"sPLT", &p);
1853 }
1854 if let Some(t) = &meta.time {
1855 let mut p = [0u8; 7];
1856 p[0..2].copy_from_slice(&t.year.to_be_bytes());
1857 p[2..7].copy_from_slice(&[t.month, t.day, t.hour, t.minute, t.second]);
1858 write_chunk(out, b"tIME", &p);
1859 }
1860 if let Some(o) = &meta.offs {
1861 let mut p = [0u8; 9];
1862 p[0..4].copy_from_slice(&o.x.to_be_bytes());
1863 p[4..8].copy_from_slice(&o.y.to_be_bytes());
1864 p[8] = o.unit;
1865 write_chunk(out, b"oFFs", &p);
1866 }
1867 if let Some(e) = &meta.exif {
1868 write_chunk(out, b"eXIf", e);
1869 }
1870 for t in &meta.text {
1871 write_text(out, t);
1872 }
1873}
1874
1875fn write_text(out: &mut Vec<u8>, t: &Text) {
1876 let mut p = Vec::with_capacity(t.keyword.len() + t.text.len() + 8);
1877 p.extend_from_slice(&t.keyword);
1878 p.push(0);
1879 match t.kind {
1880 TextKind::Text => p.extend_from_slice(&t.text),
1881 TextKind::Ztxt => {
1882 p.push(0); p.extend_from_slice(&compress(&t.text, 6));
1884 write_chunk(out, b"zTXt", &p);
1885 return;
1886 }
1887 TextKind::Itxt => {
1888 p.push(t.compressed as u8); p.push(0); p.extend_from_slice(&t.language_tag);
1891 p.push(0);
1892 p.extend_from_slice(&t.translated_keyword);
1893 p.push(0);
1894 if t.compressed {
1895 p.extend_from_slice(&compress(&t.text, 6));
1896 } else {
1897 p.extend_from_slice(&t.text);
1898 }
1899 write_chunk(out, b"iTXt", &p);
1900 return;
1901 }
1902 }
1903 write_chunk(out, b"tEXt", &p);
1904}
1905
1906fn write_unknown(out: &mut Vec<u8>, meta: &Metadata, at: Location) {
1907 for u in &meta.unknown {
1908 if u.location == at {
1909 write_chunk(out, &u.kind, &u.data);
1910 }
1911 }
1912}
1913
1914#[cfg(test)]
1915mod tests {
1916 use super::*;
1917
1918 fn lcg(n: usize, mut seed: u32) -> Vec<u8> {
1919 (0..n)
1920 .map(|_| {
1921 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
1922 (seed >> 24) as u8
1923 })
1924 .collect()
1925 }
1926
1927 #[test]
1933 fn neon_matches_scalar() {
1934 for &bpp in &[1usize, 2, 3, 4, 6, 8] {
1935 for &width in &[1usize, 5, 15, 16, 17, 31, 33, 257] {
1937 let len = width * bpp;
1938 let row = lcg(len, (bpp * 31 + width) as u32 + 1);
1939 let full_prev = lcg(len, (bpp * 17 + width) as u32 + 99);
1940 for prev in [&[][..], &full_prev[..]] {
1941 for filter in 0..=4u8 {
1944 let scalar_sad = filter_sad_scalar(filter, &row, prev, bpp, u64::MAX);
1945 let kernel_sad = filter_sad(filter, &row, prev, bpp, u64::MAX);
1946 assert_eq!(
1947 scalar_sad,
1948 kernel_sad,
1949 "sad mismatch filter={filter} bpp={bpp} width={width} prev_empty={}",
1950 prev.is_empty()
1951 );
1952
1953 let mut a = vec![0u8; len];
1954 let mut b = vec![0u8; len];
1955 apply_filter_scalar(filter, &row, prev, bpp, &mut a);
1956 apply_filter(filter, &row, prev, bpp, &mut b);
1957 assert_eq!(
1958 a,
1959 b,
1960 "apply mismatch filter={filter} bpp={bpp} width={width} prev_empty={}",
1961 prev.is_empty()
1962 );
1963 }
1964 }
1965 }
1966 }
1967 }
1968}