Skip to content
Open
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
73 changes: 73 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,79 @@ removes that whole class).
- Remove `SourceColor::has_hdr_transfer()` — moves to a pipeline-level
utility that consults `ColorProfileSource` and `HdrPolicy` together
rather than inspecting raw CICP/ICC fields.
- Remove `ComputeEnvironment::new()` (deprecated in 0.1.27) — construction
goes through the explicit `conservative()` / `host()` constructors.

### Added
- `CodecSet` / `CodecSetError` — multi-codec registry: register decoder /
encoder configs one line each and get detect→decode, `probe`, push /
streaming / animation decode, and format-keyed encode through one
`Send + Sync + 'static` handle whose operations all take `&self` — build it
once and share it app-wide (`LazyLock` / `OnceLock` / `Arc`). Detection
derives from the registered decoders in `ImageFormatRegistry::common()`
priority order (custom formats after, in registration order); set-level
limits / stop / policies are stamped onto every job; `encode_with(Fidelity)`
clones the registered encoder template per call; `decode_job` / `encode_job`
expose the stamped jobs for per-operation control (0edaf64).
- `CodecSet::transcode(input, target, fidelity, metadata_policy, color_policy)`
— the one-call proxy operation: decode → carry the source's metadata → encode.
Reads the source's ICC / EXIF / XMP / CICP / HDR / orientation off the decode
result (via `Metadata::from(&ImageInfo)`, with orientation reconciliation),
re-embeds under the `MetadataPolicy` (`Web` strips privacy / `PreserveExact`
keeps all), and applies a `ColorEmitPolicy` for color signaling. Single-image,
no pixel processing. Precision-preserving: decodes at the source's native
descriptor and encodes it verbatim when the target accepts it (a 16-bit / HDR
source is not flattened to 8-bit when the target can carry it), re-decoding to
bridge only when the native descriptor is unsupported. A pair errors (rather
than corrupting) only when the decoder and encoder supported descriptor sets
are disjoint (this crate carries no pixel-conversion dep — the common raster
codecs bridge on sRGB RGB8/RGBA8).
- `CodecSet::estimate_encode` / `estimate_decode` — by-format resource
estimate (peak memory / wall-time / core-scaling) forwarding to the
registered codec's `estimate_{encode,decode}_resources`, so a `CodecSet`
covers estimation too; `NoEncoder` / `NoDecoder` when the format has no
registered codec. `estimate_decode_of(data, compute)` probes the input
first (format + dimensions) and estimates in the decoder's native output
format — the bytes-based convenience, as one-call as `probe`.
`estimate_encode_of(format, pixels, compute)` is the encode analog — it
reads the dimensions and format straight off the pixel slice you already
hold, no `ImageCharacteristics` to build by hand.
- `ComputeEnvironment::conservative()` / `ComputeEnvironment::host()` — explicit
estimate-environment constructors. `conservative()` is the single-core
baseline (the old `new()` behavior, now explicitly named); `host()` (std)
detects the running machine (`available_parallelism()` cores +
`SimdTier::CurrentHost`; RAM left unknown — std has no portable query).
- One-shot provided methods on the config traits: `DecoderConfig::decode`,
`DecoderConfig::probe`, and `EncoderConfig::encode` — single-line
config→result use with default job settings (08dabea).
- Job-level one-shot encode: `EncodeJob::encode(pixels)` /
`DynEncodeJob::encode(pixels)` — configure a job (metadata, limits, policy),
then encode in one call instead of the two-step `job.encoder()?.encode(pixels)`
/ `job.into_encoder()?.encode(pixels)`. Provided methods (adding them broke no
implementor); the job-level analog of `EncoderConfig::encode`, which encodes
with *default* settings.
- `zencodec::prelude` — one-import bundle of every encode/decode trait,
generic and dyn (0edaf64).
- testkit: `CodecSet` behavior suite against the reference codec — roundtrip,
registration-scoped detection, custom-format detect→decode, static
`LazyLock` sharing across threads, template cloning, typed errors (a4fec14).
- testkit: `ReferenceZcrDecoderConfig` + `ZCR_FORMAT` — the reference decoder
registered under a self-detecting format (matches the codec's own `ZCR1` wire
magic) instead of `ImageFormat::Pnm`. Drop-in for `ReferenceDecoderConfig` so
detection-based decode (`decode` / `probe` / `estimate_decode_of`) works end to
end; removes the ~35-line custom-format scaffolding each detect→decode test and
example previously hand-rolled.
- testkit: `tests/usage.rs` — worked, runnable examples of the common
`CodecSet` usages (encode / decode / probe / app-wide sharing / fidelity +
metadata via the job's direct `encode` / pixel-format request / strip
streaming / estimate) plus two `zenpixels-convert`-assisted paths: adapting a
foreign pixel format into an encoder's supported set (`adapt_for_encode`) and
converting a decoded buffer to a caller-chosen format (`convert_to`).

### Deprecated
- `ComputeEnvironment::new()` — construction should be explicit. Use
`conservative()` (the single-core baseline, identical behavior) or `host()`
(std, detect the running machine). Removal is queued for the next 0.x minor.

## [0.1.26] - 2026-07-14

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ resolver = "3"

[package]
name = "zencodec"
version = "0.1.26"
version = "0.1.27"
edition = "2024"
rust-version = "1.88"
license = "Apache-2.0 OR MIT"
Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ types (metadata, limits, format detection, color emission) at the root.

```toml
[dependencies]
zencodec = "0.1.26"
zencodec = "0.1.27"
```

