Skip to content

Commit 2ffbcb0

Browse files
committed
Performance: Avoid spending time decompressing skipped frames.
This commit adds an internal `DecodeOptions::skip_frame_decoding` flag and sets it to true from `ReadDecoder::finish_decoding_image_data`. This results in the following performance gains when using the `next_frame_info` public API to skip frames: `change: [-94.186% -94.170% -94.157%] (p = 0.00 < 0.05)` This commit is mainly motivated by https://crbug.com/371060427 and #510. Hat tip to @kornelski for suggesting this approach in #510 (comment)
1 parent 2a619fe commit 2ffbcb0

File tree

2 files changed

+21
-2
lines changed

2 files changed

+21
-2
lines changed

src/decoder/read_decoder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ impl<R: Read> ReadDecoder<R> {
119119
_ => {}
120120
}
121121
}
122+
self.decoder.set_skip_frame_decoding(false);
122123
Ok(())
123124
}
124125

@@ -148,6 +149,7 @@ impl<R: Read> ReadDecoder<R> {
148149
///
149150
/// Prerequisite: Input is currently positioned within `IDAT` / `fdAT` chunk sequence.
150151
pub fn finish_decoding_image_data(&mut self) -> Result<(), DecodingError> {
152+
self.decoder.set_skip_frame_decoding(true);
151153
loop {
152154
let mut to_be_discarded = vec![];
153155
if let ImageDataCompletionStatus::Done = self.decode_image_data(&mut to_be_discarded)? {

src/decoder/stream.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ pub struct DecodeOptions {
428428
ignore_text_chunk: bool,
429429
ignore_iccp_chunk: bool,
430430
skip_ancillary_crc_failures: bool,
431+
skip_frame_decoding: bool,
431432
}
432433

433434
impl Default for DecodeOptions {
@@ -438,6 +439,7 @@ impl Default for DecodeOptions {
438439
ignore_text_chunk: false,
439440
ignore_iccp_chunk: false,
440441
skip_ancillary_crc_failures: true,
442+
skip_frame_decoding: false,
441443
}
442444
}
443445
}
@@ -615,6 +617,10 @@ impl StreamingDecoder {
615617
.set_skip_ancillary_crc_failures(skip_ancillary_crc_failures)
616618
}
617619

620+
pub(crate) fn set_skip_frame_decoding(&mut self, skip_frame_decoding: bool) {
621+
self.decode_options.skip_frame_decoding = skip_frame_decoding;
622+
}
623+
618624
/// Low level StreamingDecoder interface.
619625
///
620626
/// Allows to stream partial data to the encoder. Returns a tuple containing the bytes that have
@@ -752,7 +758,16 @@ impl StreamingDecoder {
752758
debug_assert!(type_str == IDAT || type_str == chunk::fdAT);
753759
let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize);
754760
let buf = &buf[..len];
755-
let consumed = self.inflater.decompress(buf, image_data)?;
761+
let consumed = if self.decode_options.skip_frame_decoding {
762+
// `inflater.reset()` is not strictly necessary. We do it anyway to ensure
763+
// that if (unexpectedly) `skip_frame_decoding` changes before the end of this
764+
// frame, then it will (most likely) lead to decompression errors later (when
765+
// restarting again from the middle).
766+
self.inflater.reset();
767+
len
768+
} else {
769+
self.inflater.decompress(buf, image_data)?
770+
};
756771
self.current_chunk.crc.update(&buf[..consumed]);
757772
self.current_chunk.remaining -= consumed as u32;
758773
if self.current_chunk.remaining == 0 {
@@ -811,7 +826,9 @@ impl StreamingDecoder {
811826
&& (self.current_chunk.type_ == IDAT || self.current_chunk.type_ == chunk::fdAT)
812827
{
813828
self.current_chunk.type_ = type_str;
814-
self.inflater.finish_compressed_chunks(image_data)?;
829+
if !self.decode_options.skip_frame_decoding {
830+
self.inflater.finish_compressed_chunks(image_data)?;
831+
}
815832
self.inflater.reset();
816833
self.ready_for_idat_chunks = false;
817834
self.ready_for_fdat_chunks = false;

0 commit comments

Comments
 (0)