#![allow(dead_code)]
use super::encoder_types::DownsamplingMethod;
use super::encoder_types::HuffmanStrategy;
use super::encoder_types::Quality;
use super::encoder_types::ScanStrategy;
use crate::types::{EdgePaddingConfig, JpegMode, PixelFormat, Subsampling};
#[derive(Debug, Clone)]
pub(crate) struct ProgressiveScan {
pub(crate) components: Vec<u8>,
pub(crate) ss: u8,
pub(crate) se: u8,
pub(crate) ah: u8,
pub(crate) al: u8,
}
#[derive(Debug, Clone)]
pub struct ComputedConfig {
pub width: u32,
pub height: u32,
pub pixel_format: PixelFormat,
pub quality: Quality,
pub mode: JpegMode,
pub subsampling: Subsampling,
pub use_xyb: bool,
pub restart_interval: u16,
#[cfg(feature = "parallel")]
pub parallel: bool,
pub(crate) huffman: HuffmanStrategy,
pub chroma_downsampling: DownsamplingMethod,
#[cfg(feature = "trellis")]
pub hybrid_config: super::trellis::HybridConfig,
pub custom_aq_map: Option<crate::quant::aq::AQStrengthMap>,
#[doc(hidden)]
pub(crate) encoding_tables: Option<Box<crate::encode::tuning::EncodingTables>>,
pub edge_padding: EdgePaddingConfig,
pub(crate) original_width: Option<u32>,
pub(crate) original_height: Option<u32>,
pub allow_16bit_quant_tables: bool,
pub force_sof1: bool,
pub scan_strategy: ScanStrategy,
pub separate_chroma_tables: bool,
#[cfg(feature = "trellis")]
pub trellis: Option<super::trellis::TrellisConfig>,
}
const MIN_MCUS_PER_RESTART: u32 = 64;
const EST_BYTES_PER_MARKER: u32 = 8;
const DRI_HEADER_BYTES: u32 = 6;
const MAX_OVERHEAD_PER_MILLE: u32 = 3;
pub(crate) fn resolve_restart_rows(
rows: u16,
width: u32,
height: u32,
subsampling: Subsampling,
) -> u16 {
if rows == 0 {
return 0;
}
let h_samp = match subsampling {
Subsampling::S444 | Subsampling::S440 => 1u32,
Subsampling::S422 | Subsampling::S420 => 2,
};
let v_samp = match subsampling {
Subsampling::S444 | Subsampling::S422 => 1u32,
Subsampling::S440 | Subsampling::S420 => 2,
};
let mcu_w = h_samp * 8;
let mcu_h = v_samp * 8;
let mcu_cols = (width + mcu_w - 1) / mcu_w;
let mcu_rows = (height + mcu_h - 1) / mcu_h;
let total_mcus = mcu_cols * mcu_rows;
let total_pixels = width * height;
let est_file_bytes = total_pixels / 16;
let max_overhead = est_file_bytes * MAX_OVERHEAD_PER_MILLE / 1000;
let min_rows_for_overhead = if max_overhead <= DRI_HEADER_BYTES + EST_BYTES_PER_MARKER {
mcu_rows
} else {
let max_markers = (max_overhead - DRI_HEADER_BYTES) / EST_BYTES_PER_MARKER;
if max_markers == 0 {
mcu_rows
} else {
let min_ri = (total_mcus + max_markers - 1) / max_markers;
(min_ri + mcu_cols - 1) / mcu_cols.max(1)
}
};
let min_rows_for_parallel = (MIN_MCUS_PER_RESTART + mcu_cols - 1) / mcu_cols.max(1);
let rows = (rows as u32)
.max(min_rows_for_parallel)
.max(min_rows_for_overhead);
let max_rows = (u16::MAX as u32) / mcu_cols.max(1);
let rows = rows.min(max_rows);
(rows * mcu_cols) as u16
}
impl ComputedConfig {
pub(crate) fn mcu_cols(&self) -> u32 {
let h_samp = match self.subsampling {
Subsampling::S444 | Subsampling::S440 => 1u32,
Subsampling::S422 | Subsampling::S420 => 2,
};
let mcu_w = h_samp * 8;
(self.width + mcu_w - 1) / mcu_w
}
pub(crate) fn align_restart_to_row(&self, interval: u16) -> u16 {
let mcu_cols = self.mcu_cols() as u16;
if mcu_cols == 0 {
return 0;
}
(interval / mcu_cols) * mcu_cols
}
}
impl Default for ComputedConfig {
fn default() -> Self {
Self {
width: 0,
height: 0,
pixel_format: PixelFormat::Rgb,
quality: Quality::default(),
mode: JpegMode::Baseline,
subsampling: Subsampling::S444,
use_xyb: false,
restart_interval: 0,
#[cfg(feature = "parallel")]
parallel: false,
huffman: HuffmanStrategy::Optimize,
chroma_downsampling: DownsamplingMethod::Box,
#[cfg(feature = "trellis")]
hybrid_config: super::trellis::HybridConfig::disabled(),
custom_aq_map: None,
encoding_tables: None,
edge_padding: EdgePaddingConfig::default(),
original_width: None,
original_height: None,
allow_16bit_quant_tables: false,
force_sof1: false,
scan_strategy: ScanStrategy::Default,
separate_chroma_tables: true,
#[cfg(feature = "trellis")]
trellis: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_restart_overhead_limit() {
let ri = resolve_restart_rows(4, 512, 512, Subsampling::S420);
assert_eq!(ri, 7 * 32, "512x512 4:2:0 should use 7 rows × 32 cols");
let ri = resolve_restart_rows(4, 1024, 1024, Subsampling::S420);
assert_eq!(ri, 4 * 64, "1024x1024 4:2:0 should use 4 rows × 64 cols");
let ri = resolve_restart_rows(4, 4096, 4096, Subsampling::S420);
assert_eq!(ri, 4 * 256);
}
#[test]
fn test_resolve_restart_small_image_caps_markers() {
let ri = resolve_restart_rows(4, 64, 64, Subsampling::S420);
let total = 4u16 * 4;
assert!(
ri >= total,
"64×64 should have no restart markers, ri={ri} total={total}"
);
let ri = resolve_restart_rows(4, 128, 128, Subsampling::S420);
let total = 8u16 * 8;
assert!(
ri >= total,
"128×128 should have no restart markers, ri={ri} total={total}"
);
let ri = resolve_restart_rows(4, 256, 256, Subsampling::S420);
let total = 16u16 * 16;
assert!(
ri >= total,
"256×256 should have no restart markers, ri={ri} total={total}"
);
}
#[test]
fn test_resolve_restart_overhead_under_limit() {
for &(w, h) in &[
(64, 64),
(128, 128),
(256, 256),
(512, 512),
(1024, 1024),
(4096, 4096),
(1920, 1080),
(320, 240),
(16, 4096),
(4096, 16),
] {
for &ss in &[Subsampling::S420, Subsampling::S444, Subsampling::S422] {
let ri = resolve_restart_rows(4, w, h, ss);
if ri == 0 {
continue;
}
let h_samp: u32 = match ss {
Subsampling::S444 | Subsampling::S440 => 1,
Subsampling::S422 | Subsampling::S420 => 2,
};
let v_samp: u32 = match ss {
Subsampling::S444 | Subsampling::S422 => 1,
Subsampling::S440 | Subsampling::S420 => 2,
};
let mcu_w = h_samp * 8;
let mcu_h = v_samp * 8;
let mcu_cols = (w + mcu_w - 1) / mcu_w;
let mcu_rows = (h + mcu_h - 1) / mcu_h;
let total = mcu_cols * mcu_rows;
let est_file = w * h / 16; let num_markers = if ri as u32 >= total {
0
} else {
total / ri as u32
};
let overhead = DRI_HEADER_BYTES + num_markers * EST_BYTES_PER_MARKER;
let max_overhead = est_file * MAX_OVERHEAD_PER_MILLE / 1000;
assert!(
overhead <= max_overhead || num_markers == 0,
"{w}×{h} {ss:?}: overhead {overhead} > max {max_overhead} \
(ri={ri}, markers={num_markers}, est_file={est_file})"
);
}
}
}
#[test]
fn test_resolve_restart_disabled() {
assert_eq!(resolve_restart_rows(0, 512, 512, Subsampling::S420), 0);
assert_eq!(resolve_restart_rows(0, 64, 64, Subsampling::S444), 0);
}
}