zencodec defines the traits; a concrete codec crate (here `zenjpeg`) supplies the
Expand Down Expand Up @@ -42,6 +42,53 @@ let pixels = decoded.into_buffer();
For untrusted input, attach a resource limit and a cancellation token to the
**job** — see [Untrusted input](#untrusted-input-limits-cancellation-errors) below.

### One-shots

When default job settings are fine, each config trait ships one-shot
conveniences — `EncoderConfig::encode`, `DecoderConfig::decode` / `probe`:

```rust,ignore
let jpeg = JpegEncoderConfig::new().with_generic_quality(85.0).encode(pixels)?;
let info = JpegDecoderConfig::new().probe(jpeg.data())?; // header only
let image = JpegDecoderConfig::new().decode(jpeg.data())?; // full decode
```

`use zencodec::prelude::*;` brings every encode/decode trait into scope in one
line.

### Multi-codec: `CodecSet`

To handle several formats behind one handle, register configs in a
[`CodecSet`]: each decoder announces its own formats, magic-byte detection
derives from what's registered (in the curated priority order — AVIF before
HEIC, DNG before TIFF), and encoding is keyed by `ImageFormat`. The set is
`Send + Sync` with `&self` operations, so build it once — in a
`LazyLock`/`OnceLock` static or an `Arc` — and share it app-wide:

```rust,ignore
use std::sync::LazyLock;
use zencodec::{CodecSet, ImageFormat, ResourceLimits};

static CODECS: LazyLock<CodecSet> = LazyLock::new(|| {
CodecSet::new()
.with_limits(ResourceLimits::default())
.with_decoder(zenjpeg::JpegDecoderConfig::new())
.with_decoder(zenpng::PngDecoderConfig::new())
.with_encoder(zenjpeg::JpegEncoderConfig::new().with_generic_quality(85.0))
});

let image = CODECS.decode(&bytes)?; // detect → decode
let jpeg = CODECS.encode(ImageFormat::Jpeg, image.pixels())?; // format-keyed
```

Limits, stop tokens, and policies set on the set are stamped onto every job it
creates; `decode_job` / `encode_job` expose the underlying job for
per-operation control (decode hints, metadata, animation). Per-call quality
goes through `encode_with(format, Fidelity, pixels)`, which clones the
registered template.

[`CodecSet`]: https://docs.rs/zencodec/latest/zencodec/struct.CodecSet.html

## Crates in the zen\* family

| Crate | Format | Repo |
Expand Down Expand Up @@ -408,7 +455,8 @@ let plan = resolve_color_emit(&source_color, &target_caps, ColorEmitPolicy::Bala
| `zencodec::gainmap` | `GainMapInfo`, `GainMapParams`, `GainMapChannel`, `GainMapDirection`, `GainMapPresence`, `Iso21496Format` (wire-format variant: `AvifTmap`, `JxlJhgm`, `JpegApp2BodyWithUrn`; the original `JpegApp2` is deprecated since 0.1.20), `ISO_21496_1_URN`, `ISO_21496_1_PRIMARY_APP2_BODY`, `serialize_iso21496_fmt` / `serialize_iso21496_fmt_into` / `parse_iso21496_fmt`, `GainMapParseError` — cross-codec gain map types and wire-format helpers (ISO 21496-1) |
| `zencodec::exif` | Structured EXIF/TIFF: `Exif` (borrowing parse → prune → serialize), `ExifPolicy` (7 keep/discard categories), `Retention`, `ByteOrder`, `retain` |
| `zencodec::helpers` | Codec implementation helpers (not consumer API) — shared boilerplate for trait implementors, plus the lightweight `parse_exif_orientation` accessor |
| root | `ImageFormat`, `ImageFormatDefinition`, `ImageFormatRegistry` (format detection via `ImageFormatRegistry::detect()`), `ImageInfo`, `Metadata`, `MetadataPolicy`, `MetadataFields`, `IccRetention`, `Exif`, `ExifPolicy`, `Retention`, `ByteOrder`, `Orientation`, `OrientationHint`, `ResourceLimits`, `AllocPreference`, `LimitExceeded`, `ThreadingPolicy`, `UnsupportedOperation`, `CodecErrorExt`, `find_cause`, `Unsupported`, `Extensions`, `AnimationFrame`, `OwnedAnimationFrame`, `resolve_color_emit`, `ColorEmitPolicy`, `ColorEmitPlan`, `ColorEmitFields`, `IccDisposition`, `CicpEmission`, `ColorAuthority`, `Cicp`, `ContentLightLevel`, `MasteringDisplay`, `StopToken`, `Unstoppable` |
| `zencodec::prelude` | One-import bundle of every encode/decode trait (generic + dyn) |
| root | `CodecSet` / `CodecSetError` (multi-codec registry: registration-derived detection, format-keyed encode, shareable app-wide), `ImageFormat`, `ImageFormatDefinition`, `ImageFormatRegistry` (format detection via `ImageFormatRegistry::detect()`), `ImageInfo`, `Metadata`, `MetadataPolicy`, `MetadataFields`, `IccRetention`, `Exif`, `ExifPolicy`, `Retention`, `ByteOrder`, `Orientation`, `OrientationHint`, `ResourceLimits`, `AllocPreference`, `LimitExceeded`, `ThreadingPolicy`, `UnsupportedOperation`, `CodecErrorExt`, `find_cause`, `Unsupported`, `Extensions`, `AnimationFrame`, `OwnedAnimationFrame`, `resolve_color_emit`, `ColorEmitPolicy`, `ColorEmitPlan`, `ColorEmitFields`, `IccDisposition`, `CicpEmission`, `ColorAuthority`, `Cicp`, `ContentLightLevel`, `MasteringDisplay`, `StopToken`, `Unstoppable` |

zencodec has no feature flags. The full API is always available.

Expand Down
Loading
Loading