Coverage Report

Created: 2025-07-18 06:49

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