Coverage Report

Created: 2025-10-14 06:57

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