From 83e6d64751000fc6cfa07e4ed05e1b6a116f6e79 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Sun, 15 Jun 2025 15:50:14 -0700 Subject: [PATCH] Create feature flags for more compression algorithms (#270) --- .github/workflows/rust.yml | 13 +++++++++++++ Cargo.toml | 15 +++++++++++---- src/decoder/image.rs | 21 +++++++++++++-------- src/decoder/stream.rs | 4 ++++ src/encoder/compression/mod.rs | 10 ++++++++++ src/encoder/mod.rs | 7 +++++++ src/error.rs | 7 ++++--- tests/encode_images_with_compression.rs | 2 ++ 8 files changed, 64 insertions(+), 15 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d8b6af1..b1ca465 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,6 +32,19 @@ jobs: if: ${{ matrix.rust != '1.74.0' }} run: cargo test -v && cargo doc -v + features: + runs-on: ubuntu-latest + strategy: + matrix: + feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"] + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Cargo check + run: cargo check --features "${{ matrix.feature }}" --no-default-features + rustfmt: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 30677d5..f4d75ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,16 +18,23 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"] [dependencies] half = { version = "2.4.1" } -weezl = "0.1.0" -zune-jpeg = "0.4.17" -flate2 = "1.0.20" -zstd = { version = "0.13", optional = true } quick-error = "2.0.1" +flate2 = { version = "1.0.20", optional = true } +weezl = { version = "0.1.0", optional = true } +zstd = { version = "0.13", optional = true } +zune-jpeg = { version = "0.4.17", optional = true } + [dev-dependencies] criterion = "0.3.1" [features] +default = ["deflate", "jpeg", "lzw"] + +# Compression algorithms +deflate = ["dep:flate2"] +jpeg = ["dep:zune-jpeg"] +lzw = ["dep:weezl"] zstd = ["dep:zstd"] [[bench]] diff --git a/src/decoder/image.rs b/src/decoder/image.rs index b8d93dc..f1c5294 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,5 +1,5 @@ use super::ifd::{Directory, Value}; -use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader}; +use super::stream::{ByteOrder, PackBitsReader}; use super::tag_reader::TagReader; use super::{predict_f16, predict_f32, predict_f64, Limits}; use super::{stream::EndianReader, ChunkType}; @@ -9,7 +9,6 @@ use crate::tags::{ use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; use std::io::{self, Cursor, Read, Seek}; use std::sync::Arc; -use zune_jpeg::zune_core; #[derive(Debug)] pub(crate) struct StripDecodeState { @@ -370,23 +369,29 @@ impl Image { fn create_reader<'r, R: 'r + Read>( reader: R, - photometric_interpretation: PhotometricInterpretation, + #[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, compressed_length: u64, - jpeg_tables: Option<&[u8]>, + #[allow(unused_variables)] jpeg_tables: Option<&[u8]>, ) -> TiffResult> { Ok(match compression_method { CompressionMethod::None => Box::new(reader), - CompressionMethod::LZW => { - Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?)) - } + #[cfg(feature = "lzw")] + CompressionMethod::LZW => Box::new(super::stream::LZWReader::new( + reader, + usize::try_from(compressed_length)?, + )), #[cfg(feature = "zstd")] CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), + #[cfg(feature = "deflate")] CompressionMethod::Deflate | CompressionMethod::OldDeflate => { - Box::new(DeflateReader::new(reader)) + Box::new(super::stream::DeflateReader::new(reader)) } + #[cfg(feature = "jpeg")] CompressionMethod::ModernJPEG => { + use zune_jpeg::zune_core; + if jpeg_tables.is_some() && compressed_length < 2 { return Err(TiffError::FormatError( TiffFormatError::InvalidTagValueType(Tag::JPEGTables), diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 3718e34..6c13ab1 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -140,6 +140,7 @@ impl EndianReader { /// ## Deflate Reader /// +#[cfg(feature = "deflate")] pub type DeflateReader = flate2::read::ZlibDecoder; /// @@ -147,11 +148,13 @@ pub type DeflateReader = flate2::read::ZlibDecoder; /// /// Reader that decompresses LZW streams +#[cfg(feature = "lzw")] pub struct LZWReader { reader: BufReader>, decoder: weezl::decode::Decoder, } +#[cfg(feature = "lzw")] impl LZWReader { /// Wraps a reader pub fn new(reader: R, compressed_length: usize) -> LZWReader { @@ -165,6 +168,7 @@ impl LZWReader { } } +#[cfg(feature = "lzw")] impl Read for LZWReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { loop { diff --git a/src/encoder/compression/mod.rs b/src/encoder/compression/mod.rs index 1778b60..e402a55 100644 --- a/src/encoder/compression/mod.rs +++ b/src/encoder/compression/mod.rs @@ -1,13 +1,19 @@ use crate::tags::CompressionMethod; use std::io::{self, Write}; +#[cfg(feature = "deflate")] mod deflate; +#[cfg(feature = "lzw")] mod lzw; mod packbits; mod uncompressed; +#[cfg(feature = "deflate")] pub use self::deflate::{Deflate, DeflateLevel}; + +#[cfg(feature = "lzw")] pub use self::lzw::Lzw; + pub use self::packbits::Packbits; pub use self::uncompressed::Uncompressed; @@ -30,7 +36,9 @@ pub trait Compression: CompressionAlgorithm { /// An enum to store each compression algorithm. pub enum Compressor { Uncompressed(Uncompressed), + #[cfg(feature = "lzw")] Lzw(Lzw), + #[cfg(feature = "deflate")] Deflate(Deflate), Packbits(Packbits), } @@ -46,7 +54,9 @@ impl CompressionAlgorithm for Compressor { fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { match self { Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes), + #[cfg(feature = "lzw")] Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes), + #[cfg(feature = "deflate")] Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes), Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes), } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index c510474..47c62e6 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -34,12 +34,15 @@ use self::writer::*; /// /// [Predictor::FloatingPoint] is currently not supported. pub type Predictor = crate::tags::Predictor; +#[cfg(feature = "deflate")] pub type DeflateLevel = compression::DeflateLevel; #[derive(Clone, Copy, PartialEq)] pub enum Compression { Uncompressed, + #[cfg(feature = "lzw")] Lzw, + #[cfg(feature = "deflate")] Deflate(DeflateLevel), Packbits, } @@ -54,7 +57,9 @@ impl Compression { fn tag(&self) -> CompressionMethod { match self { Compression::Uncompressed => CompressionMethod::None, + #[cfg(feature = "lzw")] Compression::Lzw => CompressionMethod::LZW, + #[cfg(feature = "deflate")] Compression::Deflate(_) => CompressionMethod::Deflate, Compression::Packbits => CompressionMethod::PackBits, } @@ -63,7 +68,9 @@ impl Compression { fn get_algorithm(&self) -> Compressor { match self { Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(), + #[cfg(feature = "lzw")] Compression::Lzw => compression::Lzw {}.get_algorithm(), + #[cfg(feature = "deflate")] Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(), Compression::Packbits => compression::Packbits {}.get_algorithm(), } diff --git a/src/error.rs b/src/error.rs index 5a2bbf7..64ef5bb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,6 @@ use std::str; use std::string; use quick_error::quick_error; -use weezl::LzwError; use crate::decoder::ChunkType; use crate::tags::{ @@ -206,12 +205,14 @@ impl From for TiffError { } } -impl From for TiffError { - fn from(err: LzwError) -> TiffError { +#[cfg(feature = "lzw")] +impl From for TiffError { + fn from(err: weezl::LzwError) -> TiffError { TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string())) } } +#[cfg(feature = "jpeg")] impl From for TiffError { fn from(err: zune_jpeg::errors::DecodeErrors) -> Self { TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string())) diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index 2855c51..9b8a92f 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -139,11 +139,13 @@ fn encode_decode_without_compression() { } #[test] +#[cfg(feature = "lzw")] fn encode_decode_with_lzw() { encode_decode_with_compression(Compression::Lzw); } #[test] +#[cfg(feature = "deflate")] fn encode_decode_with_deflate() { encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));