Coverage Report

Created: 2025-07-12 07:18

/src/image/src/codecs/pnm/encoder.rs
Line
Count
Source (jump to first uncovered line)
1
//! Encoding of PNM Images
2
use crate::utils::vec_try_with_capacity;
3
use std::fmt;
4
use std::io;
5
use std::io::Write;
6
7
use super::AutoBreak;
8
use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader};
9
use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding};
10
11
use crate::color::ExtendedColorType;
12
use crate::error::{
13
    ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError,
14
    UnsupportedErrorKind,
15
};
16
use crate::{ImageEncoder, ImageFormat};
17
18
use byteorder_lite::{BigEndian, WriteBytesExt};
19
20
enum HeaderStrategy {
21
    Dynamic,
22
    Subtype(PnmSubtype),
23
    Chosen(PnmHeader),
24
}
25
26
#[derive(Clone, Copy)]
27
pub enum FlatSamples<'a> {
28
    U8(&'a [u8]),
29
    U16(&'a [u16]),
30
}
31
32
/// Encodes images to any of the `pnm` image formats.
33
pub struct PnmEncoder<W: Write> {
34
    writer: W,
35
    header: HeaderStrategy,
36
}
37
38
/// Encapsulate the checking system in the type system. Non of the fields are actually accessed
39
/// but requiring them forces us to validly construct the struct anyways.
40
struct CheckedImageBuffer<'a> {
41
    _image: FlatSamples<'a>,
42
    _width: u32,
43
    _height: u32,
44
    _color: ExtendedColorType,
45
}
46
47
// Check the header against the buffer. Each struct produces the next after a check.
48
struct UncheckedHeader<'a> {
49
    header: &'a PnmHeader,
50
}
51
52
struct CheckedDimensions<'a> {
53
    unchecked: UncheckedHeader<'a>,
54
    width: u32,
55
    height: u32,
56
}
57
58
struct CheckedHeaderColor<'a> {
59
    dimensions: CheckedDimensions<'a>,
60
    color: ExtendedColorType,
61
}
62
63
struct CheckedHeader<'a> {
64
    color: CheckedHeaderColor<'a>,
65
    encoding: TupleEncoding<'a>,
66
    _image: CheckedImageBuffer<'a>,
67
}
68
69
enum TupleEncoding<'a> {
70
    PbmBits {
71
        samples: FlatSamples<'a>,
72
        width: u32,
73
    },
74
    Ascii {
75
        samples: FlatSamples<'a>,
76
    },
77
    Bytes {
78
        samples: FlatSamples<'a>,
79
    },
