libjpeg_turbo_rs/lib.rs
1//! Pure-Rust reimplementation of [libjpeg-turbo] with NEON / SSE2 /
2//! AVX2 / WASM-SIMD128 acceleration — no C dependencies, no unsafe FFI.
3//!
4//! The one-shot functions cover most uses: [`decompress`] /
5//! [`decompress_to`] / [`decompress_into`] for decoding, [`compress`]
6//! and the [`Encoder`] builder for encoding, [`transform()`] for
7//! lossless (DCT-domain) rotation and flips. The configurable
8//! [`Decoder`] adds scaling, cropping, output formats, and resource
9//! limits.
10//!
11//! [libjpeg-turbo]: https://github.com/libjpeg-turbo/libjpeg-turbo
12//!
13//! # Quickstart
14//!
15//! Decode a JPEG to RGB pixels:
16//!
17//! ```
18//! use libjpeg_turbo_rs::{decompress, PixelFormat, Subsampling};
19//! # let jpeg_bytes = libjpeg_turbo_rs::compress(
20//! # &[128; 16 * 16 * 3], 16, 16, PixelFormat::Rgb, 90, Subsampling::S420,
21//! # ).unwrap();
22//! let image = decompress(&jpeg_bytes)?;
23//! assert_eq!(image.pixel_format, PixelFormat::Rgb);
24//! assert_eq!(image.data.len(), image.width * image.height * 3);
25//! # Ok::<(), libjpeg_turbo_rs::JpegError>(())
26//! ```
27//!
28//! Encode RGB pixels to a JPEG:
29//!
30//! ```
31//! use libjpeg_turbo_rs::{compress, PixelFormat, Subsampling};
32//!
33//! let (width, height) = (32, 24);
34//! let rgb = vec![200u8; width * height * 3];
35//! let jpeg = compress(&rgb, width, height, PixelFormat::Rgb, 85, Subsampling::S420)?;
36//! assert_eq!(&jpeg[..2], &[0xFF, 0xD8], "SOI marker");
37//! # Ok::<(), libjpeg_turbo_rs::JpegError>(())
38//! ```
39//!
40//! Probe a header without decoding any pixels — [`probe`] parses
41//! markers only (multi-scan streams are walked byte-wise to find scan
42//! boundaries, so worst-case cost is linear in the compressed size,
43//! far below a decode):
44//!
45//! ```
46//! # let jpeg_bytes = libjpeg_turbo_rs::compress(
47//! # &[0; 64 * 48 * 3], 64, 48,
48//! # libjpeg_turbo_rs::PixelFormat::Rgb, 90,
49//! # libjpeg_turbo_rs::Subsampling::S444,
50//! # ).unwrap();
51//! let info = libjpeg_turbo_rs::probe(&jpeg_bytes)?;
52//! assert_eq!((info.width, info.height), (64, 48));
53//! assert_eq!(info.exif_orientation, None); // no EXIF in this fixture
54//! # Ok::<(), libjpeg_turbo_rs::JpegError>(())
55//! ```
56//!
57//! # Canonical API map
58//!
59//! Rustdoc renders every export flat, so here is which entry point to
60//! reach for first (issue #386):
61//!
62//! | Task | Canonical path |
63//! |---|---|
64//! | Decode, defaults fine | [`decompress`] (or [`decompress_to`] / [`decompress_into`]) |
65//! | Decode, configured | [`Decoder`] — chainable `with_*`, or imperative `set_*` |
66//! | Probe header/metadata | [`probe`] (one call), [`Decoder::new`] + accessors for more |
67//! | Encode, defaults fine | [`compress`] |
68//! | Encode, configured | [`Encoder`] builder — quality, subsampling, progressive, markers |
69//! | Lossless transform | [`transform()`] — DCT-domain rotate/flip/crop, no re-encode |
70//! | Row streaming | [`ScanlineDecoder`] / [`ScanlineEncoder`], `stream::*` for readers |
71//! | Bounded-memory stream decode | [`decompress_from_reader_incremental`] — sliding input window for interleaved baseline JPEGs |
72//! | 12/16-bit & lossless | [`precision`] module |
73//!
74//! **Specialised `compress_*` entry points.** The remaining
75//! free-function encode variants ([`compress_optimized`],
76//! [`compress_progressive`], [`compress_arithmetic`],
77//! [`compress_arithmetic_progressive`], the `compress_lossless*`
78//! family, [`compress_with_metadata`], …) predate the [`Encoder`]
79//! builder, which can express every one of them —
80//! e.g. `Encoder::new(&px, w, h, fmt).progressive(true)` replaces
81//! [`compress_progressive`]. [`compress_into`] is the exception: it
82//! fills a caller-owned buffer, and [`Encoder::encode`] always returns
83//! a fresh `Vec`. They stay for source compatibility and
84//! one-line convenience; new code should prefer [`Encoder`] once more
85//! than one knob is involved. **Deprecation intent:** these variants
86//! are candidates for `#[deprecated]` at the next semver-major once
87//! [`Encoder`] has shipped a full release cycle without gaps; none are
88//! removed before that.
89//!
90//! # Feature flags
91//!
92//! - **`std`** *(default)*: `std::io` streaming API, file-path helpers,
93//! runtime CPU-feature detection, and std-backed error types.
94//! - **`no_std` + `alloc`**: disable default features for the core
95//! codec (headers, entropy decode, IDCT, upsample, colour convert,
96//! encode). SIMD then dispatches on compile-time `target_feature`
97//! only — there is no CPUID probe without `std` (issue #356).
98//! - **`simd`** *(default)*: architecture intrinsics (NEON / SSE2 /
99//! AVX2 / WASM SIMD128).
100//! - **`png`**: PNG helpers for `tj3LoadImage8` / `tj3SaveImage8`
101//! (implies `std`).
102//!
103//! Performance against C libjpeg-turbo and zune-jpeg, replacement-tier
104//! status for the C ABI shims, and build-flag guidance live in the
105//! [repository README](https://github.com/developer0hye/libjpeg-turbo-rs).
106#![cfg_attr(not(feature = "std"), no_std)]
107#![cfg_attr(docsrs, feature(doc_cfg))]
108// Safety posture (issue #389): every unsafe operation inside an unsafe fn
109// must be acknowledged with its own block + SAFETY reasoning, matching
110// the capi crate's existing policy.
111#![deny(unsafe_op_in_unsafe_fn)]
112
113// libjpeg-turbo-rs: alloc prelude (no_std support, issue #356)
114#[allow(unused_imports)]
115use alloc::vec::Vec;
116extern crate alloc;
117
118pub mod api;
119pub mod common;
120pub mod decode;
121pub mod encode;
122// The SIMD backends predate the crate-level lint and carry ~780 bare
123// unsafe operations inside their unsafe fns; wrapping each with its own
124// block + SAFETY note is a mechanical-but-careful sweep tracked in
125// LAST_MILE (filed with issue #389). Non-SIMD code gets the full deny
126// immediately.
127#[allow(unsafe_op_in_unsafe_fn)]
128pub mod simd;
129pub mod transform;
130
131pub use api::abbreviated::{read_header, HeaderResult, TablesOnlyState};
132#[doc(inline)]
133pub use api::coefficient::{
134 copy_critical_parameters, read_coefficients, transform_jpeg as transform,
135 transform_jpeg_with_options, write_coefficients, write_coefficients_arithmetic,
136 write_coefficients_optimized, write_coefficients_progressive,
137 write_coefficients_progressive_arithmetic, ComponentCoefficients, EncoderComponentInfo,
138 EncoderConfig, JpegCoefficients,
139};
140#[doc(inline)]
141pub use api::encoder::{Encoder, HuffmanTableDef};
142
143/// Produce a tables-only abbreviated JPEG datastream for the given encoder configuration.
144///
145/// Returns `SOI + DQT(s) + DHT(s) + EOI` with no image data. Equivalent to
146/// libjpeg-turbo's `jpeg_write_tables()`. The stream can be parsed by `read_header()`
147/// to preload tables for subsequent decoding of body-only streams.
148pub fn jpeg_write_tables(encoder: &Encoder<'_>) -> Vec<u8> {
149 encoder.write_tables()
150}
151#[doc(inline)]
152pub use api::high_level::{
153 compress, compress_arithmetic, compress_arithmetic_progressive, compress_into,
154 compress_lossless, compress_lossless_arithmetic, compress_lossless_extended,
155 compress_optimized, compress_progressive, compress_with_metadata, decompress,
156 decompress_cropped, decompress_into, decompress_lenient, decompress_to, output_buffer_size,
157};
158#[cfg(feature = "png")]
159#[cfg(feature = "std")]
160#[cfg_attr(docsrs, doc(cfg(feature = "png")))]
161pub use api::image_io::load_png_from_bytes;
162#[cfg(all(feature = "png", any(not(target_arch = "wasm32"), target_os = "wasi")))]
163#[cfg(feature = "std")]
164#[cfg_attr(
165 docsrs,
166 doc(cfg(all(feature = "png", any(not(target_arch = "wasm32"), target_os = "wasi"))))
167)]
168pub use api::image_io::save_png;
169#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
170#[cfg(feature = "std")]
171#[cfg_attr(
172 docsrs,
173 doc(cfg(all(feature = "std", any(not(target_arch = "wasm32"), target_os = "wasi"))))
174)]
175pub use api::image_io::{
176 load_image, load_ppm_12bit, load_ppm_16bit, save_bmp, save_ppm, save_ppm_12bit, save_ppm_16bit,
177};
178#[cfg(feature = "std")]
179#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
180pub use api::image_io::{
181 load_image_from_bytes, load_ppm_12bit_from_bytes, load_ppm_16bit_from_bytes, LoadedImage,
182 LoadedImage12, LoadedImage16,
183};
184#[doc(inline)]
185#[cfg(feature = "std")]
186#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
187pub use api::incremental::{
188 decompress_from_reader_incremental, decompress_from_reader_incremental_instrumented,
189};
190pub use api::precision::{
191 compress_12bit, compress_16bit, decompress_12bit, decompress_16bit, read_scanlines_12,
192 read_scanlines_16, write_scanlines_12, write_scanlines_16,
193};
194pub use api::quality::quality_scaling;
195pub use api::quantize::requantize;
196pub use api::raw_data::{compress_raw, decompress_raw, RawImage};
197/// 12-bit raw planar encode/decode (YCbCr component planes at native resolution).
198pub mod raw_data_12 {
199 pub use crate::api::raw_data_12::{compress_raw_12, decompress_raw_12, RawImage12};
200}
201pub use api::raw_thumbnail::extract_embedded_jpeg;
202pub use encode::marker_writer::MarkerStreamWriter;
203/// Color quantization for 8-bit indexed/palette output.
204pub mod quantize {
205 pub use crate::api::quantize::{
206 dequantize, quantize, requantize, DitherMode, QuantizeOptions, QuantizedImage,
207 };
208}
209#[doc(inline)]
210pub use api::progressive_output::ProgressiveDecoder;
211#[doc(inline)]
212pub use api::scanline::{ScanlineDecoder, ScanlineEncoder};
213/// Streaming I/O functions for reading/writing JPEG via `std::io` traits and file paths.
214#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
215#[cfg(feature = "std")]
216pub use api::stream;
217pub use common::bufsize::{
218 calc_jpeg_dimensions, calc_output_dimensions, jpeg_buf_size, transform_buf_size, yuv_buf_size,
219 yuv_plane_height, yuv_plane_size, yuv_plane_width,
220};
221#[doc(inline)]
222pub use common::error::{DecodeWarning, JpegError, Result};
223pub use common::jfif::extract_jfif_thumbnail;
224pub use common::sample::Sample;
225pub use common::traits::{DefaultErrorHandler, ErrorHandler, ProgressInfo, ProgressListener};
226pub use common::types::*;
227#[doc(inline)]
228pub use decode::pipeline::{probe, Decoder, Image, ImageInfo, JpegInfo};
229pub use decode::resync::{DefaultResyncStrategy, RestartResyncStrategy, ResyncAction};
230#[doc(inline)]
231pub use transform::{MarkerCopyMode, TransformOp, TransformOptions};
232
233/// Whether this build of the codec has both the `simd` and `std` cargo
234/// features — the feature wiring a repackaging crate must preserve.
235///
236/// This is a feature-flag predicate, not a claim about the current CPU
237/// or target. What the two features buy is target-dependent: `simd`
238/// compiles the vector kernels (NEON / SSE2 / AVX2 / WASM SIMD128 where
239/// the target has them; other architectures run scalar either way), and
240/// `std` additionally lets `x86_64` builds pick AVX2 vs SSE2 through
241/// `std::arch::is_x86_feature_detected!` at runtime — without it a
242/// stock x86-64-baseline build never takes the AVX2 paths.
243///
244/// Downstream crates that repackage this codec (e.g. the `image`
245/// bridge) assert on this to catch a feature-wiring regression — issue
246/// #381 shipped exactly that: the bridge depended on the codec with
247/// `default-features = false`, silently turning its performance
248/// headline off.
249pub const fn simd_and_std_features_enabled() -> bool {
250 cfg!(all(feature = "std", feature = "simd"))
251}
252/// 12-bit and 16-bit sample precision support.
253pub mod precision {
254 pub use crate::api::precision::{
255 compress_12bit, compress_16bit, compress_lossless_arbitrary, decompress_12bit,
256 decompress_16bit, decompress_lossless_arbitrary, read_scanlines_12, read_scanlines_16,
257 write_scanlines_12, write_scanlines_16, Image12, Image16,
258 };
259}
260/// TJ3-compatible handle/parameter API.
261pub mod tj3 {
262 pub use crate::api::tj3::{TjHandle, TjParam};
263}