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

Introduces functionality for optionally returning incomplete pixel data #252

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
19 changes: 16 additions & 3 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ impl<R: Read> Decoder<R> {
));
}

let mut recoverable_error = None;
let mut previous_marker = Marker::SOI;
let mut pending_marker = None;
let mut scans_processed = 0;
Expand All @@ -291,7 +292,13 @@ impl<R: Read> Decoder<R> {
loop {
let marker = match pending_marker.take() {
Some(m) => m,
None => self.read_marker()?,
None => match self.read_marker() {
Err(e) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to ingore all IO errors? see io::Error::kind() and ErrorKind::UnexpectedEof, I would start with a small set.

recoverable_error = Some(e);
break;
},
Ok(v) => v,
},
};

match marker {
Expand Down Expand Up @@ -566,10 +573,16 @@ impl<R: Read> Decoder<R> {
let frame = self.frame.as_ref().unwrap();
let preference = Self::select_worker(&frame, PreferWorkerKind::Multithreaded);

worker_scope.get_or_init_worker(
let pixels = worker_scope.get_or_init_worker(
preference,
|worker| self.decode_planes(worker, planes, planes_u16)
)
)?;

if let Some(error) = recoverable_error {
Err(Error::Recoverable { err: Box::new(error), pixels: pixels })
} else {
Ok(pixels)
}
}

fn decode_planes(
Expand Down
38 changes: 37 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ pub enum UnsupportedFeature {
}

/// Errors that can occur while decoding a JPEG image.
#[derive(Debug)]
pub enum Error {
/// The image is not formatted properly. The string contains detailed information about the
/// error.
Expand All @@ -41,6 +40,13 @@ pub enum Error {
Io(IoError),
/// An internal error occurred while decoding the image.
Internal(Box<dyn StdError + Send + Sync + 'static>), //TODO: not used, can be removed with the next version bump
/// An error that occurred during the decode, but allows for incomplete image pixel data to be returned with `try_recover()`
Recoverable {
/// Error occurred while decoding the image
err: Box<Self>,
/// Incomplete pixel data of the image
pixels: Vec<u8>
}
Comment on lines +44 to +49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the variant stored a struct RecoverableError that contains the fields instead we could later add different error information and state in a SemVer compatible upgrade. And provide methods specifically for recoverable errors, such as listing of errors or a summary of their kinds / specificity. Nothing specific comes to mind but it's been quite useful to keep the option available.

Compare roughly design of image::ImageError for some inspiration, maybe, but I don't want to cause rash complexification either. Simply these fields in a struct seems fine to me for the moment. That would answer the question of multiple errors by not having to answer it, yet 🙂

}

impl fmt::Display for Error {
Expand All @@ -50,6 +56,19 @@ impl fmt::Display for Error {
Error::Unsupported(ref feat) => write!(f, "unsupported JPEG feature: {:?}", feat),
Error::Io(ref err) => err.fmt(f),
Error::Internal(ref err) => err.fmt(f),
Error::Recoverable { ref err, .. } => err.fmt(f),
}
}
}

impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Format(err) => f.debug_tuple("Format").field(err).finish(),
Self::Unsupported(err) => f.debug_tuple("Unsupported").field(err).finish(),
Self::Io(err) => f.debug_tuple("Io").field(err).finish(),
Self::Internal(err) => f.debug_tuple("Internal").field(err).finish(),
Self::Recoverable { ref err, .. } => f.debug_tuple("Recoverable").field(err).finish(),
}
}
}
Expand All @@ -69,3 +88,20 @@ impl From<IoError> for Error {
Error::Io(err)
}
}

/// A trait that, if supported, returns incomplete image pixel data
pub trait TryRecover {
/// Returns incomplete image pixel data if supported. Otherwise, simply passes through the original value.
fn try_recover(self) -> Self;
}

impl TryRecover for Result<Vec<u8>> {
fn try_recover(self) -> Self {
self.or_else(|err| match err {
Error::Recoverable { pixels, .. } => {
Ok(pixels)
},
_ => Err(err),
})
Comment on lines +98 to +105
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather provide this as an inherent method of Error:

impl Error {
    pub fn partial_recover(self) -> Result<Vec<u8>, Error>;
}

Each usage site can easily emulate the above trait by calling Result::{map_,}or_else themselves which is even slightly more powerful by allowing other Ok-types than Vec<u8>.

}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extern crate core;
extern crate rayon;

pub use decoder::{Decoder, ImageInfo, PixelFormat};
pub use error::{Error, UnsupportedFeature};
pub use error::{Error, UnsupportedFeature, TryRecover};
pub use parser::CodingProcess;

use std::io;
Expand Down