80
}
81
82
impl<W: Write> PnmEncoder<W> {
83
    /// Create new `PnmEncoder` from the `writer`.
84
    ///
85
    /// The encoded images will have some `pnm` format. If more control over the image type is
86
    /// required, use either one of `with_subtype` or `with_header`. For more information on the
87
    /// behaviour, see `with_dynamic_header`.
88
0
    pub fn new(writer: W) -> Self {
89
0
        PnmEncoder {
90
0
            writer,
91
0
            header: HeaderStrategy::Dynamic,
92
0
        }
93
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::new
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
94
95
    /// Encode a specific pnm subtype image.
96
    ///
97
    /// The magic number and encoding type will be chosen as provided while the rest of the header
98
    /// data will be generated dynamically. Trying to encode incompatible images (e.g. encoding an
99
    /// RGB image as Graymap) will result in an error.
100
    ///
101
    /// This will overwrite the effect of earlier calls to `with_header` and `with_dynamic_header`.
102
0
    pub fn with_subtype(self, subtype: PnmSubtype) -> Self {
103
0
        PnmEncoder {
104
0
            writer: self.writer,
105
0
            header: HeaderStrategy::Subtype(subtype),
106
0
        }
107
0
    }
108
109
    /// Enforce the use of a chosen header.
110
    ///
111
    /// While this option gives the most control over the actual written data, the encoding process
112
    /// will error in case the header data and image parameters do not agree. It is the users
113
    /// obligation to ensure that the width and height are set accordingly, for example.
114
    ///
115
    /// Choose this option if you want a lossless decoding/encoding round trip.
116
    ///
117
    /// This will overwrite the effect of earlier calls to `with_subtype` and `with_dynamic_header`.
118
0
    pub fn with_header(self, header: PnmHeader) -> Self {
119
0
        PnmEncoder {
120
0
            writer: self.writer,
121
0
            header: HeaderStrategy::Chosen(header),
122
0
        }
123
0
    }
124
125
    /// Create the header dynamically for each image.
126
    ///
127
    /// This is the default option upon creation of the encoder. With this, most images should be
128
    /// encodable but the specific format chosen is out of the users control. The pnm subtype is
129
    /// chosen arbitrarily by the library.
130
    ///
131
    /// This will overwrite the effect of earlier calls to `with_subtype` and `with_header`.
132
0
    pub fn with_dynamic_header(self) -> Self {
133
0
        PnmEncoder {
134
0
            writer: self.writer,
135
0
            header: HeaderStrategy::Dynamic,
136
0
        }
137
0
    }
138
139
    /// Encode an image whose samples are represented as a sequence of `u8` or `u16` data.
140
    ///
141
    /// If `image` is a slice of `u8`, the samples will be interpreted based on the chosen `color` option.
142
    /// Color types of 16-bit precision means that the bytes are reinterpreted as 16-bit samples,
143
    /// otherwise they are treated as 8-bit samples.
144
    /// If `image` is a slice of `u16`, the samples will be interpreted as 16-bit samples directly.
145
    ///
146
    /// Some `pnm` subtypes are incompatible with some color options, a chosen header most
147
    /// certainly with any deviation from the original decoded image.
148
0
    pub fn encode<'s, S>(
149
0
        &mut self,
150
0
        image: S,
151
0
        width: u32,
152
0
        height: u32,
153
0
        color: ExtendedColorType,
154
0
    ) -> ImageResult<()>
155
0
    where
156
0
        S: Into<FlatSamples<'s>>,
157
0
    {
158
0
        let image = image.into();
159
160
        // adapt samples so that they are aligned even in 16-bit samples,
161
        // required due to the narrowing of the image buffer to &[u8]
162
        // on dynamic image writing
163
0
        let image = match (image, color) {
164
            (
165
0
                FlatSamples::U8(samples),
166
0
                ExtendedColorType::L16
167
0
                | ExtendedColorType::La16
168
0
                | ExtendedColorType::Rgb16
169
0
                | ExtendedColorType::Rgba16,
170
0
            ) => {
171
0
                match bytemuck::try_cast_slice(samples) {
172
                    // proceed with aligned 16-bit samples
173
0
                    Ok(samples) => FlatSamples::U16(samples),
174
0
                    Err(_e) => {
175
0
                        // reallocation is required
176
0
                        let new_samples: Vec<u16> = samples
177
0
                            .chunks(2)
178
0
                            .map(|chunk| u16::from_ne_bytes([chunk[0], chunk[1]]))
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::encode::<_>::{closure#0}
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode::<&[u8]>::{closure#0}
179
0
                            .collect();
180
0
181
0
                        let image = FlatSamples::U16(&new_samples);
182
0
183
0
                        // make a separate encoding path,
184
0
                        // because the image buffer lifetime has changed
185
0
                        return self.encode_impl(image, width, height, color);
186
                    }
187
                }
188
            }
189
            // should not be necessary for any other case
190
0
            _ => image,
191
        };
192
193
0
        self.encode_impl(image, width, height, color)
194
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::encode::<_>
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode::<&[u8]>
195
196
    /// Encode an image whose samples are already interpreted correctly.
197
0
    fn encode_impl(
198
0
        &mut self,
199
0
        samples: FlatSamples<'_>,
200
0
        width: u32,
201
0
        height: u32,
202
0
        color: ExtendedColorType,
203
0
    ) -> ImageResult<()> {
204
0
        match self.header {
205
0
            HeaderStrategy::Dynamic => self.write_dynamic_header(samples, width, height, color),
206
0
            HeaderStrategy::Subtype(subtype) => {
207
0
                self.write_subtyped_header(subtype, samples, width, height, color)
208
            }
209
0
            HeaderStrategy::Chosen(ref header) => {
210
0
                Self::write_with_header(&mut self.writer, header, samples, width, height, color)
211
            }
212
        }
213
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::encode_impl
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_impl
214
215
    /// Choose any valid pnm format that the image can be expressed in and write its header.
216
    ///
217
    /// Returns how the body should be written if successful.
218
0
    fn write_dynamic_header(
219
0
        &mut self,
220
0
        image: FlatSamples,
221
0
        width: u32,
222
0
        height: u32,
223
0
        color: ExtendedColorType,
224
0
    ) -> ImageResult<()> {
225
0
        let depth = u32::from(color.channel_count());
226
0
        let (maxval, tupltype) = match color {
227
0
            ExtendedColorType::L1 => (1, ArbitraryTuplType::BlackAndWhite),
228
0
            ExtendedColorType::L8 => (0xff, ArbitraryTuplType::Grayscale),
229
0
            ExtendedColorType::L16 => (0xffff, ArbitraryTuplType::Grayscale),
230
0
            ExtendedColorType::La1 => (1, ArbitraryTuplType::BlackAndWhiteAlpha),
231
0
            ExtendedColorType::La8 => (0xff, ArbitraryTuplType::GrayscaleAlpha),
232
0
            ExtendedColorType::La16 => (0xffff, ArbitraryTuplType::GrayscaleAlpha),
233
0
            ExtendedColorType::Rgb8 => (0xff, ArbitraryTuplType::RGB),
234
0
            ExtendedColorType::Rgb16 => (0xffff, ArbitraryTuplType::RGB),
235
0
            ExtendedColorType::Rgba8 => (0xff, ArbitraryTuplType::RGBAlpha),
236
0
            ExtendedColorType::Rgba16 => (0xffff, ArbitraryTuplType::RGBAlpha),
237
            _ => {
238
0
                return Err(ImageError::Unsupported(
239
0
                    UnsupportedError::from_format_and_kind(
240
0
                        ImageFormat::Pnm.into(),
241
0
                        UnsupportedErrorKind::Color(color),
242
0
                    ),
243
0
                ))
244
            }
245
        };
246
247
0
        let header = PnmHeader {
248
0
            decoded: HeaderRecord::Arbitrary(ArbitraryHeader {
249
0
                width,
250
0
                height,
251
0
                depth,
252
0
                maxval,
253
0
                tupltype: Some(tupltype),
254
0
            }),
255
0
            encoded: None,
256
0
        };
257
0
258
0
        Self::write_with_header(&mut self.writer, &header, image, width, height, color)
259
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::write_dynamic_header
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_dynamic_header
260
261
    /// Try to encode the image with the chosen format, give its corresponding pixel encoding type.
262
0
    fn write_subtyped_header(
263
0
        &mut self,
264
0
        subtype: PnmSubtype,
265
0
        image: FlatSamples,
266
0
        width: u32,
267
0
        height: u32,
268
0
        color: ExtendedColorType,
269
0
    ) -> ImageResult<()> {
270
0
        let header = match (subtype, color) {
271
0
            (PnmSubtype::ArbitraryMap, color) => {
272
0
                return self.write_dynamic_header(image, width, height, color)
273
            }
274
0
            (PnmSubtype::Pixmap(encoding), ExtendedColorType::Rgb8) => PnmHeader {
275
0
                decoded: HeaderRecord::Pixmap(PixmapHeader {
276
0
                    encoding,
277
0
                    width,
278
0
                    height,
279
0
                    maxval: 255,
280
0
                }),
281
0
                encoded: None,
282
0
            },
283
0
            (PnmSubtype::Graymap(encoding), ExtendedColorType::L8) => PnmHeader {
284
0
                decoded: HeaderRecord::Graymap(GraymapHeader {
285
0
                    encoding,
286
0
                    width,
287
0
                    height,
288
0
                    maxwhite: 255,
289
0
                }),
290
0
                encoded: None,
291
0
            },
292
0
            (PnmSubtype::Bitmap(encoding), ExtendedColorType::L8 | ExtendedColorType::L1) => {
293
0
                PnmHeader {
294
0
                    decoded: HeaderRecord::Bitmap(BitmapHeader {
295
0
                        encoding,
296
0
                        height,
297
0
                        width,
298
0
                    }),
299
0
                    encoded: None,
300
0
                }
301
            }
302
            (_, _) => {
303
0
                return Err(ImageError::Parameter(ParameterError::from_kind(
304
0
                    ParameterErrorKind::Generic(
305
0
                        "Color type can not be represented in the chosen format".to_owned(),
306
0
                    ),
307
0
                )));
308
            }
309
        };
310
311
0
        Self::write_with_header(&mut self.writer, &header, image, width, height, color)
312
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::write_subtyped_header
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_subtyped_header
313
314
    /// Try to encode the image with the chosen header, checking if values are correct.
315
    ///
316
    /// Returns how the body should be written if successful.
317
0
    fn write_with_header(
318
0
        writer: &mut dyn Write,
319
0
        header: &PnmHeader,
320
0
        image: FlatSamples,
321
0
        width: u32,
322
0
        height: u32,
323
0
        color: ExtendedColorType,
324
0
    ) -> ImageResult<()> {
325
0
        let unchecked = UncheckedHeader { header };
326
0
327
0
        unchecked
328
0
            .check_header_dimensions(width, height)?
329
0
            .check_header_color(color)?
330
0
            .check_sample_values(image)?
331
0
            .write_header(writer)?
332
0
            .write_image(writer)
333
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_>>::write_with_header
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_with_header
334
}
335
336
impl<W: Write> ImageEncoder for PnmEncoder<W> {
337
    #[track_caller]
338
0
    fn write_image(
339
0
        mut self,
340
0
        buf: &[u8],
341
0
        width: u32,
342
0
        height: u32,
343
0
        color_type: ExtendedColorType,
344
0
    ) -> ImageResult<()> {
345
0
        let expected_buffer_len = color_type.buffer_size(width, height);
346
0
        assert_eq!(
347
0
            expected_buffer_len,
348
0
            buf.len() as u64,
349
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
350
0
            buf.len(),
351
        );
352
353
0
        self.encode(buf, width, height, color_type)
354
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<_> as image::io::encoder::ImageEncoder>::write_image
Unexecuted instantiation: <image::codecs::pnm::encoder::PnmEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image
355
}
356
357
impl<'a> CheckedImageBuffer<'a> {
358
0
    fn check(
359
0
        image: FlatSamples<'a>,
360
0
        width: u32,
361
0
        height: u32,
362
0
        color: ExtendedColorType,
363
0
    ) -> ImageResult<CheckedImageBuffer<'a>> {
364
0
        let components = color.channel_count() as usize;
365
0
        let uwidth = width as usize;
366
0
        let uheight = height as usize;
367
0
        let expected_len = components
368
0
            .checked_mul(uwidth)
369
0
            .and_then(|v| v.checked_mul(uheight));
370
0
        if Some(image.len()) != expected_len {
371
            // Image buffer does not correspond to size and colour.
372
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
373
0
                ParameterErrorKind::DimensionMismatch,
374
0
            )));
375
0
        }
376
0
        Ok(CheckedImageBuffer {
377
0
            _image: image,
378
0
            _width: width,
379
0
            _height: height,
380
0
            _color: color,
381
0
        })
382
0
    }
383
}
384
385
impl<'a> UncheckedHeader<'a> {
386
0
    fn check_header_dimensions(
387
0
        self,
388
0
        width: u32,
389
0
        height: u32,
390
0
    ) -> ImageResult<CheckedDimensions<'a>> {
391
0
        if self.header.width() != width || self.header.height() != height {
392
            // Chosen header does not match Image dimensions.
393
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
394
0
                ParameterErrorKind::DimensionMismatch,
395
0
            )));
396
0
        }
397
0
398
0
        Ok(CheckedDimensions {
399
0
            unchecked: self,
400
0
            width,
401
0
            height,
402
0
        })
403
0
    }
404
}
405
406
impl<'a> CheckedDimensions<'a> {
407
    // Check color compatibility with the header. This will only error when we are certain that
408
    // the combination is bogus (e.g. combining Pixmap and Palette) but allows uncertain
409
    // combinations (basically a ArbitraryTuplType::Custom with any color of fitting depth).
410
0
    fn check_header_color(self, color: ExtendedColorType) -> ImageResult<CheckedHeaderColor<'a>> {
411
0
        let components = u32::from(color.channel_count());
412
0
413
0
        match *self.unchecked.header {
414
            PnmHeader {
415
                decoded: HeaderRecord::Bitmap(_),
416
                ..
417
0
            } => match color {
418
0
                ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (),
419
                _ => {
420
0
                    return Err(ImageError::Parameter(ParameterError::from_kind(
421
0
                        ParameterErrorKind::Generic(
422
0
                            "PBM format only support luma color types".to_owned(),
423
0
                        ),
424
0
                    )))
425
                }
426
            },
427
            PnmHeader {
428
                decoded: HeaderRecord::Graymap(_),
429
                ..
430
0
            } => match color {
431
0
                ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (),
432
                _ => {
433
0
                    return Err(ImageError::Parameter(ParameterError::from_kind(
434
0
                        ParameterErrorKind::Generic(
435
0
                            "PGM format only support luma color types".to_owned(),
436
0
                        ),
437
0
                    )))
438
                }
439
            },
440
            PnmHeader {
441
                decoded: HeaderRecord::Pixmap(_),
442
                ..
443
0
            } => match color {
444
0
                ExtendedColorType::Rgb8 => (),
445
                _ => {
446
0
                    return Err(ImageError::Parameter(ParameterError::from_kind(
447
0
                        ParameterErrorKind::Generic(
448
0
                            "PPM format only support ExtendedColorType::Rgb8".to_owned(),
449
0
                        ),
450
0
                    )))
451
                }
452
            },
453
            PnmHeader {
454
                decoded:
455
                    HeaderRecord::Arbitrary(ArbitraryHeader {
456
0
                        depth,
457
0
                        ref tupltype,
458
0
                        ..
459
0
                    }),
460
0
                ..
461
0
            } => match (tupltype, color) {
462
0
                (&Some(ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L1) => (),
463
0
                (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8) => (),
464
465
0
                (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L1) => (),
466
0
                (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L8) => (),
467
0
                (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L16) => (),
468
0
                (&Some(ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La8) => (),
469
470
0
                (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb8) => (),
471
0
                (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb16) => (),
472
0
                (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba8) => (),
473
0
                (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba16) => (),
474
475
0
                (&None, _) if depth == components => (),
476
0
                (&Some(ArbitraryTuplType::Custom(_)), _) if depth == components => (),
477
0
                _ if depth != components => {
478
0
                    return Err(ImageError::Parameter(ParameterError::from_kind(
479
0
                        ParameterErrorKind::Generic(format!(
480
0
                            "Depth mismatch: header {depth} vs. color {components}"
481
0
                        )),
482
0
                    )))
483
                }
484
                _ => {
485
0
                    return Err(ImageError::Parameter(ParameterError::from_kind(
486
0
                        ParameterErrorKind::Generic(
487
0
                            "Invalid color type for selected PAM color type".to_owned(),
488
0
                        ),
489
0
                    )))
490
                }
491
            },
492
        }
493
494
0
        Ok(CheckedHeaderColor {
495
0
            dimensions: self,
496
0
            color,
497
0
        })
498
0
    }
499
}
500
501
impl<'a> CheckedHeaderColor<'a> {
502
0
    fn check_sample_values(self, image: FlatSamples<'a>) -> ImageResult<CheckedHeader<'a>> {
503
0
        let header_maxval = match self.dimensions.unchecked.header.decoded {
504
0
            HeaderRecord::Bitmap(_) => 1,
505
0
            HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite,
506
0
            HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval,
507
0
            HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval,
508
        };
509
510
        // We trust the image color bit count to be correct at least.
511
0
        let max_sample = match self.color {
512
0
            ExtendedColorType::Unknown(n) if n <= 16 => (1 << n) - 1,
513
0
            ExtendedColorType::L1 => 1,
514
            ExtendedColorType::L8
515
            | ExtendedColorType::La8
516
            | ExtendedColorType::Rgb8
517
            | ExtendedColorType::Rgba8
518
            | ExtendedColorType::Bgr8
519
0
            | ExtendedColorType::Bgra8 => 0xff,
520
            ExtendedColorType::L16
521
            | ExtendedColorType::La16
522
            | ExtendedColorType::Rgb16
523
0
            | ExtendedColorType::Rgba16 => 0xffff,
524
            _ => {
525
                // Unsupported target color type.
526
0
                return Err(ImageError::Unsupported(
527
0
                    UnsupportedError::from_format_and_kind(
528
0
                        ImageFormat::Pnm.into(),
529
0
                        UnsupportedErrorKind::Color(self.color),
530
0
                    ),
531
0
                ));
532
            }
533
        };
534
535
        // Avoid the performance heavy check if possible, e.g. if the header has been chosen by us.
536
0
        if header_maxval < max_sample && !image.all_smaller(header_maxval) {
537
            // Sample value greater than allowed for chosen header.
538
0
            return Err(ImageError::Unsupported(
539
0
                UnsupportedError::from_format_and_kind(
540
0
                    ImageFormat::Pnm.into(),
541
0
                    UnsupportedErrorKind::GenericFeature(
542
0
                        "Sample value greater than allowed for chosen header".to_owned(),
543
0
                    ),
544
0
                ),
545
0
            ));
546
0
        }
547
0
548
0
        let encoding = image.encoding_for(&self.dimensions.unchecked.header.decoded);
549
550
0
        let image = CheckedImageBuffer::check(
551
0
            image,
552
0
            self.dimensions.width,
553
0
            self.dimensions.height,
554
0
            self.color,
555
0
        )?;
556
557
0
        Ok(CheckedHeader {
558
0
            color: self,
559
0
            encoding,
560
0
            _image: image,
561
0
        })
562
0
    }
563
}
564
565
impl<'a> CheckedHeader<'a> {
566
0
    fn write_header(self, writer: &mut dyn Write) -> ImageResult<TupleEncoding<'a>> {
567
0
        self.header().write(writer)?;
568
0
        Ok(self.encoding)
569
0
    }
570
571
0
    fn header(&self) -> &PnmHeader {
572
0
        self.color.dimensions.unchecked.header
573
0
    }
574
}
575
576
struct SampleWriter<'a>(&'a mut dyn Write);
577
578
impl SampleWriter<'_> {
579
0
    fn write_samples_ascii<V>(self, samples: V) -> io::Result<()>
