Skip to content

Commit

Permalink
feature: Implement load_lossy
Browse files Browse the repository at this point in the history
Add support for lossy image loading.

The new `load_lossy` function adds error recovery.

If loading reaches the step where it allocates the pixel buffer, then it always succeeds: any error following this step will result in a partially filled buffer where the missing pixels will use their default "zero" value.

This allows for example to read truncated files or other minor issues.

- Closes image-rs#1752
  • Loading branch information
demurgos committed Jul 6, 2022
1 parent 04052e6 commit 6faaf1f
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/codecs/bmp/decoder.rs
Expand Up @@ -1514,7 +1514,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BmpDecoder<R> {

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(BmpReader(
Cursor::new(image::decoder_to_vec(self)?),
Cursor::new(image::decoder_to_vec(self).map_err(|(_, e)| e)?),
PhantomData,
))
}
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/gif.rs
Expand Up @@ -109,7 +109,7 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder<R> {

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(GifReader(
Cursor::new(image::decoder_to_vec(self)?),
Cursor::new(image::decoder_to_vec(self).map_err(|(_, e)| e)?),
PhantomData,
))
}
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/hdr/decoder.rs
Expand Up @@ -201,7 +201,7 @@ impl<'a, R: 'a + BufRead> ImageDecoder<'a> for HdrAdapter<R> {

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(HdrReader(
Cursor::new(image::decoder_to_vec(self)?),
Cursor::new(image::decoder_to_vec(self).map_err(|(_, e)| e)?),
PhantomData,
))
}
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/ico/decoder.rs
Expand Up @@ -296,7 +296,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder<R> {

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(IcoReader(
Cursor::new(image::decoder_to_vec(self)?),
Cursor::new(image::decoder_to_vec(self).map_err(|(_, e)| e)?),
PhantomData,
))
}
Expand Down
6 changes: 3 additions & 3 deletions src/codecs/openexr.rs
Expand Up @@ -138,7 +138,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder<R> {
/// Use `read_image` instead if possible,
/// as this method creates a whole new buffer just to contain the entire image.
fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(Cursor::new(decoder_to_vec(self)?))
Ok(Cursor::new(decoder_to_vec(self).map_err(|(_, e)| e)?))
}

