rasterrocket_encode/
pgm.rs1use std::io::{self, Write};
9
10use color::{Pixel, PixelMode};
11use raster::Bitmap;
12
13use crate::EncodeError;
14
15pub fn write_pgm<P: Pixel, W: Write>(bitmap: &Bitmap<P>, mut out: W) -> Result<(), EncodeError> {
26 match P::MODE {
27 PixelMode::Mono8 => {}
28 PixelMode::Mono1
29 | PixelMode::Rgb8
30 | PixelMode::Bgr8
31 | PixelMode::Xbgr8
32 | PixelMode::Cmyk8
33 | PixelMode::DeviceN8 => {
34 return Err(EncodeError::UnsupportedMode(
35 "non-grayscale bitmap: use write_ppm or write_png",
36 ));
37 }
38 }
39
40 write_pgm_header(&mut out, bitmap.width, bitmap.height)?;
41
42 let w = bitmap.width as usize;
45 for y in 0..bitmap.height {
46 let row = bitmap.row_bytes(y);
47 out.write_all(&row[..w])?;
48 }
49
50 out.flush()?;
51 Ok(())
52}
53
54fn write_pgm_header<W: Write>(out: &mut W, width: u32, height: u32) -> io::Result<()> {
56 writeln!(out, "P5")?;
57 writeln!(out, "{width} {height}")?;
58 writeln!(out, "255")?;
59 Ok(())
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use color::{Gray8, Rgb8};
66 use raster::Bitmap;
67
68 fn make_gray_bitmap(w: u32, h: u32, fill: u8) -> Bitmap<Gray8> {
69 let mut bmp = Bitmap::new(w, h, 1, false);
70 for y in 0..h {
71 bmp.row_bytes_mut(y).fill(fill);
72 }
73 bmp
74 }
75
76 #[test]
77 fn pgm_header_and_pixels() {
78 let bmp = make_gray_bitmap(3, 2, 128);
79 let mut out = Vec::new();
80 write_pgm::<Gray8, _>(&bmp, &mut out).unwrap();
81
82 let header = "P5\n3 2\n255\n";
83 assert!(
84 out.starts_with(header.as_bytes()),
85 "header mismatch: {:?}",
86 &out[..header.len().min(out.len())]
87 );
88 let pixels = &out[header.len()..];
89 assert_eq!(pixels.len(), 6, "3×2 = 6 pixel bytes");
90 assert!(pixels.iter().all(|&v| v == 128), "all pixels should be 128");
91 }
92
93 #[test]
94 fn stride_padding_excluded() {
95 let bmp_padded: Bitmap<Gray8> = Bitmap::new(3, 1, 4, false);
97 assert!(
98 bmp_padded.stride >= 3,
99 "padded stride must be at least width (sanity check on Bitmap::new)"
100 );
101
102 let mut out = Vec::new();
103 write_pgm::<Gray8, _>(&bmp_padded, &mut out).unwrap();
104 let header = "P5\n3 1\n255\n";
105 let pixels = &out[header.len()..];
106 assert_eq!(
108 pixels.len(),
109 3,
110 "stride padding must not appear in PGM output"
111 );
112 }
113
114 #[test]
115 fn rgb8_returns_unsupported_error() {
116 let bmp: Bitmap<Rgb8> = Bitmap::new(1, 1, 1, false);
117 let mut out = Vec::new();
118 let result = write_pgm::<Rgb8, _>(&bmp, &mut out);
119 assert!(
120 matches!(result, Err(EncodeError::UnsupportedMode(_))),
121 "Rgb8 should return UnsupportedMode for PGM"
122 );
123 }
124}