From d9d4f0a905cdb6624d6c17bc97d36ff7c3807ec7 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 5 Mar 2026 13:10:07 +0000 Subject: [PATCH 1/5] Add safe Rust Zstd decoding option --- Cargo.toml | 4 ++++ src/decoder/image.rs | 2 ++ tests/decode_images.rs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d1531f7..f4f5223 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ fax34 = { package = "fax", version = "0.2.6", optional = true } flate2 = { version = "1.0.20", optional = true } weezl = { version = "0.1.10", optional = true } zstd = { version = "0.13", optional = true } +ruzstd = { version = "0.8.2", optional = true } zune-jpeg = { version = "0.5.1", optional = true } image-webp = {version = "0.2.4", optional = true } @@ -45,6 +46,9 @@ jpeg = ["dep:zune-jpeg"] lzw = ["dep:weezl"] webp = ["dep:image-webp"] zstd = ["dep:zstd"] +# Fully safe Rust but much slower Zstd decoding. +# When both zstd and safe-rust-zstd are enabled, zstd takes precedence. +safe-rust-zstd = ["dep:ruzstd"] [[bench]] name = "lzw" diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 2a4366f..fb8fcb4 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -659,6 +659,8 @@ impl Image { )), #[cfg(feature = "zstd")] CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), + #[cfg(all(not(feature = "zstd"), feature = "safe-rust-zstd"))] + CompressionMethod::ZSTD => Box::new(ruzstd::decoding::StreamingDecoder::new(reader).unwrap()), // TODO: Error handling CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), #[cfg(feature = "deflate")] CompressionMethod::Deflate | CompressionMethod::OldDeflate => { diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 74f9f9f..08314af 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -569,7 +569,7 @@ fn test_predictor_3_gray_f32() { } #[test] -#[cfg(feature = "zstd")] +#[cfg(any(feature = "zstd", feature = "safe-rust-zstd"))] fn test_zstd_compression() { // gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396); From 61dbcc13922e948472fc2fca558acd9aa257bc53 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 5 Mar 2026 13:15:33 +0000 Subject: [PATCH 2/5] Add error handling --- src/decoder/image.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index fb8fcb4..eb513fc 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -660,7 +660,10 @@ impl Image { #[cfg(feature = "zstd")] CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), #[cfg(all(not(feature = "zstd"), feature = "safe-rust-zstd"))] - CompressionMethod::ZSTD => Box::new(ruzstd::decoding::StreamingDecoder::new(reader).unwrap()), // TODO: Error handling + CompressionMethod::ZSTD => Box::new( + ruzstd::decoding::StreamingDecoder::new(reader) + .map_err(|e| std::io::Error::other(e))?, + ), CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), #[cfg(feature = "deflate")] CompressionMethod::Deflate | CompressionMethod::OldDeflate => { From 19caae4f534f1ebae71ad34a50fd9da4161f1399 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Fri, 6 Mar 2026 09:36:09 +0000 Subject: [PATCH 3/5] Rename safe-rust-zstd to zstd-safe-rust --- Cargo.toml | 4 ++-- src/decoder/image.rs | 2 +- tests/decode_images.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4f5223..2896ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,8 @@ lzw = ["dep:weezl"] webp = ["dep:image-webp"] zstd = ["dep:zstd"] # Fully safe Rust but much slower Zstd decoding. -# When both zstd and safe-rust-zstd are enabled, zstd takes precedence. -safe-rust-zstd = ["dep:ruzstd"] +# When both zstd and zstd-safe-rust are enabled, zstd takes precedence. +zstd-safe-rust = ["dep:ruzstd"] [[bench]] name = "lzw" diff --git a/src/decoder/image.rs b/src/decoder/image.rs index eb513fc..6d0dc24 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -659,7 +659,7 @@ impl Image { )), #[cfg(feature = "zstd")] CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), - #[cfg(all(not(feature = "zstd"), feature = "safe-rust-zstd"))] + #[cfg(all(not(feature = "zstd"), feature = "zstd-safe-rust"))] CompressionMethod::ZSTD => Box::new( ruzstd::decoding::StreamingDecoder::new(reader) .map_err(|e| std::io::Error::other(e))?, diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 08314af..6e3deab 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -569,7 +569,7 @@ fn test_predictor_3_gray_f32() { } #[test] -#[cfg(any(feature = "zstd", feature = "safe-rust-zstd"))] +#[cfg(any(feature = "zstd", feature = "zstd-safe-rust"))] fn test_zstd_compression() { // gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396); From c0043c1a8413c7d523bf68399508ad5b964e8f30 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Sat, 27 Jun 2026 12:47:51 +0100 Subject: [PATCH 4/5] Use zrip-decode crate instead of ruzstd --- Cargo.toml | 6 +++--- src/decoder/image.rs | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2896ebb..a8537be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ fax34 = { package = "fax", version = "0.2.6", optional = true } flate2 = { version = "1.0.20", optional = true } weezl = { version = "0.1.10", optional = true } zstd = { version = "0.13", optional = true } -ruzstd = { version = "0.8.2", optional = true } +zrip-decode = { version = "0.7.0", features = ["paranoid"], optional = true } zune-jpeg = { version = "0.5.1", optional = true } image-webp = {version = "0.2.4", optional = true } @@ -46,9 +46,9 @@ jpeg = ["dep:zune-jpeg"] lzw = ["dep:weezl"] webp = ["dep:image-webp"] zstd = ["dep:zstd"] -# Fully safe Rust but much slower Zstd decoding. +# Fully safe Rust but somewhat slower Zstd decoding. # When both zstd and zstd-safe-rust are enabled, zstd takes precedence. -zstd-safe-rust = ["dep:ruzstd"] +zstd-safe-rust = ["dep:zrip-decode"] [[bench]] name = "lzw" diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 6d0dc24..3a107b7 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -660,10 +660,7 @@ impl Image { #[cfg(feature = "zstd")] CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), #[cfg(all(not(feature = "zstd"), feature = "zstd-safe-rust"))] - CompressionMethod::ZSTD => Box::new( - ruzstd::decoding::StreamingDecoder::new(reader) - .map_err(|e| std::io::Error::other(e))?, - ), + CompressionMethod::ZSTD => Box::new(zrip_decode::streaming::FrameDecoder::new(reader)), CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), #[cfg(feature = "deflate")] CompressionMethod::Deflate | CompressionMethod::OldDeflate => { From 11133cb1e320a6bfe801b6544dc4f7220cc8dd01 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Mon, 6 Jul 2026 15:15:04 +0100 Subject: [PATCH 5/5] upgrade zrip to 0.8.x series --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a8537be..7f512d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ fax34 = { package = "fax", version = "0.2.6", optional = true } flate2 = { version = "1.0.20", optional = true } weezl = { version = "0.1.10", optional = true } zstd = { version = "0.13", optional = true } -zrip-decode = { version = "0.7.0", features = ["paranoid"], optional = true } +zrip-decode = { version = "0.8.2", features = ["paranoid"], optional = true } zune-jpeg = { version = "0.5.1", optional = true } image-webp = {version = "0.2.4", optional = true }