image/codecs/pnm/
header.rs

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) => 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}