mozjpeg_rs/lib.rs
1//! # mozjpeg-oxide
2//!
3//! Pure Rust JPEG encoder based on Mozilla's [mozjpeg](https://github.com/mozilla/mozjpeg).
4//!
5//! This library provides a high-quality JPEG encoder with mozjpeg's advanced compression features:
6//!
7//! - **Trellis quantization** - Rate-distortion optimized coefficient selection
8//! - **Progressive JPEG** - Multi-scan encoding with DC-first, AC-band progression
9//! - **Huffman optimization** - 2-pass encoding for optimal entropy coding
10//! - **Overshoot deringing** - Reduces ringing artifacts near hard edges
11//!
12//! ## Quick Start
13//!
14//! The [`Encoder`] struct is the main entry point for encoding images:
15//!
16//! ```no_run
17//! use mozjpeg_rs::{Encoder, Preset};
18//!
19//! # fn main() -> Result<(), mozjpeg_rs::Error> {
20//! // RGB pixel data (3 bytes per pixel, row-major order)
21//! let rgb_pixels: Vec<u8> = vec![0; 640 * 480 * 3];
22//!
23//! // Default: progressive with all optimizations (recommended)
24//! let jpeg_data = Encoder::new(Preset::default())
25//! .quality(85)
26//! .encode_rgb(&rgb_pixels, 640, 480)?;
27//!
28//! // Fastest encoding (no optimizations)
29//! let jpeg_data = Encoder::new(Preset::BaselineFastest)
30//! .quality(85)
31//! .encode_rgb(&rgb_pixels, 640, 480)?;
32//!
33//! // Maximum compression (matches C mozjpeg)
34//! let jpeg_data = Encoder::new(Preset::ProgressiveSmallest)
35//! .quality(85)
36//! .encode_rgb(&rgb_pixels, 640, 480)?;
37//! # Ok(())
38//! # }
39//! ```
40//!
41//! ## Encoder Presets
42//!
43//! | Preset | Time | Size | Use Case |
44//! |--------|------|------|----------|
45//! | [`Preset::BaselineFastest`] | ~2ms | baseline | Real-time, thumbnails |
46//! | [`Preset::BaselineBalanced`] | ~7ms | -13% | Sequential playback |
47//! | [`Preset::ProgressiveBalanced`] | ~9ms | -13% | Web images (default) |
48//! | [`Preset::ProgressiveSmallest`] | ~21ms | -14% | Storage, archival |
49//!
50//! *Benchmarks: 512×512 Q75 image*
51//!
52//! ## Advanced Configuration
53//!
54//! ```no_run
55//! use mozjpeg_rs::{Encoder, Preset, Subsampling, TrellisConfig, QuantTableIdx};
56//!
57//! # fn main() -> Result<(), mozjpeg_rs::Error> {
58//! # let rgb_pixels: Vec<u8> = vec![0; 100 * 100 * 3];
59//! let jpeg_data = Encoder::new(Preset::BaselineBalanced)
60//! .quality(75)
61//! .progressive(true) // Override to progressive
62//! .subsampling(Subsampling::S420) // 4:2:0 chroma subsampling
63//! .quant_tables(QuantTableIdx::Flat) // Flat quantization tables
64//! .trellis(TrellisConfig::default()) // Enable trellis quantization
65//! .optimize_huffman(true) // 2-pass Huffman optimization
66//! .overshoot_deringing(true) // Reduce ringing at edges
67//! .encode_rgb(&rgb_pixels, 100, 100)?;
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! ## Grayscale Encoding
73//!
74//! ```no_run
75//! use mozjpeg_rs::{Encoder, Preset};
76//!
77//! # fn main() -> Result<(), mozjpeg_rs::Error> {
78//! let gray_pixels: Vec<u8> = vec![128; 100 * 100]; // 1 byte per pixel
79//!
80//! let jpeg_data = Encoder::new(Preset::default())
81//! .quality(85)
82//! .encode_gray(&gray_pixels, 100, 100)?;
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! ## Writing to a File or Stream
88//!
89//! ```no_run
90//! use mozjpeg_rs::{Encoder, Preset};
91//! use std::fs::File;
92//!
93//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
94//! # let rgb_pixels: Vec<u8> = vec![0; 100 * 100 * 3];
95//! let mut file = File::create("output.jpg")?;
96//!
97//! Encoder::new(Preset::default())
98//! .quality(85)
99//! .encode_rgb_to_writer(&rgb_pixels, 100, 100, &mut file)?;
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! ## Metadata
105//!
106//! ```no_run
107//! use mozjpeg_rs::{Encoder, Preset, PixelDensity};
108//!
109//! # fn main() -> Result<(), mozjpeg_rs::Error> {
110//! # let rgb_pixels: Vec<u8> = vec![0; 100 * 100 * 3];
111//! # let exif_bytes: Vec<u8> = vec![];
112//! # let icc_profile: Vec<u8> = vec![];
113//! let jpeg_data = Encoder::new(Preset::default())
114//! .quality(85)
115//! .pixel_density(PixelDensity::dpi(300, 300)) // 300 DPI
116//! .exif_data(exif_bytes) // EXIF metadata
117//! .icc_profile(icc_profile) // Color profile
118//! .encode_rgb(&rgb_pixels, 100, 100)?;
119//! # Ok(())
120//! # }
121//! ```
122
123// Enforce no unsafe code in this crate, except where explicitly allowed.
124// SIMD intrinsics in dct.rs and test FFI code are the only exceptions.
125#![deny(unsafe_code)]
126#![warn(missing_docs)]
127
128// ============================================================================
129// Internal modules - hidden from public docs but accessible for tests
130// ============================================================================
131// These modules contain internal implementation details. They are exposed
132// for testing and advanced use cases but are not part of the stable API.
133
134/// Bitstream writing utilities (internal).
135#[doc(hidden)]
136#[allow(dead_code)]
137pub mod bitstream;
138
139/// Color conversion utilities (internal).
140#[doc(hidden)]
141#[allow(dead_code)]
142pub mod color;
143
144/// Constants and quantization tables (internal).
145#[doc(hidden)]
146#[allow(dead_code)]
147pub mod consts;
148
149/// DCT transform (internal).
150#[doc(hidden)]
151#[allow(dead_code)]
152pub mod dct;
153
154/// Overshoot deringing (internal).
155#[doc(hidden)]
156#[allow(dead_code)]
157pub mod deringing;
158
159/// Entropy encoding (internal).
160#[doc(hidden)]
161#[allow(dead_code)]
162pub mod entropy;
163
164/// Huffman table utilities (internal).
165#[doc(hidden)]
166#[allow(dead_code)]
167pub mod huffman;
168
169/// JPEG marker writing (internal).
170#[doc(hidden)]
171#[allow(dead_code)]
172pub mod marker;
173
174/// Progressive scan generation (internal).
175#[doc(hidden)]
176#[allow(dead_code)]
177pub mod progressive;
178
179/// Quantization utilities (internal).
180#[doc(hidden)]
181#[allow(dead_code)]
182pub mod quant;
183
184/// Chroma subsampling (internal).
185#[doc(hidden)]
186#[allow(dead_code)]
187pub mod sample;
188
189/// Input smoothing filter (internal).
190#[doc(hidden)]
191#[allow(dead_code)]
192pub mod smooth;
193
194/// Scan optimization (internal).
195#[doc(hidden)]
196#[allow(dead_code)]
197pub mod scan_optimize;
198
199/// Sequential scan trial encoding (internal).
200#[doc(hidden)]
201#[allow(dead_code)]
202pub mod scan_trial;
203
204/// SIMD acceleration (internal).
205#[doc(hidden)]
206#[allow(dead_code)]
207pub mod simd;
208
209/// Trellis quantization (internal).
210#[doc(hidden)]
211#[allow(dead_code)]
212pub mod trellis;
213
214/// Type definitions (internal).
215#[doc(hidden)]
216#[allow(dead_code)]
217pub mod types;
218
219// Main encoder module (not hidden)
220mod encode;
221mod error;
222
223// Optional mozjpeg-sys configuration layer
224#[cfg(feature = "mozjpeg-sys-config")]
225pub mod compat;
226
227// ============================================================================
228// Test support modules - hidden from public API
229// ============================================================================
230
231/// Test encoder module for comparing Rust vs C implementations.
232#[doc(hidden)]
233pub mod test_encoder;
234
235/// Corpus utilities for locating test images.
236#[doc(hidden)]
237pub mod corpus;
238
239// ============================================================================
240// Public API
241// ============================================================================
242
243/// The main JPEG encoder.
244///
245/// Use the builder pattern to configure encoding options, then call
246/// [`encode_rgb()`](Encoder::encode_rgb) or [`encode_gray()`](Encoder::encode_gray)
247/// to produce JPEG data.
248///
249/// # Presets
250///
251/// Use [`Encoder::new(preset)`](Encoder::new) with a [`Preset`] to choose your settings:
252///
253/// - [`Preset::ProgressiveBalanced`] - Progressive with all optimizations (default)
254/// - [`Preset::BaselineBalanced`] - Baseline with all optimizations
255/// - [`Preset::BaselineFastest`] - No optimizations, maximum speed
256/// - [`Preset::ProgressiveSmallest`] - Maximum compression (matches C mozjpeg)
257///
258/// # Example
259///
260/// ```no_run
261/// use mozjpeg_rs::{Encoder, Preset};
262///
263/// # fn main() -> Result<(), mozjpeg_rs::Error> {
264/// let pixels: Vec<u8> = vec![0; 640 * 480 * 3];
265///
266/// let jpeg = Encoder::new(Preset::default())
267/// .quality(85)
268/// .encode_rgb(&pixels, 640, 480)?;
269/// # Ok(())
270/// # }
271/// ```
272pub use encode::{Encode, Encoder, EncodingStream, StreamingEncoder};
273
274/// Error type for encoding operations.
275///
276/// All encoding errors are represented by this type. Use the [`Error`] variants
277/// to determine the specific failure mode.
278///
279/// # Example
280///
281/// ```no_run
282/// use mozjpeg_rs::{Encoder, Error, Preset};
283///
284/// # fn example() {
285/// let result = Encoder::new(Preset::default()).encode_rgb(&[], 0, 0);
286/// match result {
287/// Ok(data) => println!("Encoded {} bytes", data.len()),
288/// Err(Error::InvalidDimensions { width, height }) => {
289/// eprintln!("Invalid dimensions: {}x{}", width, height);
290/// }
291/// Err(e) => eprintln!("Encoding failed: {}", e),
292/// }
293/// # }
294/// ```
295pub use error::Error;
296
297/// Result type alias for encoding operations.
298///
299/// Equivalent to `std::result::Result<T, mozjpeg_rs::Error>`.
300pub use error::Result;
301
302/// Chroma subsampling mode.
303///
304/// Controls how color information is stored relative to luminance.
305/// Lower subsampling ratios reduce file size but may cause color artifacts.
306///
307/// | Mode | Ratio | Description |
308/// |------|-------|-------------|
309/// | [`S444`](Subsampling::S444) | 4:4:4 | No subsampling (highest quality) |
310/// | [`S422`](Subsampling::S422) | 4:2:2 | Horizontal subsampling |
311/// | [`S420`](Subsampling::S420) | 4:2:0 | Both directions (most common) |
312/// | [`S440`](Subsampling::S440) | 4:4:0 | Vertical subsampling only |
313/// | [`Gray`](Subsampling::Gray) | N/A | Grayscale (1 component) |
314///
315/// # Example
316///
317/// ```no_run
318/// use mozjpeg_rs::{Encoder, Preset, Subsampling};
319///
320/// # fn main() -> Result<(), mozjpeg_rs::Error> {
321/// # let pixels: Vec<u8> = vec![0; 100 * 100 * 3];
322/// // High quality - no color subsampling
323/// let jpeg = Encoder::new(Preset::default())
324/// .subsampling(Subsampling::S444)
325/// .encode_rgb(&pixels, 100, 100)?;
326///
327/// // Smaller files - standard 4:2:0 subsampling
328/// let jpeg = Encoder::new(Preset::default())
329/// .subsampling(Subsampling::S420)
330/// .encode_rgb(&pixels, 100, 100)?;
331/// # Ok(())
332/// # }
333/// ```
334pub use types::Subsampling;
335
336/// Encoder preset controlling compression mode and optimization level.
337///
338/// # Example
339///
340/// ```no_run
341/// use mozjpeg_rs::{Encoder, Preset};
342///
343/// # fn main() -> Result<(), mozjpeg_rs::Error> {
344/// # let pixels: Vec<u8> = vec![0; 100 * 100 * 3];
345/// // Default: progressive with good balance
346/// let jpeg = Encoder::new(Preset::default())
347/// .quality(85)
348/// .encode_rgb(&pixels, 100, 100)?;
349///
350/// // Fastest encoding
351/// let jpeg = Encoder::new(Preset::BaselineFastest)
352/// .quality(80)
353/// .encode_rgb(&pixels, 100, 100)?;
354///
355/// // Maximum compression (matches C mozjpeg)
356/// let jpeg = Encoder::new(Preset::ProgressiveSmallest)
357/// .quality(85)
358/// .encode_rgb(&pixels, 100, 100)?;
359/// # Ok(())
360/// # }
361/// ```
362pub use types::Preset;
363
364/// Pixel density for JFIF metadata.
365///
366/// Specifies the physical resolution or aspect ratio of the image.
367/// Most software ignores JFIF density in favor of EXIF metadata.
368///
369/// # Example
370///
371/// ```no_run
372/// use mozjpeg_rs::{Encoder, Preset, PixelDensity};
373///
374/// # fn main() -> Result<(), mozjpeg_rs::Error> {
375/// # let pixels: Vec<u8> = vec![0; 100 * 100 * 3];
376/// // 300 DPI for print
377/// let jpeg = Encoder::new(Preset::default())
378/// .pixel_density(PixelDensity::dpi(300, 300))
379/// .encode_rgb(&pixels, 100, 100)?;
380///
381/// // 2:1 pixel aspect ratio
382/// let jpeg = Encoder::new(Preset::default())
383/// .pixel_density(PixelDensity::aspect_ratio(2, 1))
384/// .encode_rgb(&pixels, 100, 100)?;
385/// # Ok(())
386/// # }
387/// ```
388pub use types::PixelDensity;
389
390/// Pixel density unit for JFIF metadata.
391///
392/// Used with [`PixelDensity`] to specify the unit of measurement.
393pub use types::DensityUnit;
394
395/// Configuration for trellis quantization.
396///
397/// Trellis quantization is mozjpeg's signature feature - it uses dynamic
398/// programming to find the optimal quantized coefficients that minimize
399/// a rate-distortion cost function.
400///
401/// # Example
402///
403/// ```no_run
404/// use mozjpeg_rs::{Encoder, Preset, TrellisConfig};
405///
406/// # fn main() -> Result<(), mozjpeg_rs::Error> {
407/// # let pixels: Vec<u8> = vec![0; 100 * 100 * 3];
408/// // Default trellis settings (enabled by default with most presets)
409/// let jpeg = Encoder::new(Preset::default())
410/// .trellis(TrellisConfig::default())
411/// .encode_rgb(&pixels, 100, 100)?;
412///
413/// // Disable trellis for faster encoding
414/// let jpeg = Encoder::new(Preset::default())
415/// .trellis(TrellisConfig::disabled())
416/// .encode_rgb(&pixels, 100, 100)?;
417/// # Ok(())
418/// # }
419/// ```
420pub use types::TrellisConfig;
421
422/// Estimated resource usage for an encoding operation.
423///
424/// Use [`Encoder::estimate_resources()`] to predict memory and CPU requirements
425/// before encoding. Useful for scheduling, resource limits, or progress feedback.
426///
427/// # Example
428///
429/// ```no_run
430/// use mozjpeg_rs::{Encoder, Preset};
431///
432/// let encoder = Encoder::new(Preset::ProgressiveBalanced).quality(85);
433/// let estimate = encoder.estimate_resources(1920, 1080);
434///
435/// println!("Peak memory: {} MB", estimate.peak_memory_bytes / 1_000_000);
436/// println!("CPU cost: {:.1}x baseline", estimate.cpu_cost_multiplier);
437/// println!("Blocks to process: {}", estimate.block_count);
438/// ```
439pub use types::ResourceEstimate;
440
441/// Resource limits for the encoder.
442///
443/// Use this to restrict encoding operations by dimensions, memory usage,
444/// or metadata size. All limits default to 0 (disabled).
445///
446/// # Example
447///
448/// ```
449/// use mozjpeg_rs::{Encoder, Preset, Limits};
450///
451/// // Create limits for a thumbnail service
452/// let limits = Limits::default()
453/// .max_width(4096)
454/// .max_height(4096)
455/// .max_pixel_count(16_000_000) // 16 megapixels
456/// .max_alloc_bytes(100 * 1024 * 1024); // 100 MB
457///
458/// let encoder = Encoder::new(Preset::BaselineFastest)
459/// .limits(limits);
460/// ```
461pub use types::Limits;
462
463/// Quantization table preset.
464///
465/// mozjpeg provides 9 different quantization tables optimized for different
466/// use cases. The default is [`ImageMagick`](QuantTableIdx::ImageMagick).
467///
468/// | Preset | Description |
469/// |--------|-------------|
470/// | [`JpegAnnexK`](QuantTableIdx::JpegAnnexK) | JPEG standard (Annex K) |
471/// | [`Flat`](QuantTableIdx::Flat) | Uniform quantization |
472/// | [`MssimTuned`](QuantTableIdx::MssimTuned) | Optimized for MS-SSIM metric |
473/// | [`ImageMagick`](QuantTableIdx::ImageMagick) | ImageMagick default (mozjpeg default) |
474/// | [`PsnrHvsM`](QuantTableIdx::PsnrHvsM) | PSNR-HVS-M tuned |
475/// | [`Klein`](QuantTableIdx::Klein) | Klein, Silverstein, Carney (1992) |
476/// | [`Watson`](QuantTableIdx::Watson) | Watson, Taylor, Borthwick (1997) |
477/// | [`Ahumada`](QuantTableIdx::Ahumada) | Ahumada, Watson, Peterson (1993) |
478/// | [`Peterson`](QuantTableIdx::Peterson) | Peterson, Ahumada, Watson (1993) |
479///
480/// # Example
481///
482/// ```no_run
483/// use mozjpeg_rs::{Encoder, Preset, QuantTableIdx};
484///
485/// # fn main() -> Result<(), mozjpeg_rs::Error> {
486/// # let pixels: Vec<u8> = vec![0; 100 * 100 * 3];
487/// let jpeg = Encoder::new(Preset::default())
488/// .quant_tables(QuantTableIdx::Flat)
489/// .encode_rgb(&pixels, 100, 100)?;
490/// # Ok(())
491/// # }
492/// ```
493pub use consts::QuantTableIdx;
494
495/// Number of coefficients in an 8x8 DCT block (64).
496///
497/// Used when providing custom quantization tables via
498/// [`Encoder::custom_luma_qtable()`] or [`Encoder::custom_chroma_qtable()`].
499pub use consts::DCTSIZE2;
500
501// ============================================================================
502// mozjpeg-sys compatibility (optional feature)
503// ============================================================================
504
505/// Warnings from configuring a C mozjpeg encoder.
506///
507/// Some settings cannot be applied to `jpeg_compress_struct` directly
508/// and must be handled separately after `jpeg_start_compress`.
509#[cfg(feature = "mozjpeg-sys-config")]
510pub use compat::ConfigWarnings;
511
512/// Error configuring a C mozjpeg encoder.
513#[cfg(feature = "mozjpeg-sys-config")]
514pub use compat::ConfigError;
515
516/// C mozjpeg encoder with settings from a Rust [`Encoder`].
517///
518/// Created via [`Encoder::to_c_mozjpeg()`]. Provides methods for encoding
519/// images using the C mozjpeg library.
520///
521/// # Example
522///
523/// ```no_run
524/// use mozjpeg_rs::{Encoder, Preset};
525///
526/// let pixels: Vec<u8> = vec![128; 64 * 64 * 3];
527/// let jpeg = Encoder::new(Preset::ProgressiveBalanced)
528/// .quality(85)
529/// .to_c_mozjpeg()
530/// .encode_rgb(&pixels, 64, 64)?;
531/// # Ok::<(), mozjpeg_rs::Error>(())
532/// ```
533#[cfg(feature = "mozjpeg-sys-config")]
534pub use compat::CMozjpeg;