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:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ impl Preset {
|
||||
watermark: self.watermark.clone(),
|
||||
rename: self.rename.clone(),
|
||||
preserve_directory_structure: false,
|
||||
progressive_jpeg: false,
|
||||
avif_speed: 6,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1265,8 +1265,30 @@ fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
}
|
||||
|
||||
if cfg.compress_enabled {
|
||||
// Check if user has customized per-format quality values beyond the preset defaults
|
||||
let preset_jpeg = cfg.quality_preset.jpeg_quality();
|
||||
let preset_webp = cfg.quality_preset.webp_quality();
|
||||
let has_custom = cfg.jpeg_quality != preset_jpeg
|
||||
|| cfg.webp_quality != preset_webp as u8
|
||||
|| cfg.avif_quality != preset_webp as u8
|
||||
|| cfg.avif_speed != 6
|
||||
|| cfg.webp_effort != 4;
|
||||
|
||||
if has_custom {
|
||||
job.compress = Some(pixstrip_core::operations::CompressConfig::Custom {
|
||||
jpeg_quality: Some(cfg.jpeg_quality),
|
||||
png_level: Some(cfg.png_level),
|
||||
webp_quality: Some(cfg.webp_quality as f32),
|
||||
avif_quality: Some(cfg.avif_quality as f32),
|
||||
});
|
||||
} else {
|
||||
job.compress = Some(pixstrip_core::operations::CompressConfig::Preset(cfg.quality_preset));
|
||||
}
|
||||
}
|
||||
|
||||
// Pass encoder options to the job
|
||||
job.progressive_jpeg = cfg.progressive_jpeg;
|
||||
job.avif_speed = cfg.avif_speed;
|
||||
|
||||
if cfg.metadata_enabled {
|
||||
job.metadata = Some(match cfg.metadata_mode {
|
||||
|
||||
Reference in New Issue
Block a user