diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1e17f3e..d7d98c9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - rust: ["1.62.0", stable, beta, nightly] + rust: ["1.81.0", stable, beta, nightly] features: ["", "std", "color_quant"] steps: - uses: actions/checkout@v2 @@ -23,7 +23,6 @@ jobs: - name: test run: > cargo test --tests --benches --no-default-features --features "$FEATURES" - if: ${{ matrix.rust != '1.62.0' }} env: FEATURES: ${{ matrix.features }} rustfmt: diff --git a/Cargo.toml b/Cargo.toml index 2834821..a91ce42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,13 @@ repository = "https://github.com/image-rs/image-gif" documentation = "https://docs.rs/gif" edition = "2021" include = ["src/**", "LICENSE-*", "README.md", "benches/*.rs"] -rust-version = "1.62" +rust-version = "1.81" [lib] bench = false [dependencies] -weezl = "0.1.8" +weezl = { version = "0.1.8", default-features = false, features = ["alloc"] } color_quant = { version = "1.1", optional = true } [dev-dependencies] @@ -32,7 +32,7 @@ default = ["raii_no_panic", "std", "color_quant"] raii_no_panic = [] color_quant = ["dep:color_quant"] # Reservation for a feature turning off std -std = [] +std = ["weezl/std"] [[bench]] name = "decode" diff --git a/benches/decode.rs b/benches/decode.rs index 94977d1..8862751 100644 --- a/benches/decode.rs +++ b/benches/decode.rs @@ -1,4 +1,6 @@ -use criterion::{black_box, BenchmarkId, BenchmarkGroup, Criterion, Throughput, measurement::Measurement}; +use criterion::{ + black_box, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput, +}; use gif::Decoder; fn read_image(image: &[u8]) -> Option> { @@ -42,38 +44,49 @@ fn main() { let mut c = Criterion::default().configure_from_args(); let mut group = c.benchmark_group("gif"); - run_bench_def(&mut group, BenchDef { - data: include_bytes!("note.gif"), - id: "note.gif", - sample_size: 100, - }); + run_bench_def( + &mut group, + BenchDef { + data: include_bytes!("note.gif"), + id: "note.gif", + sample_size: 100, + }, + ); - run_bench_def(&mut group, BenchDef { - data: include_bytes!("photo.gif"), - id: "photo.gif", - sample_size: 20, - }); + run_bench_def( + &mut group, + BenchDef { + data: include_bytes!("photo.gif"), + id: "photo.gif", + sample_size: 20, + }, + ); - run_bench_def(&mut group, BenchDef { - data: include_bytes!("../tests/samples/sample_1.gif"), - id: "sample_1.gif", - sample_size: 100, - }); + run_bench_def( + &mut group, + BenchDef { + data: include_bytes!("../tests/samples/sample_1.gif"), + id: "sample_1.gif", + sample_size: 100, + }, + ); - run_bench_def(&mut group, BenchDef { - data: include_bytes!("../tests/samples/sample_big.gif"), - id: "sample_big.gif", - sample_size: 20, - }); + run_bench_def( + &mut group, + BenchDef { + data: include_bytes!("../tests/samples/sample_big.gif"), + id: "sample_big.gif", + sample_size: 20, + }, + ); - group - .bench_with_input( - "extract-metadata-note", - include_bytes!("note.gif"), - |b, input| { - b.iter(|| read_metadata(input)); - } - ); + group.bench_with_input( + "extract-metadata-note", + include_bytes!("note.gif"), + |b, input| { + b.iter(|| read_metadata(input)); + }, + ); group.finish(); diff --git a/benches/rgb_frame.rs b/benches/rgb_frame.rs index 6e9bc09..e58dc57 100644 --- a/benches/rgb_frame.rs +++ b/benches/rgb_frame.rs @@ -35,20 +35,20 @@ fn main() { group .sample_size(50) .throughput(Throughput::Bytes(size as u64)) - .bench_function(path.file_name().unwrap().to_str().unwrap(), - |b| { - match info.color_type { - png::ColorType::Rgb => b.iter(|| { - Frame::from_rgb_speed(w, h, &mut buf[..size], 30) - }), - png::ColorType::Rgba => b.iter(|| { - Frame::from_rgba_speed(w, h, &mut buf[..size], 30) - }), + .bench_function( + path.file_name().unwrap().to_str().unwrap(), + |b| match info.color_type { + png::ColorType::Rgb => { + b.iter(|| Frame::from_rgb_speed(w, h, &mut buf[..size], 30)) + } + png::ColorType::Rgba => { + b.iter(|| Frame::from_rgba_speed(w, h, &mut buf[..size], 30)) + } c => { println!("Image has wrong color type: {c:?}"); } - } - }); + }, + ); // actually write the image as a singe frame gif... while MSE can be used // for quality check, it might not be as good as visual inspection diff --git a/examples/check.rs b/examples/check.rs index 03dc294..ea288f2 100644 --- a/examples/check.rs +++ b/examples/check.rs @@ -1,10 +1,8 @@ use std::{env, fs, process}; fn main() { - let file = env::args().nth(1) - .unwrap_or_else(|| explain_usage()); - let file = fs::File::open(file) - .expect("failed to open input file"); + let file = env::args().nth(1).unwrap_or_else(|| explain_usage()); + let file = fs::File::open(file).expect("failed to open input file"); let mut reader = { let mut options = gif::DecodeOptions::new(); options.allow_unknown_blocks(true); @@ -28,7 +26,10 @@ fn main() { dispose: {:?}\n \ needs_input: {:?}", frame.delay, - frame.width, frame.height, frame.left, frame.top, + frame.width, + frame.height, + frame.left, + frame.top, frame.dispose, frame.needs_user_input ); diff --git a/examples/parallel.rs b/examples/parallel.rs index 893368c..28475c4 100644 --- a/examples/parallel.rs +++ b/examples/parallel.rs @@ -40,17 +40,21 @@ fn main() -> Result<(), Box> { let (send, recv) = std::sync::mpsc::channel(); - decoder.into_iter().enumerate().par_bridge().try_for_each(move |(frame_number, frame)| { - let mut frame = frame?; - FrameDecoder::new(DecodeOptions::new()) - .decode_lzw_encoded_frame(&mut frame) - .unwrap(); - // frame is now pixels - frame.make_lzw_pre_encoded(); - // frame is now LZW again, re-encoded - send.send((frame_number, frame)).unwrap(); - Ok::<_, gif::DecodingError>(()) - })?; + decoder + .into_iter() + .enumerate() + .par_bridge() + .try_for_each(move |(frame_number, frame)| { + let mut frame = frame?; + FrameDecoder::new(DecodeOptions::new()) + .decode_lzw_encoded_frame(&mut frame) + .unwrap(); + // frame is now pixels + frame.make_lzw_pre_encoded(); + // frame is now LZW again, re-encoded + send.send((frame_number, frame)).unwrap(); + Ok::<_, gif::DecodingError>(()) + })?; // Decoding and encoding can happen in parallel, but writing to the GIF file is sequential let mut next_frame_number = 0; @@ -59,7 +63,10 @@ fn main() -> Result<(), Box> { // frames can arrive in any order, since they're processed in parallel, // so they have to be stored in a queue frames_to_process.push((frame_number, frame)); - while let Some(index) = frames_to_process.iter().position(|&(num, _)| num == next_frame_number) { + while let Some(index) = frames_to_process + .iter() + .position(|&(num, _)| num == next_frame_number) + { let frame = frames_to_process.remove(index).1; encoder.write_lzw_pre_encoded_frame(&frame)?; next_frame_number += 1; @@ -70,6 +77,13 @@ fn main() -> Result<(), Box> { let seconds = start.elapsed().as_millis() as f64 / 1000.; let rate = (input_size / 1024 / 1024) as f64 / seconds; - eprintln!("Finished in {seconds:0.2}s, {rate:0.0}MiB/s {}", if cfg!(debug_assertions) { ". Run with --release for more speed." } else { "" }); + eprintln!( + "Finished in {seconds:0.2}s, {rate:0.0}MiB/s {}", + if cfg!(debug_assertions) { + ". Run with --release for more speed." + } else { + "" + } + ); Ok(()) } diff --git a/src/common.rs b/src/common.rs index 2458d06..7510343 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,10 @@ -use std::borrow::Cow; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::error; +use core::fmt; + #[cfg(feature = "color_quant")] -use std::collections::{HashMap, HashSet}; +use alloc::collections::{BTreeMap, BTreeSet}; /// Disposal method #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -212,7 +216,10 @@ impl Frame<'static> { #[track_caller] pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Self { assert_eq!(width as usize * height as usize * 4, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame"); - assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]"); + assert!( + speed >= 1 && speed <= 30, + "speed needs to be in the range [1, 30]" + ); let mut transparent = None; for pix in pixels.chunks_exact_mut(4) { if pix[3] != 0 { @@ -224,7 +231,7 @@ impl Frame<'static> { // Attempt to build a palette of all colors. If we go over 256 colors, // switch to the NeuQuant algorithm. - let mut colors: HashSet<(u8, u8, u8, u8)> = HashSet::new(); + let mut colors: BTreeSet<(u8, u8, u8, u8)> = BTreeSet::new(); for pixel in pixels.chunks_exact(4) { if colors.insert((pixel[0], pixel[1], pixel[2], pixel[3])) && colors.len() > 256 { // > 256 colours, let's use NeuQuant. @@ -233,7 +240,12 @@ impl Frame<'static> { return Frame { width, height, - buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()), + buffer: Cow::Owned( + pixels + .chunks_exact(4) + .map(|pix| nq.index_of(pix) as u8) + .collect(), + ), palette: Some(nq.color_map_rgb()), transparent: transparent.map(|t| nq.index_of(&t) as u8), ..Frame::default() @@ -244,11 +256,19 @@ impl Frame<'static> { // Palette size <= 256 elements, we can build an exact palette. let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect(); colors_vec.sort_unstable(); - let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect(); - let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect(); + let palette = colors_vec + .iter() + .flat_map(|&(r, g, b, _a)| [r, g, b]) + .collect(); + let colors_lookup: BTreeMap<(u8, u8, u8, u8), u8> = + colors_vec.into_iter().zip(0..=255).collect(); - let index_of = | pixel: &[u8] | - colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).copied().unwrap_or(0); + let index_of = |pixel: &[u8]| { + colors_lookup + .get(&(pixel[0], pixel[1], pixel[2], pixel[3])) + .copied() + .unwrap_or(0) + }; Frame { width, @@ -266,11 +286,24 @@ impl Frame<'static> { /// * If the length of pixels does not equal `width * height`. /// * If the length of palette > `256 * 3`. #[track_caller] - pub fn from_palette_pixels(width: u16, height: u16, pixels: impl Into>, palette: impl Into>, transparent: Option) -> Self { + pub fn from_palette_pixels( + width: u16, + height: u16, + pixels: impl Into>, + palette: impl Into>, + transparent: Option, + ) -> Self { let pixels = pixels.into(); let palette = palette.into(); - assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame"); - assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame"); + assert_eq!( + width as usize * height as usize, + pixels.len(), + "Too many or too little pixels for the given width and height to create a GIF Frame" + ); + assert!( + palette.len() <= 256 * 3, + "Too many palette values to create a GIF Frame" + ); Frame { width, @@ -287,9 +320,18 @@ impl Frame<'static> { /// # Panics: /// * If the length of pixels does not equal `width * height`. #[track_caller] - pub fn from_indexed_pixels(width: u16, height: u16, pixels: impl Into>, transparent: Option) -> Self { + pub fn from_indexed_pixels( + width: u16, + height: u16, + pixels: impl Into>, + transparent: Option, + ) -> Self { let pixels = pixels.into(); - assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame"); + assert_eq!( + width as usize * height as usize, + pixels.len(), + "Too many or too little pixels for the given width and height to create a GIF Frame" + ); Frame { width, @@ -338,7 +380,8 @@ impl Frame<'static> { pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Self { assert_eq!(width as usize * height as usize * 3, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame"); let mut vec: Vec = Vec::new(); - vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM"); + vec.try_reserve_exact(pixels.len() + width as usize * height as usize) + .expect("OOM"); for v in pixels.chunks_exact(3) { vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]); } @@ -358,12 +401,29 @@ impl Frame<'static> { width: self.width, height: self.height, interlaced: self.interlaced, - palette: std::mem::take(&mut self.palette), - buffer: std::mem::replace(&mut self.buffer, Cow::Borrowed(&[])), + palette: core::mem::take(&mut self.palette), + buffer: core::mem::replace(&mut self.buffer, Cow::Borrowed(&[])), } } } +/// Wrapper for an error-like `T` which may not itself implement [`Error`](error::Error). +pub(crate) struct WrappedError(pub T); + +impl fmt::Debug for WrappedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(&self.0, f) + } +} + +impl fmt::Display for WrappedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(&self.0, f) + } +} + +impl error::Error for WrappedError {} + #[test] #[cfg(feature = "color_quant")] // Creating the `colors_lookup` hashmap in Frame::from_rgba_speed panics due to diff --git a/src/encoder.rs b/src/encoder.rs index 1933478..4e09275 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,14 +1,18 @@ //! # Minimal gif encoder -use std::io; -use std::io::prelude::*; -use std::fmt; -use std::error; -use std::borrow::Cow; -use weezl::{BitOrder, encode::Encoder as LzwEncoder}; +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::error; +use core::fmt; + +use weezl::{encode::Encoder as LzwEncoder, BitOrder}; -use crate::traits::WriteBytesExt; use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; +use crate::traits::{Write, WriteBytesExt as _}; + +#[cfg(feature = "std")] +use {crate::traits::std_impls::IoWriter, std::io}; /// The image has incorrect properties, making it impossible to encode as a gif. #[derive(Debug)] @@ -20,6 +24,8 @@ pub enum EncodingFormatError { MissingColorPalette, /// LZW data is not valid for GIF. This may happen when wrong buffer is given to `write_lzw_pre_encoded_frame` InvalidMinCodeSize, + /// Returned if the buffer associated with a frame is not large enough for the amount of data it claims. + BufferImproperlySized, } impl error::Error for EncodingFormatError {} @@ -28,19 +34,28 @@ impl fmt::Display for EncodingFormatError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::TooManyColors => write!(fmt, "the image has too many colors"), - Self::MissingColorPalette => write!(fmt, "the GIF format requires a color palette but none was given"), + Self::MissingColorPalette => write!( + fmt, + "the GIF format requires a color palette but none was given" + ), Self::InvalidMinCodeSize => write!(fmt, "LZW data is invalid"), + Self::BufferImproperlySized => write!(fmt, "Frame buffer improperly sized"), } } } -#[derive(Debug)] /// Encoding error. +#[derive(Debug)] +#[non_exhaustive] pub enum EncodingError { /// Returned if the to image is not encodable as a gif. Format(EncodingFormatError), - /// Wraps `std::io::Error`. - Io(io::Error), + /// Returned if the encoder was not provided a writer. + MissingWriter, + /// Returned if the encoder could not allocate enough memory to proceed. + OutOfMemory, + /// Returned when an IO error has occurred. + Io(Box), } impl fmt::Display for EncodingError { @@ -48,6 +63,8 @@ impl fmt::Display for EncodingError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Io(err) => err.fmt(fmt), + Self::MissingWriter => fmt.write_str("Missing Writer"), + Self::OutOfMemory => fmt.write_str("Out of Memory"), Self::Format(err) => err.fmt(fmt), } } @@ -57,16 +74,26 @@ impl error::Error for EncodingError { #[cold] fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { - Self::Io(err) => Some(err), + Self::Io(err) => Some(err.as_ref()), + Self::MissingWriter => None, + Self::OutOfMemory => None, Self::Format(err) => Some(err), } } } +impl EncodingError { + #[cold] + fn io(err: impl Into>) -> Self { + Self::Io(err.into()) + } +} + +#[cfg(feature = "std")] impl From for EncodingError { #[cold] fn from(err: io::Error) -> Self { - Self::Io(err) + Self::Io(err.into()) } } @@ -113,13 +140,18 @@ impl ExtensionData { /// /// `delay` is given in units of 10 ms. #[must_use] - pub fn new_control_ext(delay: u16, dispose: DisposalMethod, needs_user_input: bool, trns: Option) -> Self { + pub fn new_control_ext( + delay: u16, + dispose: DisposalMethod, + needs_user_input: bool, + trns: Option, + ) -> Self { let mut flags = 0; let trns = match trns { Some(trns) => { flags |= 1; trns - }, + } None => 0, }; flags |= u8::from(needs_user_input) << 1; @@ -128,18 +160,41 @@ impl ExtensionData { } } -impl Encoder { +#[cfg(feature = "std")] +impl Encoder> { /// Creates a new encoder. /// /// `global_palette` gives the global color palette in the format `[r, g, b, ...]`, /// if no global palette shall be used an empty slice may be supplied. - pub fn new(w: W, width: u16, height: u16, global_palette: &[u8]) -> Result { + pub fn new( + w: W, + width: u16, + height: u16, + global_palette: &[u8], + ) -> Result { + Self::new_with_writer(IoWriter(w), width, height, global_palette) + } +} + +impl Encoder { + /// Creates a new encoder with the provided [`WriteAll`] implementing type `W`. + /// + /// `global_palette` gives the global color palette in the format `[r, g, b, ...]`, + /// if no global palette shall be used an empty slice may be supplied. + pub fn new_with_writer( + w: W, + width: u16, + height: u16, + global_palette: &[u8], + ) -> Result { Self { w: Some(w), global_palette: false, - width, height, + width, + height, buffer: Vec::new(), - }.write_global_palette(global_palette) + } + .write_global_palette(global_palette) } /// Write an extension block that signals a repeat behaviour. @@ -167,10 +222,18 @@ impl Encoder { /// /// Note: This function also writes a control extension if necessary. pub fn write_frame(&mut self, frame: &Frame<'_>) -> Result<(), EncodingError> { - if usize::from(frame.width).checked_mul(usize::from(frame.height)).map_or(true, |size| frame.buffer.len() < size) { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "frame.buffer is too small for its width/height").into()); + if usize::from(frame.width) + .checked_mul(usize::from(frame.height)) + .map_or(true, |size| frame.buffer.len() < size) + { + return Err(EncodingError::Format( + EncodingFormatError::BufferImproperlySized, + )); } - debug_assert!((frame.width > 0 && frame.height > 0) || frame.buffer.is_empty(), "the frame has 0 pixels, but non-empty buffer"); + debug_assert!( + (frame.width > 0 && frame.height > 0) || frame.buffer.is_empty(), + "the frame has 0 pixels, but non-empty buffer" + ); self.write_frame_header(frame)?; self.write_image_block(&frame.buffer) } @@ -192,19 +255,24 @@ impl Encoder { let (palette, padding, table_size) = Self::check_color_table(palette)?; flags |= table_size; Some((palette, padding)) - }, + } None if self.global_palette => None, - _ => return Err(EncodingError::from(EncodingFormatError::MissingColorPalette)), + _ => { + return Err(EncodingError::Format( + EncodingFormatError::MissingColorPalette, + )) + } }; let mut tmp = tmp_buf::<10>(); - tmp.write_le(Block::Image as u8)?; - tmp.write_le(frame.left)?; - tmp.write_le(frame.top)?; - tmp.write_le(frame.width)?; - tmp.write_le(frame.height)?; - tmp.write_le(flags)?; + tmp.write_le(Block::Image as u8) + .map_err(EncodingError::io)?; + tmp.write_le(frame.left).map_err(EncodingError::io)?; + tmp.write_le(frame.top).map_err(EncodingError::io)?; + tmp.write_le(frame.width).map_err(EncodingError::io)?; + tmp.write_le(frame.height).map_err(EncodingError::io)?; + tmp.write_le(flags).map_err(EncodingError::io)?; let writer = self.writer()?; - tmp.finish(&mut *writer)?; + tmp.finish(&mut *writer).map_err(EncodingError::io)?; if let Some((palette, padding)) = palette { Self::write_color_table(writer, palette, padding)?; } @@ -213,38 +281,68 @@ impl Encoder { fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> { self.buffer.clear(); - self.buffer.try_reserve(data.len() / 4) - .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; + self.buffer + .try_reserve(data.len() / 4) + .ok() + .ok_or(EncodingError::OutOfMemory)?; lzw_encode(data, &mut self.buffer); - let writer = self.w.as_mut().ok_or(io::Error::from(io::ErrorKind::Unsupported))?; + let writer = self.w.as_mut().ok_or(EncodingError::MissingWriter)?; Self::write_encoded_image_block(writer, &self.buffer) } - fn write_encoded_image_block(writer: &mut W, data_with_min_code_size: &[u8]) -> Result<(), EncodingError> { + fn write_encoded_image_block( + writer: &mut W, + data_with_min_code_size: &[u8], + ) -> Result<(), EncodingError> { let (&min_code_size, data) = data_with_min_code_size.split_first().unwrap_or((&2, &[])); - writer.write_le(min_code_size)?; + writer + .write_le(min_code_size) + .map_err(Into::into) + .map_err(EncodingError::Io)?; // Write blocks. `chunks_exact` seems to be slightly faster // than `chunks` according to both Rust docs and benchmark results. let mut iter = data.chunks_exact(0xFF); for full_block in iter.by_ref() { - writer.write_le(0xFFu8)?; - writer.write_all(full_block)?; + writer + .write_le(0xFFu8) + .map_err(Into::into) + .map_err(EncodingError::Io)?; + writer + .write_all(full_block) + .map_err(Into::into) + .map_err(EncodingError::Io)?; } let last_block = iter.remainder(); if !last_block.is_empty() { - writer.write_le(last_block.len() as u8)?; - writer.write_all(last_block)?; + writer + .write_le(last_block.len() as u8) + .map_err(Into::into) + .map_err(EncodingError::Io)?; + writer + .write_all(last_block) + .map_err(Into::into) + .map_err(EncodingError::Io)?; } - writer.write_le(0u8).map_err(Into::into) - } - - fn write_color_table(writer: &mut W, table: &[u8], padding: usize) -> Result<(), EncodingError> { - writer.write_all(table)?; + writer + .write_le(0u8) + .map_err(Into::into) + .map_err(EncodingError::Io) + } + + fn write_color_table( + writer: &mut W, + table: &[u8], + padding: usize, + ) -> Result<(), EncodingError> { + writer.write_all(table).map_err(EncodingError::io)?; // Waste some space as of gif spec for _ in 0..padding { - writer.write_all(&[0, 0, 0])?; + writer + .write_all(&[0, 0, 0]) + .map_err(Into::into) + .map_err(EncodingError::Io)?; } Ok(()) } @@ -253,7 +351,7 @@ impl Encoder { fn check_color_table(table: &[u8]) -> Result<(&[u8], usize, u8), EncodingError> { let num_colors = table.len() / 3; if num_colors > 256 { - return Err(EncodingError::from(EncodingFormatError::TooManyColors)); + return Err(EncodingError::Format(EncodingFormatError::TooManyColors)); } let table_size = flag_size(num_colors); let padding = (2 << table_size) - num_colors; @@ -265,38 +363,48 @@ impl Encoder { /// It is normally not necessary to call this method manually. pub fn write_extension(&mut self, extension: ExtensionData) -> Result<(), EncodingError> { use self::ExtensionData::*; + // 0 finite repetitions can only be achieved // if the corresponting extension is not written if let Repetitions(Repeat::Finite(0)) = extension { return Ok(()); } let writer = self.writer()?; - writer.write_le(Block::Extension as u8)?; + writer + .write_le(Block::Extension as u8) + .map_err(Into::into) + .map_err(EncodingError::Io)?; match extension { Control { flags, delay, trns } => { let mut tmp = tmp_buf::<6>(); - tmp.write_le(Extension::Control as u8)?; - tmp.write_le(4u8)?; - tmp.write_le(flags)?; - tmp.write_le(delay)?; - tmp.write_le(trns)?; - tmp.finish(&mut *writer)?; + tmp.write_le(Extension::Control as u8) + .map_err(EncodingError::io)?; + tmp.write_le(4u8).map_err(EncodingError::io)?; + tmp.write_le(flags).map_err(EncodingError::io)?; + tmp.write_le(delay).map_err(EncodingError::io)?; + tmp.write_le(trns).map_err(EncodingError::io)?; + tmp.finish(&mut *writer).map_err(EncodingError::io)?; } Repetitions(repeat) => { let mut tmp = tmp_buf::<17>(); - tmp.write_le(Extension::Application as u8)?; - tmp.write_le(11u8)?; - tmp.write_all(b"NETSCAPE2.0")?; - tmp.write_le(3u8)?; - tmp.write_le(1u8)?; + tmp.write_le(Extension::Application as u8) + .map_err(EncodingError::io)?; + tmp.write_le(11u8).map_err(EncodingError::io)?; + tmp.write_all(b"NETSCAPE2.0").map_err(EncodingError::io)?; + tmp.write_le(3u8).map_err(EncodingError::io)?; + tmp.write_le(1u8).map_err(EncodingError::io)?; tmp.write_le(match repeat { Repeat::Finite(no) => no, Repeat::Infinite => 0u16, - })?; - tmp.finish(&mut *writer)?; + }) + .map_err(EncodingError::io)?; + tmp.finish(&mut *writer).map_err(EncodingError::io)?; } } - writer.write_le(0u8).map_err(Into::into) + writer + .write_le(0u8) + .map_err(Into::into) + .map_err(EncodingError::Io) } /// Writes a raw extension to the image. @@ -304,17 +412,36 @@ impl Encoder { /// This method can be used to write an unsupported extension to the file. `func` is the extension /// identifier (e.g. `Extension::Application as u8`). `data` are the extension payload blocks. If any /// contained slice has a lenght > 255 it is automatically divided into sub-blocks. - pub fn write_raw_extension(&mut self, func: AnyExtension, data: &[&[u8]]) -> io::Result<()> { + pub fn write_raw_extension( + &mut self, + func: AnyExtension, + data: &[&[u8]], + ) -> Result<(), EncodingError> { let writer = self.writer()?; - writer.write_le(Block::Extension as u8)?; - writer.write_le(func.0)?; + writer + .write_le(Block::Extension as u8) + .map_err(Into::into) + .map_err(EncodingError::Io)?; + writer + .write_le(func.0) + .map_err(Into::into) + .map_err(EncodingError::Io)?; for block in data { for chunk in block.chunks(0xFF) { - writer.write_le(chunk.len() as u8)?; - writer.write_all(chunk)?; + writer + .write_le(chunk.len() as u8) + .map_err(Into::into) + .map_err(EncodingError::Io)?; + writer + .write_all(chunk) + .map_err(Into::into) + .map_err(EncodingError::Io)?; } } - writer.write_le(0u8) + writer + .write_le(0u8) + .map_err(Into::into) + .map_err(EncodingError::Io) } /// Writes a frame to the image, but expects `Frame.buffer` to contain LZW-encoded data @@ -325,7 +452,9 @@ impl Encoder { // empty data is allowed if let Some(&min_code_size) = frame.buffer.first() { if min_code_size > 11 || min_code_size < 2 { - return Err(EncodingError::Format(EncodingFormatError::InvalidMinCodeSize)); + return Err(EncodingError::Format( + EncodingFormatError::InvalidMinCodeSize, + )); } } @@ -335,15 +464,15 @@ impl Encoder { } /// Writes the logical screen desriptor - fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> { + fn write_screen_desc(&mut self, flags: u8) -> Result<(), EncodingError> { let mut tmp = tmp_buf::<13>(); - tmp.write_all(b"GIF89a")?; - tmp.write_le(self.width)?; - tmp.write_le(self.height)?; - tmp.write_le(flags)?; // packed field - tmp.write_le(0u8)?; // bg index - tmp.write_le(0u8)?; // aspect ratio - tmp.finish(self.writer()?) + tmp.write_all(b"GIF89a").map_err(EncodingError::io)?; + tmp.write_le(self.width).map_err(EncodingError::io)?; + tmp.write_le(self.height).map_err(EncodingError::io)?; + tmp.write_le(flags).map_err(EncodingError::io)?; // packed field + tmp.write_le(0u8).map_err(EncodingError::io)?; // bg index + tmp.write_le(0u8).map_err(EncodingError::io)?; // aspect ratio + tmp.finish(self.writer()?).map_err(EncodingError::io) } /// Gets a reference to the writer instance used by this encoder. @@ -359,19 +488,22 @@ impl Encoder { } /// Finishes writing, and returns the `io::Write` instance used by this encoder - pub fn into_inner(mut self) -> io::Result { + pub fn into_inner(mut self) -> Result { self.write_trailer()?; - self.w.take().ok_or(io::Error::from(io::ErrorKind::Unsupported)) + self.w.take().ok_or(EncodingError::MissingWriter) } /// Write the final tailer. - fn write_trailer(&mut self) -> io::Result<()> { - self.writer()?.write_le(Block::Trailer as u8) + fn write_trailer(&mut self) -> Result<(), EncodingError> { + self.writer()? + .write_le(Block::Trailer as u8) + .map_err(Into::into) + .map_err(EncodingError::Io) } #[inline] - fn writer(&mut self) -> io::Result<&mut W> { - self.w.as_mut().ok_or(io::Error::from(io::ErrorKind::Unsupported)) + fn writer(&mut self) -> Result<&mut W, EncodingError> { + self.w.as_mut().ok_or(EncodingError::MissingWriter) } } @@ -444,15 +576,15 @@ fn flag_size(size: usize) -> u8 { fn test_flag_size() { fn expected(size: usize) -> u8 { match size { - 0 ..=2 => 0, - 3 ..=4 => 1, - 5 ..=8 => 2, - 9 ..=16 => 3, - 17 ..=32 => 4, - 33 ..=64 => 5, - 65 ..=128 => 6, + 0..=2 => 0, + 3..=4 => 1, + 5..=8 => 2, + 9..=16 => 3, + 17..=32 => 4, + 33..=64 => 5, + 65..=128 => 6, 129..=256 => 7, - _ => 7 + _ => 7, } } @@ -464,7 +596,10 @@ fn test_flag_size() { 1 => 2, n => n, }; - let actual = (u32::from(i) + 1).max(4).next_power_of_two().trailing_zeros() as u8; + let actual = (u32::from(i) + 1) + .max(4) + .next_power_of_two() + .trailing_zeros() as u8; assert_eq!(actual, expected); } } @@ -474,28 +609,43 @@ struct Buf { pos: usize, } +#[derive(Debug)] +struct WriteZero; + +impl fmt::Display for WriteZero { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Write Zero") + } +} + +impl error::Error for WriteZero {} + impl Write for Buf { + type Error = WriteZero; + #[inline(always)] - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteZero> { let len = buf.len(); let pos = self.pos; - self.buf.get_mut(pos..pos + len) - .ok_or(io::ErrorKind::WriteZero)? + self.buf + .get_mut(pos..pos + len) + .ok_or(WriteZero)? .copy_from_slice(buf); self.pos += len; - Ok(len) + Ok(()) } - - fn flush(&mut self) -> io::Result<()> { Ok(()) } } fn tmp_buf() -> Buf { - Buf { buf: [0; N], pos: 0 } + Buf { + buf: [0; N], + pos: 0, + } } impl Buf { #[inline(always)] - fn finish(&self, mut w: impl Write) -> io::Result<()> { + fn finish(&self, mut w: W) -> Result<(), W::Error> { debug_assert_eq!(self.pos, N); w.write_all(&self.buf) } @@ -503,5 +653,6 @@ impl Buf { #[test] fn error_cast() { - let _: Box = EncodingError::from(EncodingFormatError::MissingColorPalette).into(); + let _: Box = + EncodingError::from(EncodingFormatError::MissingColorPalette).into(); } diff --git a/src/lib.rs b/src/lib.rs index c5de6f4..0057138 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! ```rust //! use gif::{Frame, Encoder, Repeat}; //! use std::fs::File; -//! use std::borrow::Cow; +//! use alloc::borrow::Cow; //! //! let color_map = &[0xFF, 0xFF, 0xFF, 0, 0, 0]; //! let (width, height) = (6, 6); @@ -110,22 +110,30 @@ // # })().unwrap(); // ``` #![deny(missing_docs)] -#![cfg(feature = "std")] #![allow(clippy::manual_range_contains)] #![allow(clippy::new_without_default)] +#![no_std] + +#[macro_use] +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; -mod traits; mod common; -mod reader; mod encoder; +mod reader; +mod traits; -pub use crate::common::{AnyExtension, Extension, DisposalMethod, Frame}; +pub use crate::traits::{BufRead, Write}; + +pub use crate::common::{AnyExtension, DisposalMethod, Extension, Frame}; -pub use crate::reader::{DecodingError, DecodingFormatError}; pub use crate::reader::{ColorOutput, MemoryLimit}; pub use crate::reader::{DecodeOptions, Decoder, Version}; +pub use crate::reader::{DecodingError, DecodingFormatError}; -pub use crate::encoder::{Encoder, ExtensionData, Repeat, EncodingError, EncodingFormatError}; +pub use crate::encoder::{Encoder, EncodingError, EncodingFormatError, ExtensionData, Repeat}; /// Low-level, advanced decoder. Prefer [`Decoder`] instead, which can stream frames too. pub mod streaming_decoder { @@ -133,6 +141,9 @@ pub mod streaming_decoder { pub use crate::reader::{Decoded, FrameDataType, FrameDecoder, OutputBuffer, StreamingDecoder}; } +#[cfg(feature = "std")] +pub use {crate::traits::std_impls::IoBufReader, crate::traits::std_impls::IoWriter}; + #[cfg(feature = "color_quant")] macro_rules! insert_as_doc { { $content:expr } => { diff --git a/src/reader/converter.rs b/src/reader/converter.rs index dbc80df..f32d7e2 100644 --- a/src/reader/converter.rs +++ b/src/reader/converter.rs @@ -1,9 +1,9 @@ -use std::borrow::Cow; -use std::io; -use std::mem; -use std::iter; use crate::common::Frame; use crate::MemoryLimit; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::iter; +use core::mem; use super::decoder::{DecodingError, OutputBuffer, PLTE_CHANNELS}; @@ -23,7 +23,8 @@ pub enum ColorOutput { Indexed = 1, } -pub(crate) type FillBufferCallback<'a> = &'a mut dyn FnMut(&mut OutputBuffer<'_>) -> Result; +pub(crate) type FillBufferCallback<'a> = + &'a mut dyn FnMut(&mut OutputBuffer<'_>) -> Result; /// Deinterlaces and expands to RGBA if needed pub(crate) struct PixelConverter { @@ -44,26 +45,32 @@ impl PixelConverter { } pub(crate) fn check_buffer_size(&self, frame: &Frame<'_>) -> Result { - let pixel_bytes = self.memory_limit + let pixel_bytes = self + .memory_limit .buffer_size(self.color_output, frame.width, frame.height) - .ok_or_else(|| io::Error::new(io::ErrorKind::OutOfMemory, "image is too large"))?; + .ok_or_else(|| DecodingError::InsufficientBuffer)?; debug_assert_eq!( - pixel_bytes, self.buffer_size(frame).unwrap(), + pixel_bytes, + self.buffer_size(frame).unwrap(), "Checked computation diverges from required buffer size" ); Ok(pixel_bytes) } #[inline] - pub(crate) fn read_frame(&mut self, frame: &mut Frame<'_>, data_callback: FillBufferCallback<'_>) -> Result<(), DecodingError> { + pub(crate) fn read_frame( + &mut self, + frame: &mut Frame<'_>, + data_callback: FillBufferCallback<'_>, + ) -> Result<(), DecodingError> { let pixel_bytes = self.check_buffer_size(frame)?; let mut vec = match mem::replace(&mut frame.buffer, Cow::Borrowed(&[])) { // reuse buffer if possible without reallocating Cow::Owned(mut vec) if vec.capacity() >= pixel_bytes => { vec.resize(pixel_bytes, 0); vec - }, + } // resizing would realloc anyway, and 0-init is faster than a copy _ => vec![0; pixel_bytes], }; @@ -89,7 +96,12 @@ impl PixelConverter { /// Use `read_into_buffer` to deinterlace #[inline(never)] - pub(crate) fn fill_buffer(&mut self, current_frame: &Frame<'_>, mut buf: &mut [u8], data_callback: FillBufferCallback<'_>) -> Result { + pub(crate) fn fill_buffer( + &mut self, + current_frame: &Frame<'_>, + mut buf: &mut [u8], + data_callback: FillBufferCallback<'_>, + ) -> Result { loop { let decode_into = match self.color_output { // When decoding indexed data, LZW can write the pixels directly @@ -113,27 +125,38 @@ impl PixelConverter { match self.color_output { ColorOutput::RGBA => { let transparent = current_frame.transparent; - let palette: &[u8] = current_frame.palette.as_deref() + let palette: &[u8] = current_frame + .palette + .as_deref() .or(self.global_palette.as_deref()) .unwrap_or_default(); // next_frame_info already checked it won't happen let (pixels, rest) = buf.split_at_mut(bytes_decoded * N_CHANNELS); buf = rest; - for (rgba, idx) in pixels.chunks_exact_mut(N_CHANNELS).zip(self.buffer.iter().copied().take(bytes_decoded)) { + for (rgba, idx) in pixels + .chunks_exact_mut(N_CHANNELS) + .zip(self.buffer.iter().copied().take(bytes_decoded)) + { let plte_offset = PLTE_CHANNELS * idx as usize; - if let Some(colors) = palette.get(plte_offset..plte_offset+PLTE_CHANNELS) { + if let Some(colors) = + palette.get(plte_offset..plte_offset + PLTE_CHANNELS) + { rgba[0] = colors[0]; rgba[1] = colors[1]; rgba[2] = colors[2]; rgba[3] = if let Some(t) = transparent { - if t == idx { 0x00 } else { 0xFF } + if t == idx { + 0x00 + } else { + 0xFF + } } else { 0xFF }; } } - }, + } ColorOutput::Indexed => { buf = &mut buf[bytes_decoded..]; } @@ -141,7 +164,7 @@ impl PixelConverter { if buf.is_empty() { return Ok(true); } - }, + } } } } @@ -161,21 +184,34 @@ impl PixelConverter { /// Applies deinterlacing /// /// Set `frame.interlaced = false` afterwards if you're putting the buffer back into the `Frame` - pub(crate) fn read_into_buffer(&mut self, frame: &Frame<'_>, buf: &mut [u8], data_callback: FillBufferCallback<'_>) -> Result<(), DecodingError> { + pub(crate) fn read_into_buffer( + &mut self, + frame: &Frame<'_>, + buf: &mut [u8], + data_callback: FillBufferCallback<'_>, + ) -> Result<(), DecodingError> { if frame.interlaced { let width = self.line_length(frame); - for row in (InterlaceIterator { len: frame.height, next: 0, pass: 0 }) { + for row in (InterlaceIterator { + len: frame.height, + next: 0, + pass: 0, + }) { // this can't overflow 32-bit, because row never equals (maximum) height let start = row * width; // Handle a too-small buffer and 32-bit usize overflow without panicking - let line = buf.get_mut(start..).and_then(|b| b.get_mut(..width)) + let line = buf + .get_mut(start..) + .and_then(|b| b.get_mut(..width)) .ok_or_else(|| DecodingError::format("buffer too small"))?; if !self.fill_buffer(frame, line, data_callback)? { return Err(DecodingError::format("image truncated")); } } } else { - let buf = self.buffer_size(frame).and_then(|buffer_size| buf.get_mut(..buffer_size)) + let buf = self + .buffer_size(frame) + .and_then(|buffer_size| buf.get_mut(..buffer_size)) .ok_or_else(|| DecodingError::format("buffer too small"))?; if !self.fill_buffer(frame, buf, data_callback)? { return Err(DecodingError::format("image truncated")); @@ -214,6 +250,8 @@ impl iter::Iterator for InterlaceIterator { #[cfg(test)] mod test { + use alloc::vec::Vec; + use super::InterlaceIterator; #[test] @@ -235,10 +273,20 @@ mod test { (13, &[0, 8, 4, 12, 2, 6, 10, 1, 3, 5, 7, 9, 11][..]), (14, &[0, 8, 4, 12, 2, 6, 10, 1, 3, 5, 7, 9, 11, 13][..]), (15, &[0, 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13][..]), - (16, &[0, 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..]), - (17, &[0, 8, 16, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..]), + ( + 16, + &[0, 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..], + ), + ( + 17, + &[0, 8, 16, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..], + ), ] { - let iter = InterlaceIterator { len, next: 0, pass: 0 }; + let iter = InterlaceIterator { + len, + next: 0, + pass: 0, + }; let lines = iter.collect::>(); assert_eq!(lines, expect); } @@ -246,7 +294,11 @@ mod test { #[test] fn interlace_max() { - let iter = InterlaceIterator { len: 0xFFFF, next: 0, pass: 0 }; + let iter = InterlaceIterator { + len: 0xFFFF, + next: 0, + pass: 0, + }; assert_eq!(65533, iter.last().unwrap()); } } diff --git a/src/reader/decoder.rs b/src/reader/decoder.rs index 35994f3..e06d4eb 100644 --- a/src/reader/decoder.rs +++ b/src/reader/decoder.rs @@ -1,18 +1,20 @@ -use std::borrow::Cow; -use std::cmp; -use std::error; -use std::fmt; -use std::io; -use std::mem; -use std::default::Default; -use std::num::NonZeroUsize; - -use crate::Repeat; -use crate::MemoryLimit; +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp; +use core::default::Default; +use core::error; +use core::fmt; +use core::mem; +use core::num::NonZeroUsize; + +use weezl::{decode::Decoder as LzwDecoder, BitOrder, BufferResult, LzwError, LzwStatus}; + +use crate::common::WrappedError; use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; use crate::reader::DecodeOptions; - -use weezl::{BitOrder, decode::Decoder as LzwDecoder, LzwError, LzwStatus}; +use crate::MemoryLimit; +use crate::Repeat; /// GIF palettes are RGB pub const PLTE_CHANNELS: usize = 3; @@ -37,22 +39,33 @@ impl error::Error for DecodingFormatError { } } -#[derive(Debug)] /// Decoding error. +#[derive(Debug)] pub enum DecodingError { /// Returned if the image is found to be malformed. Format(DecodingFormatError), - /// Wraps `std::io::Error`. - Io(io::Error), + /// Returned if provided an insufficiently allocated buffer. + InsufficientBuffer, + /// Returned if additional data is required to finish decoding. + UnexpectedEof, + /// A decoder was not provided. + MissingDecoder, + /// Some kind of IO error. + Io(Box), } impl DecodingError { #[cold] - pub(crate) fn format(err: &'static str) -> Self { + pub(crate) fn format(err: impl Into>) -> Self { Self::Format(DecodingFormatError { underlying: err.into(), }) } + + #[cold] + pub(crate) fn io(err: impl Into>) -> Self { + Self::Io(err.into()) + } } impl fmt::Display for DecodingError { @@ -60,7 +73,10 @@ impl fmt::Display for DecodingError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Format(ref d) => d.fmt(fmt), - Self::Io(ref err) => err.fmt(fmt), + Self::InsufficientBuffer => fmt.write_str("Insufficient Buffer"), + Self::UnexpectedEof => fmt.write_str("Unexpected End of File"), + Self::MissingDecoder => fmt.write_str("Missing Decoder"), + Self::Io(ref inner) => inner.fmt(fmt), } } } @@ -70,29 +86,19 @@ impl error::Error for DecodingError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Self::Format(ref err) => Some(err), - Self::Io(ref err) => Some(err), + Self::InsufficientBuffer => None, + Self::UnexpectedEof => None, + Self::MissingDecoder => None, + Self::Io(ref err) => Some(err.as_ref()), } } } -impl From for DecodingError { - #[inline] - fn from(err: io::Error) -> Self { - Self::Io(err) - } -} - -impl From for DecodingError { +#[cfg(feature = "std")] +impl From for DecodingError { #[cold] - fn from(err: io::ErrorKind) -> Self { - Self::Io(io::Error::from(err)) - } -} - -impl From for DecodingError { - #[inline] - fn from(err: DecodingFormatError) -> Self { - Self::Format(err) + fn from(err: std::io::Error) -> Self { + Self::Io(err.into()) } } @@ -219,19 +225,22 @@ impl FrameDecoder { /// Converts into the given buffer. It must be [`buffer_size()`] bytes large. /// /// Pixels are always deinterlaced, so update `frame.interlaced` afterwards if you're putting the buffer back into the frame. - pub fn decode_lzw_encoded_frame_into_buffer(&mut self, frame: &Frame<'_>, buf: &mut [u8]) -> Result<(), DecodingError> { + pub fn decode_lzw_encoded_frame_into_buffer( + &mut self, + frame: &Frame<'_>, + buf: &mut [u8], + ) -> Result<(), DecodingError> { let (&min_code_size, mut data) = frame.buffer.split_first().unwrap_or((&2, &[])); self.lzw_reader.reset(min_code_size)?; let lzw_reader = &mut self.lzw_reader; - self.pixel_converter.read_into_buffer(frame, buf, &mut move |out| { - loop { + self.pixel_converter + .read_into_buffer(frame, buf, &mut move |out| loop { let (bytes_read, bytes_written) = lzw_reader.decode_bytes(data, out)?; data = data.get(bytes_read..).unwrap_or_default(); if bytes_written > 0 || bytes_read == 0 || data.is_empty() { return Ok(bytes_written); } - } - })?; + })?; Ok(()) } @@ -275,7 +284,10 @@ impl LzwReader { self.min_code_size = min_code_size; self.decoder = Some(LzwDecoder::new(BitOrder::Lsb, min_code_size)); } else { - self.decoder.as_mut().ok_or_else(|| DecodingError::format("bad state"))?.reset(); + self.decoder + .as_mut() + .ok_or_else(|| DecodingError::format("bad state"))? + .reset(); } Ok(()) @@ -285,29 +297,42 @@ impl LzwReader { self.decoder.as_ref().map_or(true, |e| e.has_ended()) } - pub fn decode_bytes(&mut self, lzw_data: &[u8], decode_buffer: &mut OutputBuffer<'_>) -> io::Result<(usize, usize)> { - let decoder = self.decoder.as_mut().ok_or(io::ErrorKind::Unsupported)?; - - let decode_buffer = match decode_buffer { - OutputBuffer::Slice(buf) => &mut **buf, - OutputBuffer::None => &mut [], - OutputBuffer::Vec(_) => return Err(io::Error::from(io::ErrorKind::Unsupported)), + pub fn decode_bytes( + &mut self, + lzw_data: &[u8], + decode_buffer: &mut OutputBuffer<'_>, + ) -> Result<(usize, usize), DecodingError> { + let decoder = self.decoder.as_mut().ok_or(DecodingError::MissingDecoder)?; + + let (consumed_in, consumed_out, status) = match decode_buffer { + OutputBuffer::Slice(buf) => { + let BufferResult { + consumed_in, + consumed_out, + status, + } = decoder.decode_bytes(lzw_data, &mut **buf); + (consumed_in, consumed_out, status) + } + OutputBuffer::None => return Ok((0, 0)), + OutputBuffer::Vec(buf) => { + let mut vec_decoder = decoder.into_vec(&mut **buf); + let result = vec_decoder.decode(lzw_data); + (result.consumed_in, result.consumed_out, result.status) + } }; - let decoded = decoder.decode_bytes(lzw_data, decode_buffer); - - match decoded.status { - Ok(LzwStatus::Done | LzwStatus::Ok) => {}, + match status { + Ok(LzwStatus::Done | LzwStatus::Ok) => {} Ok(LzwStatus::NoProgress) => { if self.check_for_end_code { - return Err(io::Error::new(io::ErrorKind::InvalidData, "no end code in lzw stream")); + return Err(DecodingError::format("no end code in lzw stream")); } - }, + } Err(err @ LzwError::InvalidCode) => { - return Err(io::Error::new(io::ErrorKind::InvalidData, err)); + return Err(DecodingError::format(WrappedError(err))); } } - Ok((decoded.consumed_in, decoded.consumed_out)) + Ok((consumed_in, consumed_out)) } } @@ -362,23 +387,28 @@ pub enum OutputBuffer<'a> { } impl OutputBuffer<'_> { - fn append(&mut self, buf: &[u8], memory_limit: &MemoryLimit) -> Result<(usize, usize), DecodingError> { + fn append( + &mut self, + buf: &[u8], + memory_limit: &MemoryLimit, + ) -> Result<(usize, usize), DecodingError> { let (consumed, copied) = match self { OutputBuffer::Slice(slice) => { let len = cmp::min(buf.len(), slice.len()); slice[..len].copy_from_slice(&buf[..len]); (len, len) - }, + } OutputBuffer::Vec(vec) => { let vec: &mut Vec = vec; let len = buf.len(); memory_limit.check_size(vec.len() + len)?; - vec.try_reserve(len).map_err(|_| io::ErrorKind::OutOfMemory)?; + vec.try_reserve(len) + .map_err(|_| DecodingError::InsufficientBuffer)?; if vec.capacity() - vec.len() >= len { vec.extend_from_slice(buf); } (len, len) - }, + } // It's valid that bytes are discarded. For example, // when using next_frame_info() with skip_frame_decoding to only get metadata. OutputBuffer::None => (buf.len(), 0), @@ -433,10 +463,10 @@ impl StreamingDecoder { let (bytes, decoded) = self.next_state(buf, write_into)?; buf = buf.get(bytes..).unwrap_or_default(); match decoded { - Decoded::Nothing => {}, + Decoded::Nothing => {} result => { - return Ok((len-buf.len(), result)); - }, + return Ok((len - buf.len(), result)); + } }; } Ok((len - buf.len(), Decoded::Nothing)) @@ -465,7 +495,9 @@ impl StreamingDecoder { /// Current frame info as a mutable ref. #[inline(always)] fn try_current_frame(&mut self) -> Result<&mut Frame<'static>, DecodingError> { - self.current.as_mut().ok_or_else(|| DecodingError::format("bad state")) + self.current + .as_mut() + .ok_or_else(|| DecodingError::format("bad state")) } /// Width of the image @@ -490,7 +522,11 @@ impl StreamingDecoder { } #[inline] - fn next_state(&mut self, buf: &[u8], write_into: &mut OutputBuffer<'_>) -> Result<(usize, Decoded), DecodingError> { + fn next_state( + &mut self, + buf: &[u8], + write_into: &mut OutputBuffer<'_>, + ) -> Result<(usize, Decoded), DecodingError> { macro_rules! goto ( ($n:expr, $state:expr) => ({ self.state = $state; @@ -535,7 +571,7 @@ impl StreamingDecoder { }) ); - let b = *buf.first().ok_or(io::ErrorKind::UnexpectedEof)?; + let b = *buf.first().ok_or(DecodingError::UnexpectedEof)?; match self.state { Magic => { @@ -548,7 +584,7 @@ impl StreamingDecoder { }; goto!(consumed, ScreenDescriptor) - }, + } ScreenDescriptor => { let (consumed, desc) = ensure_min_length_buffer!(7); @@ -560,7 +596,9 @@ impl StreamingDecoder { let global_table = global_flags & 0x80 != 0; let table_size = if global_table { let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize); - self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?; + self.global_color_table + .try_reserve_exact(table_size) + .map_err(|_| DecodingError::InsufficientBuffer)?; table_size } else { 0usize @@ -571,11 +609,14 @@ impl StreamingDecoder { GlobalPalette(table_size), emit Decoded::BackgroundColor(background_color) ) - }, + } ImageBlockStart => { let (consumed, header) = ensure_min_length_buffer!(9); - let frame = self.current.as_mut().ok_or_else(|| DecodingError::format("bad state"))?; + let frame = self + .current + .as_mut() + .ok_or_else(|| DecodingError::format("bad state"))?; frame.left = u16::from_le_bytes(header[..2].try_into().unwrap()); frame.top = u16::from_le_bytes(header[2..4].try_into().unwrap()); frame.width = u16::from_le_bytes(header[4..6].try_into().unwrap()); @@ -597,13 +638,16 @@ impl StreamingDecoder { if local_table { let table_size = flags & 0b0000_0111; let pal_len = PLTE_CHANNELS * (1 << (table_size + 1)); - frame.palette.get_or_insert_with(Vec::new) - .try_reserve_exact(pal_len).map_err(|_| io::ErrorKind::OutOfMemory)?; + frame + .palette + .get_or_insert_with(Vec::new) + .try_reserve_exact(pal_len) + .map_err(|_| DecodingError::InsufficientBuffer)?; goto!(consumed, LocalPalette(pal_len)) } else { goto!(consumed, LocalPalette(0)) } - }, + } GlobalPalette(left) => { // the global_color_table is guaranteed to have the exact capacity required if left > 0 { @@ -649,7 +693,7 @@ impl StreamingDecoder { } } } - }, + } BlockEnd => { if b == Block::Trailer as u8 { // can't consume yet, because the trailer is not a real block, @@ -667,7 +711,10 @@ impl StreamingDecoder { if left > 0 { let n = cmp::min(left, buf.len()); self.memory_limit.check_size(self.ext.data.len() + n)?; - self.ext.data.try_reserve(n).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; + self.ext + .data + .try_reserve(n) + .map_err(|_| DecodingError::InsufficientBuffer)?; self.ext.data.extend_from_slice(&buf[..n]); goto!(n, ExtensionDataBlock(left - n)) } else if b == 0 { @@ -679,7 +726,7 @@ impl StreamingDecoder { Some(Extension::Control) => { self.read_control_extension()?; goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) - }, + } _ => { goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) } @@ -745,7 +792,8 @@ impl StreamingDecoder { return goto!(n, DecodeSubBlock(left - n), emit Decoded::Nothing); } - let (mut consumed, bytes_len) = self.lzw_reader.decode_bytes(&buf[..n], write_into)?; + let (mut consumed, bytes_len) = + self.lzw_reader.decode_bytes(&buf[..n], write_into)?; // skip if can't make progress (decode would fail if check_for_end_code was set) if consumed == 0 && bytes_len == 0 { @@ -808,5 +856,5 @@ impl StreamingDecoder { #[test] fn error_cast() { - let _ : Box = DecodingError::format("testing").into(); + let _: Box = DecodingError::format("testing").into(); } diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 8911d8e..a4a2f8f 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -1,25 +1,27 @@ -use std::borrow::Cow; -use std::io; -use std::iter::FusedIterator; -use std::mem; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::convert::{TryFrom, TryInto}; +use core::iter::FusedIterator; +use core::mem; +use core::num::NonZeroU64; -use std::io::prelude::*; -use std::num::NonZeroU64; -use std::convert::{TryFrom, TryInto}; - -use crate::Repeat; use crate::common::{Block, Frame}; +use crate::traits::BufRead; +use crate::Repeat; + +#[cfg(feature = "std")] +use {crate::traits::std_impls::IoBufReader, std::io}; -mod decoder; mod converter; +mod decoder; pub use self::decoder::{ - PLTE_CHANNELS, StreamingDecoder, Decoded, DecodingError, DecodingFormatError, - Version, FrameDataType, OutputBuffer, FrameDecoder + Decoded, DecodingError, DecodingFormatError, FrameDataType, FrameDecoder, OutputBuffer, + StreamingDecoder, Version, PLTE_CHANNELS, }; -use self::converter::PixelConverter; pub use self::converter::ColorOutput; +use self::converter::PixelConverter; #[derive(Clone, Debug)] /// The maximum amount of memory the decoder is allowed to use for each frame @@ -56,7 +58,7 @@ impl MemoryLimit { } else { Err(DecodingError::format("memory limit reached")) } - }, + } } } @@ -82,7 +84,7 @@ impl MemoryLimit { } else { Some(usize_bytes) } - }, + } } } } @@ -189,25 +191,42 @@ impl DecodeOptions { /// Reads the logical screen descriptor including the global color palette /// /// Returns a [`Decoder`]. All decoder configuration has to be done beforehand. - pub fn read_info(self, r: R) -> Result, DecodingError> { + pub fn read_info_with_buffered_reader( + self, + r: R, + ) -> Result, DecodingError> { Decoder::with_no_init(r, StreamingDecoder::with_options(&self), self).init() } + + /// Reads the logical screen descriptor including the global color palette + /// + /// Returns a [`Decoder`]. All decoder configuration has to be done beforehand. + #[cfg(feature = "std")] + pub fn read_info( + self, + r: R, + ) -> Result>>, DecodingError> { + self.read_info_with_buffered_reader(IoBufReader(io::BufReader::new(r))) + } } -struct ReadDecoder { - reader: io::BufReader, +struct ReadDecoder { + reader: R, decoder: StreamingDecoder, at_eof: bool, } -impl ReadDecoder { +impl ReadDecoder { #[inline(never)] - fn decode_next(&mut self, write_into: &mut OutputBuffer<'_>) -> Result, DecodingError> { + fn decode_next( + &mut self, + write_into: &mut OutputBuffer<'_>, + ) -> Result, DecodingError> { while !self.at_eof { let (consumed, result) = { - let buf = self.reader.fill_buf()?; + let buf = self.reader.fill_buf().map_err(DecodingError::io)?; if buf.is_empty() { - return Err(io::ErrorKind::UnexpectedEof.into()); + return Err(DecodingError::UnexpectedEof); } self.decoder.update(buf, write_into)? @@ -217,14 +236,14 @@ impl ReadDecoder { Decoded::Nothing => (), Decoded::BlockStart(Block::Trailer) => { self.at_eof = true; - }, + } result => return Ok(Some(result)), } } Ok(None) } - fn into_inner(self) -> io::BufReader { + fn into_inner(self) -> R { self.reader } @@ -239,7 +258,7 @@ impl ReadDecoder { #[allow(dead_code)] /// GIF decoder. Create [`DecodeOptions`] to get started, and call [`DecodeOptions::read_info`]. -pub struct Decoder { +pub struct Decoder { decoder: ReadDecoder, pixel_converter: PixelConverter, bg_color: Option, @@ -248,12 +267,21 @@ pub struct Decoder { current_frame_data_type: FrameDataType, } -impl Decoder where R: Read { +#[cfg(feature = "std")] +impl Decoder>> { /// Create a new decoder with default options. #[inline] pub fn new(reader: R) -> Result { DecodeOptions::new().read_info(reader) } +} + +impl Decoder { + /// Create a new decoder with default options and the provided [`BufRead`]. + #[inline] + pub fn new_with_buffered_reader(reader: R) -> Result { + DecodeOptions::new().read_info_with_buffered_reader(reader) + } /// Return a builder that allows configuring limits etc. #[must_use] @@ -265,7 +293,7 @@ impl Decoder where R: Read { fn with_no_init(reader: R, decoder: StreamingDecoder, options: DecodeOptions) -> Self { Self { decoder: ReadDecoder { - reader: io::BufReader::new(reader), + reader, decoder, at_eof: false, }, @@ -285,18 +313,20 @@ impl Decoder where R: Read { } Some(Decoded::GlobalPalette(palette)) => { self.pixel_converter.set_global_palette(palette.into()); - }, + } Some(Decoded::Repetitions(repeat)) => { self.repeat = repeat; - }, + } Some(Decoded::HeaderEnd) => break, Some(_) => { // There will be extra events when parsing application extension continue; - }, - None => return Err(DecodingError::format( - "file does not contain any image data" - )) + } + None => { + return Err(DecodingError::format( + "file does not contain any image data", + )) + } } } // If the background color is invalid, ignore it @@ -339,22 +369,31 @@ impl Decoder where R: Read { if self.next_frame_info()?.is_some() { match self.current_frame_data_type { FrameDataType::Pixels => { - self.pixel_converter.read_frame(&mut self.current_frame, &mut |out| self.decoder.decode_next_bytes(out))?; - }, + self.pixel_converter + .read_frame(&mut self.current_frame, &mut |out| { + self.decoder.decode_next_bytes(out) + })?; + } FrameDataType::Lzw { min_code_size } => { let mut vec = if matches!(self.current_frame.buffer, Cow::Owned(_)) { - let mut vec = mem::replace(&mut self.current_frame.buffer, Cow::Borrowed(&[])).into_owned(); + let mut vec = + mem::replace(&mut self.current_frame.buffer, Cow::Borrowed(&[])) + .into_owned(); vec.clear(); vec } else { Vec::new() }; // Guesstimate 2bpp - vec.try_reserve(usize::from(self.current_frame.width) * usize::from(self.current_frame.height) / 4) - .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; + vec.try_reserve( + usize::from(self.current_frame.width) + * usize::from(self.current_frame.height) + / 4, + ) + .map_err(|_| DecodingError::InsufficientBuffer)?; self.copy_lzw_into_buffer(min_code_size, &mut vec)?; self.current_frame.buffer = Cow::Owned(vec); - }, + } } Ok(Some(&self.current_frame)) } else { @@ -376,15 +415,22 @@ impl Decoder where R: Read { /// The length of `buf` must be at least `Self::buffer_size`. /// Deinterlaces the result. pub fn read_into_buffer(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> { - self.pixel_converter.read_into_buffer(&self.current_frame, buf, &mut |out| self.decoder.decode_next_bytes(out)) + self.pixel_converter + .read_into_buffer(&self.current_frame, buf, &mut |out| { + self.decoder.decode_next_bytes(out) + }) } - fn copy_lzw_into_buffer(&mut self, min_code_size: u8, buf: &mut Vec) -> Result<(), DecodingError> { + fn copy_lzw_into_buffer( + &mut self, + min_code_size: u8, + buf: &mut Vec, + ) -> Result<(), DecodingError> { // `write_lzw_pre_encoded_frame` smuggles `min_code_size` in the first byte. buf.push(min_code_size); loop { match self.decoder.decode_next(&mut OutputBuffer::Vec(buf))? { - Some(Decoded::LzwDataCopied(_len)) => {}, + Some(Decoded::LzwDataCopied(_len)) => {} Some(Decoded::DataEnd) => return Ok(()), _ => return Err(DecodingError::format("unexpected data")), } @@ -399,12 +445,17 @@ impl Decoder where R: Read { /// `Self::next_frame_info` needs to be called beforehand. Returns `true` if the supplied /// buffer could be filled completely. Should not be called after `false` had been returned. pub fn fill_buffer(&mut self, buf: &mut [u8]) -> Result { - self.pixel_converter.fill_buffer(&self.current_frame, buf, &mut |out| self.decoder.decode_next_bytes(out)) + self.pixel_converter + .fill_buffer(&self.current_frame, buf, &mut |out| { + self.decoder.decode_next_bytes(out) + }) } /// Output buffer size pub fn buffer_size(&self) -> usize { - self.pixel_converter.buffer_size(&self.current_frame).unwrap() + self.pixel_converter + .buffer_size(&self.current_frame) + .unwrap() } /// Line length of the current frame @@ -440,8 +491,8 @@ impl Decoder where R: Read { self.decoder.decoder.height() } - /// Abort decoding and recover the `io::Read` instance - pub fn into_inner(self) -> io::BufReader { + /// Abort decoding and recover the [`BufRead`] instance + pub fn into_inner(self) -> R { self.decoder.into_inner() } @@ -460,7 +511,7 @@ impl Decoder where R: Read { } } -impl IntoIterator for Decoder { +impl IntoIterator for Decoder { type Item = Result, DecodingError>; type IntoIter = DecoderIter; @@ -474,23 +525,23 @@ impl IntoIterator for Decoder { } /// Use `decoder.into_iter()` to iterate over the frames -pub struct DecoderIter { +pub struct DecoderIter { inner: Decoder, ended: bool, } -impl DecoderIter { - /// Abort decoding and recover the `io::Read` instance +impl DecoderIter { + /// Abort decoding and recover the [`BufRead`] instance /// /// Use `for frame in iter.by_ref()` to be able to call this afterwards. - pub fn into_inner(self) -> io::BufReader { + pub fn into_inner(self) -> R { self.inner.into_inner() } } -impl FusedIterator for DecoderIter {} +impl FusedIterator for DecoderIter {} -impl Iterator for DecoderIter { +impl Iterator for DecoderIter { type Item = Result, DecodingError>; fn next(&mut self) -> Option { @@ -500,11 +551,11 @@ impl Iterator for DecoderIter { Ok(None) => { self.ended = true; None - }, + } Err(err) => { self.ended = true; Some(Err(err)) - }, + } } } else { None diff --git a/src/traits.rs b/src/traits.rs index 2d043d1..5985dae 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,10 +1,78 @@ //! Traits used in this library -use std::io; + +use core::error::Error; + +/// Intermediate for `std::io::BufRead` which is suitable for `no_std`. +pub trait BufRead { + /// An [`Error`] returned when attempting to call [`fill_buf`](BufRead::fill_buf). + type Error: Error + Send + Sync + 'static; + + /// Fill the internal buffer and return it as a slice, or an [`Error`](BufRead::Error) + fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; + + /// Mark that `amt` bytes have been consumed from a slice previously provided by [`fill_buf`](BufRead::fill_buf) + fn consume(&mut self, amt: usize); +} + +impl BufRead for &mut R { + type Error = R::Error; + + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + ::fill_buf(self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + ::consume(self, amt) + } +} + +impl BufRead for alloc::boxed::Box { + type Error = R::Error; + + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + ::fill_buf(self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + ::consume(self, amt) + } +} + +/// Intermediate for `std::io::Write` which is suitable for `no_std`. +pub trait Write { + /// An [`Error`] returned when attempting to call [`write_all`](Write::write_all). + type Error: Error + Send + Sync + 'static; + + /// Write all the data in the provided `buf`, or return an [`Error`](Write::Error) + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error>; +} + +impl Write for &mut W { + type Error = W::Error; + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + ::write_all(self, buf) + } +} + +impl Write for alloc::boxed::Box { + type Error = W::Error; + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + ::write_all(self, buf) + } +} /// Writer extension to write little endian data -pub trait WriteBytesExt { +pub(crate) trait WriteBytesExt: Write { /// Writes `T` to a bytes stream. Least significant byte first. - fn write_le(&mut self, n: T) -> io::Result<()>; + fn write_le(&mut self, n: T) -> Result<(), Self::Error>; /* #[inline] @@ -14,32 +82,92 @@ pub trait WriteBytesExt { */ } -impl WriteBytesExt for W { +impl WriteBytesExt for W { #[inline(always)] - fn write_le(&mut self, n: u8) -> io::Result<()> { + fn write_le(&mut self, n: u8) -> Result<(), Self::Error> { self.write_all(&[n]) } } -impl WriteBytesExt for W { +impl WriteBytesExt for W { #[inline] - fn write_le(&mut self, n: u16) -> io::Result<()> { - self.write_all(&[n as u8, (n >> 8) as u8]) + fn write_le(&mut self, n: u16) -> Result<(), Self::Error> { + self.write_all(&n.to_le_bytes()) } } -impl WriteBytesExt for W { +impl WriteBytesExt for W { #[inline] - fn write_le(&mut self, n: u32) -> io::Result<()> { - self.write_le(n as u16)?; - self.write_le((n >> 16) as u16) + fn write_le(&mut self, n: u32) -> Result<(), Self::Error> { + self.write_all(&n.to_le_bytes()) } } -impl WriteBytesExt for W { +impl WriteBytesExt for W { #[inline] - fn write_le(&mut self, n: u64) -> io::Result<()> { - self.write_le(n as u32)?; - self.write_le((n >> 32) as u32) + fn write_le(&mut self, n: u64) -> Result<(), Self::Error> { + self.write_all(&n.to_le_bytes()) + } +} + +#[cfg(feature = "std")] +pub(crate) mod std_impls { + use super::{BufRead, Write}; + + /// Wrapper for a [`std::io::BufRead`] reader compatible with [`BufRead`]. + pub struct IoBufReader(pub R); + + impl BufRead for IoBufReader { + type Error = std::io::Error; + + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + ::fill_buf(&mut self.0) + } + + #[inline] + fn consume(&mut self, amt: usize) { + ::consume(&mut self.0, amt) + } + } + + impl core::ops::Deref for IoBufReader { + type Target = R; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl core::ops::DerefMut for IoBufReader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + /// Wrapper for an [`std::io::Write`] writer compatible with [`Write`]. + pub struct IoWriter(pub W); + + impl Write for IoWriter { + type Error = std::io::Error; + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + ::write_all(&mut self.0, buf) + } + } + + impl core::ops::Deref for IoWriter { + type Target = W; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl core::ops::DerefMut for IoWriter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } } diff --git a/tests/check_testimages.rs b/tests/check_testimages.rs index 1336cc7..08505f2 100644 --- a/tests/check_testimages.rs +++ b/tests/check_testimages.rs @@ -4,13 +4,15 @@ use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; -use std::io::BufReader; use std::io::prelude::*; +use std::io::BufReader; const BASE_PATH: [&str; 2] = [".", "tests"]; fn process_images(func: F) -where F: Fn(PathBuf) -> Result { +where + F: Fn(PathBuf) -> Result, +{ let base: PathBuf = BASE_PATH.iter().collect(); let test_suites = &["samples"]; let mut results = HashMap::new(); @@ -26,11 +28,11 @@ where F: Fn(PathBuf) -> Result { Ok(crc) => { results.insert(path, format!("{crc}")); println!("{crc}"); - }, + } Err(_) if path.file_name().unwrap().to_str().unwrap().starts_with('x') => { expected_failures += 1; println!("Expected failure"); - }, + } err => panic!("{err:?}"), } } @@ -51,7 +53,9 @@ where F: Fn(PathBuf) -> Result { assert_eq!(expected_failures, failures); for (path, crc) in &results { assert_eq!( - ref_results.get(path).unwrap_or_else(|| panic!("reference for {path:?} is missing")), + ref_results + .get(path) + .unwrap_or_else(|| panic!("reference for {path:?} is missing")), crc ); } @@ -77,60 +81,39 @@ fn render_images() { }); } - const CRC_TABLE: [u32; 256] = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, - 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, - 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, - 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, - 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, - 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, - 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, - 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, - 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, - 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, - 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, - 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, - 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, - 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, - 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, - 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, - 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, - 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, - 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, - 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, - 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, - 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, - 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, - 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, - 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, - 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, - 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, - 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, - 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, - 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, - 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, - 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, - 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, - 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, - 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, - 0x2d02ef8d + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, ]; /// Crc-32 checksum calculation diff --git a/tests/crashtest.rs b/tests/crashtest.rs index 74a387f..b9726f0 100644 --- a/tests/crashtest.rs +++ b/tests/crashtest.rs @@ -1,7 +1,7 @@ #![cfg(feature = "std")] -use std::{fs, io}; use gif::DecodeOptions; +use std::{fs, io}; #[test] fn try_decode_crash_regression() { @@ -11,9 +11,17 @@ fn try_decode_crash_regression() { for entry in files { let entry = entry.unwrap(); if let Some(ext) = entry.path().extension() { - assert_eq!(ext.to_str(), Some("gif"), "Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + assert_eq!( + ext.to_str(), + Some("gif"), + "Unexpected file {} in crashtests, should end with .gif", + entry.path().display() + ); } else { - panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + panic!( + "Unexpected file {} in crashtests, should end with .gif", + entry.path().display() + ); } let file_data = fs::read(entry.path()).unwrap(); diff --git a/tests/decode.rs b/tests/decode.rs index 38b9f13..e8b425e 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -1,24 +1,21 @@ #![cfg(feature = "std")] -use gif::{Decoder, DecodeOptions, DisposalMethod, Encoder, Frame}; +use gif::{DecodeOptions, Decoder, DisposalMethod, Encoder, Frame}; use std::fs::File; #[test] fn test_simple_indexed() { let mut decoder = Decoder::new(File::open("tests/samples/sample_1.gif").unwrap()).unwrap(); let frame = decoder.read_next_frame().unwrap().unwrap(); - assert_eq!(&*frame.buffer, &[ - 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, - 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, - 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, - 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 - ][..]); + assert_eq!( + &*frame.buffer, + &[ + 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, + 1, 1, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 + ][..] + ); } #[test] @@ -119,7 +116,13 @@ fn rebuild_without_reencode(image: &[u8]) { options.skip_frame_decoding(true); let mut decoder = options.read_info(image).unwrap(); - let mut encoder = Encoder::new(Vec::new(), decoder.width(), decoder.height(), decoder.global_palette().unwrap_or_default()).unwrap(); + let mut encoder = Encoder::new( + Vec::new(), + decoder.width(), + decoder.height(), + decoder.global_palette().unwrap_or_default(), + ) + .unwrap(); let mut num_frames = 0; while let Some(frame) = decoder.read_next_frame().unwrap() { diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 073c965..0ffce88 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -1,13 +1,16 @@ #![cfg(feature = "std")] -use gif::{ColorOutput, Decoder, Encoder, Frame, AnyExtension, DecodeOptions}; +use gif::{AnyExtension, ColorOutput, DecodeOptions, Decoder, Encoder, Frame}; #[test] fn round_trip() { - use std::io::prelude::*; use std::fs::File; + use std::io::prelude::*; let mut data = vec![]; - File::open("tests/samples/sample_1.gif").unwrap().read_to_end(&mut data).unwrap(); + File::open("tests/samples/sample_1.gif") + .unwrap() + .read_to_end(&mut data) + .unwrap(); let mut decoder = Decoder::new(&*data).unwrap(); let palette: Vec = decoder.palette().unwrap().into(); let frame = decoder.read_next_frame().unwrap().unwrap(); @@ -57,18 +60,15 @@ fn round_trip_from_image(original: &[u8]) { width = decoder.width(); height = decoder.height(); repeat = decoder.repeat(); - global_palette = decoder - .global_palette() - .unwrap_or_default() - .to_vec(); - core::iter::from_fn(move || { - decoder.read_next_frame().unwrap().cloned() - }).collect() + global_palette = decoder.global_palette().unwrap_or_default().to_vec(); + core::iter::from_fn(move || decoder.read_next_frame().unwrap().cloned()).collect() }; let mut encoder = Encoder::new(vec![], width, height, &global_palette).unwrap(); encoder.set_repeat(repeat).unwrap(); - encoder.write_raw_extension(AnyExtension(gif::Extension::Comment as _), &[b"hello"]).unwrap(); + encoder + .write_raw_extension(AnyExtension(gif::Extension::Comment as _), &[b"hello"]) + .unwrap(); for frame in &frames { encoder.write_frame(frame).unwrap(); } @@ -80,9 +80,8 @@ fn round_trip_from_image(original: &[u8]) { assert_eq!(decoder.height(), height); assert_eq!(decoder.repeat(), repeat); assert_eq!(global_palette, decoder.global_palette().unwrap_or_default()); - let new_frames: Vec<_> = core::iter::from_fn(|| { - decoder.read_next_frame().unwrap().cloned() - }).collect(); + let new_frames: Vec<_> = + core::iter::from_fn(|| decoder.read_next_frame().unwrap().cloned()).collect(); assert_eq!(new_frames.len(), frames.len(), "Diverging number of frames"); for (new, reference) in new_frames.iter().zip(&frames) { assert_eq!(new.delay, reference.delay); @@ -134,16 +133,17 @@ fn encode_roundtrip_few_colors() { let mut decoder = { let mut builder = Decoder::<&[u8]>::build(); builder.set_color_output(ColorOutput::RGBA); - builder.read_info(&buffer[..]).expect("Invalid info encoded") + builder + .read_info(&buffer[..]) + .expect("Invalid info encoded") }; // Only check key fields, assuming "round_trip_from_image" // covers the rest. We are primarily concerned with quantisation. assert_eq!(decoder.width(), WIDTH); assert_eq!(decoder.height(), HEIGHT); - let new_frames: Vec<_> = core::iter::from_fn(move || { - decoder.read_next_frame().unwrap().cloned() - }).collect(); + let new_frames: Vec<_> = + core::iter::from_fn(move || decoder.read_next_frame().unwrap().cloned()).collect(); assert_eq!(new_frames.len(), 2, "Diverging number of frames"); // NB: reference.buffer can't be used as it contains the palette version. assert_eq!(new_frames[0].buffer, pixels); @@ -153,8 +153,12 @@ fn encode_roundtrip_few_colors() { #[test] fn palette_sizes() { - let global_pal = (0..=255u8).flat_map(|i| [i, i/2, i.wrapping_add(13)]).collect::>(); - let local_pal = (0..=255u8).flat_map(|i| [i^0x55, i, i.wrapping_add(7)]).collect::>(); + let global_pal = (0..=255u8) + .flat_map(|i| [i, i / 2, i.wrapping_add(13)]) + .collect::>(); + let local_pal = (0..=255u8) + .flat_map(|i| [i ^ 0x55, i, i.wrapping_add(7)]) + .collect::>(); for size in 1..=256 { let global = &global_pal[..size * 3]; @@ -183,7 +187,13 @@ fn palette_sizes() { assert!(padding.iter().all(|&b| b == 0)); assert!(d.read_next_frame().unwrap().unwrap().palette.is_none()); - let decoded_local_pal = d.read_next_frame().unwrap().unwrap().palette.as_deref().unwrap(); + let decoded_local_pal = d + .read_next_frame() + .unwrap() + .unwrap() + .palette + .as_deref() + .unwrap(); let (decoded_local_pal, padding) = decoded_local_pal.split_at(local.len()); assert_eq!(local, decoded_local_pal); assert_eq!(padding.len(), 3 * (size.max(2).next_power_of_two() - size)); @@ -200,5 +210,10 @@ fn palette_fail() { f.width = 1; f.height = 1; f.buffer = [1][..].into(); - assert!(matches!(encoder.write_frame(&f), Err(gif::EncodingError::Format(gif::EncodingFormatError::MissingColorPalette)))); + assert!(matches!( + encoder.write_frame(&f), + Err(gif::EncodingError::Format( + gif::EncodingFormatError::MissingColorPalette + )) + )); } diff --git a/tests/stall.rs b/tests/stall.rs index 6cc681e..47b9c7d 100644 --- a/tests/stall.rs +++ b/tests/stall.rs @@ -1,6 +1,6 @@ #![cfg(feature = "std")] -use std::{fs, sync::mpsc, thread, time::Duration, io}; +use std::{fs, io, sync::mpsc, thread, time::Duration}; #[test] fn try_decode_crash_regression() { @@ -9,9 +9,17 @@ fn try_decode_crash_regression() { for entry in files { let entry = entry.unwrap(); if let Some(ext) = entry.path().extension() { - assert_eq!(ext.to_str(), Some("gif"), "Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + assert_eq!( + ext.to_str(), + Some("gif"), + "Unexpected file {} in crashtests, should end with .gif", + entry.path().display() + ); } else { - panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + panic!( + "Unexpected file {} in crashtests, should end with .gif", + entry.path().display() + ); } let file_data = fs::read(entry.path()).unwrap(); @@ -27,7 +35,8 @@ fn decode_on_timer(data: Vec) { send.send(result).expect("still waiting"); }); - let _ = recv.recv_timeout(Duration::from_secs(1)) + let _ = recv + .recv_timeout(Duration::from_secs(1)) .expect("any result"); } @@ -55,8 +64,11 @@ fn test_truncated_file() { #[track_caller] fn decode_chopped_anim(r: ChoppedReader) { - let frames = gif::DecodeOptions::new().read_info(r).unwrap() - .into_iter().enumerate() + let frames = gif::DecodeOptions::new() + .read_info(r) + .unwrap() + .into_iter() + .enumerate() .map(|(n, f)| f.expect(&n.to_string())) .count(); assert_eq!(frames, 14);