1use std::{fmt, io};
2
3/// The kind of encoding used to store sample values
4#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5pub 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)]
15pub 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).
35pub struct PnmHeader {
36 pub(crate) decoded: HeaderRecord,
37 pub(crate) encoded: Option<Vec<u8>>,
38}
39
40pub(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)]
49pub 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)]
62pub 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)]
78pub 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)]
94pub 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)]
113pub 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
136impl 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
150impl PnmSubtype {
151 /// Get the two magic constant bytes corresponding to this format subtype.
152 #[must_use]
153 pub fn magic_constant(self) -> &'static [u8; 2] {
154 match self {
155 PnmSubtype::Bitmap(SampleEncoding::Ascii) => b"P1",
156 PnmSubtype::Graymap(SampleEncoding::Ascii) => b"P2",
157 PnmSubtype::Pixmap(SampleEncoding::Ascii) => b"P3",
158 PnmSubtype::Bitmap(SampleEncoding::Binary) => b"P4",
159 PnmSubtype::Graymap(SampleEncoding::Binary) => b"P5",
160 PnmSubtype::Pixmap(SampleEncoding::Binary) => b"P6",
161 PnmSubtype::ArbitraryMap => b"P7",
162 }
163 }
164
165 /// Whether samples are stored as binary or as decimal ascii
166 #[must_use]
167 pub fn sample_encoding(self) -> SampleEncoding {
168 match self {
169 PnmSubtype::ArbitraryMap => SampleEncoding::Binary,
170 PnmSubtype::Bitmap(enc) => enc,
171 PnmSubtype::Graymap(enc) => enc,
172 PnmSubtype::Pixmap(enc) => enc,
173 }
174 }
175}
176
177impl PnmHeader {
178 /// Retrieve the format subtype from which the header was created.
179 #[must_use]
180 pub fn subtype(&self) -> PnmSubtype {
181 match self.decoded {
182 HeaderRecord::Bitmap(BitmapHeader { encoding, .. }) => PnmSubtype::Bitmap(encoding),
183 HeaderRecord::Graymap(GraymapHeader { encoding, .. }) => PnmSubtype::Graymap(encoding),
184 HeaderRecord::Pixmap(PixmapHeader { encoding, .. }) => PnmSubtype::Pixmap(encoding),
185 HeaderRecord::Arbitrary(ArbitraryHeader { .. }) => PnmSubtype::ArbitraryMap,
186 }
187 }
188
189 /// The width of the image this header is for.
190 #[must_use]
191 pub fn width(&self) -> u32 {
192 match self.decoded {
193 HeaderRecord::Bitmap(BitmapHeader { width, .. }) => width,
194 HeaderRecord::Graymap(GraymapHeader { width, .. }) => width,
195 HeaderRecord::Pixmap(PixmapHeader { width, .. }) => width,
196 HeaderRecord::Arbitrary(ArbitraryHeader { width, .. }) => width,
197 }
198 }
199
200 /// The height of the image this header is for.
201 #[must_use]
202 pub fn height(&self) -> u32 {
203 match self.decoded {
204 HeaderRecord::Bitmap(BitmapHeader { height, .. }) => height,
205 HeaderRecord::Graymap(GraymapHeader { height, .. }) => height,
206 HeaderRecord::Pixmap(PixmapHeader { height, .. }) => height,
207 HeaderRecord::Arbitrary(ArbitraryHeader { height, .. }) => height,
208 }
209 }
210
211 /// The biggest value a sample can have. In other words, the colour resolution.
212 #[must_use]
213 pub fn maximal_sample(&self) -> u32 {
214 match self.decoded {
215 HeaderRecord::Bitmap(BitmapHeader { .. }) => 1,
216 HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite,
217 HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval,
218 HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval,
219 }
220 }
221
222 /// Retrieve the underlying bitmap header if any
223 #[must_use]
224 pub fn as_bitmap(&self) -> Option<&BitmapHeader> {
225 match self.decoded {
226 HeaderRecord::Bitmap(ref bitmap) => Some(bitmap),
227 _ => None,
228 }
229 }
230
231 /// Retrieve the underlying graymap header if any
232 #[must_use]
233 pub fn as_graymap(&self) -> Option<&GraymapHeader> {
234 match self.decoded {
235 HeaderRecord::Graymap(ref graymap) => Some(graymap),
236 _ => None,
237 }
238 }
239
240 /// Retrieve the underlying pixmap header if any
241 #[must_use]
242 pub fn as_pixmap(&self) -> Option<&PixmapHeader> {
243 match self.decoded {
244 HeaderRecord::Pixmap(ref pixmap) => Some(pixmap),
245 _ => None,
246 }
247 }
248
249 /// Retrieve the underlying arbitrary header if any
250 #[must_use]
251 pub fn as_arbitrary(&self) -> Option<&ArbitraryHeader> {
252 match self.decoded {
253 HeaderRecord::Arbitrary(ref arbitrary) => Some(arbitrary),
254 _ => None,
255 }
256 }
257
258 /// Write the header back into a binary stream
259 pub fn write(&self, writer: &mut dyn io::Write) -> io::Result<()> {
260 writer.write_all(self.subtype().magic_constant())?;
261 match *self {
262 PnmHeader {
263 encoded: Some(ref content),
264 ..
265 } => writer.write_all(content),
266 PnmHeader {
267 decoded:
268 HeaderRecord::Bitmap(BitmapHeader {
269 encoding: _encoding,
270 width,
271 height,
272 }),
273 ..
274 } => writeln!(writer, "\n{width} {height}"),
275 PnmHeader {
276 decoded:
277 HeaderRecord::Graymap(GraymapHeader {
278 encoding: _encoding,
279 width,
280 height,
281 maxwhite,
282 }),
283 ..
284 } => writeln!(writer, "\n{width} {height} {maxwhite}"),
285 PnmHeader {
286 decoded:
287 HeaderRecord::Pixmap(PixmapHeader {
288 encoding: _encoding,
289 width,
290 height,
291 maxval,
292 }),
293 ..
294 } => writeln!(writer, "\n{width} {height} {maxval}"),
295 PnmHeader {
296 decoded:
297 HeaderRecord::Arbitrary(ArbitraryHeader {
298 width,
299 height,
300 depth,
301 maxval,
302 ref tupltype,
303 }),
304 ..
305 } => {
306 struct TupltypeWriter<'a>(&'a Option<ArbitraryTuplType>);
307 impl fmt::Display for TupltypeWriter<'_> {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self.0 {
310 Some(tt) => writeln!(f, "TUPLTYPE {}", tt.name()),
311 None => Ok(()),
312 }
313 }
314 }
315
316 writeln!(
317 writer,
318 "\nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL {}\n{}ENDHDR",
319 width,
320 height,
321 depth,
322 maxval,
323 TupltypeWriter(tupltype)
324 )
325 }
326 }
327 }
328}
329
330impl From<BitmapHeader> for PnmHeader {
331 fn from(header: BitmapHeader) -> Self {
332 PnmHeader {
333 decoded: HeaderRecord::Bitmap(header),
334 encoded: None,
335 }
336 }
337}
338
339impl From<GraymapHeader> for PnmHeader {
340 fn from(header: GraymapHeader) -> Self {
341 PnmHeader {
342 decoded: HeaderRecord::Graymap(header),
343 encoded: None,
344 }
345 }
346}
347
348impl From<PixmapHeader> for PnmHeader {
349 fn from(header: PixmapHeader) -> Self {
350 PnmHeader {
351 decoded: HeaderRecord::Pixmap(header),
352 encoded: None,
353 }
354 }
355}
356
357impl From<ArbitraryHeader> for PnmHeader {
358 fn from(header: ArbitraryHeader) -> Self {
359 PnmHeader {
360 decoded: HeaderRecord::Arbitrary(header),
361 encoded: None,
362 }
363 }
364}
365