Skip to content

Commit

Permalink
Performance: Avoid spending time decompressing skipped frames.
Browse files Browse the repository at this point in the history
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 image-rs#510.  Hat tip
to @kornelski for suggesting this approach in
image-rs#510 (comment)
  • Loading branch information
anforowicz committed Nov 14, 2024
1 parent 2a619fe commit 2ffbcb0
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/decoder/read_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl<R: Read> ReadDecoder<R> {
_ => {}
}
}
self.decoder.set_skip_frame_decoding(false);
Ok(())
}

Expand Down Expand Up @@ -148,6 +149,7 @@ impl<R: Read> ReadDecoder<R> {
///
/// Prerequisite: Input is currently positioned within `IDAT` / `fdAT` chunk sequence.
pub fn finish_decoding_image_data(&mut self) -> Result<(), DecodingError> {
self.decoder.set_skip_frame_decoding(true);
loop {
let mut to_be_discarded = vec![];
if let ImageDataCompletionStatus::Done = self.decode_image_data(&mut to_be_discarded)? {
Expand Down
21 changes: 19 additions & 2 deletions src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ pub struct DecodeOptions {
ignore_text_chunk: bool,
ignore_iccp_chunk: bool,
skip_ancillary_crc_failures: bool,
skip_frame_decoding: bool,
}

impl Default for DecodeOptions {
Expand All @@ -438,6 +439,7 @@ impl Default for DecodeOptions {
ignore_text_chunk: false,
ignore_iccp_chunk: false,
skip_ancillary_crc_failures: true,
skip_frame_decoding: false,
}
}
}
Expand Down Expand Up @@ -615,6 +617,10 @@ impl StreamingDecoder {
.set_skip_ancillary_crc_failures(skip_ancillary_crc_failures)
}

pub(crate) fn set_skip_frame_decoding(&mut self, skip_frame_decoding: bool) {
self.decode_options.skip_frame_decoding = skip_frame_decoding;
}

/// Low level StreamingDecoder interface.
///
/// Allows to stream partial data to the encoder. Returns a tuple containing the bytes that have
Expand Down Expand Up @@ -752,7 +758,16 @@ impl StreamingDecoder {
debug_assert!(type_str == IDAT || type_str == chunk::fdAT);
let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize);
let buf = &buf[..len];
let consumed = self.inflater.decompress(buf, image_data)?;
let consumed = if self.decode_options.skip_frame_decoding {
// `inflater.reset()` is not strictly necessary. We do it anyway to ensure
// that if (unexpectedly) `skip_frame_decoding` changes before the end of this
// frame, then it will (most likely) lead to decompression errors later (when
// restarting again from the middle).
self.inflater.reset();
len
} else {
self.inflater.decompress(buf, image_data)?
};
self.current_chunk.crc.update(&buf[..consumed]);
self.current_chunk.remaining -= consumed as u32;
if self.current_chunk.remaining == 0 {
Expand Down Expand Up @@ -811,7 +826,9 @@ impl StreamingDecoder {
&& (self.current_chunk.type_ == IDAT || self.current_chunk.type_ == chunk::fdAT)
{
self.current_chunk.type_ = type_str;
self.inflater.finish_compressed_chunks(image_data)?;
if !self.decode_options.skip_frame_decoding {
self.inflater.finish_compressed_chunks(image_data)?;
}
self.inflater.reset();
self.ready_for_idat_chunks = false;
self.ready_for_fdat_chunks = false;
Expand Down

0 comments on commit 2ffbcb0

Please sign in to comment.