| 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 | 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: &String) => custom, |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | impl 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 | |
| 177 | impl 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 | |
| 330 | impl From<BitmapHeader> for PnmHeader { |
| 331 | fn from(header: BitmapHeader) -> Self { |
| 332 | PnmHeader { |
| 333 | decoded: HeaderRecord::Bitmap(header), |
| 334 | encoded: None, |
| 335 | } |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | impl From<GraymapHeader> for PnmHeader { |
| 340 | fn from(header: GraymapHeader) -> Self { |
| 341 | PnmHeader { |
| 342 | decoded: HeaderRecord::Graymap(header), |
| 343 | encoded: None, |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | impl From<PixmapHeader> for PnmHeader { |
| 349 | fn from(header: PixmapHeader) -> Self { |
| 350 | PnmHeader { |
| 351 | decoded: HeaderRecord::Pixmap(header), |
| 352 | encoded: None, |
| 353 | } |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | impl From<ArbitraryHeader> for PnmHeader { |
| 358 | fn from(header: ArbitraryHeader) -> Self { |
| 359 | PnmHeader { |
| 360 | decoded: HeaderRecord::Arbitrary(header), |
| 361 | encoded: None, |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | |