Conversation
…nfig traits DecoderConfig::decode(data) / DecoderConfig::probe(data) and EncoderConfig::encode(pixels) — provided methods with default bodies so every codec's config gets single-line one-shot use (config -> pixels) without touching the job layer. Additive: default bodies, no codec changes.
Register DecoderConfig/EncoderConfig values one line each; the set derives magic-byte detection from what's registered (built-ins in ImageFormatRegistry::common() priority order, so AVIF-before-HEIC and DNG-before-TIFF hold regardless of registration order), keys encode by ImageFormat, and stamps set-level ResourceLimits/StopToken/policies onto every job. Send + Sync + 'static with &self operations — build once, share app-wide via LazyLock/OnceLock/Arc. Clone capability is captured at registration (private entry traits), so the public Dyn* traits gain no new required methods; per-call quality goes through encode_with(Fidelity) on a clone of the registered template. Adds zencodec::prelude (one-import trait bundle) wiring and the regenerated public-API snapshot.
Roundtrip, detection-only-consults-registered-formats, custom-format detect->decode end to end, static LazyLock sharing across 4 threads, encode_with template cloning, metadata via encode_job, animation + streaming through the set, typed NoDecoder/NoEncoder/Codec errors, Clone independence, and the one-shot trait methods.
…mp to 0.1.27 README quick start gains the one-shot and CodecSet (static LazyLock sharing) sections; docs/spec.md gains the CodecSet/prelude sections and the provided methods in the trait signature blocks; CHANGELOG entries under [Unreleased] with commit hashes. Version 0.1.26 -> 0.1.27: the new API is additive and 0.1.26 is already published (cargo semver-checks: 196 pass, minor change).
…e convenience
Mirror the decode/encode one-shots for resource estimation: look up the
registered encoder/decoder for a format and forward to its
estimate_{encode,decode}_resources, returning NoEncoder/NoDecoder when the
format is not registered. Estimation was already reachable via
encoder_for(fmt)?.estimate_encode_resources(..) but had no named sugar; this
puts it next to probe/decode/encode so a CodecSet is a one-stop handle.
…enience Probe the input for format + dimensions, then estimate the decode of a still frame at the probed canvas size in the decoder's native output descriptor (its first supported_descriptors, so bit depth / channel count are real, not guessed from alpha). The bytes-based counterpart to estimate_decode — as one-call as probe; same UnrecognizedFormat / NoDecoder / codec errors.
Make estimate-environment construction explicit. host() (std) detects the running machine — available_parallelism() cores + SimdTier::CurrentHost, RAM left unknown (std has no portable query). conservative() is the single-core baseline under an explicit name. new() is deprecated (delegates to conservative()) and queued for removal in the next 0.x minor; Default and the internal callers/tests now use conservative().
- CodecSet::estimate_encode_of(format, pixels, compute): pixels-based encode estimate that reads dims + descriptor off the slice you already hold (the encode analog of estimate_decode_of for decode). - zencodec-testkit/tests/usage.rs: 10 worked, runnable examples of the common CodecSet usages — encode / decode / probe / app-wide sharing / fidelity+metadata / pixel-format request / strip streaming / estimate — plus two zenpixels-convert-assisted paths: adapt_for_encode into an encoder's supported set, and convert_to on a decoded buffer. - testkit: dev-dep zenpixels-convert 0.2.14; zencodec dep gains features=["std"] so the reference codec + ComputeEnvironment::host() are reachable from the examples. - CHANGELOG + docs/spec.md updated for estimate_encode_of and the examples.
…coder Shortens the common configured-encode and detect->decode paths surfaced by the usage examples. - EncodeJob::encode / DynEncodeJob::encode: provided one-shot methods so a configured job (metadata / limits / policy) encodes in one call instead of job.encoder()?.encode(pixels) / job.into_encoder()?.encode(pixels) — the job-level analog of EncoderConfig::encode. CodecSet::encode / encode_with use it internally. - testkit ReferenceZcrDecoderConfig + ZCR_FORMAT: the reference decoder registered under a format that self-detects the codec's ZCR1 wire magic, so detection-based decode (decode / probe / estimate_decode_of) works end to end. usage.rs and codec_set.rs both drop their hand-rolled ~35-line custom-format scaffolding. - usage.rs metadata example uses the job's direct encode; new codec_set regression test asserts job.encode == the two-step form (typed + dyn). - CHANGELOG + docs/spec.md updated; public-API snapshots regenerated (also captures the estimate_* + conservative()/host() additions from earlier this session that the non-CI apidoc pass had not snapshotted). - Fix a pre-existing broken intra-doc link (crate::minimal, now pub(crate)).
The headline proxy operation on CodecSet: decode input, carry the source's metadata under an explicit retention policy, re-encode to the target with a color-emission policy — one call. - transcode(input, target, fidelity, metadata_policy, color_policy) composes decode() + Metadata::from(&ImageInfo) (ICC/EXIF/XMP/CICP/HDR/orientation, with orientation reconciliation) + set_metadata_policy + set_policy(color) + the job's encode(). The color policy merges onto the set-level EncodePolicy (overriding only its color field), preserving stamped security settings. - Single-image, no pixel processing, no descriptor adaptation: a source->target pair whose decoder-output and encoder-input descriptors are disjoint surfaces the encoder's error rather than corrupting (zencodec carries no pixel-conversion dep; the common zen codecs bridge on sRGB RGBA8/RGB8). - Tests: transcode roundtrip + ICC retention (codec_set); a one-call transcode example (usage.rs). CHANGELOG + docs/spec.md + public-API snapshot updated.
A descriptor-support audit across the zen codecs showed CodecSet::transcode calls the codec's encode() directly (the traits crate does no adaptation), and codecs REJECT any pixel descriptor outside their supported_descriptors(). So a plain decode() that returns a native descriptor the target can't take (e.g. a 16-bit source into an 8-bit-only encoder) would error needlessly. transcode now decode_preferring(target_encoder.supported_descriptors()), so the decoder emits a descriptor the encoder accepts when it can — decoder and encoder meet on a shared format with no conversion. Failures collapse to pairs whose supported descriptor sets are genuinely disjoint (among current zen codecs, only a RAW decoder's 16-bit/f32-only output into BMP/farbfeld's 8-bit-only input). Strict improvement: decode_preferring falls back to native when unhonored, so no pair that worked before regresses. Doc + CHANGELOG updated.
…tten The prior decode_preferring(target.supported_descriptors()) passed the encoder's list in its native order — which every zen encoder lists 8-bit-first. Since decode_preferring picks the first descriptor the decoder can produce, a 16-bit or HDR source that can also emit 8-bit was flattened to 8-bit SDR even when the target (jpeg-16/png-16/avif/jxl) could carry the full precision — a needless quality loss (HDR->SDR in the worst case). transcode now decodes at the source's NATIVE descriptor first and encodes it verbatim when the target accepts it (membership check), so precision is kept whenever the target can hold it. It re-decodes (decode_preferring) to bridge only when the native descriptor is unsupported — a second decode, and a precision drop only where the target genuinely can't carry the source format. RAW sources were already safe (16-bit/f32-only, no 8-bit path to fall to). Doc + CHANGELOG updated.
… design Synthesizes 8 parallel source surveys (JPEG/PNG/WebP/GIF/AVIF/JXL/HEIC/RAW/ zenbitmaps ×6) into docs/pixel-descriptor-negotiation.md: - 3-tier probe-certainty verdict: only some codecs can emit a certain source descriptor at probe (farbfeld/QOI/PNM/HDR/PNG certain; JPEG/WebP/JXL/BMP/TGA caveated; GIF/RAW cannot). Key reframe: negotiation must run in the DECODER off the exact native descriptor, not reconstructed from probe's lossy scalar SourceColor (probe drops float-ness, RAW decoder-decides output, GIF unpopulated). - Descriptor capability matrix (decode-produces / encode fidelity ceiling). - Accept-and-downconvert bloat traps: WebP/GIF/JPEG/AVIF advertise *F32 (and JPEG/PNG 16-bit) encode descriptors they silently downconvert. - Decoders handle 'preferred' 5 inconsistent ways today; PNG's use of the precision-blind shared negotiate_pixel_format flattens 16-bit/HDR (the bug). - Navigator: lift JXL choose_pixel_format/can_produce_losslessly (decode.rs: 391-475) into shared negotiate_pixel_format(source, preferred, available); redefine encode supported_descriptors() as a fidelity envelope. Doc-only; no code change.
Adds the unified pipeline-composition model to pixel-descriptor-negotiation.md from 4 further surveys (zenpixels-convert; TIFF/PDF multi-page; color-emit decision; moxcms). Revises the Phase-1 'lift JXL' plan: - Fidelity is a per-image PIPELINE (decode ∘ convert ∘ encode); worst link wins. - The fidelity classifier ALREADY EXISTS in zenpixels-convert (conversion_cost_with_provenance, loss==0 = lossless; LossBucket) — richer than JXL's slice. It's pure descriptor math (no_std-able) → RELOCATE to zenpixels, don't reinvent. Two-tier: descriptor classifier (no_std) + moxcms ColorContext check (std) for custom-ICC pairs it's blind to. - StorageFidelity static encode tag still needed (codec-internal downconvert is invisible to the descriptor classifier). - Multi-page: TIFF/PDF pages genuinely differ but decoders FLATTEN (CMYK→RGB); MultiPageDecoder is vaporware; ImageInfo single-valued; CMYK→CMYK impossible. - Color: resolve_color_emit is a lossless signaling retag (zero prod callers); pixel CMS unwired; retag-vs-Lossy rule; HDR→SDR fails OPEN (silent highlight clip on default build) — a correctness hazard the negotiator must refuse. - Revised architecture (§13): relocate classifier, thin negotiate wrapper, SupportedDescriptor encode type, MultiPageDecoder, wire color decision→mechanism. Doc-only; no code change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes zencodec implementors trivially usable on their own and in groups, per the "registry easy, implementors super easy" design discussion:
CodecSet(src/set.rs, re-exported at the root) — a multi-codec registry over the existing object-safeDynDecoderConfig/DynEncoderConfigtraits. Register configs one line each; the set gives youdetect→decode,probe,push_decode,animation_decoder,streaming_decoder, and format-keyedencode/encode_with(Fidelity), plusdecode_job/encode_jobescape hatches with the set'sResourceLimits/StopToken/ policies pre-stamped.DecoderConfig::decode(data),DecoderConfig::probe(data),EncoderConfig::encode(pixels)— single-line config→result use with default job settings. Default bodies; no codec crate changes needed.zencodec::prelude— one-import bundle of every encode/decode trait (generic + dyn).Shareable app-wide (statically)
CodecSetisSend + Sync + 'static, every operation takes&self, and it'sClone(base set → per-tenant variants). The intended pattern, exercised by a dedicated test that decodes from 4 threads through astatic LazyLock<CodecSet>:Design notes
Dyn*traits. Clone/fidelity capability is captured at registration via private entry supertraits (DecoderEntry/EncoderEntry), so manual implementors ofDynDecoderConfig/DynEncoderConfigare unaffected —cargo semver-checks: 196 pass, valid minor.detect()walksImageFormatRegistry::common()'s curated priority (AVIF-before-HEIC, DNG-before-TIFF) filtered to registered decoders, thenCustomformats in registration order.encode_withclones the template and applies a per-callFidelity.CodecSetError::Codecwith the codec's error chain intact (source()/find_cause).Testing
set.rs(Send+Sync+'static assertion, empty-set behavior, typed errors, Clone/Debug).zencodec-testkit/tests/codec_set.rsagainst the reference codec: roundtrip, registration-scoped detection, custom-format detect→decode end to end, staticLazyLockmulti-thread sharing, template cloning, metadata viaencode_job, animation + streaming through the set, typed errors, one-shot trait methods.cargo fmt --check,clippy --workspace --all-targetsclean, apidoc snapshot regenerated +ZEN_API_DOC=checkgreen,cargo semver-checks check-releasegreen (0.1.26 → 0.1.27 minor).Version bumped to 0.1.27 in-tree because 0.1.26 is already published and CI's semver gate enforces version adequacy for the additive API. Publishing remains a separate, user-approved step.
Noticed while editing (not changed here):
docs/spec.mdmentionsImageFormat::from_magic(), which doesn't exist in source (detection isImageFormatRegistry::detect()); the repo CLAUDE.md "No CodecError here" line is similarly stale. Happy to batch those corrections separately.