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
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion crates/jpeg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,8 +12,14 @@ workspace = true

[dependencies]
thiserror = { workspace = true }
rayon = { workspace = true, optional = true }

[dev-dependencies]
# Reference encoder and baseline decoder for the comparison tests.
jpeg-encoder = "0.6"
zune-jpeg = "0.5"
jpeg-decoder = "0.3"

[features]
default = []
parallel = ["dep:rayon"]
45 changes: 35 additions & 10 deletions crates/jpeg/README.md
Original file line number Diff line number Diff line change
@@ -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>" }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -141,17 +162,21 @@ 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

`cargo test -p craft-jpeg` runs:

- 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.
Expand Down
Loading
Loading