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

generic progress callback implementation on a byte-level #2014

Closed
Closed
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
14 changes: 14 additions & 0 deletions .idea/image.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion src/codecs/tiff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use crate::error::{
ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
};
use crate::image::{ImageDecoder, ImageEncoder, ImageFormat};
use crate::utils;
use crate::{Progress, utils};
use crate::io::track_progress::TrackProgressReader;

/// Decoder for TIFF images.
pub struct TiffDecoder<R>
Expand Down Expand Up @@ -226,6 +227,15 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> {
Ok(TiffReader(Cursor::new(buf), PhantomData))
}

fn read_image_with_progress<F: FnMut(Progress)>(self, buf: &mut [u8], progress_callback: F) -> ImageResult<()> {
self.inner = TrackProgressReader::estimate(
self.inner, progress_callback, self.color_type,
self.dimensions().0, self.dimensions().1
);

self.read_image(buf)
}

fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
match self
Expand Down
1 change: 1 addition & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{error, ImageError, ImageResult};

pub(crate) mod free_functions;
mod reader;
pub(crate) mod track_progress;

pub use self::reader::Reader;

Expand Down
167 changes: 167 additions & 0 deletions src/io/track_progress.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@

//! Tracks read and write progress on the level of byte writers and readers.
//! The progress is reported based on the size of the uncompressed image.
//! This is not an exact progress report, but good enough for most cases.
//! When file compression is strong, the file will be done writing
//! before the progress is reported as completed. Optionally,
//! the trackers can report a completed progress on drop.
//!
//! You should _never rely on the progress report for your application logic_,
//! it is only an approximate hint.
//!
//! Any buffering, like `BufRead`, should be in the inner reader.
//! Otherwise, the tracker will only be able to see occasional large byte chunks.

use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use crate::{ColorType, Progress};

/// A byte reader, like a file or a network stream.
/// Tracks how many bytes are taken from the inner reader.
/// Calls the progress callback occasionally.
/// Does not support seeking currently.
/// The progress callback will never overshoot.
pub struct TrackProgressReader<R, F: FnMut(Progress)> {
inner: R,
on_progress: Option<F>,
complete_on_drop: bool,
loaded_bytes: u64,
approximate_total_bytes: Option<u64>,
}

/// A byte writer, like a file or a network stream.
/// Tracks how many bytes are written to the inner writer.
/// Calls the progress callback occasionally.
/// Does not support seeking currently.
/// The progress callback will never overshoot.
pub struct TrackProgressWriter<R, F: FnMut(Progress)> {
inner: R,
on_progress: Option<F>,
complete_on_drop: bool,
written_bytes: u64,
approximate_total_bytes: Option<u64>,
}

impl<F: FnMut(Progress)> TrackProgressReader<File, F> {
/// Usually called before anything is decoded, but only available for files. Most precise option.
pub fn open_file(path: impl AsRef<Path>, on_progress: Option<F>) -> std::io::Result<Self> {
let file = File::open(path)?;

Ok(Self {
on_progress,
approximate_total_bytes: file.metadata().map(|m| m.len()).ok(),
complete_on_drop: true,
loaded_bytes: 0,

inner: file,
})
}
}

impl<R: Read, F: FnMut(Progress)> TrackProgressReader<R, F> {
/// Usually called after meta data has been extracted. May undershoot when file contents are compressed.
pub fn new(inner: R, on_progress: Option<F>, file_size: Option<u64>) -> Self {
Self {
on_progress,
inner,
approximate_total_bytes: file_size,
complete_on_drop: true,
loaded_bytes: 0,
}
}

/// Usually called after meta data has been extracted. May undershoot when file contents are compressed.
pub fn estimate(inner: R, on_progress: Option<F>, color: ColorType, width: u64, height: u64) -> Self {
Self::new(inner, on_progress, Some(color.bytes_per_pixel() as u64 * width * height))
}

/// Called after meta data was read, but before reading the content of the file.
pub fn update_expected_size(&mut self, file_size: u64){
self.approximate_total_bytes = Some(file_size);
}
}

impl<W: Write, F: FnMut(Progress)> TrackProgressWriter<W, F> {
/// Usually called after meta data has been extracted. May undershoot when file contents are compressed.
pub fn new(inner: W, on_progress: Option<F>, estimated_compressed_file_size: Option<u64>) -> Self {
Self {
inner,
on_progress,
approximate_total_bytes: estimated_compressed_file_size,
complete_on_drop: true,
written_bytes: 0,
}
}

/// Usually called after meta data has been extracted. May undershoot when file contents are compressed.
pub fn estimate(inner: W, on_progress: Option<F>, color: ColorType, width: u64, height: u64) -> Self {
Self::new(inner, on_progress, Some(color.bytes_per_pixel() as u64 * width * height))
}
}

impl<R,F> Read for TrackProgressReader<R, F>
where R: Read, F: FnMut(Progress),
{
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
let result = self.inner.read(buffer);

if let (Ok(count), Some(total), Some(callback)) =
(&result, self.approximate_total_bytes, &mut self.on_progress)
{
// report progress of the bytes that have definitely been processed
(callback)(new_progress(self.loaded_bytes, total));
self.loaded_bytes += count as u64;
}

result
}
}


impl<W,F> Write for TrackProgressWriter<W, F>
where W: Write, F: FnMut(Progress),
{
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
let result = self.inner.write(buffer);

if let (Ok(count), Some(total), Some(callback)) =
(&result, self.approximate_total_bytes, &mut self.on_progress)
{
self.written_bytes += count as u64;

// report progress of written state
(callback)(new_progress(self.written_bytes, total));
}

result
}

fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

/*impl<R, F: FnMut(Progress)> Drop for TrackProgressReader<R, F> {
fn drop(&mut self) {
if self.complete_on_drop {
(self.on_progress)(complete_progress(self.approximate_total_bytes))
}
}
}

impl<W, F: FnMut(Progress)> Drop for TrackProgressWriter<W, F> {
fn drop(&mut self) {
if self.complete_on_drop {
(self.on_progress)(complete_progress(self.approximate_total_bytes))
}
}
}*/

fn new_progress(current: u64, expected: u64) -> Progress {
Progress::new(current.min(expected), expected)
}

fn complete_progress(total: u64) -> Progress {
Progress::new(total, total)
}
Loading