Wire progressive JPEG, AVIF speed, and custom per-format quality to encoder

- Add EncoderOptions struct with progressive_jpeg and avif_speed fields
- Pass encoder options through ProcessingJob to PipelineExecutor
- mozjpeg set_progressive_mode() called when progressive JPEG enabled
- AVIF encoder speed now configurable (was hardcoded to 6)
- run_processing uses CompressConfig::Custom when user overrides preset defaults
- Executor properly handles AVIF quality and PNG level in Custom mode
This commit is contained in:
2026-03-06 15:07:54 +02:00
parent 52b7a7fed2
commit fdaedd8d1a
5 changed files with 66 additions and 9 deletions

View File

@@ -4,11 +4,28 @@ use std::path::Path;
use crate::error::{PixstripError, Result};
use crate::types::{ImageFormat, QualityPreset};
pub struct OutputEncoder;
#[derive(Debug, Clone, Default)]
pub struct EncoderOptions {
pub progressive_jpeg: bool,
pub avif_speed: u8,
}
pub struct OutputEncoder {
pub options: EncoderOptions,
}
impl OutputEncoder {
pub fn new() -> Self {
Self
Self {
options: EncoderOptions {
progressive_jpeg: false,
avif_speed: 6,
},
}
}
pub fn with_options(options: EncoderOptions) -> Self {
Self { options }
}
pub fn encode(
@@ -60,6 +77,9 @@ impl OutputEncoder {
let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_RGB);
comp.set_size(width, height);
comp.set_quality(quality as f32);
if self.options.progressive_jpeg {
comp.set_progressive_mode();
}
let mut output = Vec::new();
let mut started = comp.start_compress(&mut output).map_err(|e| PixstripError::Processing {
@@ -121,9 +141,10 @@ impl OutputEncoder {
let mut buf = Vec::new();
let cursor = Cursor::new(&mut buf);
let rgba = img.to_rgba8();
let speed = self.options.avif_speed.clamp(1, 10);
let encoder = image::codecs::avif::AvifEncoder::new_with_speed_quality(
cursor,
6,
speed,
quality,
);
image::ImageEncoder::write_image(

View File

@@ -4,7 +4,7 @@ use std::time::Instant;
use rayon::prelude::*;
use crate::encoder::OutputEncoder;
use crate::encoder::{EncoderOptions, OutputEncoder};
use crate::error::{PixstripError, Result};
use crate::loader::ImageLoader;
use crate::operations::adjustments::apply_adjustments;
@@ -182,7 +182,10 @@ impl PipelineExecutor {
});
let loader = ImageLoader::new();
let encoder = OutputEncoder::new();
let encoder = OutputEncoder::with_options(EncoderOptions {
progressive_jpeg: job.progressive_jpeg,
avif_speed: job.avif_speed,
});
match Self::process_single_static(job, source, &loader, &encoder, idx) {
Ok((in_size, out_size)) => {
@@ -237,7 +240,10 @@ impl PipelineExecutor {
let start = Instant::now();
let total = job.sources.len();
let loader = ImageLoader::new();
let encoder = OutputEncoder::new();
let encoder = OutputEncoder::with_options(EncoderOptions {
progressive_jpeg: job.progressive_jpeg,
avif_speed: job.avif_speed,
});
let mut result = BatchResult {
total,
@@ -368,12 +374,14 @@ impl PipelineExecutor {
}
crate::operations::CompressConfig::Custom {
jpeg_quality,
png_level: _,
png_level,
webp_quality,
avif_quality: _,
avif_quality,
} => match output_format {
ImageFormat::Jpeg => jpeg_quality.unwrap_or(85),
ImageFormat::Png => png_level.unwrap_or(6),
ImageFormat::WebP => webp_quality.map(|q| q as u8).unwrap_or(80),
ImageFormat::Avif => avif_quality.map(|q| q as u8).unwrap_or(50),
_ => 85,
},
});

View File

@@ -21,6 +21,8 @@ pub struct ProcessingJob {
pub watermark: Option<WatermarkConfig>,
pub rename: Option<RenameConfig>,
pub preserve_directory_structure: bool,
pub progressive_jpeg: bool,
pub avif_speed: u8,
}
impl ProcessingJob {
@@ -39,6 +41,8 @@ impl ProcessingJob {
watermark: None,
rename: None,
preserve_directory_structure: false,
progressive_jpeg: false,
avif_speed: 6,
}
}

View File

@@ -40,6 +40,8 @@ impl Preset {
watermark: self.watermark.clone(),
rename: self.rename.clone(),
preserve_directory_structure: false,
progressive_jpeg: false,
avif_speed: 6,
}
}