image_optimizer/optimization/
jpeg_optimizer.rs

1use anyhow::{Context, Result};
2use image::DynamicImage;
3use std::fs;
4use std::path::Path;
5
6use crate::cli::Cli;
7
8/// Optimizes a JPEG image using mozjpeg compression.
9///
10/// This function uses the mozjpeg library to achieve superior compression compared to
11/// standard libjpeg implementations. It supports both quality-based compression and
12/// lossless mode, and can work with either the original image data or a pre-resized image.
13///
14/// # Arguments
15///
16/// * `input_path` - Path to the source JPEG file
17/// * `output_path` - Path where the optimized JPEG will be written
18/// * `args` - CLI configuration containing quality settings and lossless flag
19/// * `resized_img` - Optional pre-resized image data; if None, reads from `input_path`
20///
21/// # Returns
22///
23/// Returns `Ok(())` on successful optimization.
24///
25/// # Errors
26///
27/// Returns an error if:
28/// - JPEG decompression or compression fails
29/// - File I/O operations fail (reading input or writing output)
30/// - Image dimensions are too large to convert to u32
31/// - RGB color space conversion fails
32pub fn optimize_jpeg(
33    input_path: &Path,
34    output_path: &Path,
35    args: &Cli,
36    resized_img: Option<DynamicImage>,
37) -> Result<()> {
38    let quality = args.jpeg_quality;
39
40    let (width, height, rgb_data) = if let Some(img) = resized_img {
41        let rgb_img = img.to_rgb8();
42        (rgb_img.width(), rgb_img.height(), rgb_img.into_raw())
43    } else {
44        let input_data = fs::read(input_path)?;
45        let decompress = mozjpeg::Decompress::new_mem(&input_data)?;
46        let width = u32::try_from(decompress.width()).context("Width too large")?;
47        let height = u32::try_from(decompress.height()).context("Height too large")?;
48        let mut decompress_started = decompress.rgb()?;
49        let rgb_data: Vec<u8> = decompress_started.read_scanlines()?;
50        (width, height, rgb_data)
51    };
52
53    let mut compress = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_RGB);
54    compress.set_quality(f32::from(quality));
55    compress.set_size(width as usize, height as usize);
56
57    let mut output_data = Vec::new();
58    let mut compress_started = compress.start_compress(&mut output_data)?;
59
60    let row_stride = (width * 3) as usize;
61    for row in rgb_data.chunks(row_stride) {
62        compress_started.write_scanlines(row)?;
63    }
64
65    compress_started.finish()?;
66    fs::write(output_path, output_data)?;
67
68    Ok(())
69}