/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 | | } |