use crate::encode::dct::forward_dct_8x8;
use crate::error::Result;
use crate::foundation::consts::DCT_BLOCK_SIZE;
use crate::hybrid::config::HybridConfig;
use crate::hybrid::core::{hybrid_quantize_block, StandardHuffmanTables};
use crate::quant::aq::AQStrengthMap;
use crate::quant::{self, QuantTable, ZeroBiasParams};
use super::mozjpeg_compat::TrellisConfig;
use super::natural_to_zigzag_into;
use super::config::ComputedConfig;
#[inline]
pub(super) 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(super) fn create_hybrid_ctx(config: &ComputedConfig) -> Option<HybridQuantContext> {
if let Some(ref trellis) = config.trellis {
if 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(super) 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 {
huff_tables: StandardHuffmanTables,
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 {
huff_tables: StandardHuffmanTables::new(),
mode: TrellisMode::Hybrid(config),
}
}
pub(crate) fn from_trellis_config(config: TrellisConfig) -> Self {
Self {
huff_tables: StandardHuffmanTables::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.huff_tables.luma_ac
} else {
&self.huff_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.to_mozjpeg_config()
}
};
hybrid_quantize_block(dct_coeffs, quant, aq_strength, ac_table, &trellis_config)
}
}
#[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))
}