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

Faster JPEG decoding with jpeg_decoder #1879

Closed
wants to merge 5 commits into from
Closed
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
64 changes: 47 additions & 17 deletions src/codecs/jpeg/decoder.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ use std::mem;

use crate::color::ColorType;
use crate::error::{
DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind,
DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, UnsupportedError,
UnsupportedErrorKind,
};
use crate::image::{ImageDecoder, ImageFormat};

@@ -61,6 +62,16 @@ impl<R: Read> JpegDecoder<R> {

Ok(result)
}

// Returns the image data as a Vec<u8> with color conversions already performed
fn get_image_data(mut self) -> ImageResult<Vec<u8>> {
let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?;
data = match self.decoder.info().unwrap().pixel_format {
jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data),
_ => data,
};
Ok(data)
}
}

/// Wrapper struct around a `Cursor<Vec<u8>>`
@@ -97,27 +108,46 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for JpegDecoder<R> {
self.decoder.icc_profile().clone()
}

fn into_reader(mut self) -> ImageResult<Self::Reader> {
let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?;
data = match self.decoder.info().unwrap().pixel_format {
jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data),
_ => data,
};

Ok(JpegReader(Cursor::new(data), PhantomData))
fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(JpegReader(Cursor::new(self.get_image_data()?), PhantomData))
}

fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
buf.copy_from_slice(&self.get_image_data()?);
Ok(())
}

let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?;
data = match self.decoder.info().unwrap().pixel_format {
jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data),
_ => data,
};
// `jpeg_decoder` crate always returns a `Vec<u8>`
// and provides no option to read into a pre-allocated buffer.
// So we specialize this method to simply return the Vec whenever possible
// for a 10% to 15% reduction in decoding time.
fn read_to_vec<T>(self) -> ImageResult<Vec<T>>
where
T: crate::traits::Primitive + bytemuck::Pod,
{
let total_bytes = usize::try_from(self.total_bytes());
if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize {
return Err(ImageError::Limits(LimitError::from_kind(
LimitErrorKind::InsufficientMemory,
)));
}

buf.copy_from_slice(&data);
Ok(())
let data: Vec<u8> = self.get_image_data()?;
match bytemuck::allocation::try_cast_vec(data) {
Ok(new_vec) => Ok(new_vec),
Err((_, old_vec)) => {
// T has alignment larger than 1
// and the old Vec<u8> is actually laid out in memory in such a way
// that it's insufficiently aligned for casting to T.
// So we have to perform a copying conversion to T.
let mut new_vec =
vec![num_traits::Zero::zero(); total_bytes.unwrap() / std::mem::size_of::<T>()];
let destination: &mut [u8] = bytemuck::cast_slice_mut(new_vec.as_mut_slice());
destination.copy_from_slice(&old_vec);
Ok(new_vec)
}
}
}
}

31 changes: 21 additions & 10 deletions src/image.rs
Original file line number Diff line number Diff line change
@@ -597,16 +597,7 @@ pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> ImageResu
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,
)));
}

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)
decoder.read_to_vec()
}

/// Represents the progress of an image operation.
@@ -751,6 +742,26 @@ pub trait ImageDecoder<'a>: Sized {
Ok(())
}

/// Reads all of the bytes of a decoder into a Vec<T>.
///
/// Panics if there isn't enough memory to decode the image.
fn read_to_vec<T>(self) -> ImageResult<Vec<T>>
where
T: crate::traits::Primitive + bytemuck::Pod,
{
let total_bytes = usize::try_from(self.total_bytes());
if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize {
return Err(ImageError::Limits(LimitError::from_kind(
LimitErrorKind::InsufficientMemory,
)));
}

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

/// Set decoding limits for this decoder. See [`Limits`] for the different kinds of
/// limits that is possible to set.
///