#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Quality {
ApproxJpegli(f32),
ApproxMozjpeg(u8),
ApproxSsim2(f32),
ApproxButteraugli(f32),
}
impl Default for Quality {
fn default() -> Self {
Quality::ApproxJpegli(90.0)
}
}
impl From<f32> for Quality {
fn from(q: f32) -> Self {
Quality::ApproxJpegli(q)
}
}
impl From<u8> for Quality {
fn from(q: u8) -> Self {
Quality::ApproxJpegli(q as f32)
}
}
impl From<i32> for Quality {
fn from(q: i32) -> Self {
Quality::ApproxJpegli(q as f32)
}
}
impl Quality {
#[must_use]
pub fn to_internal(&self) -> f32 {
match self {
Quality::ApproxJpegli(q) => *q,
Quality::ApproxMozjpeg(q) => mozjpeg_to_internal(*q),
Quality::ApproxSsim2(score) => ssim2_to_internal(*score),
Quality::ApproxButteraugli(dist) => butteraugli_to_internal(*dist),
}
}
#[must_use]
pub fn for_mozjpeg_tables(&self) -> u8 {
match self {
Quality::ApproxMozjpeg(q) => *q,
_ => self.to_internal().round().clamp(1.0, 100.0) as u8,
}
}
#[must_use]
pub fn to_distance(&self) -> f32 {
if let Quality::ApproxButteraugli(d) = self {
return *d;
}
let q = self.to_internal();
if q >= 100.0 {
0.01
} else if q >= 30.0 {
0.1 + (100.0 - q) * 0.09
} else {
53.0 / 3000.0 * q * q - 23.0 / 20.0 * q + 25.0
}
}
}
const MOZJPEG_TO_JPEGLI: [(u8, u8); 10] = [
(30, 28),
(40, 37),
(50, 47),
(60, 55),
(70, 65),
(75, 71),
(80, 77),
(85, 83),
(90, 89),
(95, 94),
];
fn mozjpeg_to_internal(q: u8) -> f32 {
if q >= 100 {
return 100.0;
}
if q <= 30 {
return (q as f32 / 30.0) * 28.0;
}
let mut lower = (30u8, 28u8);
let mut upper = (95u8, 94u8);
for &(moz_q, jpegli_q) in &MOZJPEG_TO_JPEGLI {
if moz_q <= q && moz_q > lower.0 {
lower = (moz_q, jpegli_q);
}
if moz_q >= q && moz_q < upper.0 {
upper = (moz_q, jpegli_q);
}
}
if lower.0 == upper.0 {
return lower.1 as f32;
}
let t = (q - lower.0) as f32 / (upper.0 - lower.0) as f32;
lower.1 as f32 + t * (upper.1 as f32 - lower.1 as f32)
}
const SSIM2_TO_JPEGLI: [(u8, u8); 8] = [
(70, 55), (75, 65),
(80, 73),
(85, 80),
(88, 85),
(90, 88),
(93, 92),
(95, 95),
];
fn ssim2_to_internal(score: f32) -> f32 {
if score >= 100.0 {
return 100.0;
}
if score <= 70.0 {
return (score / 70.0) * 55.0;
}
let q = score as u8;
let mut lower = (70u8, 55u8);
let mut upper = (95u8, 95u8);
for &(ssim_score, jpegli_q) in &SSIM2_TO_JPEGLI {
if ssim_score <= q && ssim_score > lower.0 {
lower = (ssim_score, jpegli_q);
}
if ssim_score >= q && ssim_score < upper.0 {
upper = (ssim_score, jpegli_q);
}
}
if lower.0 == upper.0 {
return lower.1 as f32;
}
let t = (score - lower.0 as f32) / (upper.0 - lower.0) as f32;
lower.1 as f32 + t * (upper.1 as f32 - lower.1 as f32)
}
const BUTTERAUGLI_TO_JPEGLI: [(f32, f32); 7] = [
(0.3, 96.0),
(0.5, 93.0),
(1.0, 88.0),
(1.5, 82.0),
(2.0, 76.0),
(3.0, 68.0),
(5.0, 55.0),
];
fn butteraugli_to_internal(dist: f32) -> f32 {
if dist <= 0.0 {
return 100.0;
}
if dist <= 0.3 {
return 96.0 + (0.3 - dist) / 0.3 * 4.0;
}
if dist >= 5.0 {
return 55.0 - (dist - 5.0) * 3.0;
}
let mut lower = (0.3f32, 96.0f32);
let mut upper = (5.0f32, 55.0f32);
for &(ba_dist, jpegli_q) in &BUTTERAUGLI_TO_JPEGLI {
if ba_dist <= dist && ba_dist > lower.0 {
lower = (ba_dist, jpegli_q);
}
if ba_dist >= dist && ba_dist < upper.0 {
upper = (ba_dist, jpegli_q);
}
}
if (lower.0 - upper.0).abs() < 0.001 {
return lower.1;
}
let t = (dist - lower.0) / (upper.0 - lower.0);
lower.1 + t * (upper.1 - lower.1)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ColorMode {
YCbCr { subsampling: ChromaSubsampling },
Xyb { subsampling: XybSubsampling },
Grayscale,
}
impl Default for ColorMode {
fn default() -> Self {
ColorMode::YCbCr {
subsampling: ChromaSubsampling::None, }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChromaSubsampling {
None,
HalfHorizontal,
Quarter,
HalfVertical,
}
impl ChromaSubsampling {
#[must_use]
pub const fn h_factor(&self) -> u8 {
match self {
ChromaSubsampling::None | ChromaSubsampling::HalfVertical => 1,
ChromaSubsampling::HalfHorizontal | ChromaSubsampling::Quarter => 2,
}
}
#[must_use]
pub const fn v_factor(&self) -> u8 {
match self {
ChromaSubsampling::None | ChromaSubsampling::HalfHorizontal => 1,
ChromaSubsampling::HalfVertical | ChromaSubsampling::Quarter => 2,
}
}
#[must_use]
pub const fn h_samp_factor_luma(self) -> u8 {
self.h_factor()
}
#[must_use]
pub const fn v_samp_factor_luma(self) -> u8 {
self.v_factor()
}
#[must_use]
pub const fn mcu_size(self) -> usize {
match self {
ChromaSubsampling::None => 8,
ChromaSubsampling::Quarter
| ChromaSubsampling::HalfHorizontal
| ChromaSubsampling::HalfVertical => 16,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum XybSubsampling {
Full,
#[default]
BQuarter,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum DownsamplingMethod {
#[default]
Box,
GammaAware,
GammaAwareIterative,
}
impl DownsamplingMethod {
#[must_use]
pub const fn uses_gamma_aware(self) -> bool {
matches!(self, Self::GammaAware | Self::GammaAwareIterative)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PixelLayout {
Rgb8Srgb,
Bgr8Srgb,
Rgbx8Srgb,
Rgba8Srgb,
Bgrx8Srgb,
Bgra8Srgb,
Gray8Srgb,
Rgb16Linear,
Rgbx16Linear,
Rgba16Linear,
Gray16Linear,
RgbF32Linear,
RgbxF32Linear,
RgbaF32Linear,
GrayF32Linear,
YCbCr8,
YCbCrF32,
}
impl PixelLayout {
#[must_use]
pub const fn bytes_per_pixel(&self) -> usize {
match self {
Self::Gray8Srgb => 1,
Self::Gray16Linear => 2,
Self::Rgb8Srgb | Self::Bgr8Srgb | Self::YCbCr8 => 3,
Self::Rgbx8Srgb
| Self::Rgba8Srgb
| Self::Bgrx8Srgb
| Self::Bgra8Srgb
| Self::GrayF32Linear => 4,
Self::Rgb16Linear => 6,
Self::Rgbx16Linear | Self::Rgba16Linear => 8,
Self::RgbF32Linear | Self::YCbCrF32 => 12,
Self::RgbxF32Linear | Self::RgbaF32Linear => 16,
}
}
#[must_use]
pub const fn channels(&self) -> usize {
match self {
Self::Gray8Srgb | Self::Gray16Linear | Self::GrayF32Linear => 1,
Self::Rgb8Srgb
| Self::Bgr8Srgb
| Self::Rgb16Linear
| Self::RgbF32Linear
| Self::YCbCr8
| Self::YCbCrF32 => 3,
Self::Rgbx8Srgb
| Self::Rgba8Srgb
| Self::Bgrx8Srgb
| Self::Bgra8Srgb
| Self::Rgbx16Linear
| Self::Rgba16Linear
| Self::RgbxF32Linear
| Self::RgbaF32Linear => 4,
}
}
#[must_use]
pub const fn is_grayscale(&self) -> bool {
matches!(
self,
Self::Gray8Srgb | Self::Gray16Linear | Self::GrayF32Linear
)
}
#[must_use]
pub const fn is_ycbcr(&self) -> bool {
matches!(self, Self::YCbCr8 | Self::YCbCrF32)
}
#[must_use]
pub const fn is_bgr(&self) -> bool {
matches!(self, Self::Bgr8Srgb | Self::Bgrx8Srgb | Self::Bgra8Srgb)
}
#[must_use]
pub const fn is_float(&self) -> bool {
matches!(
self,
Self::RgbF32Linear
| Self::RgbxF32Linear
| Self::RgbaF32Linear
| Self::GrayF32Linear
| Self::YCbCrF32
)
}
#[must_use]
pub const fn is_16bit(&self) -> bool {
matches!(
self,
Self::Rgb16Linear | Self::Rgbx16Linear | Self::Rgba16Linear | Self::Gray16Linear
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct YCbCrPlanes<'a> {
pub y: &'a [f32],
pub y_stride: usize,
pub cb: &'a [f32],
pub cb_stride: usize,
pub cr: &'a [f32],
pub cr_stride: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PixelFormat;
#[test]
fn test_quality_default() {
let q = Quality::default();
assert!(matches!(q, Quality::ApproxJpegli(90.0)));
}
#[test]
fn test_quality_from() {
let q: Quality = 85.0.into();
assert!(matches!(q, Quality::ApproxJpegli(85.0)));
let q: Quality = 75u8.into();
assert!(matches!(q, Quality::ApproxJpegli(75.0)));
}
#[test]
fn test_pixel_layout_bytes() {
assert_eq!(PixelLayout::Rgb8Srgb.bytes_per_pixel(), 3);
assert_eq!(PixelLayout::Rgbx8Srgb.bytes_per_pixel(), 4);
assert_eq!(PixelLayout::RgbF32Linear.bytes_per_pixel(), 12);
assert_eq!(PixelLayout::Gray8Srgb.bytes_per_pixel(), 1);
}
#[test]
fn test_chroma_subsampling_factors() {
assert_eq!(ChromaSubsampling::None.h_factor(), 1);
assert_eq!(ChromaSubsampling::None.v_factor(), 1);
assert_eq!(ChromaSubsampling::Quarter.h_factor(), 2);
assert_eq!(ChromaSubsampling::Quarter.v_factor(), 2);
assert_eq!(ChromaSubsampling::HalfHorizontal.h_factor(), 2);
assert_eq!(ChromaSubsampling::HalfHorizontal.v_factor(), 1);
}
#[test]
fn test_optimization_preset_all_variants() {
let all: Vec<_> = OptimizationPreset::all().collect();
#[cfg(feature = "trellis")]
assert_eq!(all.len(), 8);
#[cfg(not(feature = "trellis"))]
assert_eq!(all.len(), 2);
let mut set = std::collections::HashSet::new();
for p in &all {
assert!(set.insert(p), "duplicate preset: {:?}", p);
}
}
#[test]
fn test_optimization_preset_progressive_jpegli() {
assert!(!OptimizationPreset::JpegliBaseline.is_progressive());
assert!(OptimizationPreset::JpegliProgressive.is_progressive());
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_progressive_trellis() {
assert!(!OptimizationPreset::MozjpegBaseline.is_progressive());
assert!(OptimizationPreset::MozjpegProgressive.is_progressive());
assert!(OptimizationPreset::MozjpegMaxCompression.is_progressive());
assert!(!OptimizationPreset::HybridBaseline.is_progressive());
assert!(OptimizationPreset::HybridProgressive.is_progressive());
assert!(OptimizationPreset::HybridMaxCompression.is_progressive());
}
#[test]
fn test_optimization_preset_trellis_jpegli() {
assert!(!OptimizationPreset::JpegliBaseline.uses_trellis());
assert!(!OptimizationPreset::JpegliProgressive.uses_trellis());
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_trellis() {
assert!(OptimizationPreset::MozjpegBaseline.uses_trellis());
assert!(OptimizationPreset::MozjpegProgressive.uses_trellis());
assert!(OptimizationPreset::MozjpegMaxCompression.uses_trellis());
assert!(OptimizationPreset::HybridBaseline.uses_trellis());
assert!(OptimizationPreset::HybridProgressive.uses_trellis());
assert!(OptimizationPreset::HybridMaxCompression.uses_trellis());
}
#[test]
fn test_optimization_preset_aq_jpegli() {
assert!(OptimizationPreset::JpegliBaseline.uses_aq());
assert!(OptimizationPreset::JpegliProgressive.uses_aq());
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_aq_trellis() {
assert!(!OptimizationPreset::MozjpegBaseline.uses_aq());
assert!(!OptimizationPreset::MozjpegProgressive.uses_aq());
assert!(!OptimizationPreset::MozjpegMaxCompression.uses_aq());
assert!(OptimizationPreset::HybridBaseline.uses_aq());
assert!(OptimizationPreset::HybridProgressive.uses_aq());
assert!(OptimizationPreset::HybridMaxCompression.uses_aq());
}
#[test]
fn test_optimization_preset_scan_strategy_jpegli() {
assert_eq!(
OptimizationPreset::JpegliBaseline.scan_strategy(),
ScanStrategy::Default
);
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_scan_strategy_trellis() {
assert_eq!(
OptimizationPreset::MozjpegProgressive.scan_strategy(),
ScanStrategy::Mozjpeg
);
assert_eq!(
OptimizationPreset::MozjpegMaxCompression.scan_strategy(),
ScanStrategy::Search
);
assert_eq!(
OptimizationPreset::HybridMaxCompression.scan_strategy(),
ScanStrategy::Search
);
}
#[test]
fn test_optimization_preset_quant_table_source_jpegli() {
assert_eq!(
OptimizationPreset::JpegliBaseline.quant_table_source(),
QuantTableSource::Jpegli
);
assert_eq!(
OptimizationPreset::JpegliProgressive.quant_table_source(),
QuantTableSource::Jpegli
);
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_quant_table_source_trellis() {
assert_eq!(
OptimizationPreset::MozjpegBaseline.quant_table_source(),
QuantTableSource::MozjpegDefault
);
assert_eq!(
OptimizationPreset::MozjpegProgressive.quant_table_source(),
QuantTableSource::MozjpegDefault
);
assert_eq!(
OptimizationPreset::MozjpegMaxCompression.quant_table_source(),
QuantTableSource::MozjpegDefault
);
assert_eq!(
OptimizationPreset::HybridBaseline.quant_table_source(),
QuantTableSource::Jpegli
);
assert_eq!(
OptimizationPreset::HybridProgressive.quant_table_source(),
QuantTableSource::Jpegli
);
assert_eq!(
OptimizationPreset::HybridMaxCompression.quant_table_source(),
QuantTableSource::Jpegli
);
}
#[test]
fn test_quant_table_source_default() {
assert_eq!(QuantTableSource::default(), QuantTableSource::Jpegli);
}
#[test]
fn test_optimization_preset_display() {
assert_eq!(
OptimizationPreset::JpegliBaseline.to_string(),
"jpegli-baseline"
);
}
#[cfg(feature = "trellis")]
#[test]
fn test_optimization_preset_display_trellis() {
assert_eq!(
OptimizationPreset::HybridMaxCompression.to_string(),
"hybrid-max"
);
}
#[test]
fn test_pixel_format_to_layout_conversion() {
assert_eq!(PixelLayout::from(PixelFormat::Rgb), PixelLayout::Rgb8Srgb);
assert_eq!(PixelLayout::from(PixelFormat::Rgba), PixelLayout::Rgba8Srgb);
assert_eq!(PixelLayout::from(PixelFormat::Bgr), PixelLayout::Bgr8Srgb);
assert_eq!(PixelLayout::from(PixelFormat::Bgra), PixelLayout::Bgra8Srgb);
assert_eq!(PixelLayout::from(PixelFormat::Bgrx), PixelLayout::Bgrx8Srgb);
assert_eq!(PixelLayout::from(PixelFormat::Gray), PixelLayout::Gray8Srgb);
assert_eq!(
PixelLayout::from(PixelFormat::Rgb16),
PixelLayout::Rgb16Linear
);
assert_eq!(
PixelLayout::from(PixelFormat::Rgba16),
PixelLayout::Rgba16Linear
);
assert_eq!(
PixelLayout::from(PixelFormat::Gray16),
PixelLayout::Gray16Linear
);
assert_eq!(
PixelLayout::from(PixelFormat::RgbF32),
PixelLayout::RgbF32Linear
);
assert_eq!(
PixelLayout::from(PixelFormat::RgbaF32),
PixelLayout::RgbaF32Linear
);
assert_eq!(
PixelLayout::from(PixelFormat::GrayF32),
PixelLayout::GrayF32Linear
);
}
#[test]
fn test_pixel_layout_to_format_conversion() {
assert_eq!(PixelFormat::from(PixelLayout::Rgb8Srgb), PixelFormat::Rgb);
assert_eq!(PixelFormat::from(PixelLayout::Bgr8Srgb), PixelFormat::Bgr);
assert_eq!(PixelFormat::from(PixelLayout::Rgba8Srgb), PixelFormat::Rgba);
assert_eq!(PixelFormat::from(PixelLayout::Rgbx8Srgb), PixelFormat::Rgba);
assert_eq!(
PixelFormat::from(PixelLayout::Rgb16Linear),
PixelFormat::Rgb16
);
assert_eq!(
PixelFormat::from(PixelLayout::Rgba16Linear),
PixelFormat::Rgba16
);
assert_eq!(
PixelFormat::from(PixelLayout::RgbF32Linear),
PixelFormat::RgbF32
);
assert_eq!(
PixelFormat::from(PixelLayout::RgbaF32Linear),
PixelFormat::RgbaF32
);
}
#[test]
fn test_roundtrip_conversions() {
let formats = vec![
PixelFormat::Rgb,
PixelFormat::Bgr,
PixelFormat::Gray,
PixelFormat::Rgb16,
PixelFormat::RgbF32,
];
for pf in formats {
let pl: PixelLayout = pf.into();
let pf2: PixelFormat = pl.into();
assert_eq!(
pf, pf2,
"Roundtrip failed for {:?} -> {:?} -> {:?}",
pf, pl, pf2
);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OptimizationPreset {
JpegliBaseline,
JpegliProgressive,
#[cfg(feature = "trellis")]
MozjpegBaseline,
#[cfg(feature = "trellis")]
MozjpegProgressive,
#[cfg(feature = "trellis")]
MozjpegMaxCompression,
#[cfg(feature = "trellis")]
HybridBaseline,
#[cfg(feature = "trellis")]
HybridProgressive,
#[cfg(feature = "trellis")]
HybridMaxCompression,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Effort {
Fast,
#[cfg(feature = "trellis")]
Balanced,
#[cfg(feature = "trellis")]
Max,
}
impl Effort {
#[must_use]
pub const fn to_preset(self) -> OptimizationPreset {
match self {
Self::Fast => OptimizationPreset::JpegliBaseline,
#[cfg(feature = "trellis")]
Self::Balanced => OptimizationPreset::HybridProgressive,
#[cfg(feature = "trellis")]
Self::Max => OptimizationPreset::HybridMaxCompression,
}
}
}
#[cfg(feature = "trellis")]
const ALL_PRESETS: [OptimizationPreset; 8] = [
OptimizationPreset::JpegliBaseline,
OptimizationPreset::JpegliProgressive,
OptimizationPreset::MozjpegBaseline,
OptimizationPreset::MozjpegProgressive,
OptimizationPreset::MozjpegMaxCompression,
OptimizationPreset::HybridBaseline,
OptimizationPreset::HybridProgressive,
OptimizationPreset::HybridMaxCompression,
];
#[cfg(not(feature = "trellis"))]
const ALL_PRESETS: [OptimizationPreset; 2] = [
OptimizationPreset::JpegliBaseline,
OptimizationPreset::JpegliProgressive,
];
impl OptimizationPreset {
pub fn all() -> impl Iterator<Item = Self> {
ALL_PRESETS.iter().copied()
}
#[must_use]
pub const fn is_progressive(self) -> bool {
match self {
Self::JpegliBaseline => false,
Self::JpegliProgressive => true,
#[cfg(feature = "trellis")]
Self::MozjpegBaseline => false,
#[cfg(feature = "trellis")]
Self::MozjpegProgressive => true,
#[cfg(feature = "trellis")]
Self::MozjpegMaxCompression => true,
#[cfg(feature = "trellis")]
Self::HybridBaseline => false,
#[cfg(feature = "trellis")]
Self::HybridProgressive => true,
#[cfg(feature = "trellis")]
Self::HybridMaxCompression => true,
}
}
#[must_use]
pub const fn uses_trellis(self) -> bool {
match self {
Self::JpegliBaseline | Self::JpegliProgressive => false,
#[cfg(feature = "trellis")]
Self::MozjpegBaseline
| Self::MozjpegProgressive
| Self::MozjpegMaxCompression
| Self::HybridBaseline
| Self::HybridProgressive
| Self::HybridMaxCompression => true,
}
}
#[must_use]
pub const fn uses_aq(self) -> bool {
match self {
Self::JpegliBaseline | Self::JpegliProgressive => true,
#[cfg(feature = "trellis")]
Self::MozjpegBaseline | Self::MozjpegProgressive | Self::MozjpegMaxCompression => false,
#[cfg(feature = "trellis")]
Self::HybridBaseline | Self::HybridProgressive | Self::HybridMaxCompression => true,
}
}
#[must_use]
pub const fn quant_table_source(self) -> QuantTableSource {
match self {
#[cfg(feature = "trellis")]
Self::MozjpegBaseline | Self::MozjpegProgressive | Self::MozjpegMaxCompression => {
QuantTableSource::MozjpegDefault
}
_ => QuantTableSource::Jpegli,
}
}
#[must_use]
pub const fn scan_strategy(self) -> ScanStrategy {
match self {
Self::JpegliBaseline => ScanStrategy::Default,
Self::JpegliProgressive => ScanStrategy::Default,
#[cfg(feature = "trellis")]
Self::MozjpegBaseline | Self::HybridBaseline => ScanStrategy::Default,
#[cfg(feature = "trellis")]
Self::HybridProgressive => ScanStrategy::Default,
#[cfg(feature = "trellis")]
Self::MozjpegProgressive => ScanStrategy::Mozjpeg,
#[cfg(feature = "trellis")]
Self::MozjpegMaxCompression | Self::HybridMaxCompression => ScanStrategy::Search,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::JpegliBaseline => "jpegli-baseline",
Self::JpegliProgressive => "jpegli-progressive",
#[cfg(feature = "trellis")]
Self::MozjpegBaseline => "mozjpeg-baseline",
#[cfg(feature = "trellis")]
Self::MozjpegProgressive => "mozjpeg-progressive",
#[cfg(feature = "trellis")]
Self::MozjpegMaxCompression => "mozjpeg-max",
#[cfg(feature = "trellis")]
Self::HybridBaseline => "hybrid-baseline",
#[cfg(feature = "trellis")]
Self::HybridProgressive => "hybrid-progressive",
#[cfg(feature = "trellis")]
Self::HybridMaxCompression => "hybrid-max",
}
}
}
impl core::fmt::Display for OptimizationPreset {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
#[cfg(feature = "parallel")]
pub enum ParallelEncoding {
#[default]
Auto,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub enum HuffmanStrategy {
#[default]
Optimize,
Fixed,
FixedAnnexK,
Custom(Box<crate::huffman::optimize::HuffmanTableSet>),
}
impl From<bool> for HuffmanStrategy {
fn from(optimize: bool) -> Self {
if optimize {
HuffmanStrategy::Optimize
} else {
HuffmanStrategy::Fixed
}
}
}
impl From<crate::huffman::optimize::HuffmanTableSet> for HuffmanStrategy {
fn from(tables: crate::huffman::optimize::HuffmanTableSet) -> Self {
HuffmanStrategy::Custom(Box::new(tables))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScanStrategy {
#[default]
Default,
Search,
Mozjpeg,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum QuantTableSource {
#[default]
Jpegli,
MozjpegDefault,
}
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub enum QuantTableConfig {
#[default]
Jpegli,
JpegliSharedChroma,
MozjpegRobidoux,
Custom(Box<super::tuning::EncodingTables>),
GlassaLowBpp(u8),
}
impl QuantTableConfig {
#[must_use]
pub const fn quant_source(&self) -> QuantTableSource {
match self {
Self::Jpegli | Self::JpegliSharedChroma | Self::Custom(_) | Self::GlassaLowBpp(_) => {
QuantTableSource::Jpegli
}
Self::MozjpegRobidoux => QuantTableSource::MozjpegDefault,
}
}
#[must_use]
pub const fn separate_chroma_tables(&self) -> bool {
match self {
Self::Jpegli => true,
Self::JpegliSharedChroma | Self::MozjpegRobidoux | Self::GlassaLowBpp(_) => false,
Self::Custom(_) => true,
}
}
#[must_use]
pub fn custom_tables(&self) -> Option<super::tuning::EncodingTables> {
match self {
Self::Custom(t) => Some((**t).clone()),
Self::GlassaLowBpp(q) => Some(super::tables::glassa::tables_for_quality(*q)),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProgressiveScanMode {
#[default]
Baseline,
Progressive,
ProgressiveMozjpeg,
ProgressiveSearch,
}
impl ProgressiveScanMode {
#[must_use]
pub const fn is_progressive(self) -> bool {
!matches!(self, Self::Baseline)
}
#[must_use]
pub const fn scan_strategy(self) -> ScanStrategy {
match self {
Self::Baseline | Self::Progressive => ScanStrategy::Default,
Self::ProgressiveMozjpeg => ScanStrategy::Mozjpeg,
Self::ProgressiveSearch => ScanStrategy::Search,
}
}
}
impl From<bool> for ProgressiveScanMode {
fn from(progressive: bool) -> Self {
if progressive {
ProgressiveScanMode::Progressive
} else {
ProgressiveScanMode::Baseline
}
}
}
#[cfg(feature = "trellis")]
#[derive(Clone, Debug, Default)]
pub struct ExpertConfig {
pub tables: Option<QuantTableConfig>,
pub trellis: Option<super::trellis::TrellisConfig>,
pub hybrid: Option<super::trellis::HybridConfig>,
}
#[cfg(feature = "trellis")]
impl ExpertConfig {
#[must_use]
pub fn tables(mut self, tables: QuantTableConfig) -> Self {
self.tables = Some(tables);
self
}
#[must_use]
pub fn trellis(mut self, config: super::trellis::TrellisConfig) -> Self {
self.trellis = Some(config);
self
}
#[must_use]
pub fn hybrid(mut self, config: super::trellis::HybridConfig) -> Self {
self.hybrid = Some(config);
self
}
}
impl From<crate::types::PixelFormat> for PixelLayout {
fn from(format: crate::types::PixelFormat) -> Self {
use crate::types::PixelFormat;
match format {
PixelFormat::Gray => Self::Gray8Srgb,
PixelFormat::Rgb => Self::Rgb8Srgb,
PixelFormat::Rgba => Self::Rgba8Srgb,
PixelFormat::Bgr => Self::Bgr8Srgb,
PixelFormat::Bgra => Self::Bgra8Srgb,
PixelFormat::Bgrx => Self::Bgrx8Srgb,
PixelFormat::Gray16 => Self::Gray16Linear,
PixelFormat::Rgb16 => Self::Rgb16Linear,
PixelFormat::Rgba16 => Self::Rgba16Linear,
PixelFormat::GrayF32 => Self::GrayF32Linear,
PixelFormat::RgbF32 => Self::RgbF32Linear,
PixelFormat::RgbaF32 => Self::RgbaF32Linear,
PixelFormat::Cmyk => Self::Rgba8Srgb,
}
}
}