Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reader.read_row for decoding into caller-provided buffer. #493

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions benches/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ fn load_all(c: &mut Criterion) {
bench_noncompressed_png(&mut g, 2048, 0x7fffffff); // 16 MB
bench_noncompressed_png(&mut g, 12288, 0x7fffffff); // 576 MB
g.finish();

// Incremental decoding via `read_row`
let mut g = c.benchmark_group("row-by-row");
let mut data = Vec::new();
test_utils::write_noncompressed_png(&mut data, 128, 4096);
bench_read_row(&mut g, data, "128x128-4k-idat");
g.finish();
}

criterion_group! {benches, load_all}
Expand All @@ -52,21 +59,12 @@ fn bench_noncompressed_png(g: &mut BenchmarkGroup<WallTime>, size: u32, idat_byt
bench_file(g, data, format!("{size}x{size}.png"));
}

/// This benchmarks decoding via a call to `Reader::next_frame`.
fn bench_file(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: String) {
if data.len() > 1_000_000 {
g.sample_size(10);
}

fn create_reader(data: &[u8]) -> Reader<&[u8]> {
let mut decoder = Decoder::new(data);

// Cover default transformations used by the `image` crate when constructing
// `image::codecs::png::PngDecoder`.
decoder.set_transformations(Transformations::EXPAND);

decoder.read_info().unwrap()
}

let mut reader = create_reader(data.as_slice());
let mut image = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut image).unwrap();
Expand All @@ -79,3 +77,30 @@ fn bench_file(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: String) {
})
});
}

/// This benchmarks decoding via a sequence of `Reader::read_row` calls.
fn bench_read_row(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: &str) {
let reader = create_reader(data.as_slice());
let mut image = vec![0; reader.output_buffer_size()];
let bytes_per_row = reader.output_line_size(reader.info().width);
g.throughput(Throughput::Bytes(image.len() as u64));
g.bench_with_input(name, &data, |b, data| {
b.iter(|| {
let mut reader = create_reader(data.as_slice());

for output_row in image.chunks_exact_mut(bytes_per_row) {
reader.read_row(output_row).unwrap().unwrap();
}
})
});
}

fn create_reader(data: &[u8]) -> Reader<&[u8]> {
let mut decoder = Decoder::new(data);

// Cover default transformations used by the `image` crate when constructing
// `image::codecs::png::PngDecoder`.
decoder.set_transformations(Transformations::EXPAND);

decoder.read_info().unwrap()
}
62 changes: 45 additions & 17 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,14 +483,45 @@ impl<R: Read> Reader<R> {
Ok(())
}

/// Returns the next processed row of the image
/// Returns the next processed row of the image (discarding `InterlaceInfo`).
///
/// See also [`Reader.read_row`], which reads into a caller-provided buffer.
pub fn next_row(&mut self) -> Result<Option<Row>, DecodingError> {
self.next_interlaced_row()
.map(|v| v.map(|v| Row { data: v.data }))
}

/// Returns the next processed row of the image
/// Returns the next processed row of the image.
///
/// See also [`Reader.read_row`], which reads into a caller-provided buffer.
pub fn next_interlaced_row(&mut self) -> Result<Option<InterlacedRow>, DecodingError> {
let mut output_buffer = mem::take(&mut self.scratch_buffer);
output_buffer.resize(self.output_line_size(self.info().width), 0u8);
let result = self.read_row(&mut output_buffer);
self.scratch_buffer = output_buffer;
result.map(move |option| {
option.map(move |interlace| {
let output_line_size = self.output_line_size_for_interlace_info(&interlace);
InterlacedRow {
data: &self.scratch_buffer[..output_line_size],
interlace,
}
})
})
}

/// Reads the next row of the image into the provided `output_buffer`.
/// `Ok(None)` will be returned if the current image frame has no more rows.
///
/// `output_buffer` needs to be long enough to accommodate [`Reader.output_line_size`] for
/// [`Info.width`] (initial interlaced rows may need less than that).
///
/// See also [`Reader.next_row`] and [`Reader.next_interlaced_row`], which read into a
/// `Reader`-owned buffer.
pub fn read_row(
&mut self,
output_buffer: &mut [u8],
) -> Result<Option<InterlaceInfo>, DecodingError> {
let interlace = match self.subframe.current_interlace_info.as_ref() {
None => {
self.finish_decoding()?;
Expand All @@ -507,24 +538,21 @@ impl<R: Read> Reader<R> {
self.info().raw_row_length_from_width(width)
}
};

let output_line_size = self.output_line_size_for_interlace_info(&interlace);
let output_buffer = &mut output_buffer[..output_line_size];

self.next_interlaced_row_impl(rowlen, output_buffer)?;

Ok(Some(interlace))
}

fn output_line_size_for_interlace_info(&self, interlace: &InterlaceInfo) -> usize {
let width = match interlace {
InterlaceInfo::Adam7(Adam7Info { width, .. }) => width,
InterlaceInfo::Adam7(Adam7Info { width, .. }) => *width,
InterlaceInfo::Null(_) => self.subframe.width,
};
let output_line_size = self.output_line_size(width);

// TODO: change the interface of `next_interlaced_row` to take an output buffer instead of
// making us return a reference to a buffer that we own.
let mut output_buffer = mem::take(&mut self.scratch_buffer);
output_buffer.resize(output_line_size, 0u8);
let ret = self.next_interlaced_row_impl(rowlen, &mut output_buffer);
self.scratch_buffer = output_buffer;
ret?;

Ok(Some(InterlacedRow {
data: &self.scratch_buffer[..output_line_size],
interlace,
}))
self.output_line_size(width)
}

/// Read the rest of the image and chunks and finish up, including text chunks or others
Expand Down
1 change: 1 addition & 0 deletions src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub const CHUNK_BUFFER_SIZE: usize = 32 * 1024;
///
/// This is used only in fuzzing. `afl` automatically adds `--cfg fuzzing` to RUSTFLAGS which can
/// be used to detect that build.
#[allow(unexpected_cfgs)]
const CHECKSUM_DISABLED: bool = cfg!(fuzzing);

/// Kind of `u32` value that is being read via `State::U32`.
Expand Down
Loading