580
0
    where
581
0
        V: Iterator,
582
0
        V::Item: fmt::Display,
583
0
    {
584
0
        let mut auto_break_writer = AutoBreak::new(self.0, 70)?;
585
0
        for value in samples {
586
0
            write!(auto_break_writer, "{value} ")?;
587
        }
588
0
        auto_break_writer.flush()
589
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::SampleWriter>::write_samples_ascii::<core::slice::iter::Iter<u8>>
Unexecuted instantiation: <image::codecs::pnm::encoder::SampleWriter>::write_samples_ascii::<core::slice::iter::Iter<u16>>
590
591
0
    fn write_pbm_bits<V>(self, samples: &[V], width: u32) -> io::Result<()>
592
0
    /* Default gives 0 for all primitives. TODO: replace this with `Zeroable` once it hits stable */
593
0
    where
594
0
        V: Default + Eq + Copy,
595
0
    {
596
0
        // The length of an encoded scanline
597
0
        let line_width = (width - 1) / 8 + 1;
598
599
        // We'll be writing single bytes, so buffer
600
0
        let mut line_buffer = vec_try_with_capacity(line_width as usize)?;
601
602
0
        for line in samples.chunks(width as usize) {
603
0
            for byte_bits in line.chunks(8) {
604
0
                let mut byte = 0u8;
605
0
                for i in 0..8 {
606
                    // Black pixels are encoded as 1s
607
0
                    if let Some(&v) = byte_bits.get(i) {
608
0
                        if v == V::default() {
609
0
                            byte |= 1u8 << (7 - i);
610
0
                        }
611
0
                    }
612
                }
613
0
                line_buffer.push(byte);
614
            }
615
0
            self.0.write_all(line_buffer.as_slice())?;
616
0
            line_buffer.clear();
617
        }
618
619
0
        self.0.flush()
620
0
    }
Unexecuted instantiation: <image::codecs::pnm::encoder::SampleWriter>::write_pbm_bits::<u8>
Unexecuted instantiation: <image::codecs::pnm::encoder::SampleWriter>::write_pbm_bits::<u16>
621
}
622
623
impl<'a> FlatSamples<'a> {
624
0
    fn len(&self) -> usize {
625
0
        match *self {
626
0
            FlatSamples::U8(arr) => arr.len(),
627
0
            FlatSamples::U16(arr) => arr.len(),
628
        }
629
0
    }
630
631
0
    fn all_smaller(&self, max_val: u32) -> bool {
632
0
        match *self {
633
0
            FlatSamples::U8(arr) => arr.iter().any(|&val| u32::from(val) > max_val),
634
0
            FlatSamples::U16(arr) => arr.iter().any(|&val| u32::from(val) > max_val),
635
        }
636
0
    }
637
638
0
    fn encoding_for(&self, header: &HeaderRecord) -> TupleEncoding<'a> {
639
0
        match *header {
640
            HeaderRecord::Bitmap(BitmapHeader {
641
                encoding: SampleEncoding::Binary,
642
0
                width,
643
0
                ..
644
0
            }) => TupleEncoding::PbmBits {
645
0
                samples: *self,
646
0
                width,
647
0
            },
648
649
            HeaderRecord::Bitmap(BitmapHeader {
650
                encoding: SampleEncoding::Ascii,
651
                ..
652
0
            }) => TupleEncoding::Ascii { samples: *self },
653
654
0
            HeaderRecord::Arbitrary(_) => TupleEncoding::Bytes { samples: *self },
655
656
            HeaderRecord::Graymap(GraymapHeader {
657
                encoding: SampleEncoding::Ascii,
658
                ..
659
            })
660
            | HeaderRecord::Pixmap(PixmapHeader {
661
                encoding: SampleEncoding::Ascii,
662
                ..
663
0
            }) => TupleEncoding::Ascii { samples: *self },
664
665
            HeaderRecord::Graymap(GraymapHeader {
666
                encoding: SampleEncoding::Binary,
667
                ..
668
            })
669
            | HeaderRecord::Pixmap(PixmapHeader {
670
                encoding: SampleEncoding::Binary,
671
                ..
672
0
            }) => TupleEncoding::Bytes { samples: *self },
673
        }
674
0
    }
