1use std::io::Write;
11
12use color::{Pixel, PixelMode};
13use raster::Bitmap;
14
15use crate::EncodeError;
16
17pub fn write_png<P: Pixel, W: Write>(bitmap: &Bitmap<P>, out: W) -> Result<(), EncodeError> {
44 match P::MODE {
45 PixelMode::Rgb8 => write_png_rgb(bitmap, out),
46 PixelMode::Mono8 => write_png_gray(bitmap, out),
47 PixelMode::Xbgr8 => write_png_rgba(bitmap, out),
49 PixelMode::Bgr8 | PixelMode::Cmyk8 | PixelMode::DeviceN8 | PixelMode::Mono1 => {
50 Err(EncodeError::UnsupportedMode(
51 "unsupported mode for PNG: convert to Rgb8/Gray8/Rgba8 first",
52 ))
53 }
54 }
55}
56
57fn png_encoder<W: Write>(
59 out: W,
60 width: u32,
61 height: u32,
62 color: ::png::ColorType,
63 depth: ::png::BitDepth,
64) -> Result<::png::Writer<W>, EncodeError> {
65 let mut encoder = ::png::Encoder::new(out, width, height);
66 encoder.set_color(color);
67 encoder.set_depth(depth);
68 encoder.set_filter(::png::FilterType::Paeth);
70 encoder.set_compression(::png::Compression::Fast);
74 Ok(encoder.write_header()?)
75}
76
77fn pack_rows<P: Pixel>(bitmap: &Bitmap<P>, bytes_per_pixel: usize) -> Result<Vec<u8>, EncodeError> {
82 let w = bitmap.width as usize;
83 let h = bitmap.height as usize;
84 let total = w
85 .checked_mul(h)
86 .and_then(|wh| wh.checked_mul(bytes_per_pixel))
87 .ok_or(EncodeError::UnsupportedMode(
88 "image too large: pixel buffer would overflow usize",
89 ))?;
90 let mut buf = vec![0u8; total];
91 let row_len = w * bytes_per_pixel;
92 for y in 0..bitmap.height {
93 let row = bitmap.row_bytes(y);
94 let dst_off = y as usize * row_len;
95 buf[dst_off..dst_off + row_len].copy_from_slice(&row[..row_len]);
96 }
97 Ok(buf)
98}
99
100fn write_png_rgb<P: Pixel, W: Write>(bitmap: &Bitmap<P>, out: W) -> Result<(), EncodeError> {
102 let w = bitmap.width as usize;
103 let h = bitmap.height as usize;
104
105 if bitmap.has_alpha() {
106 let total = w.checked_mul(h).and_then(|wh| wh.checked_mul(4)).ok_or(
108 EncodeError::UnsupportedMode("image too large: RGBA buffer would overflow usize"),
109 )?;
110 let mut buf = vec![0u8; total];
111 for y in 0..bitmap.height {
112 let rgb = bitmap.row_bytes(y);
113 let alpha = bitmap
115 .alpha_row(y)
116 .expect("has_alpha is true but alpha_row returned None");
117 let row_off = y as usize * w * 4;
118 for i in 0..w {
119 buf[row_off + i * 4] = rgb[i * 3];
120 buf[row_off + i * 4 + 1] = rgb[i * 3 + 1];
121 buf[row_off + i * 4 + 2] = rgb[i * 3 + 2];
122 buf[row_off + i * 4 + 3] = alpha[i];
123 }
124 }
125 let mut writer = png_encoder(
126 out,
127 bitmap.width,
128 bitmap.height,
129 ::png::ColorType::Rgba,
130 ::png::BitDepth::Eight,
131 )?;
132 writer.write_image_data(&buf)?;
133 } else {
134 let buf = pack_rows(bitmap, 3)?;
135 let mut writer = png_encoder(
136 out,
137 bitmap.width,
138 bitmap.height,
139 ::png::ColorType::Rgb,
140 ::png::BitDepth::Eight,
141 )?;
142 writer.write_image_data(&buf)?;
143 }
144
145 Ok(())
146}
147
148fn write_png_gray<P: Pixel, W: Write>(bitmap: &Bitmap<P>, out: W) -> Result<(), EncodeError> {
150 let buf = pack_rows(bitmap, 1)?;
151 let mut writer = png_encoder(
152 out,
153 bitmap.width,
154 bitmap.height,
155 ::png::ColorType::Grayscale,
156 ::png::BitDepth::Eight,
157 )?;
158 writer.write_image_data(&buf)?;
159 Ok(())
160}
161
162fn write_png_rgba<P: Pixel, W: Write>(bitmap: &Bitmap<P>, out: W) -> Result<(), EncodeError> {
167 let w = bitmap.width as usize;
168 let h = bitmap.height as usize;
169 let total =
170 w.checked_mul(h)
171 .and_then(|wh| wh.checked_mul(4))
172 .ok_or(EncodeError::UnsupportedMode(
173 "image too large: RGBA buffer would overflow usize",
174 ))?;
175 let mut buf = vec![0u8; total];
176 for y in 0..bitmap.height {
177 let row = bitmap.row_bytes(y);
178 let row_off = y as usize * w * 4;
179 for i in 0..w {
181 let src = i * 4;
182 let dst = row_off + i * 4;
183 buf[dst] = row[src + 3]; buf[dst + 1] = row[src + 2]; buf[dst + 2] = row[src + 1]; buf[dst + 3] = row[src]; }
188 }
189 let mut writer = png_encoder(
190 out,
191 bitmap.width,
192 bitmap.height,
193 ::png::ColorType::Rgba,
194 ::png::BitDepth::Eight,
195 )?;
196 writer.write_image_data(&buf)?;
197 Ok(())
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use color::{Cmyk8, Gray8, Rgb8, Rgba8};
204 use raster::Bitmap;
205
206 fn make_rgb_bitmap(w: u32, h: u32, fill: [u8; 3]) -> Bitmap<Rgb8> {
207 let mut bmp = Bitmap::new(w, h, 1, false);
208 for y in 0..h {
209 let row = bmp.row_bytes_mut(y);
210 for chunk in row.chunks_exact_mut(3) {
211 chunk.copy_from_slice(&fill);
212 }
213 }
214 bmp
215 }
216
217 fn make_gray_bitmap(w: u32, h: u32, fill: u8) -> Bitmap<Gray8> {
218 let mut bmp = Bitmap::new(w, h, 1, false);
219 for y in 0..h {
220 bmp.row_bytes_mut(y).fill(fill);
221 }
222 bmp
223 }
224
225 fn decode_png(data: &[u8]) -> (u32, u32, Vec<u8>) {
227 let decoder = ::png::Decoder::new(std::io::Cursor::new(data));
228 let mut reader = decoder.read_info().expect("png decode header");
229 let mut buf = vec![0u8; reader.output_buffer_size()];
230 let frame = reader.next_frame(&mut buf).expect("png decode frame");
231 let info = reader.info();
232 (info.width, info.height, buf[..frame.buffer_size()].to_vec())
233 }
234
235 #[test]
236 fn rgb_png_roundtrip() {
237 let bmp = make_rgb_bitmap(4, 2, [100, 150, 200]);
238 let mut out = Vec::new();
239 write_png::<Rgb8, _>(&bmp, &mut out).unwrap();
240
241 let (w, h, pixels) = decode_png(&out);
242 assert_eq!((w, h), (4, 2));
243 assert_eq!(pixels.len(), 24, "4×2 pixels × 3 bytes");
244 for chunk in pixels.chunks_exact(3) {
245 assert_eq!(chunk, &[100, 150, 200], "pixel mismatch");
246 }
247 }
248
249 #[test]
250 fn gray_png_roundtrip() {
251 let bmp = make_gray_bitmap(3, 3, 77);
252 let mut out = Vec::new();
253 write_png::<Gray8, _>(&bmp, &mut out).unwrap();
254
255 let (w, h, pixels) = decode_png(&out);
256 assert_eq!((w, h), (3, 3));
257 assert!(pixels.iter().all(|&v| v == 77), "grayscale pixel mismatch");
258 }
259
260 #[test]
261 fn rgb_with_alpha_writes_rgba_png() {
262 let mut bmp: Bitmap<Rgb8> = Bitmap::new(2, 1, 1, true);
264 let row = bmp.row_bytes_mut(0);
265 row[..6].copy_from_slice(&[255, 0, 0, 255, 0, 0]);
266 if let Some(a) = bmp.alpha_plane_mut() {
267 a.fill(128);
268 }
269
270 let mut out = Vec::new();
271 write_png::<Rgb8, _>(&bmp, &mut out).unwrap();
272
273 let (w, h, pixels) = decode_png(&out);
274 assert_eq!((w, h), (2, 1));
275 assert_eq!(pixels.len(), 8, "2 pixels × 4 bytes (RGBA)");
276 assert_eq!(&pixels[..4], &[255, 0, 0, 128], "pixel 0 RGBA");
277 assert_eq!(&pixels[4..8], &[255, 0, 0, 128], "pixel 1 RGBA");
278 }
279
280 #[test]
281 fn stride_padding_not_included() {
282 let bmp: Bitmap<Gray8> = Bitmap::new(3, 1, 4, false);
284 let mut out = Vec::new();
285 write_png::<Gray8, _>(&bmp, &mut out).unwrap();
286 let (w, h, pixels) = decode_png(&out);
287 assert_eq!((w, h), (3, 1));
288 assert_eq!(
289 pixels.len(),
290 3,
291 "stride padding must not appear in PNG output"
292 );
293 }
294
295 #[test]
296 fn rgba8_png_roundtrip_asymmetric_2x2() {
297 let mut bmp: Bitmap<Rgba8> = Bitmap::new(2, 2, 1, false);
300 bmp.row_bytes_mut(0)
304 .copy_from_slice(&[200, 30, 20, 10, 210, 60, 50, 40]);
305 bmp.row_bytes_mut(1)
306 .copy_from_slice(&[220, 90, 80, 70, 230, 120, 110, 100]);
307
308 let mut out = Vec::new();
309 write_png::<Rgba8, _>(&bmp, &mut out).unwrap();
310
311 let (w, h, pixels) = decode_png(&out);
312 assert_eq!((w, h), (2, 2));
313 assert_eq!(pixels.len(), 16, "2×2 pixels × 4 bytes (RGBA)");
314 assert_eq!(&pixels[0..4], &[10, 20, 30, 200], "row 0 px 0");
316 assert_eq!(&pixels[4..8], &[40, 50, 60, 210], "row 0 px 1");
317 assert_eq!(&pixels[8..12], &[70, 80, 90, 220], "row 1 px 0");
318 assert_eq!(&pixels[12..16], &[100, 110, 120, 230], "row 1 px 1");
319 }
320
321 #[test]
322 fn rgb_with_alpha_multi_row_promotion() {
323 let mut bmp: Bitmap<Rgb8> = Bitmap::new(2, 2, 1, true);
327 bmp.row_bytes_mut(0)
329 .copy_from_slice(&[10, 20, 30, 40, 50, 60]);
330 bmp.row_bytes_mut(1)
331 .copy_from_slice(&[70, 80, 90, 100, 110, 120]);
332 let alpha = bmp.alpha_plane_mut().expect("alpha plane present");
333 alpha.copy_from_slice(&[200, 210, 220, 230]);
334
335 let mut out = Vec::new();
336 write_png::<Rgb8, _>(&bmp, &mut out).unwrap();
337 let (w, h, pixels) = decode_png(&out);
338 assert_eq!((w, h), (2, 2));
339 assert_eq!(pixels.len(), 16);
340 assert_eq!(&pixels[0..4], &[10, 20, 30, 200]);
341 assert_eq!(&pixels[4..8], &[40, 50, 60, 210]);
342 assert_eq!(&pixels[8..12], &[70, 80, 90, 220]);
343 assert_eq!(&pixels[12..16], &[100, 110, 120, 230]);
344 }
345
346 #[test]
347 fn cmyk_returns_unsupported_error() {
348 let bmp: Bitmap<Cmyk8> = Bitmap::new(1, 1, 1, false);
349 let mut out = Vec::new();
350 let result = write_png::<Cmyk8, _>(&bmp, &mut out);
351 assert!(
352 matches!(result, Err(EncodeError::UnsupportedMode(_))),
353 "Cmyk8 should return UnsupportedMode for PNG"
354 );
355 }
356}