Skip to content

Commit ccc6655

Browse files
committed
[pnm] Improve support for encoding 16-bit images to pnm
- move wide sample reinterpretation to the public function call - reallocate image input if samples are not aligned properly - refactor a bit of `encode` into `encode_impl` to reduce repetition
1 parent 4ae028e commit ccc6655

File tree

1 file changed

+54
-26
lines changed

1 file changed

+54
-26
lines changed

src/codecs/pnm/encoder.rs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,12 @@ impl<W: Write> PnmEncoder<W> {
135135
}
136136
}
137137

138-
/// Encode an image whose samples are represented as `u8`.
138+
/// Encode an image whose samples are represented as a sequence of `u8` or `u16` data.
139+
///
140+
/// If `image` is a slice of `u8`, the samples will be interpreted based on the chosen `color` option.
141+
/// Color types of 16-bit precision means that the bytes are reinterpreted as 16-bit samples,
142+
/// otherwise they are treated as 8-bit samples.
143+
/// If `image` is a slice of `u16`, the samples will be interpreted as 16-bit samples directly.
139144
///
140145
/// Some `pnm` subtypes are incompatible with some color options, a chosen header most
141146
/// certainly with any deviation from the original decoded image.
@@ -150,13 +155,58 @@ impl<W: Write> PnmEncoder<W> {
150155
S: Into<FlatSamples<'s>>,
151156
{
152157
let image = image.into();
158+
159+
// adapt samples so that they are aligned even in 16-bit samples,
160+
// required due to the narrowing of the image buffer to &[u8]
161+
// on dynamic image writing
162+
let image = match (image, color) {
163+
(
164+
FlatSamples::U8(samples),
165+
ExtendedColorType::L16
166+
| ExtendedColorType::La16
167+
| ExtendedColorType::Rgb16
168+
| ExtendedColorType::Rgba16,
169+
) => {
170+
match bytemuck::try_cast_slice(samples) {
171+
// proceed with aligned 16-bit samples
172+
Ok(samples) => FlatSamples::U16(samples),
173+
Err(_e) => {
174+
// reallocation is required
175+
let new_samples: Vec<u16> = samples
176+
.chunks(2)
177+
.map(|chunk| u16::from_ne_bytes([chunk[0], chunk[1]]))
178+
.collect();
179+
180+
let image = FlatSamples::U16(&new_samples);
181+
182+
// make a separate encoding path,
183+
// because the image buffer lifetime has changed
184+
return self.encode_impl(image, width, height, color);
185+
}
186+
}
187+
}
188+
// should not be necessary for any other case
189+
_ => image,
190+
};
191+
192+
self.encode_impl(image, width, height, color)
193+
}
194+
195+
/// Encode an image whose samples are already interpreted correctly.
196+
fn encode_impl<'s>(
197+
&mut self,
198+
samples: FlatSamples<'s>,
199+
width: u32,
200+
height: u32,
201+
color: ExtendedColorType,
202+
) -> ImageResult<()> {
153203
match self.header {
154-
HeaderStrategy::Dynamic => self.write_dynamic_header(image, width, height, color),
204+
HeaderStrategy::Dynamic => self.write_dynamic_header(samples, width, height, color),
155205
HeaderStrategy::Subtype(subtype) => {
156-
self.write_subtyped_header(subtype, image, width, height, color)
206+
self.write_subtyped_header(subtype, samples, width, height, color)
157207
}
158208
HeaderStrategy::Chosen(ref header) => {
159-
Self::write_with_header(&mut self.writer, header, image, width, height, color)
209+
Self::write_with_header(&mut self.writer, header, samples, width, height, color)
160210
}
161211
}
162212
}
@@ -481,28 +531,6 @@ impl<'a> CheckedHeaderColor<'a> {
481531
}
482532
};
483533

484-
// reinterpret image samples if color type is 16 bits per channel,
485-
// required due to the narrowing of the image buffer to &[u8]
486-
// on dynamic image writing
487-
let image = match (image, self.color) {
488-
(
489-
FlatSamples::U8(samples),
490-
ExtendedColorType::L16
491-
| ExtendedColorType::La16
492-
| ExtendedColorType::Rgb16
493-
| ExtendedColorType::Rgba16,
494-
) => FlatSamples::U16(bytemuck::try_cast_slice(samples).map_err(|_e| {
495-
ImageError::Unsupported(UnsupportedError::from_format_and_kind(
496-
ImageFormat::Pnm.into(),
497-
UnsupportedErrorKind::GenericFeature(
498-
"16-bit sampling from the given image buffer".to_owned(),
499-
),
500-
))
501-
})?),
502-
// should not be necessary for any other case
503-
_ => image,
504-
};
505-
506534
// Avoid the performance heavy check if possible, e.g. if the header has been chosen by us.
507535
if header_maxval < max_sample && !image.all_smaller(header_maxval) {
508536
// Sample value greater than allowed for chosen header.

0 commit comments

Comments
 (0)