1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[derive(Clone, Debug)]
5pub enum PNMImage<'a> {
6 PPMBinary {
8 width: usize,
10 height: usize,
12 maximum_pixel: usize,
14 comment: &'a str,
16 pixel_data: &'a [u8],
18 },
19}
20
21use PNMImage::*;
22
23#[derive(Debug)]
25pub enum PNMError {
26 NotPNMFormat,
28 UnsupportedPNMFormat,
30 UTF8Error,
32 ParseError {
34 pos: usize,
36 got: u8,
38 ctx: &'static str,
40 },
41}
42
43use PNMError::*;
44
45impl<'a> PNMImage<'a> {
46
47 pub fn from_parse<const N: usize>(bytes: &'a [u8; N]) -> Result<Self, PNMError> {
57 if bytes[0] != b'P' {
59 return Err(NotPNMFormat);
60 }
61 match bytes[1] {
62 b'1' ..= b'5' => return Err(UnsupportedPNMFormat),
63 b'6' => (),
64 _ => return Err(NotPNMFormat)
65 }
66 if bytes[2] != b'\n' {
67 return Err(ParseError {
68 pos: 2,
69 got: bytes[2],
70 ctx: "expected newline.",
71 });
72 }
73 let mut idx = 3;
74
75 while bytes[idx] == b'#' {
77 while bytes[idx] != b'\n' {
78 idx += 1
79 }
80 }
81 let comment = if idx == 3 {
82 ""
83 } else if let Ok(header) = core::str::from_utf8(&bytes[3..idx]) {
84 header
85 } else {
86 return Err(UTF8Error);
87 };
88 idx += 1;
89
90 macro_rules! parse_dec {
91 ($stop:expr) => {{
92 let mut acc = 0;
93 while bytes[idx] != $stop {
94 if !bytes[idx].is_ascii_digit() {
95 return Err(ParseError {
96 pos: idx,
97 got: bytes[idx],
98 ctx: "expected digit.",
99 });
100 }
101 acc *= 10;
102 acc += (bytes[idx] - b'0') as usize;
103
104 idx += 1;
105 }
106 idx += 1;
107 acc
108 }};
109 }
110
111 let width = parse_dec!(b' ');
113 let height = parse_dec!(b'\n');
114 let maximum_pixel = parse_dec!(b'\n');
116
117 let pixel_data = &bytes[idx..N];
119
120 Ok(Self::PPMBinary {
121 width,
122 height,
123 maximum_pixel,
124 comment,
125 pixel_data,
126 })
127 }
128}
129
130impl PNMImage<'_> {
131 pub fn width(&self) -> usize {
133 let PPMBinary{width, ..} = *self;
134 width
135 }
136
137 pub fn height(&self) -> usize {
139 let PPMBinary{height, ..} = *self;
140 height
141 }
142
143 pub fn maximum_pixel(&self) -> usize {
145 let PPMBinary{maximum_pixel, ..} = *self;
146 maximum_pixel
147 }
148
149 pub fn comment(&self) -> &str {
151 let PPMBinary{comment, ..} = *self;
152 comment
153 }
154
155 fn pixel_data(&self) -> &[u8] {
157 let PPMBinary{pixel_data, ..} = *self;
158 pixel_data
159 }
160
161 pub fn pixel_rgb(&self, x: usize, y: usize) -> Option<(u8, u8, u8)> {
164 let idx = (x + y * self.width()) * 3;
165 if idx >= self.pixel_data().len() {
166 None
167 } else {
168 Some((
169 self.pixel_data()[idx],
170 self.pixel_data()[idx + 1],
171 self.pixel_data()[idx + 2],
172 ))
173 }
174 }
175}
176
177#[cfg(test)]
178mod test {
179 use super::*;
180
181 #[test]
182 fn test() {
183 let raw_img = include_bytes!("./binary.ppm");
184 let ppm_img = PNMImage::from_parse(raw_img).unwrap();
185
186 assert_eq!(ppm_img.comment(), "# Created by GIMP version 2.10.34 PNM plug-in");
187 assert_eq!(ppm_img.width(), 64, "expecting image width 64");
188 assert_eq!(ppm_img.height(), 64, "expecting image height 64");
189
190 assert_eq!(ppm_img.pixel_rgb(64, 63), None);
192
193 assert_eq!(ppm_img.pixel_rgb(0, 0), Some((0,0,0)));
195 assert_eq!(ppm_img.pixel_rgb(63, 63), Some((0,0,0)));
196 assert_eq!(ppm_img.pixel_rgb(63, 0), Some((0,0,0)));
197 assert_eq!(ppm_img.pixel_rgb(0, 63), Some((0,0,0)));
198
199 assert_eq!(ppm_img.pixel_rgb(7, 7), Some((0,255,0)));
204 assert_eq!(ppm_img.pixel_rgb(31, 7), Some((255,0,0)));
205 assert_eq!(ppm_img.pixel_rgb(56, 7), Some((255,255,0)));
206
207 assert_eq!(ppm_img.pixel_rgb(7, 31), Some((255,0,0)));
208 assert_eq!(ppm_img.pixel_rgb(31, 31), Some((255,255,255)));
209 assert_eq!(ppm_img.pixel_rgb(56, 31), Some((255,0,0)));
210
211 assert_eq!(ppm_img.pixel_rgb(7, 56), Some((0,255,255)));
212 assert_eq!(ppm_img.pixel_rgb(31, 56), Some((255,0,0)));
213 assert_eq!(ppm_img.pixel_rgb(56, 56), Some((0,0,255)));
214 }
215}