1 | use std::{fmt, io}; |
2 | |
3 | /// The kind of encoding used to store sample values |
4 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
5 | pub enum SampleEncoding { |
6 | /// Samples are unsigned binary integers in big endian |
7 | Binary, |
8 | |
9 | /// Samples are encoded as decimal ascii strings separated by whitespace |
10 | Ascii, |
11 | } |
12 | |
13 | /// Denotes the category of the magic number |
14 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
15 | pub enum PnmSubtype { |
16 | /// Magic numbers P1 and P4 |
17 | Bitmap(SampleEncoding), |
18 | |
19 | /// Magic numbers P2 and P5 |
20 | Graymap(SampleEncoding), |
21 | |
22 | /// Magic numbers P3 and P6 |
23 | Pixmap(SampleEncoding), |
24 | |
25 | /// Magic number P7 |
26 | ArbitraryMap, |
27 | } |
28 | |
29 | /// Stores the complete header data of a file. |
30 | /// |
31 | /// Internally, provides mechanisms for lossless reencoding. After reading a file with the decoder |
32 | /// it is possible to recover the header and construct an encoder. Using the encoder on the just |
33 | /// loaded image should result in a byte copy of the original file (for single image pnms without |
34 | /// additional trailing data). |
35 | pub struct PnmHeader { |
36 | pub(crate) decoded: HeaderRecord, |
37 | pub(crate) encoded: Option<Vec<u8>>, |
38 | } |
39 | |
40 | pub(crate) enum HeaderRecord { |
41 | Bitmap(BitmapHeader), |
42 | Graymap(GraymapHeader), |
43 | Pixmap(PixmapHeader), |
44 | Arbitrary(ArbitraryHeader), |
45 | } |
46 | |
47 | /// Header produced by a `pbm` file ("Portable Bit Map") |
48 | #[derive (Clone, Copy, Debug)] |
49 | pub struct BitmapHeader { |
50 | /// Binary or Ascii encoded file |
51 | pub encoding: SampleEncoding, |
52 | |
53 | /// Height of the image file |
54 | pub height: u32, |
55 | |
56 | /// Width of the image file |
57 | pub width: u32, |
58 | } |
59 | |
60 | /// Header produced by a `pgm` file ("Portable Gray Map") |
61 | #[derive (Clone, Copy, Debug)] |
62 | pub struct GraymapHeader { |
63 | /// Binary or Ascii encoded file |
64 | pub encoding: SampleEncoding, |
65 | |
66 | /// Height of the image file |
67 | pub height: u32, |
68 | |
69 | /// Width of the image file |
70 | pub width: u32, |
71 | |
72 | /// Maximum sample value within the image |
73 | pub maxwhite: u32, |
74 | } |
75 | |
76 | /// Header produced by a `ppm` file ("Portable Pixel Map") |
77 | #[derive (Clone, Copy, Debug)] |
78 | pub struct PixmapHeader { |
79 | /// Binary or Ascii encoded file |
80 | pub encoding: SampleEncoding, |
81 | |
82 | /// Height of the image file |
83 | pub height: u32, |
84 | |
85 | /// Width of the image file |
86 | pub width: u32, |
87 | |
88 | /// Maximum sample value within the image |
89 | pub maxval: u32, |
90 | } |
91 | |
92 | /// Header produced by a `pam` file ("Portable Arbitrary Map") |
93 | #[derive (Clone, Debug)] |
94 | pub struct ArbitraryHeader { |
95 | /// Height of the image file |
96 | pub height: u32, |
97 | |
98 | /// Width of the image file |
99 | pub width: u32, |
100 | |
101 | /// Number of color channels |
102 | pub depth: u32, |
103 | |
104 | /// Maximum sample value within the image |
105 | pub maxval: u32, |
106 | |
107 | /// Color interpretation of image pixels |
108 | pub tupltype: Option<ArbitraryTuplType>, |
109 | } |
110 | |
111 | /// Standardized tuple type specifiers in the header of a `pam`. |
112 | #[derive (Clone, Debug)] |
113 | pub enum ArbitraryTuplType { |
114 | /// Pixels are either black (0) or white (1) |
115 | BlackAndWhite, |
116 | |
117 | /// Pixels are either black (0) or white (1) and a second alpha channel |
118 | BlackAndWhiteAlpha, |
119 | |
120 | /// Pixels represent the amount of white |
121 | Grayscale, |
122 | |
123 | /// Grayscale with an additional alpha channel |
124 | GrayscaleAlpha, |
125 | |
126 | /// Three channels: Red, Green, Blue |
127 | RGB, |
128 | |
129 | /// Four channels: Red, Green, Blue, Alpha |
130 | RGBAlpha, |
131 | |
132 | /// An image format which is not standardized |
133 | Custom(String), |
134 | } |
135 | |
136 | impl ArbitraryTuplType { |
137 | pub(crate) fn name(&self) -> &str { |
138 | match self { |
139 | ArbitraryTuplType::BlackAndWhite => "BLACKANDWHITE" , |
140 | ArbitraryTuplType::BlackAndWhiteAlpha => "BLACKANDWHITE_ALPHA" , |
141 | ArbitraryTuplType::Grayscale => "GRAYSCALE" , |
142 | ArbitraryTuplType::GrayscaleAlpha => "GRAYSCALE_ALPHA" , |
143 | ArbitraryTuplType::RGB => "RGB" , |
144 | ArbitraryTuplType::RGBAlpha => "RGB_ALPHA" , |
145 | ArbitraryTuplType::Custom(custom: &String) => custom, |
146 | } |
147 | } |
148 | } |
149 | |
150 | impl PnmSubtype { |
151 | /// Get the two magic constant bytes corresponding to this format subtype. |
152 | pub fn magic_constant(self) -> &'static [u8; 2] { |
153 | match self { |
154 | PnmSubtype::Bitmap(SampleEncoding::Ascii) => b"P1" , |
155 | PnmSubtype::Graymap(SampleEncoding::Ascii) => b"P2" , |
156 | PnmSubtype::Pixmap(SampleEncoding::Ascii) => b"P3" , |
157 | PnmSubtype::Bitmap(SampleEncoding::Binary) => b"P4" , |
158 | PnmSubtype::Graymap(SampleEncoding::Binary) => b"P5" , |
159 | PnmSubtype::Pixmap(SampleEncoding::Binary) => b"P6" , |
160 | PnmSubtype::ArbitraryMap => b"P7" , |
161 | } |
162 | } |
163 | |
164 | /// Whether samples are stored as binary or as decimal ascii |
165 | pub fn sample_encoding(self) -> SampleEncoding { |
166 | match self { |
167 | PnmSubtype::ArbitraryMap => SampleEncoding::Binary, |
168 | PnmSubtype::Bitmap(enc: SampleEncoding) => enc, |
169 | PnmSubtype::Graymap(enc: SampleEncoding) => enc, |
170 | PnmSubtype::Pixmap(enc: SampleEncoding) => enc, |
171 | } |
172 | } |
173 | } |
174 | |
175 | impl PnmHeader { |
176 | /// Retrieve the format subtype from which the header was created. |
177 | pub fn subtype(&self) -> PnmSubtype { |
178 | match self.decoded { |
179 | HeaderRecord::Bitmap(BitmapHeader { encoding, .. }) => PnmSubtype::Bitmap(encoding), |
180 | HeaderRecord::Graymap(GraymapHeader { encoding, .. }) => PnmSubtype::Graymap(encoding), |
181 | HeaderRecord::Pixmap(PixmapHeader { encoding, .. }) => PnmSubtype::Pixmap(encoding), |
182 | HeaderRecord::Arbitrary(ArbitraryHeader { .. }) => PnmSubtype::ArbitraryMap, |
183 | } |
184 | } |
185 | |
186 | /// The width of the image this header is for. |
187 | pub fn width(&self) -> u32 { |
188 | match self.decoded { |
189 | HeaderRecord::Bitmap(BitmapHeader { width, .. }) => width, |
190 | HeaderRecord::Graymap(GraymapHeader { width, .. }) => width, |
191 | HeaderRecord::Pixmap(PixmapHeader { width, .. }) => width, |
192 | HeaderRecord::Arbitrary(ArbitraryHeader { width, .. }) => width, |
193 | } |
194 | } |
195 | |
196 | /// The height of the image this header is for. |
197 | pub fn height(&self) -> u32 { |
198 | match self.decoded { |
199 | HeaderRecord::Bitmap(BitmapHeader { height, .. }) => height, |
200 | HeaderRecord::Graymap(GraymapHeader { height, .. }) => height, |
201 | HeaderRecord::Pixmap(PixmapHeader { height, .. }) => height, |
202 | HeaderRecord::Arbitrary(ArbitraryHeader { height, .. }) => height, |
203 | } |
204 | } |
205 | |
206 | /// The biggest value a sample can have. In other words, the colour resolution. |
207 | pub fn maximal_sample(&self) -> u32 { |
208 | match self.decoded { |
209 | HeaderRecord::Bitmap(BitmapHeader { .. }) => 1, |
210 | HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, |
211 | HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval, |
212 | HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval, |
213 | } |
214 | } |
215 | |
216 | /// Retrieve the underlying bitmap header if any |
217 | pub fn as_bitmap(&self) -> Option<&BitmapHeader> { |
218 | match self.decoded { |
219 | HeaderRecord::Bitmap(ref bitmap) => Some(bitmap), |
220 | _ => None, |
221 | } |
222 | } |
223 | |
224 | /// Retrieve the underlying graymap header if any |
225 | pub fn as_graymap(&self) -> Option<&GraymapHeader> { |
226 | match self.decoded { |
227 | HeaderRecord::Graymap(ref graymap) => Some(graymap), |
228 | _ => None, |
229 | } |
230 | } |
231 | |
232 | /// Retrieve the underlying pixmap header if any |
233 | pub fn as_pixmap(&self) -> Option<&PixmapHeader> { |
234 | match self.decoded { |
235 | HeaderRecord::Pixmap(ref pixmap) => Some(pixmap), |
236 | _ => None, |
237 | } |
238 | } |
239 | |
240 | /// Retrieve the underlying arbitrary header if any |
241 | pub fn as_arbitrary(&self) -> Option<&ArbitraryHeader> { |
242 | match self.decoded { |
243 | HeaderRecord::Arbitrary(ref arbitrary) => Some(arbitrary), |
244 | _ => None, |
245 | } |
246 | } |
247 | |
248 | /// Write the header back into a binary stream |
249 | pub fn write(&self, writer: &mut dyn io::Write) -> io::Result<()> { |
250 | writer.write_all(self.subtype().magic_constant())?; |
251 | match *self { |
252 | PnmHeader { |
253 | encoded: Some(ref content), |
254 | .. |
255 | } => writer.write_all(content), |
256 | PnmHeader { |
257 | decoded: |
258 | HeaderRecord::Bitmap(BitmapHeader { |
259 | encoding: _encoding, |
260 | width, |
261 | height, |
262 | }), |
263 | .. |
264 | } => writeln!(writer, " \n{} {}" , width, height), |
265 | PnmHeader { |
266 | decoded: |
267 | HeaderRecord::Graymap(GraymapHeader { |
268 | encoding: _encoding, |
269 | width, |
270 | height, |
271 | maxwhite, |
272 | }), |
273 | .. |
274 | } => writeln!(writer, " \n{} {} {}" , width, height, maxwhite), |
275 | PnmHeader { |
276 | decoded: |
277 | HeaderRecord::Pixmap(PixmapHeader { |
278 | encoding: _encoding, |
279 | width, |
280 | height, |
281 | maxval, |
282 | }), |
283 | .. |
284 | } => writeln!(writer, " \n{} {} {}" , width, height, maxval), |
285 | PnmHeader { |
286 | decoded: |
287 | HeaderRecord::Arbitrary(ArbitraryHeader { |
288 | width, |
289 | height, |
290 | depth, |
291 | maxval, |
292 | ref tupltype, |
293 | }), |
294 | .. |
295 | } => { |
296 | struct TupltypeWriter<'a>(&'a Option<ArbitraryTuplType>); |
297 | impl<'a> fmt::Display for TupltypeWriter<'a> { |
298 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
299 | match self.0 { |
300 | Some(tt) => writeln!(f, "TUPLTYPE {}" , tt.name()), |
301 | None => Ok(()), |
302 | } |
303 | } |
304 | } |
305 | |
306 | writeln!( |
307 | writer, |
308 | " \nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL {}\n{}ENDHDR" , |
309 | width, |
310 | height, |
311 | depth, |
312 | maxval, |
313 | TupltypeWriter(tupltype) |
314 | ) |
315 | } |
316 | } |
317 | } |
318 | } |
319 | |
320 | impl From<BitmapHeader> for PnmHeader { |
321 | fn from(header: BitmapHeader) -> Self { |
322 | PnmHeader { |
323 | decoded: HeaderRecord::Bitmap(header), |
324 | encoded: None, |
325 | } |
326 | } |
327 | } |
328 | |
329 | impl From<GraymapHeader> for PnmHeader { |
330 | fn from(header: GraymapHeader) -> Self { |
331 | PnmHeader { |
332 | decoded: HeaderRecord::Graymap(header), |
333 | encoded: None, |
334 | } |
335 | } |
336 | } |
337 | |
338 | impl From<PixmapHeader> for PnmHeader { |
339 | fn from(header: PixmapHeader) -> Self { |
340 | PnmHeader { |
341 | decoded: HeaderRecord::Pixmap(header), |
342 | encoded: None, |
343 | } |
344 | } |
345 | } |
346 | |
347 | impl From<ArbitraryHeader> for PnmHeader { |
348 | fn from(header: ArbitraryHeader) -> Self { |
349 | PnmHeader { |
350 | decoded: HeaderRecord::Arbitrary(header), |
351 | encoded: None, |
352 | } |
353 | } |
354 | } |
355 | |