use super::compat::TrellisConfig;
use super::{RateTable, trellis_quantize_block};
use crate::encode::config::ComputedConfig;
use crate::encode::dct::forward_dct_8x8;
use crate::encode::natural_to_zigzag_into;
use crate::error::Result;
use crate::foundation::consts::DCT_BLOCK_SIZE;
use crate::quant::aq::AQStrengthMap;
use crate::quant::{self, QuantTable, ZeroBiasParams};
pub const AQ_MEAN_THRESHOLD: f32 = 0.25;
pub fn should_use_hybrid(aq_mean: f32) -> bool {
aq_mean > AQ_MEAN_THRESHOLD
}
pub fn estimate_hybrid_improvement(aq_mean: f32) -> f32 {
(85.0 * aq_mean - 5.0).max(0.0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageType {
Photo,
Screenshot,
Mixed,
}
pub mod detection_thresholds {
pub const SCREENSHOT_CV_THRESHOLD: f32 = 1.5;
pub const PHOTO_MEAN_THRESHOLD: f32 = 0.06;
pub const FLAT_MEAN_THRESHOLD: f32 = 0.03;
}
pub fn detect_image_type(aq_mean: f32, aq_std: f32) -> ImageType {
use detection_thresholds::*;
if aq_mean < 0.001 {
return ImageType::Screenshot; }
let cv = aq_std / aq_mean;
if aq_mean < FLAT_MEAN_THRESHOLD {
return ImageType::Screenshot;
}
if cv > SCREENSHOT_CV_THRESHOLD {
return ImageType::Screenshot;
}
if aq_mean >= PHOTO_MEAN_THRESHOLD && cv <= SCREENSHOT_CV_THRESHOLD {
return ImageType::Photo;
}
ImageType::Mixed
}
pub fn adaptive_config(aq_mean: f32, aq_std: f32) -> HybridConfig {
match detect_image_type(aq_mean, aq_std) {
ImageType::Photo => {
let texture_scale = (0.15 / aq_mean.max(0.15)).min(1.0);
let coupling = -4.0 * texture_scale;
HybridConfig {
enabled: true,
aq_lambda_scale: coupling,
base_lambda_scale1: 14.75,
dc_enabled: false,
max_adjustment: 0.0, ..HybridConfig::default()
}
}
ImageType::Screenshot | ImageType::Mixed => HybridConfig::safe_compression(),
}
}
pub fn texture_adaptive_coupling(aq_mean: f32) -> f32 {
let texture_scale = (0.15 / aq_mean.max(0.15)).min(1.0);
-4.0 * texture_scale
}
#[derive(Debug, Clone, Copy)]
pub struct HybridConfig {
pub enabled: bool,
pub aq_lambda_scale: f32,
pub base_lambda_scale1: f32,
pub base_lambda_scale2: f32,
pub dc_enabled: bool,
pub aq_exponent: f32,
pub aq_threshold: f32,
pub quality_adaptive: bool,
pub chroma_scale: f32,
pub multiplicative: bool,
pub max_adjustment: f32,
}
impl Default for HybridConfig {
fn default() -> Self {
Self {
enabled: true,
aq_lambda_scale: 0.0,
base_lambda_scale1: 14.75,
base_lambda_scale2: 16.5,
dc_enabled: false,
aq_exponent: 1.0,
aq_threshold: 0.0,
quality_adaptive: false,
chroma_scale: 1.0,
multiplicative: false,
max_adjustment: 0.0,
}
}
}
impl HybridConfig {
pub fn new() -> Self {
Self::default()
}
pub fn disabled() -> Self {
Self {
enabled: false,
..Self::default()
}
}
pub fn favor_size() -> Self {
Self {
enabled: true,
aq_lambda_scale: 0.0, base_lambda_scale1: 14.0, dc_enabled: false,
..Self::default()
}
}
pub fn favor_quality() -> Self {
Self {
enabled: true,
aq_lambda_scale: 4.0, base_lambda_scale1: 15.5, dc_enabled: false,
..Self::default()
}
}
pub fn balanced() -> Self {
Self {
enabled: true,
aq_lambda_scale: 2.0,
base_lambda_scale1: 14.75,
dc_enabled: false,
..Self::default()
}
}
pub fn aggressive_compression() -> Self {
Self {
enabled: true,
aq_lambda_scale: -4.0, base_lambda_scale1: 14.75,
dc_enabled: false,
max_adjustment: 0.0, ..Self::default()
}
}
pub fn safe_compression() -> Self {
Self {
enabled: true,
aq_lambda_scale: -8.0, base_lambda_scale1: 14.75,
dc_enabled: false,
max_adjustment: 1.0, ..Self::default()
}
}
pub fn quality_boost() -> Self {
Self {
enabled: true,
aq_lambda_scale: 4.0, base_lambda_scale1: 14.75,
dc_enabled: false,
max_adjustment: 0.0,
..Self::default()
}
}
pub fn aq_lambda_scale(mut self, scale: f32) -> Self {
self.aq_lambda_scale = scale;
self
}
pub fn base_scale1(mut self, scale: f32) -> Self {
self.base_lambda_scale1 = scale;
self
}
pub fn base_scale2(mut self, scale: f32) -> Self {
self.base_lambda_scale2 = scale;
self
}
pub fn dc_trellis(mut self, enabled: bool) -> Self {
self.dc_enabled = enabled;
self
}
pub fn aq_exponent(mut self, exp: f32) -> Self {
self.aq_exponent = exp;
self
}
pub fn aq_threshold(mut self, threshold: f32) -> Self {
self.aq_threshold = threshold;
self
}
pub fn quality_adaptive(mut self, enabled: bool) -> Self {
self.quality_adaptive = enabled;
self
}
pub fn chroma_scale(mut self, scale: f32) -> Self {
self.chroma_scale = scale;
self
}
pub fn multiplicative(mut self, enabled: bool) -> Self {
self.multiplicative = enabled;
self
}
pub fn max_adjustment(mut self, max: f32) -> Self {
self.max_adjustment = max;
self
}
pub fn compute_lambda_adjustment(&self, aq_strength: f32, dampen: f32, is_chroma: bool) -> f32 {
if !self.enabled || aq_strength < self.aq_threshold {
return 0.0;
}
let effective_aq = if self.aq_exponent != 1.0 {
aq_strength.powf(self.aq_exponent)
} else {
aq_strength
};
let mut adjustment = effective_aq * self.aq_lambda_scale;
if self.quality_adaptive {
adjustment *= dampen;
}
if is_chroma {
adjustment *= self.chroma_scale;
}
if self.max_adjustment > 0.0 {
adjustment = adjustment.clamp(-self.max_adjustment, self.max_adjustment);
}
adjustment
}
pub fn to_trellis_config(
self,
aq_strength: f32,
dampen: f32,
is_chroma: bool,
) -> TrellisConfig {
let adjustment = self.compute_lambda_adjustment(aq_strength, dampen, is_chroma);
let scale1 = if self.multiplicative {
self.base_lambda_scale1 * (1.0 + adjustment)
} else {
self.base_lambda_scale1 + adjustment
};
TrellisConfig::default()
.ac_trellis(true)
.dc_trellis(self.dc_enabled)
.lambda_scales(scale1, self.base_lambda_scale2)
}
pub fn id(&self) -> String {
format!(
"aq{:.1}_s1_{:.1}_dc{}_exp{:.1}",
self.aq_lambda_scale,
self.base_lambda_scale1,
if self.dc_enabled { 1 } else { 0 },
self.aq_exponent
)
}
}
#[derive(Debug, Clone)]
pub struct SweepConfig {
pub aq_lambda_scales: Vec<f32>,
pub base_scale1_values: Vec<f32>,
pub dc_enabled_values: Vec<bool>,
pub aq_exponents: Vec<f32>,
pub quality_levels: Vec<u8>,
}
impl Default for SweepConfig {
fn default() -> Self {
Self {
aq_lambda_scales: vec![0.0, 1.0, 2.0, 3.0, 4.0],
base_scale1_values: vec![14.0, 14.75, 15.5],
dc_enabled_values: vec![false, true],
aq_exponents: vec![1.0],
quality_levels: vec![75],
}
}
}
impl SweepConfig {
pub fn quick() -> Self {
Self {
aq_lambda_scales: vec![0.0, 2.0, 4.0],
base_scale1_values: vec![14.75],
dc_enabled_values: vec![false],
aq_exponents: vec![1.0],
quality_levels: vec![75],
}
}
pub fn comprehensive() -> Self {
Self {
aq_lambda_scales: vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 6.0],
base_scale1_values: vec![13.5, 14.0, 14.5, 14.75, 15.0, 15.5, 16.0],
dc_enabled_values: vec![false, true],
aq_exponents: vec![0.5, 1.0, 2.0],
quality_levels: vec![50, 75, 90],
}
}
pub fn generate_configs(&self) -> Vec<HybridConfig> {
let mut configs = Vec::new();
for &aq_scale in &self.aq_lambda_scales {
for &base_s1 in &self.base_scale1_values {
for &dc_en in &self.dc_enabled_values {
for &aq_exp in &self.aq_exponents {
configs.push(HybridConfig {
enabled: true,
aq_lambda_scale: aq_scale,
base_lambda_scale1: base_s1,
dc_enabled: dc_en,
aq_exponent: aq_exp,
..HybridConfig::default()
});
}
}
}
}
configs
}
pub fn total_combinations(&self) -> usize {
self.aq_lambda_scales.len()
* self.base_scale1_values.len()
* self.dc_enabled_values.len()
* self.aq_exponents.len()
* self.quality_levels.len()
}
}
pub struct StandardRateTables {
pub luma_ac: RateTable,
pub chroma_ac: RateTable,
pub luma_dc: RateTable,
pub chroma_dc: RateTable,
}
impl StandardRateTables {
pub fn new() -> Self {
Self {
luma_ac: RateTable::standard_luma_ac(),
chroma_ac: RateTable::standard_chroma_ac(),
luma_dc: RateTable::standard_luma_dc(),
chroma_dc: RateTable::standard_chroma_dc(),
}
}
}
impl Default for StandardRateTables {
fn default() -> Self {
Self::new()
}
}
pub fn scale_quant_by_aq(
base_quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
) -> [u16; DCT_BLOCK_SIZE] {
let mut scaled = [0u16; DCT_BLOCK_SIZE];
let multiplier = 1.0 + aq_strength;
for i in 0..DCT_BLOCK_SIZE {
scaled[i] = (base_quant[i] as f32 * multiplier)
.round()
.clamp(1.0, 255.0) as u16;
}
scaled
}
pub fn dct_f32_to_i32(coeffs: &[f32; DCT_BLOCK_SIZE]) -> [i32; DCT_BLOCK_SIZE] {
let mut result = [0i32; DCT_BLOCK_SIZE];
for i in 0..DCT_BLOCK_SIZE {
result[i] = (coeffs[i] * 64.0).round() as i32;
}
result
}
pub fn hybrid_quantize_block(
dct_coeffs: &[f32; DCT_BLOCK_SIZE],
base_quant: &[u16; DCT_BLOCK_SIZE],
ac_table: &RateTable,
config: &TrellisConfig,
) -> [i16; DCT_BLOCK_SIZE] {
let dct_i32 = dct_f32_to_i32(dct_coeffs);
let mut quantized = [0i16; DCT_BLOCK_SIZE];
trellis_quantize_block(&dct_i32, &mut quantized, base_quant, ac_table, config);
quantized
}
pub fn hybrid_quantize_block_simple(
dct_coeffs: &[f32; DCT_BLOCK_SIZE],
base_quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
) -> [i16; DCT_BLOCK_SIZE] {
let scaled_quant = scale_quant_by_aq(base_quant, aq_strength);
let mut quantized = [0i16; DCT_BLOCK_SIZE];
for i in 0..DCT_BLOCK_SIZE {
quantized[i] = (dct_coeffs[i] * 8.0 / scaled_quant[i] as f32).round() as i16;
}
quantized
}
#[inline]
pub(crate) fn get_aq_map_or_compute(
config: &ComputedConfig,
y_plane: &[f32],
width: usize,
height: usize,
y_quant_01: u16,
) -> Result<AQStrengthMap> {
if let Some(ref custom) = config.custom_aq_map {
Ok(custom.clone())
} else {
Ok(crate::quant::aq::compute_aq_strength_map(
y_plane, width, height, y_quant_01,
)?)
}
}
#[inline]
pub(crate) fn create_hybrid_ctx(config: &ComputedConfig) -> Option<HybridQuantContext> {
if let Some(ref trellis) = config.trellis
&& trellis.is_enabled()
{
return Some(HybridQuantContext::from_trellis_config(*trellis));
}
if config.hybrid_config.enabled {
Some(HybridQuantContext::new(config.hybrid_config))
} else {
None
}
}
#[inline]
pub(crate) fn quantize_block_dispatch(
dct: &[f32; DCT_BLOCK_SIZE],
quant_values: &[u16; DCT_BLOCK_SIZE],
zero_bias: &ZeroBiasParams,
aq_strength: f32,
is_luma: bool,
hybrid_ctx: Option<&HybridQuantContext>,
) -> [i16; DCT_BLOCK_SIZE] {
if let Some(ctx) = hybrid_ctx {
ctx.quantize_block(dct, quant_values, aq_strength, 1.0, is_luma)
} else {
quant::quantize_block_with_zero_bias_simd(dct, quant_values, zero_bias, aq_strength)
}
}
enum TrellisMode {
Hybrid(HybridConfig),
Standalone(TrellisConfig),
}
pub(crate) struct HybridQuantContext {
rate_tables: StandardRateTables,
mode: TrellisMode,
}
impl std::fmt::Debug for HybridQuantContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mode_str = match &self.mode {
TrellisMode::Hybrid(_) => "Hybrid",
TrellisMode::Standalone(_) => "Standalone",
};
f.debug_struct("HybridQuantContext")
.field("mode", &mode_str)
.finish_non_exhaustive()
}
}
impl HybridQuantContext {
pub(crate) fn new(config: HybridConfig) -> Self {
Self {
rate_tables: StandardRateTables::new(),
mode: TrellisMode::Hybrid(config),
}
}
pub(crate) fn from_trellis_config(config: TrellisConfig) -> Self {
Self {
rate_tables: StandardRateTables::new(),
mode: TrellisMode::Standalone(config),
}
}
pub(crate) fn quantize_block(
&self,
dct_coeffs: &[f32; DCT_BLOCK_SIZE],
quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
dampen: f32,
is_luma: bool,
) -> [i16; DCT_BLOCK_SIZE] {
let ac_table = if is_luma {
&self.rate_tables.luma_ac
} else {
&self.rate_tables.chroma_ac
};
let trellis_config = match &self.mode {
TrellisMode::Hybrid(hybrid_config) => {
hybrid_config.to_trellis_config(aq_strength, dampen, !is_luma)
}
TrellisMode::Standalone(trellis_config) => {
*trellis_config
}
};
hybrid_quantize_block(dct_coeffs, quant, ac_table, &trellis_config)
}
pub(crate) fn is_dc_trellis_enabled(&self) -> bool {
match &self.mode {
TrellisMode::Hybrid(config) => config.dc_enabled,
TrellisMode::Standalone(config) => config.is_dc_enabled(),
}
}
pub(crate) fn trellis_config(&self) -> TrellisConfig {
match &self.mode {
TrellisMode::Hybrid(config) => config.to_trellis_config(0.0, 1.0, false),
TrellisMode::Standalone(config) => *config,
}
}
pub(crate) fn luma_dc_rate_table(&self) -> &RateTable {
&self.rate_tables.luma_dc
}
pub(crate) fn chroma_dc_rate_table(&self) -> &RateTable {
&self.rate_tables.chroma_dc
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn quantize_all_blocks_xyb_with_aq(
x_plane: &[f32],
y_plane: &[f32],
b_plane: &[f32], width: usize,
height: usize,
b_width: usize,
b_height: usize,
x_quant: &QuantTable,
y_quant: &QuantTable,
b_quant: &QuantTable,
aq_map: &AQStrengthMap,
hybrid_ctx: Option<&HybridQuantContext>,
) -> crate::error::Result<(
Vec<[i16; DCT_BLOCK_SIZE]>,
Vec<[i16; DCT_BLOCK_SIZE]>,
Vec<[i16; DCT_BLOCK_SIZE]>,
)> {
let mcu_cols = (width + 15) / 16;
let mcu_rows = (height + 15) / 16;
let num_xy_blocks = mcu_cols * mcu_rows * 4; let num_b_blocks = mcu_cols * mcu_rows;
let mut x_blocks = crate::foundation::alloc::try_alloc_dct_blocks(num_xy_blocks, "x_blocks")?;
let mut y_blocks = crate::foundation::alloc::try_alloc_dct_blocks(num_xy_blocks, "y_blocks")?;
let mut b_blocks = crate::foundation::alloc::try_alloc_dct_blocks(num_b_blocks, "b_blocks")?;
for mcu_y in 0..mcu_rows {
for mcu_x in 0..mcu_cols {
let mcu_idx = mcu_y * mcu_cols + mcu_x;
let xy_base = mcu_idx * 4;
for block_y in 0..2 {
for block_x in 0..2 {
let bx = mcu_x * 2 + block_x;
let by = mcu_y * 2 + block_y;
let block_offset = block_y * 2 + block_x;
let aq_strength = aq_map.get(bx, by);
let x_block =
crate::encode_simd::extract_block_xyb_simd(x_plane, width, height, bx, by);
let x_dct = forward_dct_8x8(&x_block);
let x_quant_coeffs = if let Some(ctx) = hybrid_ctx {
ctx.quantize_block(&x_dct, &x_quant.values, aq_strength, 1.0, true)
} else {
quant::quantize_block(&x_dct, &x_quant.values)
};
natural_to_zigzag_into(&x_quant_coeffs, &mut x_blocks[xy_base + block_offset]);
}
}
for block_y in 0..2 {
for block_x in 0..2 {
let bx = mcu_x * 2 + block_x;
let by = mcu_y * 2 + block_y;
let block_offset = block_y * 2 + block_x;
let aq_strength = aq_map.get(bx, by);
let y_block =
crate::encode_simd::extract_block_xyb_simd(y_plane, width, height, bx, by);
let y_dct = forward_dct_8x8(&y_block);
let y_quant_coeffs = if let Some(ctx) = hybrid_ctx {
ctx.quantize_block(&y_dct, &y_quant.values, aq_strength, 1.0, true)
} else {
quant::quantize_block(&y_dct, &y_quant.values)
};
natural_to_zigzag_into(&y_quant_coeffs, &mut y_blocks[xy_base + block_offset]);
}
}
let b_aq_strength = {
let mut sum = 0.0f32;
for dy in 0..2 {
for dx in 0..2 {
let bx = mcu_x * 2 + dx;
let by = mcu_y * 2 + dy;
sum += aq_map.get(bx, by);
}
}
sum / 4.0
};
let b_block = crate::encode_simd::extract_block_xyb_simd(
b_plane, b_width, b_height, mcu_x, mcu_y,
);
let b_dct = forward_dct_8x8(&b_block);
let b_quant_coeffs = if let Some(ctx) = hybrid_ctx {
ctx.quantize_block(&b_dct, &b_quant.values, b_aq_strength, 1.0, false)
} else {
quant::quantize_block(&b_dct, &b_quant.values)
};
natural_to_zigzag_into(&b_quant_coeffs, &mut b_blocks[mcu_idx]);
}
}
Ok((x_blocks, y_blocks, b_blocks))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = HybridConfig::default();
assert!(config.enabled);
assert_eq!(config.aq_lambda_scale, 0.0);
assert_eq!(config.base_lambda_scale1, 14.75);
}
#[test]
fn test_presets() {
let favor_size = HybridConfig::favor_size();
assert_eq!(favor_size.base_lambda_scale1, 14.0);
let favor_quality = HybridConfig::favor_quality();
assert_eq!(favor_quality.aq_lambda_scale, 4.0);
assert_eq!(favor_quality.base_lambda_scale1, 15.5);
let balanced = HybridConfig::balanced();
assert_eq!(balanced.aq_lambda_scale, 2.0);
}
#[test]
fn test_lambda_adjustment() {
let config = HybridConfig::default();
assert_eq!(config.compute_lambda_adjustment(0.5, 1.0, false), 0.0);
assert_eq!(config.compute_lambda_adjustment(1.0, 1.0, false), 0.0);
let balanced = HybridConfig::balanced();
assert_eq!(balanced.compute_lambda_adjustment(0.0, 1.0, false), 0.0);
assert_eq!(balanced.compute_lambda_adjustment(0.5, 1.0, false), 1.0);
assert_eq!(balanced.compute_lambda_adjustment(1.0, 1.0, false), 2.0);
}
#[test]
fn test_quality_adaptive() {
let config = HybridConfig::balanced().quality_adaptive(true);
let adj_full = config.compute_lambda_adjustment(0.5, 1.0, false);
let adj_half = config.compute_lambda_adjustment(0.5, 0.5, false);
assert_eq!(adj_half, adj_full * 0.5);
}
#[test]
fn test_aq_exponent() {
let config = HybridConfig::balanced().aq_exponent(2.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, 0.25 * 2.0); }
#[test]
fn test_sweep_config() {
let sweep = SweepConfig::quick();
let configs = sweep.generate_configs();
assert_eq!(configs.len(), 3); }
#[test]
fn test_aggressive_compression_preset() {
let config = HybridConfig::aggressive_compression();
assert!(config.enabled);
assert_eq!(config.aq_lambda_scale, -4.0); assert_eq!(config.max_adjustment, 0.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, -2.0); }
#[test]
fn test_safe_compression_preset() {
let config = HybridConfig::safe_compression();
assert!(config.enabled);
assert_eq!(config.aq_lambda_scale, -8.0); assert_eq!(config.max_adjustment, 1.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, -1.0);
let adj_low = config.compute_lambda_adjustment(0.1, 1.0, false);
assert_eq!(adj_low, -0.8); }
#[test]
fn test_quality_boost_preset() {
let config = HybridConfig::quality_boost();
assert!(config.enabled);
assert_eq!(config.aq_lambda_scale, 4.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, 2.0); }
#[test]
fn test_max_adjustment_clamping() {
let config = HybridConfig::new()
.aq_lambda_scale(-10.0)
.max_adjustment(2.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, -2.0);
let config_pos = HybridConfig::new()
.aq_lambda_scale(10.0)
.max_adjustment(2.0);
let adj_pos = config_pos.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj_pos, 2.0); }
#[test]
fn test_multiplicative_coupling() {
let config = HybridConfig::new()
.aq_lambda_scale(0.1) .multiplicative(true);
let trellis = config.to_trellis_config(0.5, 1.0, false);
let expected = 14.75 * 1.05;
assert!((trellis.lambda_log_scale1 - expected).abs() < 0.001);
let config_add = HybridConfig::new().aq_lambda_scale(0.1);
let trellis_add = config_add.to_trellis_config(0.5, 1.0, false);
assert!((trellis_add.lambda_log_scale1 - 14.80).abs() < 0.001);
}
#[test]
fn test_negative_coupling_with_threshold() {
let config = HybridConfig::new().aq_lambda_scale(-4.0).aq_threshold(0.2);
let adj_low = config.compute_lambda_adjustment(0.15, 1.0, false);
assert_eq!(adj_low, 0.0);
let adj_high = config.compute_lambda_adjustment(0.3, 1.0, false);
assert_eq!(adj_high, -1.2); }
#[test]
fn test_chroma_scale_with_negative_coupling() {
let config = HybridConfig::new().aq_lambda_scale(-4.0).chroma_scale(0.5);
let adj_luma = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj_luma, -2.0);
let adj_chroma = config.compute_lambda_adjustment(0.5, 1.0, true);
assert_eq!(adj_chroma, -1.0);
}
#[test]
fn test_detect_photo() {
assert_eq!(detect_image_type(0.090, 0.065), ImageType::Photo);
assert_eq!(detect_image_type(0.10, 0.05), ImageType::Photo);
}
#[test]
fn test_detect_screenshot() {
assert_eq!(detect_image_type(0.084, 0.198), ImageType::Screenshot);
assert_eq!(detect_image_type(0.02, 0.01), ImageType::Screenshot);
assert_eq!(detect_image_type(0.0, 0.0), ImageType::Screenshot);
assert_eq!(detect_image_type(0.08, 0.16), ImageType::Screenshot); }
#[test]
fn test_detect_mixed() {
assert_eq!(detect_image_type(0.04, 0.02), ImageType::Mixed); }
#[test]
fn test_adaptive_config_low_texture_photo() {
let config = adaptive_config(0.10, 0.05);
assert_eq!(config.aq_lambda_scale, -4.0); assert_eq!(config.max_adjustment, 0.0);
}
#[test]
fn test_adaptive_config_medium_texture_photo() {
let config = adaptive_config(0.30, 0.15);
assert!((config.aq_lambda_scale - (-2.0)).abs() < 0.1); assert_eq!(config.max_adjustment, 0.0);
}
#[test]
fn test_adaptive_config_high_texture_photo() {
let config = adaptive_config(0.60, 0.25);
assert!((config.aq_lambda_scale - (-1.0)).abs() < 0.1); assert_eq!(config.max_adjustment, 0.0);
}
#[test]
fn test_adaptive_config_screenshot() {
let config = adaptive_config(0.084, 0.198);
assert_eq!(config.aq_lambda_scale, -8.0);
assert_eq!(config.max_adjustment, 1.0);
}
#[test]
fn test_adaptive_config_mixed() {
let config = adaptive_config(0.04, 0.02);
assert!(config.max_adjustment > 0.0);
}
#[test]
fn test_texture_adaptive_coupling() {
assert_eq!(texture_adaptive_coupling(0.10), -4.0);
assert_eq!(texture_adaptive_coupling(0.15), -4.0);
assert!((texture_adaptive_coupling(0.30) - (-2.0)).abs() < 0.01);
assert!((texture_adaptive_coupling(0.60) - (-1.0)).abs() < 0.01);
assert!((texture_adaptive_coupling(0.75) - (-0.8)).abs() < 0.01);
}
#[test]
fn test_scale_quant_by_aq() {
let base = [16u16; 64];
let scaled = scale_quant_by_aq(&base, 0.0);
assert_eq!(scaled[0], 16);
let scaled = scale_quant_by_aq(&base, 0.5);
assert_eq!(scaled[0], 24);
let scaled = scale_quant_by_aq(&base, 1.0);
assert_eq!(scaled[0], 32);
}
#[test]
fn test_scale_quant_clamping() {
let base = [200u16; 64];
let scaled = scale_quant_by_aq(&base, 0.5);
assert_eq!(scaled[0], 255);
let base_low = [1u16; 64];
let scaled = scale_quant_by_aq(&base_low, 0.0);
assert_eq!(scaled[0], 1);
}
#[test]
fn test_dct_f32_to_i32() {
let f32_coeffs = [127.4f32; 64];
let i32_coeffs = dct_f32_to_i32(&f32_coeffs);
assert_eq!(i32_coeffs[0], 8154);
let f32_coeffs = [-127.6f32; 64];
let i32_coeffs = dct_f32_to_i32(&f32_coeffs);
assert_eq!(i32_coeffs[0], -8166);
}
#[test]
fn test_hybrid_quantize_simple() {
let mut dct = [0.0f32; 64];
dct[0] = 1024.0;
let base_quant = [16u16; 64];
let quantized = hybrid_quantize_block_simple(&dct, &base_quant, 0.0);
assert_eq!(quantized[0], 512);
let quantized = hybrid_quantize_block_simple(&dct, &base_quant, 0.5);
assert_eq!(quantized[0], 341); }
}