diff --git a/LibZlib/CHANGELOG.md b/LibZlib/CHANGELOG.md index b975770..d28c99d 100644 --- a/LibZlib/CHANGELOG.md +++ b/LibZlib/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +- Added `strategy` keyword argument to `ZlibEncodeOptions`, `DeflateEncodeOptions`, and `GzipEncodeOptions` to expose zlib's compression strategy (`Z_DEFAULT_STRATEGY`, `Z_FILTERED`, `Z_HUFFMAN_ONLY`, `Z_RLE`, `Z_FIXED`). + ## [v1.0.0](https://github.com/JuliaIO/ChunkCodecs.jl/tree/LibZlib-v1.0.0) - 2025-08-29 ### The API is now stable diff --git a/LibZlib/Project.toml b/LibZlib/Project.toml index 98d2576..01d63c2 100644 --- a/LibZlib/Project.toml +++ b/LibZlib/Project.toml @@ -1,7 +1,7 @@ name = "ChunkCodecLibZlib" uuid = "4c0bbee4-addc-4d73-81a0-b6caacae83c8" authors = ["nhz2 "] -version = "1.0.0" +version = "1.1.0-dev" [deps] ChunkCodecCore = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1" diff --git a/LibZlib/src/encode.jl b/LibZlib/src/encode.jl index 48d01f9..4cf0746 100644 --- a/LibZlib/src/encode.jl +++ b/LibZlib/src/encode.jl @@ -1,3 +1,34 @@ +const level_docs = """ +- `level::Integer=-1`: The compression level must be -1, or between 0 and 9. + + 1 gives best speed, 9 gives best compression, 0 gives no compression at all + (the input data is simply copied a block at a time). -1 + requests a default compromise between speed and compression (currently + equivalent to level 6). +""" + +const strategy_docs = """ +- `strategy::Integer=$(Z_DEFAULT_STRATEGY)`: The compression strategy must be between $(Z_DEFAULT_STRATEGY) and $(Z_FIXED). + + The strategy parameter is used to tune the compression algorithm. It only + affects the compression ratio but not the correctness of the compressed + output even if it is not set appropriately. + + - $(Z_DEFAULT_STRATEGY) (`Z_DEFAULT_STRATEGY`) is used for normal data. + - $(Z_FILTERED) (`Z_FILTERED`) is used for data produced by a filter (or predictor). + Filtered data consists mostly of small values with a somewhat random + distribution. In this case, the compression algorithm is tuned to compress + them better. The effect of `Z_FILTERED` is to force more Huffman coding + and less string matching; it is somewhat intermediate between + `Z_DEFAULT_STRATEGY` and `Z_HUFFMAN_ONLY`. + - $(Z_HUFFMAN_ONLY) (`Z_HUFFMAN_ONLY`) forces Huffman encoding only (no string match). + - $(Z_RLE) (`Z_RLE`) limits match distances to one (run-length encoding). `Z_RLE` + is designed to be almost as fast as `Z_HUFFMAN_ONLY`, but gives better + compression for PNG image data. + - $(Z_FIXED) (`Z_FIXED`) prevents the use of dynamic Huffman codes, allowing for a + simpler decoder for special applications. +""" + """ struct ZlibEncodeOptions <: EncodeOptions ZlibEncodeOptions(; kwargs...) @@ -9,25 +40,25 @@ This is the zlib format described in RFC 1950 # Keyword Arguments - `codec::ZlibCodec=ZlibCodec()` -- `level::Integer=-1`: The compression level must be -1, or between 0 and 9. - - 1 gives best speed, 9 gives best compression, 0 gives no compression at all - (the input data is simply copied a block at a time). -1 - requests a default compromise between speed and compression (currently - equivalent to level 6). +$(level_docs) +$(strategy_docs) """ struct ZlibEncodeOptions <: EncodeOptions codec::ZlibCodec level::Int32 + strategy::Int32 end function ZlibEncodeOptions(; codec::ZlibCodec=ZlibCodec(), level::Integer=-1, + strategy::Integer=Z_DEFAULT_STRATEGY, kwargs... ) + check_in_range(Z_DEFAULT_STRATEGY:Z_FIXED; strategy) ZlibEncodeOptions( codec, Int32(clamp(level, -1, 9)), + Int32(strategy), ) end @@ -42,25 +73,25 @@ This is the deflate format described in RFC 1951 # Keyword Arguments - `codec::DeflateCodec=DeflateCodec()` -- `level::Integer=-1`: The compression level must be -1, or between 0 and 9. - - 1 gives best speed, 9 gives best compression, 0 gives no compression at all - (the input data is simply copied a block at a time). -1 - requests a default compromise between speed and compression (currently - equivalent to level 6). +$(level_docs) +$(strategy_docs) """ struct DeflateEncodeOptions <: EncodeOptions codec::DeflateCodec level::Int32 + strategy::Int32 end function DeflateEncodeOptions(; codec::DeflateCodec=DeflateCodec(), level::Integer=-1, + strategy::Integer=Z_DEFAULT_STRATEGY, kwargs... ) + check_in_range(Z_DEFAULT_STRATEGY:Z_FIXED; strategy) DeflateEncodeOptions( codec, Int32(clamp(level, -1, 9)), + Int32(strategy), ) end @@ -75,25 +106,25 @@ This is the gzip (.gz) format described in RFC 1952 # Keyword Arguments - `codec::GzipCodec=GzipCodec()` -- `level::Integer=-1`: The compression level must be -1, or between 0 and 9. - - 1 gives best speed, 9 gives best compression, 0 gives no compression at all - (the input data is simply copied a block at a time). -1 - requests a default compromise between speed and compression (currently - equivalent to level 6). +$(level_docs) +$(strategy_docs) """ struct GzipEncodeOptions <: EncodeOptions codec::GzipCodec level::Int32 + strategy::Int32 end function GzipEncodeOptions(; codec::GzipCodec=GzipCodec(), level::Integer=-1, + strategy::Integer=Z_DEFAULT_STRATEGY, kwargs... ) + check_in_range(Z_DEFAULT_STRATEGY:Z_FIXED; strategy) GzipEncodeOptions( codec, Int32(clamp(level, -1, 9)), + Int32(strategy), ) end @@ -139,7 +170,7 @@ function try_encode!(e::_AllEncodeOptions, dst::AbstractVector{UInt8}, src::Abst return NOT_SIZE end stream = ZStream() - deflateInit2(stream, e.level, windowBits) + deflateInit2(stream, e.level, windowBits, e.strategy) try # deflate loop cconv_src = Base.cconvert(Ptr{UInt8}, src) diff --git a/LibZlib/src/libz.jl b/LibZlib/src/libz.jl index 69dfcff..3123904 100644 --- a/LibZlib/src/libz.jl +++ b/LibZlib/src/libz.jl @@ -87,14 +87,18 @@ mutable struct ZStream end end -function deflateInit2(stream::ZStream, level::Cint, windowBits::Cint) +function deflateInit2(stream::ZStream, level::Cint, windowBits::Cint, strategy::Cint) memLevel = Cint(8) # default - ret = ccall( - (:deflateInit2_, libz), - Cint, - (Ref{ZStream}, Cint, Cint, Cint, Cint, Cint, Cstring, Cint), - stream, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY, ZLIB_VERSION, sizeof(ZStream), - ) + ret = @ccall libz.deflateInit2_( + stream::Ref{ZStream}, + level::Cint, + Z_DEFLATED::Cint, + windowBits::Cint, + memLevel::Cint, + strategy::Cint, + ZLIB_VERSION::Cstring, + sizeof(ZStream)::Cint, + )::Cint if ret != Z_OK if ret == Z_MEM_ERROR throw(OutOfMemoryError()) diff --git a/LibZlib/test/runtests.jl b/LibZlib/test/runtests.jl index 86f4bc9..86a0069 100644 --- a/LibZlib/test/runtests.jl +++ b/LibZlib/test/runtests.jl @@ -55,6 +55,22 @@ tests = [ test_codec(codec(), encode_opt(; level=i), decode_opt(); trials=5) end end + @testset "strategy options" begin + # strategy must be in 0:4 + @test_throws ArgumentError encode_opt(; strategy=-1) + @test_throws ArgumentError encode_opt(; strategy=5) + for i in 0:4 + @test encode_opt(; strategy=i).strategy == i + test_codec(codec(), encode_opt(; strategy=i), decode_opt(); trials=5) + end + end + @testset "combined options" begin + for level in -1:9 + for strategy in 0:4 + test_codec(codec(), encode_opt(;level, strategy), decode_opt(); trials=5) + end + end + end @testset "unexpected eof" begin local d = decode_opt() local u = [0x00, 0x01, 0x02]