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