libjpeg_turbo_dart 1.0.0
libjpeg_turbo_dart: ^1.0.0 copied to clipboard
Pure-Dart port of libjpeg-turbo: JPEG decode, encode, and lossless transforms with output verified byte-for-byte against the C library.
libjpeg-turbo-dart #
A pure-Dart port of libjpeg-turbo: JPEG decode, encode, and lossless transforms with output verified byte-for-byte against the C library. No native dependencies.
The port tracks the libjpeg-turbo 3.2.0 sources and transliterates the
libjpeg API, so anyone familiar with jpeg_read_header /
jpeg_start_decompress will feel at home — the same call sequences, the
same knobs, the same behavior, down to the bit.
Why this exists #
Every platform binding of libjpeg-turbo drags a native build along. This
package is the other trade: a single-threaded, pure-Dart implementation
that produces identical output — not "visually identical", but
cmp-identical against djpeg, cjpeg, and jpegtran across a battery
of several hundred cross-validation cases.
Features #
All of the following are implemented and cross-validated against the native tools (or, where the CLI tools can't reach a code path, against C reference drivers compiled from the libjpeg-turbo sources):
Decompression
- Baseline, progressive, and arithmetic-coded (sequential + progressive) JPEG, at 8-bit and 12-bit data precision
- Lossless JPEG, all 7 predictors, point transform, 2–16-bit precision
- Restart markers in all coding modes; best-effort decoding of truncated and corrupt streams (identical to djpeg, including block smoothing / DC interpolation for partial progressive files)
- Scaled decoding, all factors N/8 for N = 1..16
- Fancy and merged (
-nosmooth) upsampling, all common subsamplings - Color quantization: 1-pass and 2-pass, Floyd-Steinberg / ordered / no
dithering, plus external colormaps (djpeg
-mapparity) - Output color spaces: grayscale, RGB, CMYK/YCCK, RGB565 (with ordered
dithering), and all extended pixel formats (
extRgb,extBgra,extArgb, …) with alpha fill - Partial decompression (
jpegSkipScanlines/jpegCropScanline), buffered-image mode, and a push-style suspending streaming source (jpegStreamSrc) for chunk-fed input - ICC profile extraction (multi-chunk reassembly)
Compression
- Baseline, progressive, arithmetic, and lossless encoding; 8-bit and 12-bit lossy, 2–16-bit lossless
- Optimized Huffman tables, custom scan scripts (cjpeg
-scansparity), restart intervals, input smoothing,-baselinequant clamping - Input color spaces: grayscale, RGB, YCbCr, CMYK/YCCK, and all extended pixel formats
- ICC profile embedding; growing in-memory destination
Lossless transforms (jpegtran parity)
- Rotate/flip/transpose/transverse with
-trimand-perfect, crop (including non-iMCU-aligned), wipe, drop, roll, grayscale reduction, marker copying, re-coding to progressive/arithmetic/optimized
Usage #
Decode a JPEG from memory:
import 'dart:io';
import 'dart:typed_data';
import 'package:libjpeg_turbo_dart/libjpeg_turbo_dart.dart';
void main() {
final jpeg = File('input.jpg').readAsBytesSync();
final cinfo = JpegDecompress();
cinfo.err = jpegStdError();
jpegCreateDecompress(cinfo, jpegLibVersion, 0);
jpegMemSrc(cinfo, jpeg);
jpegReadHeader(cinfo, true);
jpegStartDecompress(cinfo);
final w = cinfo.outputWidth, h = cinfo.outputHeight;
final nc = cinfo.outputComponents; // 3 for an RGB decode
final pixels = Uint8List(w * h * nc);
// Uint8List rows take a fast path in the color converters.
final row = Uint8List(w * nc);
while (cinfo.outputScanline < h) {
final y = cinfo.outputScanline;
jpegReadScanlines(cinfo, [row], 1);
pixels.setRange(y * w * nc, (y + 1) * w * nc, row);
}
jpegFinishDecompress(cinfo);
jpegDestroyDecompress(cinfo);
// pixels now holds interleaved RGB scanlines.
}
Encode RGB pixels to a JPEG in memory:
final cinfo = JpegCompress();
cinfo.err = jpegStdError();
jpegCreateCompress(cinfo);
jpegMemDest(cinfo, 1 << 20); // initial buffer size; grows as needed
cinfo.imageWidth = w;
cinfo.imageHeight = h;
cinfo.inputComponents = 3;
cinfo.inColorSpace = JColorSpace.rgb;
jpegSetDefaults(cinfo);
jpegSetQuality(cinfo, 85, false);
jpegStartCompress(cinfo, true);
final row = Uint8List(w * 3);
while (cinfo.nextScanline < cinfo.imageHeight) {
// ... fill row with interleaved RGB for scanline cinfo.nextScanline ...
jpegWriteScanlines(cinfo, [row], 1);
}
jpegFinishCompress(cinfo);
final Uint8List bytes = jpegMemDestGetBuffer(cinfo);
jpegDestroyCompress(cinfo);
Errors surface as catchable JpegError exceptions (the port's equivalent
of the C library's error_exit longjmp); recoverable corruption produces
warnings and best-effort output, exactly as in C.
The tool/ directory contains cjpeg/djpeg/jpegtran-equivalent drivers
(enc.dart, dec.dart, tran.dart, and friends) that double as usage
references and as the cross-validation harness.
Verification #
Correctness here means byte-for-byte identity with libjpeg-turbo, not approximate equivalence:
- The test suite (
dart test, ~290 tests) synthesizes a deterministic corpus withcjpegat setup, then compares decodes bit-exactly againstdjpeg(including truncated/corrupt streams, scaling, quantization), encodes byte-identically againstcjpegacross the option matrix, and transforms byte-identically againstjpegtran. The tests skip cleanly when the native tools are not onPATH. - Paths the CLI tools cannot exercise (CMYK/YCCK in both directions,
buffered-image output passes, extended pixel formats, dithered RGB565)
are verified against small C reference drivers (
tool/*.c) built from the no-SIMD libjpeg-turbo 3.2.0 sources, byte-comparing raw dumps and JPEG output in both directions.
Documented deviations:
-dct floatat 12-bit precision differs from C by ≤1 LSB on a handful of samples (the port computes in float64, C in float32; the C sources themselves disclaim cross-machine float reproducibility). 8-bit float decode is byte-identical.- RGB565 output is supported at 8-bit precision only (as in the TurboJPEG API).
- The port matches libjpeg-turbo 3.2.0 behavior; djpeg 3.1.x renders one specific mid-scan progressive truncation differently.
Performance #
Single-threaded, AOT-compiled on Apple Silicon, decoding to Uint8List
rows:
| Case | time |
|---|---|
| decode 768×1024 baseline 4:2:0 q90 | ~13 ms |
| decode 1632×1224 progressive 4:2:0 | ~46 ms |
| encode 768×1024 RGB → q85 4:2:0 | ~22 ms |
That is at parity with a scalar (no-SIMD) C build of libjpeg-turbo. The remaining ~3× gap to the stock SIMD-enabled library is hand-written NEON/SSE vector code, which no current Dart backend can express — this is close to the ceiling for a pure-Dart implementation.
Limitations #
- No SIMD (see above) and single-threaded, like the C library.
- Developed and verified on the native Dart VM (JIT and AOT); dart2js/dart2wasm are untested.
- The TurboJPEG-style wrapper (
turbojpeg.dart) is a convenience layer that has not been through the same cross-validation as the libjpeg API; prefer the core API for now.
Architecture #
The code follows the original libjpeg-turbo architecture with these key modules:
| Module | Files | Description |
|---|---|---|
| Core types | jpeglib.dart, jmorecfg.dart, jconfig.dart |
Data structures, type definitions |
| Error handling | jerror.dart |
Error codes, message table, error manager |
| Memory manager | jmemmgr.dart |
Pool-based allocation (simplified for Dart GC) |
| DCT/IDCT | jfdct_*.dart, jidct_*.dart, jdct.dart |
Forward/inverse DCT algorithms |
| Huffman coding | jchuff.dart, jdhuff.dart, jstdhuff.dart |
Huffman encode/decode |
| Arithmetic coding | jcarith.dart, jdarith.dart, jaricom.dart |
Arithmetic encode/decode |
| Color conversion | jccolor.dart, jdcolor.dart |
RGB↔YCbCr, CMYK↔YCCK, RGB565, extended formats |
| Sampling | jcsample.dart, jdsample.dart, jdmerge.dart |
Chroma up/downsampling, merged upsampling |
| Compression | jcapimin.dart, jcparam.dart, jcmaster.dart, … |
Full compression pipeline |
| Decompression | jdapimin.dart, jdmaster.dart, jdmarker.dart, … |
Full decompression pipeline |
| Progressive | jcphuff.dart, jdphuff.dart |
Progressive Huffman |
| Lossless | jclhuff.dart, jdlhuff.dart, jcdiffct.dart, jddiffct.dart |
Lossless JPEG |
| Transforms | transupp.dart |
jpegtran-style lossless transformations |
License #
This software is covered by two compatible BSD-style open source licenses, identical to the original libjpeg-turbo:
- The IJG (Independent JPEG Group) License — applies to the libjpeg API library and associated code
- The Modified (3-clause) BSD License — applies to the TurboJPEG API library and associated programs
See LICENSE and README.ijg for full license text. The zlib License section of libjpeg-turbo covers only its SIMD extensions, which are not part of this port.
Provenance #
This package is a derivative work: a translation of the
libjpeg-turbo 3.2.0 C
sources (themselves based on the Independent JPEG Group's libjpeg) into
Dart. Per the IJG license conditions, the original copyright notices are
retained unaltered in every ported source file, the accompanying
README.ijg file is included unaltered, and this section documents that
the sources have been modified (translated from C to Dart, with
Dart-specific restructuring and optimizations). The Dart port itself is
- Copyright (C) 2026, Fredrik Smedberg
and that notice is layered on top of the original ones in each ported file, the same way libjpeg-turbo layers its notices over the IJG's. The port is distributed under the same licenses as the code it derives from.
This project is not affiliated with or endorsed by the libjpeg-turbo project or the Independent JPEG Group.
Acknowledgment #
This software is based in part on the work of the Independent JPEG Group.