675
}
676
677
impl<'a> From<&'a [u8]> for FlatSamples<'a> {
678
0
    fn from(samples: &'a [u8]) -> Self {
679
0
        FlatSamples::U8(samples)
680
0
    }
681
}
682
683
impl<'a> From<&'a [u16]> for FlatSamples<'a> {
684
0
    fn from(samples: &'a [u16]) -> Self {
685
0
        FlatSamples::U16(samples)
686
0
    }
687
}
688
689
impl TupleEncoding<'_> {
690
0
    fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> {
691
0
        match *self {
692
            TupleEncoding::PbmBits {
693
0
                samples: FlatSamples::U8(samples),
694
0
                width,
695
0
            } => SampleWriter(writer)
696
0
                .write_pbm_bits(samples, width)
697
0
                .map_err(ImageError::IoError),
698
            TupleEncoding::PbmBits {
699
0
                samples: FlatSamples::U16(samples),
700
0
                width,
701
0
            } => SampleWriter(writer)
702
0
                .write_pbm_bits(samples, width)
703
0
                .map_err(ImageError::IoError),
704
705
            TupleEncoding::Bytes {
706
0
                samples: FlatSamples::U8(samples),
707
0
            } => writer.write_all(samples).map_err(ImageError::IoError),
708
            TupleEncoding::Bytes {
709
0
                samples: FlatSamples::U16(samples),
710
0
            } => samples.iter().try_for_each(|&sample| {
711
0
                writer
712
0
                    .write_u16::<BigEndian>(sample)
713
0
                    .map_err(ImageError::IoError)
714
0
            }),
715
716
            TupleEncoding::Ascii {
717
0
                samples: FlatSamples::U8(samples),
718
0
            } => SampleWriter(writer)
719
0
                .write_samples_ascii(samples.iter())
720
0
                .map_err(ImageError::IoError),
721
            TupleEncoding::Ascii {
722
0
                samples: FlatSamples::U16(samples),
723
0
            } => SampleWriter(writer)
724
0
                .write_samples_ascii(samples.iter())
725
0
                .map_err(ImageError::IoError),
726
        }
727
0
    }
728
}