fn scanline_bytes(&self) -> u64 {
Expand Down Expand Up @@ -429,7 +429,7 @@ mod test {
fn read_as_rgb_image(read: impl Read + Seek) -> ImageResult<Rgb32FImage> {
let decoder = OpenExrDecoder::with_alpha_preference(read, Some(false))?;
let (width, height) = decoder.dimensions();
let buffer: Vec<f32> = decoder_to_vec(decoder)?;
let buffer: Vec<f32> = decoder_to_vec(decoder).map_err(|(_, e)| e)?;

ImageBuffer::from_raw(width, height, buffer)
// this should be the only reason for the "from raw" call to fail,
Expand All @@ -443,7 +443,7 @@ mod test {
fn read_as_rgba_image(read: impl Read + Seek) -> ImageResult<Rgba32FImage> {
let decoder = OpenExrDecoder::with_alpha_preference(read, Some(true))?;
let (width, height) = decoder.dimensions();
let buffer: Vec<f32> = decoder_to_vec(decoder)?;
let buffer: Vec<f32> = decoder_to_vec(decoder).map_err(|(_, e)| e)?;

ImageBuffer::from_raw(width, height, buffer)
// this should be the only reason for the "from raw" call to fail,
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/pnm/decoder.rs
Expand Up @@ -615,7 +615,7 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder<R> {

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(PnmReader(
Cursor::new(image::decoder_to_vec(self)?),
Cursor::new(image::decoder_to_vec(self).map_err(|(_, e)| e)?),
PhantomData,
))
}
Expand Down
51 changes: 39 additions & 12 deletions src/dynimage.rs
Expand Up @@ -26,6 +26,7 @@ use crate::math::resize_dimensions;
use crate::traits::Pixel;
use crate::{image, Luma, LumaA};
use crate::{Rgb32FImage, Rgba32FImage};
use crate::io::free_functions::LoadErrorHandling;

/// A Dynamic Image
///
Expand Down Expand Up @@ -172,7 +173,20 @@ impl DynamicImage {

/// Decodes an encoded image into a dynamic image.
pub fn from_decoder<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult<Self> {
decoder_to_image(decoder)
Self::from_decoder_inner(decoder, LoadErrorHandling::Strict)
}

/// Decodes an encoded image into a dynamic image, even if there are some errors.
///
/// If an error is encountered while reading pixel data, use a default pixel
/// value (e.g. transparent) instead of failing the whole operation.
pub fn from_decoder_lossy<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult<Self> {
Self::from_decoder_inner(decoder, LoadErrorHandling::Lossy)
}

/// Decodes an encoded image into a dynamic image, with the supplied error handling mode.
pub(crate) fn from_decoder_inner<'a>(decoder: impl ImageDecoder<'a>, error_handling: LoadErrorHandling) -> ImageResult<Self> {
decoder_to_image(decoder, error_handling)
}

/// Returns a copy of this image as an RGB image.
Expand Down Expand Up @@ -1016,58 +1030,71 @@ impl Default for DynamicImage {
}

/// Decodes an image and stores it into a dynamic image
fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult<DynamicImage> {
fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I, error_handling: LoadErrorHandling) -> ImageResult<DynamicImage> {
let (w, h) = decoder.dimensions();
let color_type = decoder.color_type();

fn handle_result<T>(result: Result<Vec<T>, (Option<Vec<T>>, ImageError)>, error_handling: LoadErrorHandling) -> ImageResult<Vec<T>> {
match result {
Ok(buf) => Ok(buf),
Err((None, e)) => Err(e),
Err((Some(buf), e)) => {
match error_handling {
LoadErrorHandling::Strict => Err(e),
LoadErrorHandling::Lossy => Ok(buf),
}
}
}
}

let image = match color_type {
color::ColorType::Rgb8 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8)
}

color::ColorType::Rgba8 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8)
}

color::ColorType::L8 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8)
}

color::ColorType::La8 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8)
}

color::ColorType::Rgb16 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16)
}

color::ColorType::Rgba16 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16)
}

color::ColorType::Rgb32F => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb32F)
}

color::ColorType::Rgba32F => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba32F)
}

color::ColorType::L16 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16)
}

color::ColorType::La16 => {
let buf = image::decoder_to_vec(decoder)?;
let buf = handle_result(image::decoder_to_vec(decoder), error_handling)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16)
}

Expand Down
20 changes: 13 additions & 7 deletions src/image.rs
Expand Up @@ -572,20 +572,26 @@ where
/// of the output buffer is guaranteed.
///
/// Panics if there isn't enough memory to decode the image.
pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> ImageResult<Vec<T>>
///
/// If an error occurs after the pixel buffer was created, it is returned. Note
/// however that it may be incomplete. Pixels that were not read use their
/// default "zero" value (the memory is initialized and safe to access).
pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> Result<Vec<T>, (Option<Vec<T>>, ImageError)>
where
T: crate::traits::Primitive + bytemuck::Pod,
{
let total_bytes = usize::try_from(decoder.total_bytes());
if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize {
return Err(ImageError::Limits(LimitError::from_kind(
return Err((None, ImageError::Limits(LimitError::from_kind(
LimitErrorKind::InsufficientMemory,
)));
))));
}

let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / std::mem::size_of::<T>()];
decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
Ok(buf)
match decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice())) {
Ok(()) => Ok(buf),
Err(e) => Err((Some(buf), e))
}
}

/// Represents the progress of an image operation.
Expand Down Expand Up @@ -1306,7 +1312,7 @@ mod tests {
};
use crate::color::Rgba;
use crate::math::Rect;
use crate::{GrayImage, ImageBuffer};
use crate::{GrayImage, ImageBuffer, ImageError};

#[test]
#[allow(deprecated)]
Expand Down Expand Up @@ -1835,7 +1841,7 @@ mod tests {
}
assert_eq!(D.total_bytes(), u64::max_value());

