From 9ce392cb6a30687533c4dd67c902e4bbc741749f Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sat, 14 Nov 2026 11:21:01 +0000 Subject: [PATCH] jpeg: document baseline encoding --- README.md | 7 ++++--- crates/jpeg/Cargo.toml | 2 +- crates/jpeg/README.md | 45 ++++++++++++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index aa47358da..d094847d0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/crates/jpeg/Cargo.toml b/crates/jpeg/Cargo.toml index 708a36db0..fba86444d 100644 --- a/crates/jpeg/Cargo.toml +++ b/crates/jpeg/Cargo.toml @@ -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 diff --git a/crates/jpeg/README.md b/crates/jpeg/README.md index 6b04cc8ea..3b72ebd06 100644 --- a/crates/jpeg/README.md +++ b/crates/jpeg/README.md @@ -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 = "" } @@ -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.