1 | //! Decoding and Encoding of PNG Images |
2 | //! |
3 | //! PNG (Portable Network Graphics) is an image format that supports lossless compression. |
4 | //! |
5 | //! # Related Links |
6 | //! * <http://www.w3.org/TR/PNG/> - The PNG Specification |
7 | //! |
8 | |
9 | use std::fmt; |
10 | use std::io::{self, Read, Write}; |
11 | |
12 | use png::{BlendOp, DisposeOp}; |
13 | |
14 | use crate::animation::{Delay, Frame, Frames, Ratio}; |
15 | use crate::color::{Blend, ColorType, ExtendedColorType}; |
16 | use crate::error::{ |
17 | DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind, |
18 | ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, |
19 | }; |
20 | use crate::image::{AnimationDecoder, ImageDecoder, ImageEncoder, ImageFormat}; |
21 | use crate::io::Limits; |
22 | use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, Rgb, Rgba, RgbaImage}; |
23 | |
24 | // http://www.w3.org/TR/PNG-Structure.html |
25 | // The first eight bytes of a PNG file always contain the following (decimal) values: |
26 | pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; |
27 | |
28 | /// Png Reader |
29 | /// |
30 | /// This reader will try to read the png one row at a time, |
31 | /// however for interlaced png files this is not possible and |
32 | /// these are therefore read at once. |
33 | pub struct PngReader<R: Read> { |
34 | reader: png::Reader<R>, |
35 | buffer: Vec<u8>, |
36 | index: usize, |
37 | } |
38 | |
39 | impl<R: Read> PngReader<R> { |
40 | fn new(mut reader: png::Reader<R>) -> ImageResult<PngReader<R>> { |
41 | let len = reader.output_buffer_size(); |
42 | // Since interlaced images do not come in |
43 | // scanline order it is almost impossible to |
44 | // read them in a streaming fashion, however |
45 | // this shouldn't be a too big of a problem |
46 | // as most interlaced images should fit in memory. |
47 | let buffer = if reader.info().interlaced { |
48 | let mut buffer = vec![0; len]; |
49 | reader |
50 | .next_frame(&mut buffer) |
51 | .map_err(ImageError::from_png)?; |
52 | buffer |
53 | } else { |
54 | Vec::new() |
55 | }; |
56 | |
57 | Ok(PngReader { |
58 | reader, |
59 | buffer, |
60 | index: 0, |
61 | }) |
62 | } |
63 | } |
64 | |
65 | impl<R: Read> Read for PngReader<R> { |
66 | fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> { |
67 | // io::Write::write for slice cannot fail |
68 | let readed = buf.write(&self.buffer[self.index..]).unwrap(); |
69 | |
70 | let mut bytes = readed; |
71 | self.index += readed; |
72 | |
73 | while self.index >= self.buffer.len() { |
74 | match self.reader.next_row()? { |
75 | Some(row) => { |
76 | // Faster to copy directly to external buffer |
77 | let readed = buf.write(row.data()).unwrap(); |
78 | bytes += readed; |
79 | |
80 | self.buffer = row.data()[readed..].to_owned(); |
81 | self.index = 0; |
82 | } |
83 | None => return Ok(bytes), |
84 | } |
85 | } |
86 | |
87 | Ok(bytes) |
88 | } |
89 | |
90 | fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { |
91 | let mut bytes = self.buffer.len(); |
92 | if buf.is_empty() { |
93 | std::mem::swap(&mut self.buffer, buf); |
94 | } else { |
95 | buf.extend_from_slice(&self.buffer); |
96 | self.buffer.clear(); |
97 | } |
98 | |
99 | self.index = 0; |
100 | |
101 | while let Some(row) = self.reader.next_row()? { |
102 | buf.extend_from_slice(row.data()); |
103 | bytes += row.data().len(); |
104 | } |
105 | |
106 | Ok(bytes) |
107 | } |
108 | } |
109 | |
110 | /// PNG decoder |
111 | pub struct PngDecoder<R: Read> { |
112 | color_type: ColorType, |
113 | reader: png::Reader<R>, |
114 | limits: Limits, |
115 | } |
116 | |
117 | impl<R: Read> PngDecoder<R> { |
118 | /// Creates a new decoder that decodes from the stream ```r``` |
119 | pub fn new(r: R) -> ImageResult<PngDecoder<R>> { |
120 | Self::with_limits(r, Limits::no_limits()) |
121 | } |
122 | |
123 | /// Creates a new decoder that decodes from the stream ```r``` with the given limits. |
124 | pub fn with_limits(r: R, limits: Limits) -> ImageResult<PngDecoder<R>> { |
125 | limits.check_support(&crate::io::LimitSupport::default())?; |
126 | |
127 | let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX); |
128 | let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes }); |
129 | decoder.set_ignore_text_chunk(true); |
130 | |
131 | let info = decoder.read_header_info().map_err(ImageError::from_png)?; |
132 | limits.check_dimensions(info.width, info.height)?; |
133 | |
134 | // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom |
135 | // transformations must be set. EXPAND preserves the default behavior |
136 | // expanding bpc < 8 to 8 bpc. |
137 | decoder.set_transformations(png::Transformations::EXPAND); |
138 | let reader = decoder.read_info().map_err(ImageError::from_png)?; |
139 | let (color_type, bits) = reader.output_color_type(); |
140 | let color_type = match (color_type, bits) { |
141 | (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8, |
142 | (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16, |
143 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8, |
144 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16, |
145 | (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8, |
146 | (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16, |
147 | (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8, |
148 | (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16, |
149 | |
150 | (png::ColorType::Grayscale, png::BitDepth::One) => { |
151 | return Err(unsupported_color(ExtendedColorType::L1)) |
152 | } |
153 | (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => { |
154 | return Err(unsupported_color(ExtendedColorType::La1)) |
155 | } |
156 | (png::ColorType::Rgb, png::BitDepth::One) => { |
157 | return Err(unsupported_color(ExtendedColorType::Rgb1)) |
158 | } |
159 | (png::ColorType::Rgba, png::BitDepth::One) => { |
160 | return Err(unsupported_color(ExtendedColorType::Rgba1)) |
161 | } |
162 | |
163 | (png::ColorType::Grayscale, png::BitDepth::Two) => { |
164 | return Err(unsupported_color(ExtendedColorType::L2)) |
165 | } |
166 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => { |
167 | return Err(unsupported_color(ExtendedColorType::La2)) |
168 | } |
169 | (png::ColorType::Rgb, png::BitDepth::Two) => { |
170 | return Err(unsupported_color(ExtendedColorType::Rgb2)) |
171 | } |
172 | (png::ColorType::Rgba, png::BitDepth::Two) => { |
173 | return Err(unsupported_color(ExtendedColorType::Rgba2)) |
174 | } |
175 | |
176 | (png::ColorType::Grayscale, png::BitDepth::Four) => { |
177 | return Err(unsupported_color(ExtendedColorType::L4)) |
178 | } |
179 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => { |
180 | return Err(unsupported_color(ExtendedColorType::La4)) |
181 | } |
182 | (png::ColorType::Rgb, png::BitDepth::Four) => { |
183 | return Err(unsupported_color(ExtendedColorType::Rgb4)) |
184 | } |
185 | (png::ColorType::Rgba, png::BitDepth::Four) => { |
186 | return Err(unsupported_color(ExtendedColorType::Rgba4)) |
187 | } |
188 | |
189 | (png::ColorType::Indexed, bits) => { |
190 | return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8))) |
191 | } |
192 | }; |
193 | |
194 | Ok(PngDecoder { |
195 | color_type, |
196 | reader, |
197 | limits, |
198 | }) |
199 | } |
200 | |
201 | /// Returns the gamma value of the image or None if no gamma value is indicated. |
202 | /// |
203 | /// If an sRGB chunk is present this method returns a gamma value of 0.45455 and ignores the |
204 | /// value in the gAMA chunk. This is the recommended behavior according to the PNG standard: |
205 | /// |
206 | /// > When the sRGB chunk is present, [...] decoders that recognize the sRGB chunk but are not |
207 | /// > capable of colour management are recommended to ignore the gAMA and cHRM chunks, and use |
208 | /// > the values given above as if they had appeared in gAMA and cHRM chunks. |
209 | pub fn gamma_value(&self) -> ImageResult<Option<f64>> { |
210 | Ok(self |
211 | .reader |
212 | .info() |
213 | .source_gamma |
214 | .map(|x| x.into_scaled() as f64 / 100000.0)) |
215 | } |
216 | |
217 | /// Turn this into an iterator over the animation frames. |
218 | /// |
219 | /// Reading the complete animation requires more memory than reading the data from the IDAT |
220 | /// frame–multiple frame buffers need to be reserved at the same time. We further do not |
221 | /// support compositing 16-bit colors. In any case this would be lossy as the interface of |
222 | /// animation decoders does not support 16-bit colors. |
223 | /// |
224 | /// If something is not supported or a limit is violated then the decoding step that requires |
225 | /// them will fail and an error will be returned instead of the frame. No further frames will |
226 | /// be returned. |
227 | pub fn apng(self) -> ApngDecoder<R> { |
228 | ApngDecoder::new(self) |
229 | } |
230 | |
231 | /// Returns if the image contains an animation. |
232 | /// |
233 | /// Note that the file itself decides if the default image is considered to be part of the |
234 | /// animation. When it is not the common interpretation is to use it as a thumbnail. |
235 | /// |
236 | /// If a non-animated image is converted into an `ApngDecoder` then its iterator is empty. |
237 | pub fn is_apng(&self) -> bool { |
238 | self.reader.info().animation_control.is_some() |
239 | } |
240 | } |
241 | |
242 | fn unsupported_color(ect: ExtendedColorType) -> ImageError { |
243 | ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
244 | format:ImageFormat::Png.into(), |
245 | kind:UnsupportedErrorKind::Color(ect), |
246 | )) |
247 | } |
248 | |
249 | impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder<R> { |
250 | type Reader = PngReader<R>; |
251 | |
252 | fn dimensions(&self) -> (u32, u32) { |
253 | self.reader.info().size() |
254 | } |
255 | |
256 | fn color_type(&self) -> ColorType { |
257 | self.color_type |
258 | } |
259 | |
260 | fn icc_profile(&mut self) -> Option<Vec<u8>> { |
261 | self.reader.info().icc_profile.as_ref().map(|x| x.to_vec()) |
262 | } |
263 | |
264 | fn into_reader(self) -> ImageResult<Self::Reader> { |
265 | PngReader::new(self.reader) |
266 | } |
267 | |
268 | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { |
269 | use byteorder::{BigEndian, ByteOrder, NativeEndian}; |
270 | |
271 | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
272 | self.reader.next_frame(buf).map_err(ImageError::from_png)?; |
273 | // PNG images are big endian. For 16 bit per channel and larger types, |
274 | // the buffer may need to be reordered to native endianness per the |
275 | // contract of `read_image`. |
276 | // TODO: assumes equal channel bit depth. |
277 | let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count(); |
278 | |
279 | match bpc { |
280 | 1 => (), // No reodering necessary for u8 |
281 | 2 => buf.chunks_exact_mut(2).for_each(|c| { |
282 | let v = BigEndian::read_u16(c); |
283 | NativeEndian::write_u16(c, v) |
284 | }), |
285 | _ => unreachable!(), |
286 | } |
287 | Ok(()) |
288 | } |
289 | |
290 | fn scanline_bytes(&self) -> u64 { |
291 | let width = self.reader.info().width; |
292 | self.reader.output_line_size(width) as u64 |
293 | } |
294 | |
295 | fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { |
296 | limits.check_support(&crate::io::LimitSupport::default())?; |
297 | let info = self.reader.info(); |
298 | limits.check_dimensions(info.width, info.height)?; |
299 | self.limits = limits; |
300 | // TODO: add `png::Reader::change_limits()` and call it here |
301 | // to also constrain the internal buffer allocations in the PNG crate |
302 | Ok(()) |
303 | } |
304 | } |
305 | |
306 | /// An [`AnimationDecoder`] adapter of [`PngDecoder`]. |
307 | /// |
308 | /// See [`PngDecoder::apng`] for more information. |
309 | /// |
310 | /// [`AnimationDecoder`]: ../trait.AnimationDecoder.html |
311 | /// [`PngDecoder`]: struct.PngDecoder.html |
312 | /// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng |
313 | pub struct ApngDecoder<R: Read> { |
314 | inner: PngDecoder<R>, |
315 | /// The current output buffer. |
316 | current: Option<RgbaImage>, |
317 | /// The previous output buffer, used for dispose op previous. |
318 | previous: Option<RgbaImage>, |
319 | /// The dispose op of the current frame. |
320 | dispose: DisposeOp, |
321 | /// The number of image still expected to be able to load. |
322 | remaining: u32, |
323 | /// The next (first) image is the thumbnail. |
324 | has_thumbnail: bool, |
325 | } |
326 | |
327 | impl<R: Read> ApngDecoder<R> { |
328 | fn new(inner: PngDecoder<R>) -> Self { |
329 | let info = inner.reader.info(); |
330 | let remaining = match info.animation_control() { |
331 | // The expected number of fcTL in the remaining image. |
332 | Some(actl) => actl.num_frames, |
333 | None => 0, |
334 | }; |
335 | // If the IDAT has no fcTL then it is not part of the animation counted by |
336 | // num_frames. All following fdAT chunks must be preceded by an fcTL |
337 | let has_thumbnail = info.frame_control.is_none(); |
338 | ApngDecoder { |
339 | inner, |
340 | current: None, |
341 | previous: None, |
342 | dispose: DisposeOp::Background, |
343 | remaining, |
344 | has_thumbnail, |
345 | } |
346 | } |
347 | |
348 | // TODO: thumbnail(&mut self) -> Option<impl ImageDecoder<'_>> |
349 | |
350 | /// Decode one subframe and overlay it on the canvas. |
351 | fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> { |
352 | // The iterator always produces RGBA8 images |
353 | const COLOR_TYPE: ColorType = ColorType::Rgba8; |
354 | |
355 | // Allocate the buffers, honoring the memory limits |
356 | let (width, height) = self.inner.dimensions(); |
357 | { |
358 | let limits = &mut self.inner.limits; |
359 | if self.previous.is_none() { |
360 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
361 | self.previous = Some(RgbaImage::new(width, height)); |
362 | } |
363 | |
364 | if self.current.is_none() { |
365 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
366 | self.current = Some(RgbaImage::new(width, height)); |
367 | } |
368 | } |
369 | |
370 | // Remove this image from remaining. |
371 | self.remaining = match self.remaining.checked_sub(1) { |
372 | None => return Ok(None), |
373 | Some(next) => next, |
374 | }; |
375 | |
376 | // Shorten ourselves to 0 in case of error. |
377 | let remaining = self.remaining; |
378 | self.remaining = 0; |
379 | |
380 | // Skip the thumbnail that is not part of the animation. |
381 | if self.has_thumbnail { |
382 | // Clone the limits so that our one-off allocation that's destroyed after this scope doesn't persist |
383 | let mut limits = self.inner.limits.clone(); |
384 | limits.reserve_usize(self.inner.reader.output_buffer_size())?; |
385 | let mut buffer = vec![0; self.inner.reader.output_buffer_size()]; |
386 | // TODO: add `png::Reader::change_limits()` and call it here |
387 | // to also constrain the internal buffer allocations in the PNG crate |
388 | self.inner |
389 | .reader |
390 | .next_frame(&mut buffer) |
391 | .map_err(ImageError::from_png)?; |
392 | self.has_thumbnail = false; |
393 | } |
394 | |
395 | self.animatable_color_type()?; |
396 | |
397 | // We've initialized them earlier in this function |
398 | let previous = self.previous.as_mut().unwrap(); |
399 | let current = self.current.as_mut().unwrap(); |
400 | |
401 | // Dispose of the previous frame. |
402 | match self.dispose { |
403 | DisposeOp::None => { |
404 | previous.clone_from(current); |
405 | } |
406 | DisposeOp::Background => { |
407 | previous.clone_from(current); |
408 | current |
409 | .pixels_mut() |
410 | .for_each(|pixel| *pixel = Rgba([0, 0, 0, 0])); |
411 | } |
412 | DisposeOp::Previous => { |
413 | current.clone_from(previous); |
414 | } |
415 | } |
416 | |
417 | // The allocations from now on are not going to persist, |
418 | // and will be destroyed at the end of the scope. |
419 | // Clone the limits so that any changes to them die with the allocations. |
420 | let mut limits = self.inner.limits.clone(); |
421 | |
422 | // Read next frame data. |
423 | let raw_frame_size = self.inner.reader.output_buffer_size(); |
424 | limits.reserve_usize(raw_frame_size)?; |
425 | let mut buffer = vec![0; raw_frame_size]; |
426 | // TODO: add `png::Reader::change_limits()` and call it here |
427 | // to also constrain the internal buffer allocations in the PNG crate |
428 | self.inner |
429 | .reader |
430 | .next_frame(&mut buffer) |
431 | .map_err(ImageError::from_png)?; |
432 | let info = self.inner.reader.info(); |
433 | |
434 | // Find out how to interpret the decoded frame. |
435 | let (width, height, px, py, blend); |
436 | match info.frame_control() { |
437 | None => { |
438 | width = info.width; |
439 | height = info.height; |
440 | px = 0; |
441 | py = 0; |
442 | blend = BlendOp::Source; |
443 | } |
444 | Some(fc) => { |
445 | width = fc.width; |
446 | height = fc.height; |
447 | px = fc.x_offset; |
448 | py = fc.y_offset; |
449 | blend = fc.blend_op; |
450 | self.dispose = fc.dispose_op; |
451 | } |
452 | }; |
453 | |
454 | // Turn the data into an rgba image proper. |
455 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
456 | let source = match self.inner.color_type { |
457 | ColorType::L8 => { |
458 | let image = ImageBuffer::<Luma<_>, _>::from_raw(width, height, buffer).unwrap(); |
459 | DynamicImage::ImageLuma8(image).into_rgba8() |
460 | } |
461 | ColorType::La8 => { |
462 | let image = ImageBuffer::<LumaA<_>, _>::from_raw(width, height, buffer).unwrap(); |
463 | DynamicImage::ImageLumaA8(image).into_rgba8() |
464 | } |
465 | ColorType::Rgb8 => { |
466 | let image = ImageBuffer::<Rgb<_>, _>::from_raw(width, height, buffer).unwrap(); |
467 | DynamicImage::ImageRgb8(image).into_rgba8() |
468 | } |
469 | ColorType::Rgba8 => ImageBuffer::<Rgba<_>, _>::from_raw(width, height, buffer).unwrap(), |
470 | ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { |
471 | // TODO: to enable remove restriction in `animatable_color_type` method. |
472 | unreachable!("16-bit apng not yet support" ) |
473 | } |
474 | _ => unreachable!("Invalid png color" ), |
475 | }; |
476 | // We've converted the raw frame to RGBA8 and disposed of the original allocation |
477 | limits.free_usize(raw_frame_size); |
478 | |
479 | match blend { |
480 | BlendOp::Source => { |
481 | current |
482 | .copy_from(&source, px, py) |
483 | .expect("Invalid png image not detected in png" ); |
484 | } |
485 | BlendOp::Over => { |
486 | // TODO: investigate speed, speed-ups, and bounds-checks. |
487 | for (x, y, p) in source.enumerate_pixels() { |
488 | current.get_pixel_mut(x + px, y + py).blend(p); |
489 | } |
490 | } |
491 | } |
492 | |
493 | // Ok, we can proceed with actually remaining images. |
494 | self.remaining = remaining; |
495 | // Return composited output buffer. |
496 | Ok(Some(self.current.as_ref().unwrap())) |
497 | } |
498 | |
499 | fn animatable_color_type(&self) -> Result<(), ImageError> { |
500 | match self.inner.color_type { |
501 | ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()), |
502 | // TODO: do not handle multi-byte colors. Remember to implement it in `mix_next_frame`. |
503 | ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { |
504 | Err(unsupported_color(self.inner.color_type.into())) |
505 | } |
506 | _ => unreachable!(" {:?} not a valid png color" , self.inner.color_type), |
507 | } |
508 | } |
509 | } |
510 | |
511 | impl<'a, R: Read + 'a> AnimationDecoder<'a> for ApngDecoder<R> { |
512 | fn into_frames(self) -> Frames<'a> { |
513 | struct FrameIterator<R: Read>(ApngDecoder<R>); |
514 | |
515 | impl<R: Read> Iterator for FrameIterator<R> { |
516 | type Item = ImageResult<Frame>; |
517 | |
518 | fn next(&mut self) -> Option<Self::Item> { |
519 | let image = match self.0.mix_next_frame() { |
520 | Ok(Some(image)) => image.clone(), |
521 | Ok(None) => return None, |
522 | Err(err) => return Some(Err(err)), |
523 | }; |
524 | |
525 | let info = self.0.inner.reader.info(); |
526 | let fc = info.frame_control().unwrap(); |
527 | // PNG delays are rations in seconds. |
528 | let num = u32::from(fc.delay_num) * 1_000u32; |
529 | let denom = match fc.delay_den { |
530 | // The standard dictates to replace by 100 when the denominator is 0. |
531 | 0 => 100, |
532 | d => u32::from(d), |
533 | }; |
534 | let delay = Delay::from_ratio(Ratio::new(num, denom)); |
535 | Some(Ok(Frame::from_parts(image, 0, 0, delay))) |
536 | } |
537 | } |
538 | |
539 | Frames::new(Box::new(FrameIterator(self))) |
540 | } |
541 | } |
542 | |
543 | /// PNG encoder |
544 | pub struct PngEncoder<W: Write> { |
545 | w: W, |
546 | compression: CompressionType, |
547 | filter: FilterType, |
548 | } |
549 | |
550 | /// Compression level of a PNG encoder. The default setting is `Fast`. |
551 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
552 | #[non_exhaustive ] |
553 | #[derive (Default)] |
554 | pub enum CompressionType { |
555 | /// Default compression level |
556 | Default, |
557 | /// Fast, minimal compression |
558 | #[default] |
559 | Fast, |
560 | /// High compression level |
561 | Best, |
562 | /// Huffman coding compression |
563 | #[deprecated (note = "use one of the other compression levels instead, such as 'Fast'" )] |
564 | Huffman, |
565 | /// Run-length encoding compression |
566 | #[deprecated (note = "use one of the other compression levels instead, such as 'Fast'" )] |
567 | Rle, |
568 | } |
569 | |
570 | /// Filter algorithms used to process image data to improve compression. |
571 | /// |
572 | /// The default filter is `Adaptive`. |
573 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
574 | #[non_exhaustive ] |
575 | #[derive (Default)] |
576 | pub enum FilterType { |
577 | /// No processing done, best used for low bit depth grayscale or data with a |
578 | /// low color count |
579 | NoFilter, |
580 | /// Filters based on previous pixel in the same scanline |
581 | Sub, |
582 | /// Filters based on the scanline above |
583 | Up, |
584 | /// Filters based on the average of left and right neighbor pixels |
585 | Avg, |
586 | /// Algorithm that takes into account the left, upper left, and above pixels |
587 | Paeth, |
588 | /// Uses a heuristic to select one of the preceding filters for each |
589 | /// scanline rather than one filter for the entire image |
590 | #[default] |
591 | Adaptive, |
592 | } |
593 | |
594 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
595 | #[non_exhaustive ] |
596 | enum BadPngRepresentation { |
597 | ColorType(ColorType), |
598 | } |
599 | |
600 | impl<W: Write> PngEncoder<W> { |
601 | /// Create a new encoder that writes its output to ```w``` |
602 | pub fn new(w: W) -> PngEncoder<W> { |
603 | PngEncoder { |
604 | w, |
605 | compression: CompressionType::default(), |
606 | filter: FilterType::default(), |
607 | } |
608 | } |
609 | |
610 | /// Create a new encoder that writes its output to `w` with `CompressionType` `compression` and |
611 | /// `FilterType` `filter`. |
612 | /// |
613 | /// It is best to view the options as a _hint_ to the implementation on the smallest or fastest |
614 | /// option for encoding a particular image. That is, using options that map directly to a PNG |
615 | /// image parameter will use this parameter where possible. But variants that have no direct |
616 | /// mapping may be interpreted differently in minor versions. The exact output is expressly |
617 | /// __not__ part of the SemVer stability guarantee. |
618 | /// |
619 | /// Note that it is not optimal to use a single filter type, so an adaptive |
620 | /// filter type is selected as the default. The filter which best minimizes |
621 | /// file size may change with the type of compression used. |
622 | pub fn new_with_quality( |
623 | w: W, |
624 | compression: CompressionType, |
625 | filter: FilterType, |
626 | ) -> PngEncoder<W> { |
627 | PngEncoder { |
628 | w, |
629 | compression, |
630 | filter, |
631 | } |
632 | } |
633 | |
634 | /// Encodes the image `data` that has dimensions `width` and `height` and `ColorType` `c`. |
635 | /// |
636 | /// Expects data in big endian. |
637 | #[deprecated = "Use `PngEncoder::write_image` instead. Beware that `write_image` has a different endianness convention" ] |
638 | pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { |
639 | self.encode_inner(data, width, height, color) |
640 | } |
641 | |
642 | fn encode_inner( |
643 | self, |
644 | data: &[u8], |
645 | width: u32, |
646 | height: u32, |
647 | color: ColorType, |
648 | ) -> ImageResult<()> { |
649 | let (ct, bits) = match color { |
650 | ColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight), |
651 | ColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen), |
652 | ColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight), |
653 | ColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen), |
654 | ColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight), |
655 | ColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen), |
656 | ColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight), |
657 | ColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen), |
658 | _ => { |
659 | return Err(ImageError::Unsupported( |
660 | UnsupportedError::from_format_and_kind( |
661 | ImageFormat::Png.into(), |
662 | UnsupportedErrorKind::Color(color.into()), |
663 | ), |
664 | )) |
665 | } |
666 | }; |
667 | let comp = match self.compression { |
668 | CompressionType::Default => png::Compression::Default, |
669 | CompressionType::Best => png::Compression::Best, |
670 | _ => png::Compression::Fast, |
671 | }; |
672 | let (filter, adaptive_filter) = match self.filter { |
673 | FilterType::NoFilter => ( |
674 | png::FilterType::NoFilter, |
675 | png::AdaptiveFilterType::NonAdaptive, |
676 | ), |
677 | FilterType::Sub => (png::FilterType::Sub, png::AdaptiveFilterType::NonAdaptive), |
678 | FilterType::Up => (png::FilterType::Up, png::AdaptiveFilterType::NonAdaptive), |
679 | FilterType::Avg => (png::FilterType::Avg, png::AdaptiveFilterType::NonAdaptive), |
680 | FilterType::Paeth => (png::FilterType::Paeth, png::AdaptiveFilterType::NonAdaptive), |
681 | FilterType::Adaptive => (png::FilterType::Sub, png::AdaptiveFilterType::Adaptive), |
682 | }; |
683 | |
684 | let mut encoder = png::Encoder::new(self.w, width, height); |
685 | encoder.set_color(ct); |
686 | encoder.set_depth(bits); |
687 | encoder.set_compression(comp); |
688 | encoder.set_filter(filter); |
689 | encoder.set_adaptive_filter(adaptive_filter); |
690 | let mut writer = encoder |
691 | .write_header() |
692 | .map_err(|e| ImageError::IoError(e.into()))?; |
693 | writer |
694 | .write_image_data(data) |
695 | .map_err(|e| ImageError::IoError(e.into())) |
696 | } |
697 | } |
698 | |
699 | impl<W: Write> ImageEncoder for PngEncoder<W> { |
700 | /// Write a PNG image with the specified width, height, and color type. |
701 | /// |
702 | /// For color types with 16-bit per channel or larger, the contents of `buf` should be in |
703 | /// native endian. PngEncoder will automatically convert to big endian as required by the |
704 | /// underlying PNG format. |
705 | #[track_caller ] |
706 | fn write_image( |
707 | self, |
708 | buf: &[u8], |
709 | width: u32, |
710 | height: u32, |
711 | color_type: ColorType, |
712 | ) -> ImageResult<()> { |
713 | use byteorder::{BigEndian, ByteOrder, NativeEndian}; |
714 | use ColorType::*; |
715 | |
716 | let expected_bufffer_len = |
717 | (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); |
718 | assert_eq!( |
719 | expected_bufffer_len, |
720 | buf.len() as u64, |
721 | "Invalid buffer length: expected {expected_bufffer_len} got {} for {width}x {height} image" , |
722 | buf.len(), |
723 | ); |
724 | |
725 | // PNG images are big endian. For 16 bit per channel and larger types, |
726 | // the buffer may need to be reordered to big endian per the |
727 | // contract of `write_image`. |
728 | // TODO: assumes equal channel bit depth. |
729 | match color_type { |
730 | L8 | La8 | Rgb8 | Rgba8 => { |
731 | // No reodering necessary for u8 |
732 | self.encode_inner(buf, width, height, color_type) |
733 | } |
734 | L16 | La16 | Rgb16 | Rgba16 => { |
735 | // Because the buffer is immutable and the PNG encoder does not |
736 | // yet take Write/Read traits, create a temporary buffer for |
737 | // big endian reordering. |
738 | let mut reordered = vec![0; buf.len()]; |
739 | buf.chunks_exact(2) |
740 | .zip(reordered.chunks_exact_mut(2)) |
741 | .for_each(|(b, r)| BigEndian::write_u16(r, NativeEndian::read_u16(b))); |
742 | self.encode_inner(&reordered, width, height, color_type) |
743 | } |
744 | _ => Err(ImageError::Encoding(EncodingError::new( |
745 | ImageFormat::Png.into(), |
746 | BadPngRepresentation::ColorType(color_type), |
747 | ))), |
748 | } |
749 | } |
750 | } |
751 | |
752 | impl ImageError { |
753 | fn from_png(err: png::DecodingError) -> ImageError { |
754 | use png::DecodingError::*; |
755 | match err { |
756 | IoError(err: Error) => ImageError::IoError(err), |
757 | // The input image was not a valid PNG. |
758 | err: DecodingError @ Format(_) => { |
759 | ImageError::Decoding(DecodingError::new(format:ImageFormat::Png.into(), err)) |
760 | } |
761 | // Other is used when: |
762 | // - The decoder is polled for more animation frames despite being done (or not being animated |
763 | // in the first place). |
764 | // - The output buffer does not have the required size. |
765 | err: DecodingError @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind( |
766 | ParameterErrorKind::Generic(err.to_string()), |
767 | )), |
768 | LimitsExceeded => { |
769 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
770 | } |
771 | } |
772 | } |
773 | } |
774 | |
775 | impl fmt::Display for BadPngRepresentation { |
776 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
777 | match self { |
778 | Self::ColorType(color_type: &ColorType) => write!( |
779 | f, |
780 | "The color {:?} can not be represented in PNG." , |
781 | color_type |
782 | ), |
783 | } |
784 | } |
785 | } |
786 | |
787 | impl std::error::Error for BadPngRepresentation {} |
788 | |
789 | #[cfg (test)] |
790 | mod tests { |
791 | use super::*; |
792 | use crate::ImageOutputFormat; |
793 | |
794 | use std::io::Cursor; |
795 | |
796 | #[test ] |
797 | fn ensure_no_decoder_off_by_one() { |
798 | let dec = PngDecoder::new( |
799 | std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png" ) |
800 | .unwrap(), |
801 | ) |
802 | .expect("Unable to read PNG file (does it exist?)" ); |
803 | |
804 | assert_eq![(2000, 1000), dec.dimensions()]; |
805 | |
806 | assert_eq![ |
807 | ColorType::Rgb8, |
808 | dec.color_type(), |
809 | "Image MUST have the Rgb8 format" |
810 | ]; |
811 | |
812 | #[allow (deprecated)] |
813 | let correct_bytes = dec |
814 | .into_reader() |
815 | .expect("Unable to read file" ) |
816 | .bytes() |
817 | .map(|x| x.expect("Unable to read byte" )) |
818 | .collect::<Vec<u8>>(); |
819 | |
820 | assert_eq![6_000_000, correct_bytes.len()]; |
821 | } |
822 | |
823 | #[test ] |
824 | fn underlying_error() { |
825 | use std::error::Error; |
826 | |
827 | let mut not_png = |
828 | std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png" ) |
829 | .unwrap(); |
830 | not_png[0] = 0; |
831 | |
832 | let error = PngDecoder::new(¬_png[..]).err().unwrap(); |
833 | let _ = error |
834 | .source() |
835 | .unwrap() |
836 | .downcast_ref::<png::DecodingError>() |
837 | .expect("Caused by a png error" ); |
838 | } |
839 | |
840 | #[test ] |
841 | fn encode_bad_color_type() { |
842 | // regression test for issue #1663 |
843 | let image = DynamicImage::new_rgb32f(1, 1); |
844 | let mut target = Cursor::new(vec![]); |
845 | let _ = image.write_to(&mut target, ImageOutputFormat::Png); |
846 | } |
847 | } |
848 | |