Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions LibZlib/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion LibZlib/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ChunkCodecLibZlib"
uuid = "4c0bbee4-addc-4d73-81a0-b6caacae83c8"
authors = ["nhz2 <nhz2@cornell.edu>"]
version = "1.0.0"
version = "1.1.0-dev"

[deps]
ChunkCodecCore = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1"
Expand Down
69 changes: 50 additions & 19 deletions LibZlib/src/encode.jl
Original file line number Diff line number Diff line change
@@ -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...)
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down
18 changes: 11 additions & 7 deletions LibZlib/src/libz.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
16 changes: 16 additions & 0 deletions LibZlib/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading