Releases: OxideAV/oxideav-mjpeg
v0.1.8
Other
- JFIF extension APP0 (JFXX) thumbnail view (T.871 §10.2-10.5)
- SOF11 four-component (CMYK-class) lossless arithmetic encode
- SOF11 three-component (RGB-class) lossless arithmetic encode (T.81 §H.1.2.3)
- lossless arithmetic (SOF11) grayscale encode via T.81 §H.1.2.3
- DNL (Define Number of Lines) support for SOF Y = 0 (T.81 §B.2.5)
- progressive arithmetic JPEG (SOF10) via T.81 §G.1.3 scan procedures
- lossless arithmetic JPEG (SOF11) via T.81 §H.1.2.3 statistical model
- skip ICC-fixture inspect test when docs/ absent (CI fix)
- typed APP2 ICC_PROFILE chunks view (T.872 / Annex L) on JpegInfo
- typed Adobe APP14 view (T.872 §6.5.3) on JpegInfo
- typed JFIF APP0 view (T.871 §10.1) on JpegInfo
- drop release-plz.toml — use release-plz defaults across the workspace
- progressive (SOF2) single-component grayscale encode
- decode-free JPEG SOF discriminator + metadata inspector
- baseline (SOF0) packed-Rgb24 lossy encode + decoder RGB tag
- baseline (SOF0) single-component Gray8 lossy encode
- four-component lossless (SOF3, P=8) round trip
- restart-interval-aligned scan splitting for the packetizer
- criterion harness for encode + decode hot paths
- scrub decorative external-implementation attribution
- add arith_decode cargo-fuzz target for SOF9 Q-coder surface
- gate cmyk_roundtrip on the
registryfeature - promote 4-component CMYK / YCCK helpers to the public API
Added
-
JFIF extension APP0 (JFXX) inspector view (T.871 §10.2-10.5) — the
decode-free inspector now surfaces the JFIF extension APP0 segment
(identifier"JFXX\0") that conformant writers use to carry a
thumbnail (the JFIF APP0's own inline thumbnail is rarely populated —
most files emit a JFXX segment instead). A newJfxxApp0typed view
onJpegInfo::jfxxreports the thumbnail-storage variant via a
JfxxThumbnailenum exhaustive over the threeextension_codebytes
T.871 §10.2 defines:JpegEncoded { jpeg_len }(0x10, §10.3 — an
embedded baseline JPEG, length reported without recursion),
PaletteRgb { width, height }(0x11, §10.4 — a 768-byte palette +
one-byte indices), andRgb24 { width, height }(0x13, §10.5 —
packed 24-bit RGB, same layout as the §10.1 inline thumbnail).
JfxxThumbnail::extension_code()recovers the literal byte for
re-serialisation. A top-levelparse_jfxx_app0(payload) -> Result<JfxxApp0>validator is exported for callers that already hold
the APP0 payload bytes; it enforces the structural invariants
(identifier"JFXX\0", definedextension_code, non-zero thumbnail
dimensions for0x11/0x13, declared body fits the payload) and
never copies the thumbnail body.inspect_jpegpopulates the view
automatically when a JFXX extension APP0 follows the JFIF APP0; the
extension segment carries no colour-convention signal so it does not
influencecolor_hint(independent ofjfif, which keeps reporting
the leading JFIF segment). Standalone surface — noregistryfeature,
nooxideav-coredep. Eleven new tests cover the three storage
variants, the short-payload / bad-identifier / reserved-code /
zero-dimension / body-overflow rejection paths, the JFIF+JFXX
dual-APP0 inspector aggregation, and the no-JFXX baseline. -
Lossless arithmetic (SOF11) four-component (CMYK-class) encode (T.81
Annex H + §H.1.2.3) —encoder::encode_lossless_arith_jpeg_cmyk(width, height, planes, strides, predictor, adobe_transform)and its
_with_opts(..., restart_interval, point_transform)companion emit a
standalone four-component interleaved SOF11 (lossless, arithmetic-coded)
JPEG atP = 8. This is the Q-coder counterpart of the existing Huffman
encode_lossless_jpeg_cmykand the four-component extension of
encode_lossless_arith_jpeg_rgb: each component is modelled independently
(§H.1.2) with its ownLosslessStatsarea andL_Context(Da, Db)/
X1_Context(Db)difference history (§H.1.2.3.2), while a single
arithmetic-coded segment carries one residual per component per pixel
position in scan-component order (each component declaredH_i = V_i = 1).
The Adobe APP14 colour-transform flag is honoured identically to the
Huffman CMYK helper —None(no APP14, plain CMYK),Some(0)(Adobe
CMYK, samples inverted on the wire),Some(2)(Adobe YCCK, K inverted) —
with the segment emitted before SOF11 so the decoder's existing
four-component un-inversion / YCCK → CMYK path applies on output. Output
decodes to packedPixelFormat::Cmyk(4 bytes/pixel), bit-exact on the
no-APP14 / Adobe-CMYK paths for every predictor, the half-modulus
Di = 32768case (§H.1.2.2), non-zero point transforms, and
restart-interval emission (eachRSTnboundary flushes the Q-coder,
cyclesRST0..=RST7modulo 8, and re-seeds every component's statistical
model + difference history + predictor to2^(P − Pt − 1), §H.1.1 /
§H.1.2.3.4). The matching SOF11 four-component decode path already
existed. Covered by six new round-trips intests/lossless_roundtrip.rs. -
Lossless arithmetic (SOF11) three-component encode (T.81 Annex H +
§H.1.2.3) —encoder::encode_lossless_arith_jpeg_rgb(width, height, planes, strides, precision, predictor)and its
_with_opts(..., restart_interval, point_transform)companion emit a
standalone three-component interleaved SOF11 (lossless, arithmetic-coded)
JPEG. This is the Q-coder counterpart of the existing Huffman
encode_lossless_jpeg_rgband the multi-component extension of the
single-componentencode_lossless_arith_jpeg_grayscale: each component is
modelled independently (§H.1.2) with its ownLosslessStatsarea and its
ownL_Context(Da, Db)/X1_Context(Db)difference history (§H.1.2.3.2),
while a single arithmetic-coded entropy segment carries one residual per
component per pixel position in scan-component order (each component
declaredH_i = V_i = 1, so a lossless MCU is one pixel). The Annex H
Table H.1 predictors1..=7are applied per component over that
component's ownRa/Rb/Rc; no DAC segment is emitted so the
decoder uses the default conditioning bounds(L, U) = (0, 1)
(§H.1.2.3.3). Output is bit-exact for every precisionP ∈ 2..=16
(decode mapsP = 8→ packedRgb24,P ∈ {10, 12, 14}→ planar
Gbrp*Le, every otherP→ packedRgb48Le), every predictor, the
half-modulusDi = 32768case (§H.1.2.2), non-zero point transforms, and
restart-interval emission (eachRSTnboundary flushes the Q-coder,
byte-aligns, cyclesRST0..=RST7modulo 8, and re-seeds every component's
statistical model + difference history + predictor to the scan-origin
default2^(P − Pt − 1), §H.1.1 / §H.1.2.3.4). The matching SOF11
multi-component decode path already existed. Covered by six new
round-trips intests/lossless_roundtrip.rs. -
Lossless arithmetic (SOF11) grayscale encode (T.81 Annex H + §H.1.2.3) —
encoder::encode_lossless_arith_jpeg_grayscale(width, height, samples, stride, precision, predictor)and its
_with_opts(..., restart_interval, point_transform)companion emit a
standalone single-component SOF11 (lossless, arithmetic-coded) JPEG.
The spatial model reuses the Annex H Table H.1 predictors1..=7over
Ra/Rb/Rc, but each prediction difference is coded with the
Q-coder arithmetic statistical model of §H.1.2.3 (Table H.3 —
L_Context(Da, Db)/X1_Context(Db)conditioning over neighbouring
differences) rather than a Huffman magnitude category. No DAC segment
is emitted, so the decoder applies the default conditioning bounds
(L, U) = (0, 1)per §H.1.2.3.3. Output is bit-exact for every
precisionP ∈ 2..=16, every predictor, the half-modulus
Di = 32768case (§H.1.2.2), non-zero point transforms, and
restart-interval emission (eachRSTnboundary flushes the Q-coder,
byte-aligns, cyclesRST0..=RST7modulo 8, and re-seeds the
statistical model + difference history + predictor to the scan-origin
default2^(P − Pt − 1), §H.1.1 / §H.1.2.3.4). This is the
encoder-side counterpart to the existing SOF11 decode path and the
first arithmetic-coded entry point on the encoder side. Covered by
six new round-trips intests/lossless_roundtrip.rs. -
DNL (Define Number of Lines) decode support (T.81 §B.2.2 / §B.2.5) —
JPEG frames may code the number of linesY = 0in the SOF header, in
which case the real line count is supplied by a mandatory DNL segment
(0xFFDC) immediately after the first scan. The decoder now performs an
up-front marker-stream pre-pass (resolve_dnl_height) that, when it
seesY = 0, walks to the first scan, readsNLfrom the following
DNL segment, and patches the frame height before any scan decoder runs —
so every path (baseline fast path, sequential / progressive / arithmetic
accumulators, lossless) decodes at the correct height with no per-path
changes. AY = 0stream with no following DNL is rejected (the segment
is mandatory there), as is a malformed DNL carryingNL = 0
(Table B.10 constrainsNL ∈ 1..=65535). Newparse_dnlparser entry,
markers::DNLconstant, and explicit DNL handling in the main marker
loop. Covered bytests/dnl.rs(YUV 4:4:4 / 4:2:2 / 4:2:0 round-trips,
a non-MCU-aligned height, plus the two negative cases) and four
decoder::dnl_unit_testsunit tests. -
Progressive arithmetic JPEG (SOF10) decode — the SOF2 multi-scan
spectral-selection / successive-approximation structure with the
Annex D Q-coder as the entropy layer, per T.81 §G.1.3:- DC first scans (
Ss = Se = 0,Ah = 0) reuse the sequential
§F.1.4.1 DC statistical model on the point-transformed values
(DC point transform = arithmetic shift right); the decoded
difference accumulates into the per-component prediction and lands
left-shifted byAl(§G.1.3.1). - DC refinement scans (
Ah > 0) decode one binary decision per
block with the fixed 0.5 probabi...
- DC first scans (
v0.1.7
Other
- 4-component CMYK / YCCK progressive (SOF2) decode
- 12-bit precision progressive (SOF2 P=12) decode
- encoder docstring: refresh stale "P=8 only" note for lossless RGB
- lossless (SOF3) three-component decode at every P in 2..=16
- rustfmt — split InBand q if-else across lines
- fuzz packetizer + close five wire-length panic surfaces
- add rtp_depacketize cargo-fuzz target for RFC 2435 surface
- gate fixture corpus on Tier::Exact + Tier::PsnrFloor
- fix i32 overflow in dequantise when Pq=1 (16-bit quantiser)
- 12-bit 4:2:2 (Yuv422P12Le) + 4:4:4 (Yuv444P12Le) YUV
- add decode robustness target + fix seven panic surfaces
- cache static-Q in-band quantization tables across frames (RFC 2435 §4.2)
- add RFC 2435 RTP/JPEG packetizer (encode side)
- add RFC 2435 RTP/JPEG depacketizer
- lossless encoder: restart markers + non-zero point transform
- rewrite library-citation comments to remove external-library references
- lossless (SOF3) three-component encoder + decoder
- lossless (SOF3) grayscale encoder: P=2..=16 + all 7 predictors
- add multi-frame demuxer with seek_to + marker-aware scanner
- compare libjpeg cross-decode in YUV space, not RGB
Added
-
4-component (CMYK / Adobe YCCK) progressive (SOF2 with
P = 8)
decode. T.81 §G.1.1 permits the progressive coding process at every
component-count the spec admits (Nf ∈ 1..=4), but the decoder was
previously rejectingSOF2withNf = 4even though every
downstream stage already supported the geometry:
decode_progressive_scanis component-count agnostic (interleaved
DC walks every SOS component, AC scans are always non-interleaved
per the spec),init_coef_buffersalready sizes for up to 4
components, andrender_from_coefsalready produces a packed
PixelFormat::Cmykplane forNf = 4honouring the Adobe APP14
colour-transform flag (plain CMYK, Adobe-inverted CMYK at
transform=0, YCCK at transform=2). The SOF2 4-component path
therefore lights up by removing theNf > 3rejection; the
Nf = 4 & P = 12combination is still rejected with
Error::Unsupportedbecause the workspacePixelFormatenum
carries no 12-bit CMYK variant.A new test-only encoder helper
encode_jpeg_progressive_cmyk_1111
emits a 4-component SOF2 progressive JPEG atP = 8with the same
spectral-selection-only scan decomposition the existing
three-component progressive YUV helpers use: one interleaved DC
scan (Ss = Se = 0, Ah = Al = 0) followed by per-component AC
bands[1..=5]then[6..=63]for each of the four components
(1 + 4 + 4 = 9 SOS segments total). Each component is declared
H_i = V_i = 1so the MCU equals one data unit per component;
component 1 binds quant table 0 (luma_q), components 2/3/4 share
quant table 1 (chroma_q), mirroring the baseline
encode_jpeg_cmyk_1111policy. The helper accepts the same
adobe_transform: Option<u8>parameter as the baseline path
(None→ no APP14,Some(0)→ Adobe CMYK inverted-on-wire,
Some(2)→ Adobe YCCK with K-inversion).Three new tests under
decoder::cmyk_tests
(cmyk_progressive_plain_roundtrip,
cmyk_progressive_adobe_inverted_roundtrip,
ycck_progressive_k_plane_matches) drive each transform variant
through the helper and decode it back, asserting per-component
PSNR ≥ 30 dB at Q = 90 (same tolerance as the baseline tests they
mirror). A fourth test (cmyk_progressive_p12_rejected) hand-
crafts a minimal SOF2 segment withP = 12, Nf = 4and confirms
the parser still rejects the unsupported combo with
Error::Unsupportedbefore any scan is read. -
12-bit precision progressive (SOF2 with
P = 12) decode. T.81 §G.1.1
permits the progressive coding process at precision 8 or 12, but the
decoder previously rejectedSOF2atP = 12withError::Unsupported
even thoughinit_coef_buffersalready allocated 12-bit-shaped
coefficient accumulator planes andrender_from_coefsalready routed
P = 12to the dedicatedrender_from_coefs_12bitpath (level shift
2048, clamp[0, 4095], 16-bit-LE output planes). The progressive
scan path itself (decode_progressive_scan+prog_decode_dc/
prog_decode_ac_first/prog_decode_ac_refine) operates entirely on
i32coefficient planes, so the increased DC/AC residual magnitude
range atP = 12fits without numeric changes. TheBitReader::get_bits
24-bit ceiling accommodates the wider DC categories (up to 15) and AC
magnitudes (up to 14) the spec admits atP = 12.Output shape matches the sequential
P = 12path:- Grayscale →
Gray12Le(one 16-bit-LE plane, low 12 bits carry the
sample). - Three-component YUV at 4:4:4 →
Yuv444P12Le. - Three-component YUV at 4:2:2 →
Yuv422P12Le. - Three-component YUV at 4:2:0 →
Yuv420P12Le.
Non-2x luma sampling factors atP = 12continue to be rejected with
Error::Unsupported(noPixelFormatenum entry for, e.g., 4:1:1 at
12-bit).
A new test-only encoder helper
encode_yuv_jpeg_progressive_12bit
emits a three-component SOF2 progressive JPEG atP = 12with the
spectral-selection-only scan decomposition (interleaved DC pass +
Y/Cb/Cr AC bands[1..=5]then[6..=63],Ah = Al = 0), reusing
the same Annex K Huffman tables andDEFAULT_LUMA_Q50/
DEFAULT_CHROMA_Q50quant tables as the existing 12-bit baseline
helperencode_yuv_jpeg_12bit. Three new tests under
decoder::precision_12_tests(yuv444_12bit_progressive_roundtrip,
yuv422_12bit_progressive_roundtrip,yuv420_12bit_progressive_roundtrip)
drive a smooth gradient through the helper and decode it back,
asserting per-sample closeness against the originals (diff < 24,
same tolerance as the existing baseline 12-bit YUV roundtrip tests). - Grayscale →
-
High-bit-depth lossless (SOF3) three-component decode. Previously the
decoder accepted SOF3 RGB-class scans only atP = 8(output:
packedRgb24); decoding at higher precisions raised
Error::Unsupported. The decoder now covers every precision in
2..=16for three-component lossless, with output shape selected by
precision:P = 8→ packedRgb24(one plane, 3 bytes/pixel) —
unchanged.P = 10→ planarGbrp10Le(3 planes, 16-bit LE per sample,
low 10 bits carry the sample).P = 12→ planarGbrp12Le.P = 14→ planarGbrp14Le.- any other
Pin2..=16(i.e. 2..=7, 9, 11, 13, 15, 16)
→ packedRgb48Le(one plane, 6 bytes/pixel; samples
narrower than 16 bits sit in the low bits of each 16-bit word).
Per-component buffer ordering is preserved end-to-end: planes pass
through encoder → decoder in the same SOS scan order (mirroring the
existing colour-agnosticRgb24behaviour), so a caller that wants
canonicalGbrp*LeG-B-R layout passes its G, B, R planes to the
encoder in that order. Roundtrip bit-exactness verified by five new
integration tests intests/lossless_roundtrip.rs:
lossless_rgb_10bit_every_predictor_planar_gbrp10(every Annex H
predictor 1..=7),lossless_rgb_12bit_predictor_4_planar_gbrp12,
lossless_rgb_14bit_predictor_7_planar_gbrp14,
lossless_rgb_16bit_predictor_1_packed_rgb48, and
lossless_rgb_odd_precision_9_widens_to_rgb48. The previous
lossless_rgb_rejects_higher_precision_decodetest (which asserted
theError::Unsupportedfor P > 8) is removed in the same commit
per the workspace "rewrite, don't#[ignore]" guardrail.
-
MjpegPixelFormat(standalone API) gainsRgb48Le,Gbrp10Le,
Gbrp12Le, andGbrp14Levariants, with bidirectional
From<MjpegPixelFormat> for oxideav_core::PixelFormat/ reverse
mapping inregistry.rsso the registry-sideCodecParameters
surface accepts and produces them. -
fuzz/fuzz_targets/rtp_packetize.rs: cargo-fuzz harness covering
the RFC 2435 RTP/JPEG packetizer (oxideav_mjpeg::rtp::packetize).
Drives arbitrary bytes (≤ 16 KiB) through the encode-side JPEG
segment walker (fn parse_jpeg), which indexes into the input by
the big-endian length field of each SOI / SOF / DQT / DRI / SOS /
catch-all segment. The harness samples bothQMode::Quality(1..=99)
andQMode::InBand(128..=255)(and the rejection paths outside
those ranges) and a range ofmax_payloadMTUs so the
header-room rejection and the fragment-split loop both run on
every iteration. Contract: no panic, slice OOB, debug-build
integer overflow, or infinite loop from a zero-length segment.
Successful returns are shape-checked: first fragment offset 0,
marker bit set on the final packet, no payload exceeds the
caller'smax_payload. This is now the seventh fuzz harness in
fuzz/alongsidedecode,jpeg_self_roundtrip,
jpeg_progressive_self_roundtrip,libjpeg_encode_oxideav_decode,
oxideav_encode_libjpeg_decode, andrtp_depacketize. Last
local 15 s baseline (debug binary, no sanitizer instrumentation):
21 819 067 runs, 0 crashes; the daily reusable fuzz workflow runs
the release-instrumented build with proper coverage and pulls the
new harness in automatically viafuzz/Cargo.tomlauto-discovery. -
fuzz/fuzz_targets/rtp_depacketize.rs: cargo-fuzz harness covering
the RFC 2435 RTP/JPEG depacketizer (oxideav_mjpeg::rtp). Feeds
arbitrary bytes throughparse_main_header,parse_restart_header,
andJpegDepacketizer::push— the latter as a sequence of
synthetic "packets" so the §3.1.2 fragment-offset reassembly
buffer, the §3.1.7 Restart Marker header parser, the §3.1.8
Quantization Table header parser, the §4.2 static-Q table cache,
the marker-bit close path, and thereset()cache-retention
invariant are all exercised on every iteration. Contract: no
panic, slice OOB, debug-build integer overflow, or buffer
allocation the input couldn't plausibly back. Assembled frames
(when the marker bit closes a frame) are asserted to begin with
SOI and end with EOI; their interior bytes are not validated...
v0.1.6
Other
- drop stale REGISTRARS / with_all_features intra-doc links
- drop dead
linkmedep - re-export __oxideav_entry from registry sub-module
- add SA progressive + metadata pass-through API
- auto-register via oxideav_core::register! macro (linkme distributed slice)
Added
encode_jpeg_progressive_sa/encode_jpeg_progressive_sa_with_meta: full
successive-approximation (SA) progressive JPEG encoder using a 1-bit point
transform (Al=1initial,Ah=1,Al=0refinement). Produces 14 SOS scans
(1 DC initial + 6 AC initial + 1 DC refine + 6 AC refine) that round-trip
through ffmpeg, libjpeg, and ImageMagick with PSNR ≥ 40 dB relative to the
source. Implements T.81 §G.1.2.3 AC refinement with correction bits
interleaved inline during the decoder's zero-history walk.encode_jpeg_with_meta/encode_jpeg_progressive_with_meta: variants of
the baseline and spectral-selection progressive encoders that accept ameta
byte slice of pre-serialised APP0-APP15 and COM segments to embed verbatim,
replacing the default JFIF APP0 marker.extract_app_segments(jpeg: &[u8]) -> Vec<u8>: walks a JPEG byte stream and
returns a contiguous buffer of all APP (0xFF 0xEn) and COM (0xFF 0xFE)
segment bytes, ready to pass asmetato the*_with_metaencoder family.
v0.1.5
v0.1.4
Fixed
- (clippy) drop unused Decoder import + div_ceil + doc lints
Other
- cargo fmt: collapse import + match-arm wrap in registry.rs
- add default-on
registrycargo feature for standalone-friendly builds - end-to-end cross-codec roundtrip via libturbojpeg
Added
- Default-on
registryCargo feature gates theoxideav-core
dependency, theDecoder/Encodertrait implementations, the
still-image JPEG container demuxer / muxer / probe, and the
register_codecs/register/register_containersentry points.
Image-library consumers can now depend onoxideav-mjpegwith
default-features = falseand skip theoxideav-coredep tree
entirely; the standalone path exposesdecoder::decode_jpegand the
encoder::encode_jpeg_*family plus crate-localMjpegFrame/
MjpegPlane/MjpegPixelFormat/MjpegErrortypes built only on
std. - Inline
ci-standaloneCI job verifiescargo build --lib --no-default-featuresandcargo test --no-default-featuresstay
green on every change.
v0.1.3
Other
- allow dead Tier::Ignored variant
- drop no-op u32 mask + replace MSRV-1.87 is_multiple_of
- cargo-fuzz harness with libjpeg-turbo cross-decode oracle
- drop unused loop binding in K.4 test
- align QE_TABLE comments to column 28 to satisfy rustfmt
- fmt QE_TABLE column alignment to single-space
- add SOF9 arithmetic-coded JPEG entropy decoder
- cargo fmt rustfmt 1.95 if-expression wrap
- fancy chroma upsampling + green-channel rounding fix
- emit Yuv411P for JPEG 4:1:1 sampling (luma 4x1)
- cargo fmt: collapse single-line panic! in tests/docs_corpus.rs
- wire docs/image/jpeg/ fixture corpus into integration suite
v0.1.2
Other
- drop unused PixelFormat imports + ignore unused let-else binds
- cargo fmt: collapse double-blank lines after let-else (tests/)
- replace never-match regex with semver_check = false
- disable semver_check to prevent 0.2 bump
- migrate to centralized OxideAV/.github reusable workflows
- adopt slim VideoFrame shape
- pin release-plz to patch-only bumps
v0.1.1
Other
- drop oxideav-codec/oxideav-container shims, import from oxideav-core
- add progressive (SOF2) encoder
- bump usage example to oxideav-mjpeg = "0.1"
- drop Cargo.lock — this crate is a library
- release v0.0.4
v0.1.0
chore: Release package oxideav-mjpeg version 0.1.0
v0.0.3
chore: Release package oxideav-mjpeg version 0.0.3