1use image::{ImageBuffer, ImageEncoder, RgbaImage};
8use typf_core::{
9 error::{ExportError, Result},
10 traits::Exporter,
11 types::{BitmapData, BitmapFormat, RenderOutput},
12};
13
14pub fn encode_bitmap_to_png(bitmap: &BitmapData) -> Result<Vec<u8>> {
21 let expected_size = match bitmap.format {
23 BitmapFormat::Rgba8 => (bitmap.width * bitmap.height * 4) as usize,
24 BitmapFormat::Rgb8 => (bitmap.width * bitmap.height * 3) as usize,
25 BitmapFormat::Gray8 => (bitmap.width * bitmap.height) as usize,
26 BitmapFormat::Gray1 => (bitmap.width * bitmap.height).div_ceil(8) as usize,
27 };
28
29 if bitmap.data.len() < expected_size {
30 return Err(ExportError::EncodingFailed(format!(
31 "Buffer too small: expected {} bytes for {}x{} {:?}, got {}",
32 expected_size,
33 bitmap.width,
34 bitmap.height,
35 bitmap.format,
36 bitmap.data.len()
37 ))
38 .into());
39 }
40
41 let img: RgbaImage = match bitmap.format {
43 BitmapFormat::Rgba8 => {
44 ImageBuffer::from_raw(bitmap.width, bitmap.height, bitmap.data.clone()).ok_or_else(
46 || {
47 ExportError::EncodingFailed(
48 "Failed to create image buffer from RGBA data".into(),
49 )
50 },
51 )?
52 },
53 BitmapFormat::Rgb8 => {
54 let mut rgba_data = Vec::with_capacity((bitmap.width * bitmap.height * 4) as usize);
56 for chunk in bitmap.data.chunks(3) {
57 if chunk.len() < 3 {
58 break; }
60 rgba_data.push(chunk[0]); rgba_data.push(chunk[1]); rgba_data.push(chunk[2]); rgba_data.push(255); }
65 ImageBuffer::from_raw(bitmap.width, bitmap.height, rgba_data).ok_or_else(|| {
66 ExportError::EncodingFailed("Failed to create image buffer from RGB data".into())
67 })?
68 },
69 BitmapFormat::Gray8 => {
70 let mut rgba_data = Vec::with_capacity((bitmap.width * bitmap.height * 4) as usize);
72 for &gray in &bitmap.data {
73 rgba_data.push(gray); rgba_data.push(gray); rgba_data.push(gray); rgba_data.push(255); }
78 ImageBuffer::from_raw(bitmap.width, bitmap.height, rgba_data).ok_or_else(|| {
79 ExportError::EncodingFailed(
80 "Failed to create image buffer from grayscale data".into(),
81 )
82 })?
83 },
84 BitmapFormat::Gray1 => {
85 let mut rgba_data = Vec::with_capacity((bitmap.width * bitmap.height * 4) as usize);
87 for y in 0..bitmap.height {
88 for x in 0..bitmap.width {
89 let byte_idx = ((y * bitmap.width + x) / 8) as usize;
90 let bit_idx = ((y * bitmap.width + x) % 8) as usize;
91 if byte_idx >= bitmap.data.len() {
92 rgba_data.extend_from_slice(&[0, 0, 0, 255]);
94 continue;
95 }
96 let bit = (bitmap.data[byte_idx] >> (7 - bit_idx)) & 1;
97 let value = if bit == 1 { 255 } else { 0 };
98 rgba_data.push(value); rgba_data.push(value); rgba_data.push(value); rgba_data.push(255); }
103 }
104 ImageBuffer::from_raw(bitmap.width, bitmap.height, rgba_data).ok_or_else(|| {
105 ExportError::EncodingFailed("Failed to create image buffer from 1-bit data".into())
106 })?
107 },
108 };
109
110 let mut png_data = Vec::new();
112 let encoder = image::codecs::png::PngEncoder::new_with_quality(
113 &mut png_data,
114 image::codecs::png::CompressionType::Default,
115 image::codecs::png::FilterType::Sub,
116 );
117
118 encoder
119 .write_image(
120 img.as_raw(),
121 bitmap.width,
122 bitmap.height,
123 image::ExtendedColorType::Rgba8,
124 )
125 .map_err(|e| ExportError::EncodingFailed(format!("PNG encoding failed: {}", e)))?;
126
127 Ok(png_data)
128}
129
130pub struct PngExporter;
141
142impl PngExporter {
143 pub fn new() -> Self {
145 Self
146 }
147
148 fn export_bitmap(&self, bitmap: &BitmapData) -> Result<Vec<u8>> {
150 encode_bitmap_to_png(bitmap)
151 }
152}
153
154impl Exporter for PngExporter {
155 fn name(&self) -> &'static str {
156 "png"
157 }
158
159 fn export(&self, output: &RenderOutput) -> Result<Vec<u8>> {
160 match output {
161 RenderOutput::Bitmap(bitmap) => self.export_bitmap(bitmap),
162 _ => Err(ExportError::FormatNotSupported(
163 "PNG exporter only supports bitmap output".into(),
164 )
165 .into()),
166 }
167 }
168
169 fn extension(&self) -> &'static str {
170 "png"
171 }
172
173 fn mime_type(&self) -> &'static str {
174 "image/png"
175 }
176}
177
178impl Default for PngExporter {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_png_exporter_creation() {
190 let exporter = PngExporter::new();
191 assert_eq!(exporter.name(), "png");
192 assert_eq!(exporter.extension(), "png");
193 assert_eq!(exporter.mime_type(), "image/png");
194 }
195
196 #[test]
197 fn test_png_export_rgba() {
198 let exporter = PngExporter::new();
199
200 let bitmap = BitmapData {
202 width: 2,
203 height: 2,
204 format: BitmapFormat::Rgba8,
205 data: vec![
206 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, ],
211 };
212
213 let output = RenderOutput::Bitmap(bitmap);
214 let png_data = match exporter.export(&output) {
215 Ok(png_data) => png_data,
216 Err(e) => unreachable!("png export failed: {e}"),
217 };
218
219 assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
221 assert!(png_data.len() > 50); }
223
224 #[test]
225 fn test_png_export_grayscale() {
226 let exporter = PngExporter::new();
227
228 let bitmap = BitmapData {
229 width: 2,
230 height: 2,
231 format: BitmapFormat::Gray8,
232 data: vec![0, 128, 192, 255],
233 };
234
235 let output = RenderOutput::Bitmap(bitmap);
236 let png_data = match exporter.export(&output) {
237 Ok(png_data) => png_data,
238 Err(e) => unreachable!("png export failed: {e}"),
239 };
240
241 assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
243 }
244
245 #[test]
246 fn test_png_default() {
247 let exporter = PngExporter;
248 assert_eq!(exporter.name(), "png");
249 }
250}