jpeg: document baseline encoding

This commit is contained in:
Dylan M. Taylor committed 2026-11-14 11:21:01 +00:00
1 parent 55901e1d1a
commit 9ce392cb6a
3 files changed
+40 -14

No files matched your search

+4 -3
View File
@@ -231,9 +231,10 @@ See the [release notes](CHANGELOG.md) for included functionality and known limit
sockets: session, requests of method calls with back-references, responses, ids, address
templates, credentials and the judgement of HTTP answers, over any HTTP implementation. Bounded
on hostile answers; what a program asks and makes of the answers stays in it.
- [`craft-jpeg`](crates/jpeg/README.md): clean-room decoder for 12-bit and arithmetic-coded
JPEG (T.81 SOF0/SOF1 at 12 bits, SOF9, SOF10), from PhotoCraft, and a frame probe for routing.
Metadata stays in each app.
- [`craft-jpeg`](crates/jpeg/README.md): clean-room JPEG decoder for 12-bit and arithmetic-coded
frames (T.81 SOF0/SOF1 at 12 bits, SOF9, SOF10), band decoding, and an optimized 8-bit baseline
encoder, from PhotoCraft; includes a frame probe for routing. Metadata interpretation stays in
each app.
- [`craft-raster-codecs`](crates/raster-codecs/README.md): small raster formats, one module each,
from PhotoCraft: DDS (BC1 to BC3, read and write), DICOM, FITS (read and write), IFF/ILBM (read
and write), PCX, PNM/PAM/PFM (read and write) and XPM, into one flat image type. Bounded and
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "craft-jpeg"
description = "Clean-room JPEG decoder (ITU-T T.81): 12-bit and arithmetic-coded frames, and baseline frames a band of rows at a time."
description = "Clean-room JPEG decoder and 8-bit optimized baseline encoder (ITU-T T.81)."
version.workspace = true
edition.workspace = true
license.workspace = true
+35 -10
View File
@@ -1,11 +1,14 @@
# craft-jpeg
A clean-room JPEG decoder for the Craft apps, written from ITU-T T.81 (ISO/IEC 10918-1). It
covers the processes that common Rust JPEG decoders skip: 12-bit samples and arithmetic coding.
It also decodes the common sequential 8-bit files a band of rows at a time ([`Bands`](#decoding-in-bands)),
for images too large to decode into one buffer.
No `unsafe`, no panics on malformed input, and it builds for `wasm32-unknown-unknown`. The only
dependency is `thiserror`.
A clean-room JPEG decoder and 8-bit baseline encoder for the Craft apps, written from ITU-T T.81
(ISO/IEC 10918-1). The decoder covers processes that common Rust JPEG decoders skip: 12-bit
samples and arithmetic coding. It also decodes the common sequential 8-bit files a band of rows
at a time ([`Bands`](#decoding-in-bands)), for images too large to decode into one buffer. The
baseline encoder writes per-image optimized Huffman tables and supports grayscale and YCbCr 4:4:4,
4:2:2 and 4:2:0.
No `unsafe`, no panics on malformed input, and it builds for `wasm32-unknown-unknown`. The required
dependency is `thiserror`; the optional `parallel` feature adds Rayon for multi-core encoding.
```toml
craft-jpeg = { git = "https://github.com/storytold/craft-libs", rev = "<commit>" }
@@ -99,6 +102,24 @@ fn use_craft_jpeg(bytes: &[u8]) -> bool {
SOF11 (lossless) falls in that range and returns `Unsupported`.
## Baseline encoding
`encode_baseline(data, width, height, channels, quality, subsampling, app_segments)` writes an
interleaved 8-bit baseline JPEG with optimized Huffman tables. `channels` is 1 (gray), 3 (RGB), or
4 (RGBA; alpha is ignored). Dimensions must be in `1..=65535`, quality is clamped to `1..=100`,
and a short sample buffer is rejected. APP segments must use markers `0xE0..=0xEF` and payloads
no larger than 65533 bytes. Invalid input returns `EncodeError::InvalidInput` instead of an empty
or malformed JPEG.
```rust
use craft_jpeg::{ChromaSubsampling, encode_baseline};
let jpeg = encode_baseline(&rgb, width, height, 3, 90, ChromaSubsampling::S420, &[])?;
```
Enable the `parallel` Cargo feature to encode restart-interval bands concurrently. The output is
byte-stable across sequential and parallel execution.
## Decoding in bands
`Bands` decodes a sequential Huffman-coded (SOF0, SOF1) 8-bit file whose first scan holds every
@@ -141,10 +162,11 @@ if reader.damaged() { /* warn: part of the image is missing */ }
## Boundaries
- In scope: decoding the frames above into samples.
- Out of scope: metadata. The decoder skips every APPn and COM segment. ICC profiles, EXIF,
XMP, Photoshop resources, resolution and the APP14 `Adobe` segment are the app's to read.
Color management and encoding also stay in the apps.
- In scope: decoding the frames above into samples and encoding 8-bit baseline JPEG.
- Out of scope: metadata interpretation. The decoder skips every APPn and COM segment. ICC
profiles, EXIF, XMP, Photoshop resources, resolution and the APP14 `Adobe` segment are the
caller's to read; the encoder accepts caller-constructed APP segments. Color management stays
with each app.
## Testing
@@ -152,6 +174,9 @@ if reader.damaged() { /* warn: part of the image is missing */ }
- unit tests in `src/tests.rs`: arithmetic-coded streams from a test-only encoder must decode to
the coefficients that went in, and Huffman streams are compared against `zune-jpeg`.
- unit tests in `src/encode.rs`: optimized table validity and file-size reduction, independent
`jpeg-decoder` and `zune-jpeg` decoding, 4:4:4/4:2:2/4:2:0 frame-header sampling, invalid input,
and pinned output hashes for grayscale, RGB, RGBA and the existing 4:2:0 path.
- `tests/conformance.rs`, on libjpeg-turbo's test images in `tests/data` (provenance and how
the derived files were made: `tests/data/SOURCE.md`):
- `testimgari.jpg` (SOF9) decodes to exactly the samples of its jpegtran Huffman transcode.