diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/webp/extended.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/webp/extended.rs')
-rw-r--r-- | vendor/image/src/codecs/webp/extended.rs | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/webp/extended.rs b/vendor/image/src/codecs/webp/extended.rs new file mode 100644 index 0000000..3dc6b34 --- /dev/null +++ b/vendor/image/src/codecs/webp/extended.rs @@ -0,0 +1,839 @@ +use std::convert::TryInto; +use std::io::{self, Cursor, Error, Read}; +use std::{error, fmt}; + +use super::decoder::{ + read_chunk, read_fourcc, read_len_cursor, DecoderError::ChunkHeaderInvalid, WebPRiffChunk, +}; +use super::lossless::{LosslessDecoder, LosslessFrame}; +use super::vp8::{Frame as VP8Frame, Vp8Decoder}; +use crate::error::{DecodingError, ParameterError, ParameterErrorKind}; +use crate::image::ImageFormat; +use crate::{ + ColorType, Delay, Frame, Frames, ImageError, ImageResult, Rgb, RgbImage, Rgba, RgbaImage, +}; +use byteorder::{LittleEndian, ReadBytesExt}; + +//all errors that can occur while parsing extended chunks in a WebP file +#[derive(Debug, Clone, Copy)] +enum DecoderError { + // Some bits were invalid + InfoBitsInvalid { name: &'static str, value: u32 }, + // Alpha chunk doesn't match the frame's size + AlphaChunkSizeMismatch, + // Image is too large, either for the platform's pointer size or generally + ImageTooLarge, + // Frame would go out of the canvas + FrameOutsideImage, +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DecoderError::InfoBitsInvalid { name, value } => f.write_fmt(format_args!( + "Info bits `{}` invalid, received value: {}", + name, value + )), + DecoderError::AlphaChunkSizeMismatch => { + f.write_str("Alpha chunk doesn't match the size of the frame") + } + DecoderError::ImageTooLarge => f.write_str("Image is too large to be decoded"), + DecoderError::FrameOutsideImage => { + f.write_str("Frame is too large and would go outside the image") + } + } + } +} + +impl From<DecoderError> for ImageError { + fn from(e: DecoderError) -> ImageError { + ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) + } +} + +impl error::Error for DecoderError {} + +#[derive(Debug, Clone)] +pub(crate) struct WebPExtendedInfo { + _icc_profile: bool, + _alpha: bool, + _exif_metadata: bool, + _xmp_metadata: bool, + _animation: bool, + canvas_width: u32, + canvas_height: u32, + icc_profile: Option<Vec<u8>>, +} + +#[derive(Debug)] +enum ExtendedImageData { + Animation { + frames: Vec<AnimatedFrame>, + anim_info: WebPAnimatedInfo, + }, + Static(WebPStatic), +} + +#[derive(Debug)] +pub(crate) struct ExtendedImage { + info: WebPExtendedInfo, + image: ExtendedImageData, +} + +impl ExtendedImage { + pub(crate) fn dimensions(&self) -> (u32, u32) { + (self.info.canvas_width, self.info.canvas_height) + } + + pub(crate) fn has_animation(&self) -> bool { + self.info._animation + } + + pub(crate) fn icc_profile(&self) -> Option<Vec<u8>> { + self.info.icc_profile.clone() + } + + pub(crate) fn color_type(&self) -> ColorType { + match &self.image { + ExtendedImageData::Animation { frames, .. } => &frames[0].image, + ExtendedImageData::Static(image) => image, + } + .color_type() + } + + pub(crate) fn into_frames<'a>(self) -> Frames<'a> { + struct FrameIterator { + image: ExtendedImage, + index: usize, + canvas: RgbaImage, + } + + impl Iterator for FrameIterator { + type Item = ImageResult<Frame>; + + fn next(&mut self) -> Option<Self::Item> { + if let ExtendedImageData::Animation { frames, anim_info } = &self.image.image { + let frame = frames.get(self.index); + match frame { + Some(anim_image) => { + self.index += 1; + ExtendedImage::draw_subimage( + &mut self.canvas, + anim_image, + anim_info.background_color, + ) + } + None => None, + } + } else { + None + } + } + } + + let width = self.info.canvas_width; + let height = self.info.canvas_height; + let background_color = + if let ExtendedImageData::Animation { ref anim_info, .. } = self.image { + anim_info.background_color + } else { + Rgba([0, 0, 0, 0]) + }; + + let frame_iter = FrameIterator { + image: self, + index: 0, + canvas: RgbaImage::from_pixel(width, height, background_color), + }; + + Frames::new(Box::new(frame_iter)) + } + + pub(crate) fn read_extended_chunks<R: Read>( + reader: &mut R, + mut info: WebPExtendedInfo, + ) -> ImageResult<ExtendedImage> { + let mut anim_info: Option<WebPAnimatedInfo> = None; + let mut anim_frames: Vec<AnimatedFrame> = Vec::new(); + let mut static_frame: Option<WebPStatic> = None; + //go until end of file and while chunk headers are valid + while let Some((mut cursor, chunk)) = read_extended_chunk(reader)? { + match chunk { + WebPRiffChunk::EXIF | WebPRiffChunk::XMP => { + //ignore these chunks + } + WebPRiffChunk::ANIM => { + if anim_info.is_none() { + anim_info = Some(Self::read_anim_info(&mut cursor)?); + } + } + WebPRiffChunk::ANMF => { + let frame = read_anim_frame(cursor, info.canvas_width, info.canvas_height)?; + anim_frames.push(frame); + } + WebPRiffChunk::ALPH => { + if static_frame.is_none() { + let alpha_chunk = + read_alpha_chunk(&mut cursor, info.canvas_width, info.canvas_height)?; + + let vp8_frame = read_lossy_with_chunk(reader)?; + + let img = WebPStatic::from_alpha_lossy(alpha_chunk, vp8_frame)?; + + static_frame = Some(img); + } + } + WebPRiffChunk::ICCP => { + let mut icc_profile = Vec::new(); + cursor.read_to_end(&mut icc_profile)?; + info.icc_profile = Some(icc_profile); + } + WebPRiffChunk::VP8 => { + if static_frame.is_none() { + let vp8_frame = read_lossy(cursor)?; + + let img = WebPStatic::from_lossy(vp8_frame)?; + + static_frame = Some(img); + } + } + WebPRiffChunk::VP8L => { + if static_frame.is_none() { + let mut lossless_decoder = LosslessDecoder::new(cursor); + let frame = lossless_decoder.decode_frame()?; + let image = WebPStatic::Lossless(frame.clone()); + + static_frame = Some(image); + } + } + _ => return Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()), + } + } + + let image = if let Some(info) = anim_info { + if anim_frames.is_empty() { + return Err(ImageError::IoError(Error::from( + io::ErrorKind::UnexpectedEof, + ))); + } + ExtendedImageData::Animation { + frames: anim_frames, + anim_info: info, + } + } else if let Some(frame) = static_frame { + ExtendedImageData::Static(frame) + } else { + //reached end of file too early before image data was reached + return Err(ImageError::IoError(Error::from( + io::ErrorKind::UnexpectedEof, + ))); + }; + + let image = ExtendedImage { image, info }; + + Ok(image) + } + + fn read_anim_info<R: Read>(reader: &mut R) -> ImageResult<WebPAnimatedInfo> { + let mut colors: [u8; 4] = [0; 4]; + reader.read_exact(&mut colors)?; + + //background color is [blue, green, red, alpha] + let background_color = Rgba([colors[2], colors[1], colors[0], colors[3]]); + + let loop_count = reader.read_u16::<LittleEndian>()?; + + let info = WebPAnimatedInfo { + background_color, + _loop_count: loop_count, + }; + + Ok(info) + } + + fn draw_subimage( + canvas: &mut RgbaImage, + anim_image: &AnimatedFrame, + background_color: Rgba<u8>, + ) -> Option<ImageResult<Frame>> { + let mut buffer = vec![0; anim_image.image.get_buf_size()]; + anim_image.image.fill_buf(&mut buffer); + let has_alpha = anim_image.image.has_alpha(); + let pixel_len: u32 = anim_image.image.color_type().bytes_per_pixel().into(); + + 'x: for x in 0..anim_image.width { + for y in 0..anim_image.height { + let canvas_index: (u32, u32) = (x + anim_image.offset_x, y + anim_image.offset_y); + // Negative offsets are not possible due to unsigned ints + // If we go out of bounds by height, still continue by x + if canvas_index.1 >= canvas.height() { + continue 'x; + } + // If we go out of bounds by width, it doesn't make sense to continue at all + if canvas_index.0 >= canvas.width() { + break 'x; + } + let index: usize = ((y * anim_image.width + x) * pixel_len).try_into().unwrap(); + canvas[canvas_index] = if anim_image.use_alpha_blending && has_alpha { + let buffer: [u8; 4] = buffer[index..][..4].try_into().unwrap(); + ExtendedImage::do_alpha_blending(buffer, canvas[canvas_index]) + } else { + Rgba([ + buffer[index], + buffer[index + 1], + buffer[index + 2], + if has_alpha { buffer[index + 3] } else { 255 }, + ]) + }; + } + } + + let delay = Delay::from_numer_denom_ms(anim_image.duration, 1); + let img = canvas.clone(); + let frame = Frame::from_parts(img, 0, 0, delay); + + if anim_image.dispose { + for x in 0..anim_image.width { + for y in 0..anim_image.height { + let canvas_index = (x + anim_image.offset_x, y + anim_image.offset_y); + canvas[canvas_index] = background_color; + } + } + } + + Some(Ok(frame)) + } + + fn do_alpha_blending(buffer: [u8; 4], canvas: Rgba<u8>) -> Rgba<u8> { + let canvas_alpha = f64::from(canvas[3]); + let buffer_alpha = f64::from(buffer[3]); + let blend_alpha_f64 = buffer_alpha + canvas_alpha * (1.0 - buffer_alpha / 255.0); + //value should be between 0 and 255, this truncates the fractional part + let blend_alpha: u8 = blend_alpha_f64 as u8; + + let blend_rgb: [u8; 3] = if blend_alpha == 0 { + [0, 0, 0] + } else { + let mut rgb = [0u8; 3]; + for i in 0..3 { + let canvas_f64 = f64::from(canvas[i]); + let buffer_f64 = f64::from(buffer[i]); + + let val = (buffer_f64 * buffer_alpha + + canvas_f64 * canvas_alpha * (1.0 - buffer_alpha / 255.0)) + / blend_alpha_f64; + //value should be between 0 and 255, this truncates the fractional part + rgb[i] = val as u8; + } + + rgb + }; + + Rgba([blend_rgb[0], blend_rgb[1], blend_rgb[2], blend_alpha]) + } + + pub(crate) fn fill_buf(&self, buf: &mut [u8]) { + match &self.image { + // will always have at least one frame + ExtendedImageData::Animation { frames, anim_info } => { + let first_frame = &frames[0]; + let (canvas_width, canvas_height) = self.dimensions(); + if canvas_width == first_frame.width && canvas_height == first_frame.height { + first_frame.image.fill_buf(buf); + } else { + let bg_color = match &self.info._alpha { + true => Rgba::from([0, 0, 0, 0]), + false => anim_info.background_color, + }; + let mut canvas = RgbaImage::from_pixel(canvas_width, canvas_height, bg_color); + let _ = ExtendedImage::draw_subimage(&mut canvas, first_frame, bg_color) + .unwrap() + .unwrap(); + buf.copy_from_slice(canvas.into_raw().as_slice()); + } + } + ExtendedImageData::Static(image) => { + image.fill_buf(buf); + } + } + } + + pub(crate) fn get_buf_size(&self) -> usize { + match &self.image { + // will always have at least one frame + ExtendedImageData::Animation { frames, .. } => &frames[0].image, + ExtendedImageData::Static(image) => image, + } + .get_buf_size() + } + + pub(crate) fn set_background_color(&mut self, color: Rgba<u8>) -> ImageResult<()> { + match &mut self.image { + ExtendedImageData::Animation { anim_info, .. } => { + anim_info.background_color = color; + Ok(()) + } + _ => Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "Background color can only be set on animated webp".to_owned(), + ), + ))), + } + } +} + +#[derive(Debug)] +enum WebPStatic { + LossyWithAlpha(RgbaImage), + LossyWithoutAlpha(RgbImage), + Lossless(LosslessFrame), +} + +impl WebPStatic { + pub(crate) fn from_alpha_lossy( + alpha: AlphaChunk, + vp8_frame: VP8Frame, + ) -> ImageResult<WebPStatic> { + if alpha.data.len() != usize::from(vp8_frame.width) * usize::from(vp8_frame.height) { + return Err(DecoderError::AlphaChunkSizeMismatch.into()); + } + + let size = usize::from(vp8_frame.width).checked_mul(usize::from(vp8_frame.height) * 4); + let mut image_vec = match size { + Some(size) => vec![0u8; size], + None => return Err(DecoderError::ImageTooLarge.into()), + }; + + vp8_frame.fill_rgba(&mut image_vec); + + for y in 0..vp8_frame.height { + for x in 0..vp8_frame.width { + let predictor: u8 = WebPStatic::get_predictor( + x.into(), + y.into(), + vp8_frame.width.into(), + alpha.filtering_method, + &image_vec, + ); + let predictor = u16::from(predictor); + + let alpha_index = usize::from(y) * usize::from(vp8_frame.width) + usize::from(x); + let alpha_val = alpha.data[alpha_index]; + let alpha: u8 = ((predictor + u16::from(alpha_val)) % 256) + .try_into() + .unwrap(); + + let alpha_index = alpha_index * 4 + 3; + image_vec[alpha_index] = alpha; + } + } + + let image = RgbaImage::from_vec(vp8_frame.width.into(), vp8_frame.height.into(), image_vec) + .unwrap(); + + Ok(WebPStatic::LossyWithAlpha(image)) + } + + fn get_predictor( + x: usize, + y: usize, + width: usize, + filtering_method: FilteringMethod, + image_slice: &[u8], + ) -> u8 { + match filtering_method { + FilteringMethod::None => 0, + FilteringMethod::Horizontal => { + if x == 0 && y == 0 { + 0 + } else if x == 0 { + let index = (y - 1) * width + x; + image_slice[index * 4 + 3] + } else { + let index = y * width + x - 1; + image_slice[index * 4 + 3] + } + } + FilteringMethod::Vertical => { + if x == 0 && y == 0 { + 0 + } else if y == 0 { + let index = y * width + x - 1; + image_slice[index * 4 + 3] + } else { + let index = (y - 1) * width + x; + image_slice[index * 4 + 3] + } + } + FilteringMethod::Gradient => { + let (left, top, top_left) = match (x, y) { + (0, 0) => (0, 0, 0), + (0, y) => { + let above_index = (y - 1) * width + x; + let val = image_slice[above_index * 4 + 3]; + (val, val, val) + } + (x, 0) => { + let before_index = y * width + x - 1; + let val = image_slice[before_index * 4 + 3]; + (val, val, val) + } + (x, y) => { + let left_index = y * width + x - 1; + let left = image_slice[left_index * 4 + 3]; + let top_index = (y - 1) * width + x; + let top = image_slice[top_index * 4 + 3]; + let top_left_index = (y - 1) * width + x - 1; + let top_left = image_slice[top_left_index * 4 + 3]; + + (left, top, top_left) + } + }; + + let combination = i16::from(left) + i16::from(top) - i16::from(top_left); + i16::clamp(combination, 0, 255).try_into().unwrap() + } + } + } + + pub(crate) fn from_lossy(vp8_frame: VP8Frame) -> ImageResult<WebPStatic> { + let mut image = RgbImage::from_pixel( + vp8_frame.width.into(), + vp8_frame.height.into(), + Rgb([0, 0, 0]), + ); + + vp8_frame.fill_rgb(&mut image); + + Ok(WebPStatic::LossyWithoutAlpha(image)) + } + + pub(crate) fn fill_buf(&self, buf: &mut [u8]) { + match self { + WebPStatic::LossyWithAlpha(image) => { + buf.copy_from_slice(image); + } + WebPStatic::LossyWithoutAlpha(image) => { + buf.copy_from_slice(image); + } + WebPStatic::Lossless(lossless) => { + lossless.fill_rgba(buf); + } + } + } + + pub(crate) fn get_buf_size(&self) -> usize { + match self { + WebPStatic::LossyWithAlpha(rgb_image) => rgb_image.len(), + WebPStatic::LossyWithoutAlpha(rgba_image) => rgba_image.len(), + WebPStatic::Lossless(lossless) => lossless.get_buf_size(), + } + } + + pub(crate) fn color_type(&self) -> ColorType { + if self.has_alpha() { + ColorType::Rgba8 + } else { + ColorType::Rgb8 + } + } + + pub(crate) fn has_alpha(&self) -> bool { + match self { + Self::LossyWithAlpha(..) | Self::Lossless(..) => true, + Self::LossyWithoutAlpha(..) => false, + } + } +} + +#[derive(Debug)] +struct WebPAnimatedInfo { + background_color: Rgba<u8>, + _loop_count: u16, +} + +#[derive(Debug)] +struct AnimatedFrame { + offset_x: u32, + offset_y: u32, + width: u32, + height: u32, + duration: u32, + use_alpha_blending: bool, + dispose: bool, + image: WebPStatic, +} + +/// Reads a chunk, but silently ignores unknown chunks at the end of a file +fn read_extended_chunk<R>(r: &mut R) -> ImageResult<Option<(Cursor<Vec<u8>>, WebPRiffChunk)>> +where + R: Read, +{ + let mut unknown_chunk = Ok(()); + + while let Some(chunk) = read_fourcc(r)? { + let cursor = read_len_cursor(r)?; + match chunk { + Ok(chunk) => return unknown_chunk.and(Ok(Some((cursor, chunk)))), + Err(err) => unknown_chunk = unknown_chunk.and(Err(err)), + } + } + + Ok(None) +} + +pub(crate) fn read_extended_header<R: Read>(reader: &mut R) -> ImageResult<WebPExtendedInfo> { + let chunk_flags = reader.read_u8()?; + + let reserved_first = chunk_flags & 0b11000000; + let icc_profile = chunk_flags & 0b00100000 != 0; + let alpha = chunk_flags & 0b00010000 != 0; + let exif_metadata = chunk_flags & 0b00001000 != 0; + let xmp_metadata = chunk_flags & 0b00000100 != 0; + let animation = chunk_flags & 0b00000010 != 0; + let reserved_second = chunk_flags & 0b00000001; + + let reserved_third = read_3_bytes(reader)?; + + if reserved_first != 0 || reserved_second != 0 || reserved_third != 0 { + let value: u32 = if reserved_first != 0 { + reserved_first.into() + } else if reserved_second != 0 { + reserved_second.into() + } else { + reserved_third + }; + return Err(DecoderError::InfoBitsInvalid { + name: "reserved", + value, + } + .into()); + } + + let canvas_width = read_3_bytes(reader)? + 1; + let canvas_height = read_3_bytes(reader)? + 1; + + //product of canvas dimensions cannot be larger than u32 max + if u32::checked_mul(canvas_width, canvas_height).is_none() { + return Err(DecoderError::ImageTooLarge.into()); + } + + let info = WebPExtendedInfo { + _icc_profile: icc_profile, + _alpha: alpha, + _exif_metadata: exif_metadata, + _xmp_metadata: xmp_metadata, + _animation: animation, + canvas_width, + canvas_height, + icc_profile: None, + }; + + Ok(info) +} + +fn read_anim_frame<R: Read>( + mut reader: R, + canvas_width: u32, + canvas_height: u32, +) -> ImageResult<AnimatedFrame> { + //offsets for the frames are twice the values + let frame_x = read_3_bytes(&mut reader)? * 2; + let frame_y = read_3_bytes(&mut reader)? * 2; + + let frame_width = read_3_bytes(&mut reader)? + 1; + let frame_height = read_3_bytes(&mut reader)? + 1; + + if frame_x + frame_width > canvas_width || frame_y + frame_height > canvas_height { + return Err(DecoderError::FrameOutsideImage.into()); + } + + let duration = read_3_bytes(&mut reader)?; + + let frame_info = reader.read_u8()?; + let reserved = frame_info & 0b11111100; + if reserved != 0 { + return Err(DecoderError::InfoBitsInvalid { + name: "reserved", + value: reserved.into(), + } + .into()); + } + let use_alpha_blending = frame_info & 0b00000010 == 0; + let dispose = frame_info & 0b00000001 != 0; + + //read normal bitstream now + let static_image = read_image(&mut reader, frame_width, frame_height)?; + + let frame = AnimatedFrame { + offset_x: frame_x, + offset_y: frame_y, + width: frame_width, + height: frame_height, + duration, + use_alpha_blending, + dispose, + image: static_image, + }; + + Ok(frame) +} + +fn read_3_bytes<R: Read>(reader: &mut R) -> ImageResult<u32> { + let mut buffer: [u8; 3] = [0; 3]; + reader.read_exact(&mut buffer)?; + let value: u32 = + (u32::from(buffer[2]) << 16) | (u32::from(buffer[1]) << 8) | u32::from(buffer[0]); + Ok(value) +} + +fn read_lossy_with_chunk<R: Read>(reader: &mut R) -> ImageResult<VP8Frame> { + let (cursor, chunk) = + read_chunk(reader)?.ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?; + + if chunk != WebPRiffChunk::VP8 { + return Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()); + } + + read_lossy(cursor) +} + +fn read_lossy(cursor: Cursor<Vec<u8>>) -> ImageResult<VP8Frame> { + let mut vp8_decoder = Vp8Decoder::new(cursor); + let frame = vp8_decoder.decode_frame()?; + + Ok(frame.clone()) +} + +fn read_image<R: Read>(reader: &mut R, width: u32, height: u32) -> ImageResult<WebPStatic> { + let chunk = read_chunk(reader)?; + + match chunk { + Some((cursor, WebPRiffChunk::VP8)) => { + let mut vp8_decoder = Vp8Decoder::new(cursor); + let frame = vp8_decoder.decode_frame()?; + + let img = WebPStatic::from_lossy(frame.clone())?; + + Ok(img) + } + Some((cursor, WebPRiffChunk::VP8L)) => { + let mut lossless_decoder = LosslessDecoder::new(cursor); + let frame = lossless_decoder.decode_frame()?; + + let img = WebPStatic::Lossless(frame.clone()); + + Ok(img) + } + Some((mut cursor, WebPRiffChunk::ALPH)) => { + let alpha_chunk = read_alpha_chunk(&mut cursor, width, height)?; + + let vp8_frame = read_lossy_with_chunk(reader)?; + + let img = WebPStatic::from_alpha_lossy(alpha_chunk, vp8_frame)?; + + Ok(img) + } + None => Err(ImageError::IoError(Error::from( + io::ErrorKind::UnexpectedEof, + ))), + Some((_, chunk)) => Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()), + } +} + +#[derive(Debug)] +struct AlphaChunk { + _preprocessing: bool, + filtering_method: FilteringMethod, + data: Vec<u8>, +} + +#[derive(Debug, Copy, Clone)] +enum FilteringMethod { + None, + Horizontal, + Vertical, + Gradient, +} + +fn read_alpha_chunk<R: Read>(reader: &mut R, width: u32, height: u32) -> ImageResult<AlphaChunk> { + let info_byte = reader.read_u8()?; + + let reserved = info_byte & 0b11000000; + let preprocessing = (info_byte & 0b00110000) >> 4; + let filtering = (info_byte & 0b00001100) >> 2; + let compression = info_byte & 0b00000011; + + if reserved != 0 { + return Err(DecoderError::InfoBitsInvalid { + name: "reserved", + value: reserved.into(), + } + .into()); + } + + let preprocessing = match preprocessing { + 0 => false, + 1 => true, + _ => { + return Err(DecoderError::InfoBitsInvalid { + name: "reserved", + value: preprocessing.into(), + } + .into()) + } + }; + + let filtering_method = match filtering { + 0 => FilteringMethod::None, + 1 => FilteringMethod::Horizontal, + 2 => FilteringMethod::Vertical, + 3 => FilteringMethod::Gradient, + _ => unreachable!(), + }; + + let lossless_compression = match compression { + 0 => false, + 1 => true, + _ => { + return Err(DecoderError::InfoBitsInvalid { + name: "lossless compression", + value: compression.into(), + } + .into()) + } + }; + + let mut framedata = Vec::new(); + reader.read_to_end(&mut framedata)?; + + let data = if lossless_compression { + let cursor = io::Cursor::new(framedata); + + let mut decoder = LosslessDecoder::new(cursor); + //this is a potential problem for large images; would require rewriting lossless decoder to use u32 for width and height + let width: u16 = width + .try_into() + .map_err(|_| ImageError::from(DecoderError::ImageTooLarge))?; + let height: u16 = height + .try_into() + .map_err(|_| ImageError::from(DecoderError::ImageTooLarge))?; + let frame = decoder.decode_frame_implicit_dims(width, height)?; + + let mut data = vec![0u8; usize::from(width) * usize::from(height)]; + + frame.fill_green(&mut data); + + data + } else { + framedata + }; + + let chunk = AlphaChunk { + _preprocessing: preprocessing, + filtering_method, + data, + }; + + Ok(chunk) +} |