image icon indicating copy to clipboard operation
image copied to clipboard

Lossy image loading

Open demurgos opened this issue 2 years ago • 0 comments

I would like to be able to load malformed images, such as truncated files. In particular, once the pixel buffer is allocated, return it even on error so it's possible to recover at least part of the data.

My specific use case for this functionality is to generate thumbnails for a set of files where some of them may be partially invalid (e.g. truncated).

This is more generally applicable to implement more reliable image handling. Instead of failing the whole image load, missing pixels can use a default value. The idea is to handle images similarly to browsers: they are able to display images even if they have some issues (as long as the header is valid).

Draft

The main change would be in the image::decoder_to_vec function:

 /// Reads all of the bytes of a decoder into a Vec<T>. No particular alignment
 /// of the output buffer is guaranteed.
 ///
 /// Panics if there isn't enough memory to decode the image.
 ///
+/// 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>) -> ImageResult<Vec<T>>
+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(
-            LimitErrorKind::InsufficientMemory,
-        )));
+        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))
+    }
 }

On error, return the pixel buffer anyway and let the consumer decide what do.

It may be exposed to consumers by adding a load_lossy function, as a lenient version of load where bad pixels use their zero value (instead of failing the whole operation). This is somewhat similar to the relationship between String::from_utf8_lossy and String::from_utf8.

demurgos avatar Jul 05 '22 17:07 demurgos