let v: ImageResult<Vec<u8>> = super::decoder_to_vec(D);
let v: Result<Vec<u8>, (Option<Vec<_>>, ImageError)> = super::decoder_to_vec(D);
assert!(v.is_err());
}
}
39 changes: 34 additions & 5 deletions src/io/free_functions.rs
Expand Up @@ -23,6 +23,14 @@ pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> {
load(buffered_read, ImageFormat::from_path(path)?)
}

/// Enum describing how to handle load errors
pub(crate) enum LoadErrorHandling {
/// Fail the whole load if an error is encountered
Strict,
/// Ignore errors while reading pixel data (use a default value)
Lossy,
}

/// Create a new image from a Reader.
///
/// Assumes the reader is already buffered. For optimal performance,
Expand All @@ -34,7 +42,27 @@ pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> {
#[allow(unused_variables)]
// r is unused if no features are supported.
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
load_inner(r, super::Limits::default(), format)
load_inner(r, super::Limits::default(), format, LoadErrorHandling::Strict)
}

/// Create a new image from a Reader, even if there are some errors.
///
/// This is a lenient version of the image loader. If an error is encountered
/// when reading pixel data, it will use a default pixel value (e.g.
/// `transparent`) for those pixels instead of failing the whole read.
///
/// Use [`load`] to only accept well-formed images.
///
/// Assumes the reader is already buffered. For optimal performance,
/// consider wrapping the reader with a `BufReader::new()`.
///
/// Try [`io::Reader`] for more advanced uses.
///
/// [`io::Reader`]: io/struct.Reader.html
#[allow(unused_variables)]
// r is unused if no features are supported.
pub fn load_lossy<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
load_inner(r, super::Limits::default(), format, LoadErrorHandling::Lossy)
}

pub(crate) trait DecoderVisitor {
Expand Down Expand Up @@ -89,8 +117,9 @@ pub(crate) fn load_inner<R: BufRead + Seek>(
r: R,
limits: super::Limits,
format: ImageFormat,
error_handling: LoadErrorHandling,
) -> ImageResult<DynamicImage> {
struct LoadVisitor(super::Limits);
struct LoadVisitor(super::Limits, LoadErrorHandling);

impl DecoderVisitor for LoadVisitor {
type Result = DynamicImage;
Expand All @@ -99,16 +128,16 @@ pub(crate) fn load_inner<R: BufRead + Seek>(
self,
mut decoder: D,
) -> ImageResult<Self::Result> {
let mut limits = self.0;
let LoadVisitor(mut limits, error_handling) = self;
// Check that we do not allocate a bigger buffer than we are allowed to
// FIXME: should this rather go in `DynamicImage::from_decoder` somehow?
limits.reserve(decoder.total_bytes())?;
decoder.set_limits(limits)?;
DynamicImage::from_decoder(decoder)
DynamicImage::from_decoder_inner(decoder, error_handling)
}
}

load_decoder(r, format, LoadVisitor(limits))
load_decoder(r, format, LoadVisitor(limits, error_handling))
}

pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> {
Expand Down
3 changes: 2 additions & 1 deletion src/io/reader.rs
Expand Up @@ -6,6 +6,7 @@ use crate::dynimage::DynamicImage;
use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind};
use crate::image::ImageFormat;
use crate::{ImageError, ImageResult};
use crate::io::free_functions::LoadErrorHandling;

use super::free_functions;

Expand Down Expand Up @@ -225,7 +226,7 @@ impl<R: BufRead + Seek> Reader<R> {
/// If no format was determined, returns an `ImageError::Unsupported`.
pub fn decode(mut self) -> ImageResult<DynamicImage> {
let format = self.require_format()?;
free_functions::load_inner(self.inner, self.limits, format)
free_functions::load_inner(self.inner, self.limits, format, LoadErrorHandling::Strict)
}

fn require_format(&mut self) -> ImageResult<ImageFormat> {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -143,7 +143,7 @@ pub use crate::dynimage::{
image_dimensions, load_from_memory, load_from_memory_with_format, open, save_buffer,
save_buffer_with_format, write_buffer_with_format,
};
pub use crate::io::free_functions::{guess_format, load};
pub use crate::io::free_functions::{guess_format, load, load_lossy};

pub use crate::dynimage::DynamicImage;

Expand Down

0 comments on commit 6faaf1f

Please sign in to comment.