fastpack_compress/backends/
png.rs1use std::io::Cursor;
2
3use fastpack_core::types::config::PackMode;
4use image::ImageFormat;
5
6use crate::{
7 compressor::{CompressInput, CompressOutput, Compressor},
8 error::CompressError,
9};
10
11pub struct PngCompressor;
17
18impl Compressor for PngCompressor {
19 fn compress(&self, input: &CompressInput<'_>) -> Result<CompressOutput, CompressError> {
20 compress_png(input)
21 }
22
23 fn format_id(&self) -> &'static str {
24 "png"
25 }
26
27 fn file_extension(&self) -> &'static str {
28 "png"
29 }
30}
31
32fn compress_png(input: &CompressInput<'_>) -> Result<CompressOutput, CompressError> {
33 let mut buf = Cursor::new(Vec::new());
34 input.image.write_to(&mut buf, ImageFormat::Png)?;
35 let png_bytes = buf.into_inner();
36
37 #[cfg(feature = "png")]
38 match input.pack_mode {
39 PackMode::Fast => {}
40 PackMode::Good => {
41 let opts = oxipng::Options::from_preset(3);
42 let optimized = oxipng::optimize_from_memory(&png_bytes, &opts)?;
43 return Ok(CompressOutput { data: optimized });
44 }
45 PackMode::Best => {
46 let opts = oxipng::Options::from_preset(6);
47 let optimized = oxipng::optimize_from_memory(&png_bytes, &opts)?;
48 return Ok(CompressOutput { data: optimized });
49 }
50 }
51
52 Ok(CompressOutput { data: png_bytes })
53}
54
55#[cfg(feature = "png")]
63pub struct LossyPngCompressor;
64
65#[cfg(feature = "png")]
66impl Compressor for LossyPngCompressor {
67 fn compress(&self, input: &CompressInput<'_>) -> Result<CompressOutput, CompressError> {
68 compress_lossy_png(input)
69 }
70
71 fn format_id(&self) -> &'static str {
72 "png"
73 }
74
75 fn file_extension(&self) -> &'static str {
76 "png"
77 }
78}
79
80#[cfg(feature = "png")]
81fn compress_lossy_png(input: &CompressInput<'_>) -> Result<CompressOutput, CompressError> {
82 let rgba = input.image.to_rgba8();
83 let (w, h) = rgba.dimensions();
84 let quality = input.quality;
85
86 let pixels: Vec<imagequant::RGBA> = rgba
87 .pixels()
88 .map(|p| imagequant::RGBA {
89 r: p[0],
90 g: p[1],
91 b: p[2],
92 a: p[3],
93 })
94 .collect();
95
96 let mut liq = imagequant::new();
97 liq.set_quality(0, quality)
98 .map_err(|e| CompressError::Other(e.to_string()))?;
99 let mut img = liq
100 .new_image_borrowed(&pixels, w as usize, h as usize, 0.0)
101 .map_err(|e| CompressError::Other(e.to_string()))?;
102 let mut res = liq
103 .quantize(&mut img)
104 .map_err(|e| CompressError::Other(e.to_string()))?;
105 res.set_dithering_level(1.0)
106 .map_err(|e| CompressError::Other(e.to_string()))?;
107 let (palette, indexed) = res
108 .remapped(&mut img)
109 .map_err(|e| CompressError::Other(e.to_string()))?;
110
111 let mut png_bytes = Vec::new();
112 {
113 let mut enc = png::Encoder::new(&mut png_bytes, w, h);
114 enc.set_color(png::ColorType::Indexed);
115 enc.set_depth(png::BitDepth::Eight);
116 let pal: Vec<u8> = palette.iter().flat_map(|c| [c.r, c.g, c.b]).collect();
117 enc.set_palette(pal);
118 let trns: Vec<u8> = palette.iter().map(|c| c.a).collect();
119 enc.set_trns(trns);
120 let mut writer = enc
121 .write_header()
122 .map_err(|e| CompressError::Other(e.to_string()))?;
123 writer
124 .write_image_data(&indexed)
125 .map_err(|e| CompressError::Other(e.to_string()))?;
126 }
127
128 match input.pack_mode {
129 PackMode::Fast => {}
130 PackMode::Good => {
131 let opts = oxipng::Options::from_preset(3);
132 let optimized = oxipng::optimize_from_memory(&png_bytes, &opts)?;
133 return Ok(CompressOutput { data: optimized });
134 }
135 PackMode::Best => {
136 let opts = oxipng::Options::from_preset(6);
137 let optimized = oxipng::optimize_from_memory(&png_bytes, &opts)?;
138 return Ok(CompressOutput { data: optimized });
139 }
140 }
141
142 Ok(CompressOutput { data: png_bytes })
143}