#![allow(clippy::excessive_precision)]
use crate::ac_context::{
K_COEFF_ORDER_8X8, K_COEFF_ORDER_16X8, K_COEFF_ORDER_16X16, block_context, non_zero_context,
zero_density_context, zero_density_context_8x8, zero_density_contexts_offset,
};
use crate::dc_group_data::{
AcStrategyImage, DcGroupData, STRATEGY_DCT, STRATEGY_DCT8X16, STRATEGY_DCT16X8,
STRATEGY_DCT16X16,
};
use crate::dct::{
dc_from_dct8x16, dc_from_dct16x8, dc_from_dct16x16, dct8x8, dct8x16, dct16x8, dct16x16,
};
use crate::entropy::{Token, pack_signed};
use crate::image::{Image3B, Image3F, Rect};
use crate::quant_weights::{DC_QUANT, DequantMatrices, INV_DC_QUANT};
use crate::util::FastRound;
const K_GROUP_DIM_IN_BLOCKS: usize = 32;
#[inline]
fn predict_from_top_and_left(row_top: Option<&[u8]>, row: &[u8], x: usize, default_val: u8) -> u8 {
if x == 0 {
match row_top {
Some(t) => t[x],
None => default_val,
}
} else if row_top.is_none() {
row[x - 1]
} else {
(row_top.unwrap()[x] as u16 + row[x - 1] as u16).div_ceil(2) as u8
}
}
#[inline]
fn num_nonzero_except_dc(block: &[i32; 64]) -> i32 {
let mut count: i32 = 0;
for k in 1..64 {
if block[k] != 0 {
count += 1;
}
}
count
}
#[inline]
fn num_nonzero_except_llf(block: &[i32], cx: usize, cy: usize) -> i32 {
let row_stride = cx * 8;
let xsize_pixels = cx * 8;
let ysize_pixels = cy * 8;
let mut count: i32 = 0;
for v in 0..ysize_pixels {
for u in 0..xsize_pixels {
if v < cy && u < cx {
continue;
}
if block[v * row_stride + u] != 0 {
count += 1;
}
}
}
count
}
fn quantize_block_ac(
block_in: &[f32],
c: usize,
qm: &[f32],
quant: i32,
scale: f32,
qm_multiplier: f32,
xsize: usize,
ysize: usize,
block_out: &mut [i32],
) {
let qac = scale * quant as f32;
let mut thr = [0.58f32, 0.635, 0.66, 0.7];
if c == 0 {
for i in 1..4 {
thr[i] += 0.08;
}
}
if c == 2 {
for i in 1..4 {
thr[i] = 0.75;
}
}
if xsize > 1 || ysize > 1 {
let delta =
(0.003_f32 * xsize as f32 * ysize as f32).clamp(0.0, if c > 0 { 0.08 } else { 0.12 });
for i in 0..4 {
thr[i] -= delta;
}
}
let q_scaled = qac * qm_multiplier;
let width = xsize * 8;
let height = ysize * 8;
debug_assert_eq!(
qm.len(),
width * height,
"quant matrix size {} != transform size {}x{}={} (wrong matrix for strategy?)",
qm.len(),
width,
height,
width * height
);
debug_assert!(block_in.len() >= width * height, "block_in too small");
debug_assert!(block_out.len() >= width * height, "block_out too small");
let n = width * height;
let qm = &qm[..n];
let block_in = &block_in[..n];
let block_out = &mut block_out[..n];
let half = width / 2;
for y in 0..height {
let yfix = if y >= height / 2 { 2 } else { 0 };
let thr_lo = thr[yfix];
let thr_hi = thr[yfix + 1];
let row = y * width;
let qm_row = &qm[row..row + width];
let in_row = &block_in[row..row + width];
let out_row = &mut block_out[row..row + width];
for (x, ((&qmv, &inv), out)) in qm_row
.iter()
.zip(in_row.iter())
.zip(out_row.iter_mut())
.enumerate()
{
let threshold = if x >= half { thr_hi } else { thr_lo };
let q = qmv * q_scaled;
let val = q * inv;
*out = if val.abs() >= threshold {
val.fast_round() as i32
} else {
0
};
}
}
}
const DEFAULT_QUANT_BIAS_1: f32 = 1.0 - 0.07005449891748593;
const DEFAULT_QUANT_BIAS_3: f32 = 0.145;
#[inline]
fn adjust_quant_bias_y(quant: i32) -> f32 {
let aq = quant.unsigned_abs() as f32;
if aq < 1.125 {
if quant == 0 {
0.0
} else if quant > 0 {
DEFAULT_QUANT_BIAS_1
} else {
-DEFAULT_QUANT_BIAS_1
}
} else {
let q = quant as f32;
q - DEFAULT_QUANT_BIAS_3 / q
}
}
fn quantize_roundtrip_y_block(
qm: &[f32],
dqm: &[f32],
scale: f32,
quant: i32,
xsize: usize,
ysize: usize,
inout: &mut [f32],
quantized: &mut [i32],
) {
quantize_block_ac(inout, 1, qm, quant, scale, 1.0, xsize, ysize, quantized);
let inv_qac = 1.0 / (scale * quant as f32);
let size = xsize * ysize * 64;
for (out, (&q, &dq)) in inout[..size]
.iter_mut()
.zip(quantized[..size].iter().zip(dqm[..size].iter()))
{
*out = adjust_quant_bias_y(q) * dq * inv_qac;
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn write_ac_group(
opsin: &Image3F,
group_brect: Rect,
matrices: &DequantMatrices,
scale: f32,
scale_dc: f32,
x_qm_scale: u32,
dc_data: &mut DcGroupData,
num_nzeros: &mut [Image3B],
coeff_shifts: &[u32],
out: &mut [Vec<Token>],
) {
let xsize_blocks = group_brect.xsize;
let ysize_blocks = group_brect.ysize;
let inv_factor = [
INV_DC_QUANT[0] * scale_dc,
INV_DC_QUANT[1] * scale_dc,
INV_DC_QUANT[2] * scale_dc,
];
let cfl_factor_b = INV_DC_QUANT[2] * DC_QUANT[1];
let x_qm_mul = 1.25f32.powf(x_qm_scale as f32 - 2.0);
let nzeros_by0 = group_brect.y0 % K_GROUP_DIM_IN_BLOCKS;
let mut coeffs = [[0.0f32; 256]; 3];
let mut quantized = [[0i32; 256]; 3];
let mut tmp = [0.0f32; 256];
for by in 0..ysize_blocks {
let nz_by = nzeros_by0 + by;
let global_by = group_brect.y0 + by;
for bx in 0..xsize_blocks {
let global_bx = group_brect.x0 + bx;
if !dc_data.ac_strategy.is_first_block(global_bx, global_by) {
continue;
}
let raw_strategy = dc_data.ac_strategy.raw_strategy(global_bx, global_by);
let cov_x = AcStrategyImage::covered_blocks_x_of(raw_strategy);
let cov_y = AcStrategyImage::covered_blocks_y_of(raw_strategy);
let (cx, cy) = if cov_y > cov_x {
(cov_y, cov_x)
} else {
(cov_x, cov_y)
};
let size = cx * cy * 64;
let quant_ac = dc_data.raw_quant_field.row(global_by)[global_bx] as i32;
let opsin_bx = bx * 8;
let opsin_by = by * 8;
for c in 0..3 {
let plane = opsin.plane(c);
match raw_strategy {
STRATEGY_DCT => {
for yy in 0..8 {
let row = plane.row(opsin_by + yy);
tmp[yy * 8..yy * 8 + 8].copy_from_slice(&row[opsin_bx..opsin_bx + 8]);
}
let dst: &mut [f32; 64] = (&mut coeffs[c][..64]).try_into().unwrap();
let tmp_64 = tmp.as_chunks::<64>().0;
dct8x8(&tmp_64[0], dst);
}
STRATEGY_DCT16X8 => {
for yy in 0..16 {
let row = plane.row(opsin_by + yy);
tmp[yy * 8..yy * 8 + 8].copy_from_slice(&row[opsin_bx..opsin_bx + 8]);
}
let dst: &mut [f32; 128] = (&mut coeffs[c][..128]).try_into().unwrap();
let tmp_128 = tmp.as_chunks::<128>().0;
dct16x8(&tmp_128[0], dst);
}
STRATEGY_DCT8X16 => {
for yy in 0..8 {
let row = plane.row(opsin_by + yy);
tmp[yy * 16..yy * 16 + 16]
.copy_from_slice(&row[opsin_bx..opsin_bx + 16]);
}
let dst: &mut [f32; 128] = (&mut coeffs[c][..128]).try_into().unwrap();
let tmp_128 = tmp.as_chunks::<128>().0;
dct8x16(&tmp_128[0], dst);
}
STRATEGY_DCT16X16 => {
for yy in 0..16 {
let row = plane.row(opsin_by + yy);
tmp[yy * 16..yy * 16 + 16]
.copy_from_slice(&row[opsin_bx..opsin_bx + 16]);
}
let dst: &mut [f32; 256] = (&mut coeffs[c][..256]).try_into().unwrap();
let tmp_256 = tmp.as_chunks::<256>().0;
dct16x16(&tmp_256[0], dst);
}
_ => unreachable!("invalid raw strategy {}", raw_strategy),
}
}
let mut dc_vals = [[0.0f32; 4]; 3];
match raw_strategy {
STRATEGY_DCT => {
for c in 0..3 {
dc_vals[c][0] = coeffs[c][0];
}
}
STRATEGY_DCT16X8 => {
for c in 0..3 {
let cb: &[f32; 128] = (&coeffs[c][..128]).try_into().unwrap();
let mut dc2 = [0.0f32; 2];
dc_from_dct16x8(cb, &mut dc2);
dc_vals[c][0] = dc2[0]; dc_vals[c][1] = dc2[1]; }
}
STRATEGY_DCT8X16 => {
for c in 0..3 {
let cb: &[f32; 128] = (&coeffs[c][..128]).try_into().unwrap();
let mut dc2 = [0.0f32; 2];
dc_from_dct8x16(cb, &mut dc2);
dc_vals[c][0] = dc2[0]; dc_vals[c][1] = dc2[1]; }
}
STRATEGY_DCT16X16 => {
for c in 0..3 {
let cb: &[f32; 256] = (&coeffs[c][..256]).try_into().unwrap();
let mut dc4 = [0.0f32; 4];
dc_from_dct16x16(cb, &mut dc4);
dc_vals[c][0] = dc4[0];
dc_vals[c][1] = dc4[1];
dc_vals[c][2] = dc4[2];
dc_vals[c][3] = dc4[3];
}
}
_ => unreachable!(),
}
let mut y_dc_q_arr = [[0i16; 2]; 2]; for iy in 0..cov_y {
let quant_target = &mut dc_data.quant_dc.plane_row_mut(1, global_by + iy)
[global_bx..global_bx + cov_x];
let y_dc_q_target = &mut y_dc_q_arr[iy];
for (ix, (quant, dc_target)) in quant_target
.iter_mut()
.zip(y_dc_q_target.iter_mut())
.enumerate()
{
let didx = iy * cov_x + ix;
let y_dc_q = (inv_factor[1] * dc_vals[1][didx]).fast_round() as i16;
*quant = y_dc_q;
*dc_target = y_dc_q;
}
}
let (inv_qm_y, qm_y): (&[f32], &[f32]) = match raw_strategy {
STRATEGY_DCT => (&matrices.inv_matrix(1)[..], &matrices.matrix(1)[..]),
STRATEGY_DCT16X16 => (&matrices.inv_matrix_16x16(1)[..], &matrices.matrix_16x16(1)[..]),
_ => (&matrices.inv_matrix_16x8(1)[..], &matrices.matrix_16x8(1)[..]),
};
quantize_roundtrip_y_block(
inv_qm_y,
qm_y,
scale,
quant_ac,
cx,
cy,
&mut coeffs[1][..size],
&mut quantized[1][..size],
);
let tx = global_bx / 8;
let ty = global_by / 8;
let cmap_x = dc_data.ytox_map.row(ty)[tx];
let cmap_b = dc_data.ytob_map.row(ty)[tx];
let x_factor = crate::enc_color_correlation::y_to_x_ratio(cmap_x);
let b_factor = crate::enc_color_correlation::y_to_b_ratio(cmap_b);
{
let wc = cx * 8;
for iy in 0..cy {
for ix in 0..cx {
coeffs[1][iy * wc + ix] = 0.0;
}
}
}
{
let [c0, c1, c2] = &mut coeffs;
let y = &c1[..size];
for ((a, b), &yi) in c0[..size]
.iter_mut()
.zip(c2[..size].iter_mut())
.zip(y.iter())
{
*a -= x_factor * yi;
*b -= b_factor * yi;
}
}
let mut x_dc_post = [0.0f32; 4];
let mut b_dc_post = [0.0f32; 4];
match raw_strategy {
STRATEGY_DCT => {
x_dc_post[0] = coeffs[0][0];
b_dc_post[0] = coeffs[2][0];
}
STRATEGY_DCT16X8 => {
let xb: &[f32; 128] = (&coeffs[0][..128]).try_into().unwrap();
let bb: &[f32; 128] = (&coeffs[2][..128]).try_into().unwrap();
let mut xd = [0.0f32; 2];
let mut bd = [0.0f32; 2];
dc_from_dct16x8(xb, &mut xd);
dc_from_dct16x8(bb, &mut bd);
x_dc_post[..2].copy_from_slice(&xd);
b_dc_post[..2].copy_from_slice(&bd);
}
STRATEGY_DCT8X16 => {
let xb: &[f32; 128] = (&coeffs[0][..128]).try_into().unwrap();
let bb: &[f32; 128] = (&coeffs[2][..128]).try_into().unwrap();
let mut xd = [0.0f32; 2];
let mut bd = [0.0f32; 2];
dc_from_dct8x16(xb, &mut xd);
dc_from_dct8x16(bb, &mut bd);
x_dc_post[..2].copy_from_slice(&xd);
b_dc_post[..2].copy_from_slice(&bd);
}
STRATEGY_DCT16X16 => {
let xb: &[f32; 256] = (&coeffs[0][..256]).try_into().unwrap();
let bb: &[f32; 256] = (&coeffs[2][..256]).try_into().unwrap();
dc_from_dct16x16(xb, &mut x_dc_post);
dc_from_dct16x16(bb, &mut b_dc_post);
}
_ => unreachable!(),
}
for iy in 0..cov_y {
let quant_dc_row = &mut dc_data.quant_dc.plane_row_mut(0, global_by + iy)
[global_bx..global_bx + cov_x];
for (ix, target_quant) in quant_dc_row.iter_mut().enumerate() {
let didx = iy * cov_x + ix;
let x_dc_q = (inv_factor[0] * x_dc_post[didx]).fast_round() as i16;
*target_quant = x_dc_q;
}
}
let inv_qm_x: &[f32] = match raw_strategy {
STRATEGY_DCT => &matrices.inv_matrix(0)[..],
STRATEGY_DCT16X16 => &matrices.inv_matrix_16x16(0)[..],
_ => &matrices.inv_matrix_16x8(0)[..],
};
quantize_block_ac(
&coeffs[0][..size],
0,
inv_qm_x,
quant_ac,
scale,
x_qm_mul,
cx,
cy,
&mut quantized[0][..size],
);
for iy in 0..cov_y {
let quant_dc_row = &mut dc_data.quant_dc.plane_row_mut(2, global_by + iy)
[global_bx..global_bx + cov_x];
let y_dc_q_row = &y_dc_q_arr[iy];
for (ix, (quant_target, &dc_val)) in
quant_dc_row.iter_mut().zip(y_dc_q_row.iter()).enumerate()
{
let didx = iy * cov_x + ix;
let b_dc_q = (b_dc_post[didx] * inv_factor[2] - dc_val as f32 * cfl_factor_b)
.fast_round() as i16;
*quant_target = b_dc_q;
}
}
let inv_qm_b: &[f32] = match raw_strategy {
STRATEGY_DCT => &matrices.inv_matrix(2)[..],
STRATEGY_DCT16X16 => &matrices.inv_matrix_16x16(2)[..],
_ => &matrices.inv_matrix_16x8(2)[..],
};
quantize_block_ac(
&coeffs[2][..size],
2,
inv_qm_b,
quant_ac,
scale,
1.0,
cx,
cy,
&mut quantized[2][..size],
);
let strategy_code = dc_data.ac_strategy.strategy_code(global_bx, global_by);
let covered_blocks = cx * cy;
let log2_covered_blocks = match covered_blocks {
1 => 0,
2 => 1,
4 => 2,
_ => unreachable!("invalid covered_blocks {}", covered_blocks),
};
for &c in &[1usize, 0, 2] {
let full_block = &quantized[c][..size];
let order: &[u8] = match raw_strategy {
STRATEGY_DCT => &K_COEFF_ORDER_8X8[..],
STRATEGY_DCT16X16 => &K_COEFF_ORDER_16X16[..],
_ => &K_COEFF_ORDER_16X8[..],
};
for pass in 0..coeff_shifts.len() {
let mut pblock = [0i32; 256];
for k in 0..size {
let mut remaining = full_block[k];
let mut sent = 0i32;
for p in 0..=pass {
sent = remaining >> coeff_shifts[p];
remaining -= sent << coeff_shifts[p];
}
pblock[k] = sent;
}
let block = &pblock[..size];
let num_nzeros = &mut num_nzeros[pass];
let out = &mut out[pass];
let nzeros = if covered_blocks == 1 {
num_nonzero_except_dc(<&[i32; 64]>::try_from(block).unwrap())
} else {
num_nonzero_except_llf(block, cx, cy)
};
let shifted =
((nzeros as usize + covered_blocks - 1) >> log2_covered_blocks) as u8;
for iy in 0..cov_y {
let target_row =
&mut num_nzeros.plane_row_mut(c, nz_by + iy)[bx..bx + cov_x];
for target in target_row.iter_mut() {
*target = shifted;
}
}
let row_top: Option<&[u8]> = if nz_by == 0 {
None
} else {
Some(num_nzeros.plane_row(c, nz_by - 1))
};
let row = num_nzeros.plane_row(c, nz_by);
let predicted = predict_from_top_and_left(row_top, row, bx, 32);
let block_ctx = block_context(c, strategy_code);
let nzero_ctx = non_zero_context(predicted as u32, block_ctx);
let histo_offset = zero_density_contexts_offset(block_ctx);
write_token_into(Token::new(nzero_ctx, nzeros as u32), out);
let mut prev: usize = if nzeros as usize > size / 16 { 0 } else { 1 };
let mut remaining = nzeros;
let mut k = covered_blocks;
while k < size && remaining != 0 {
let coef = block[order[k] as usize];
let ctx = histo_offset as usize
+ if covered_blocks == 1 {
zero_density_context_8x8(remaining as usize, k, prev)
} else {
zero_density_context(
remaining as usize,
k,
covered_blocks,
log2_covered_blocks,
prev,
)
};
write_token_into(Token::new(ctx as u32, pack_signed(coef)), out);
prev = if coef != 0 { 1 } else { 0 };
if coef != 0 {
remaining -= 1;
}
k += 1;
}
debug_assert_eq!(
remaining, 0,
"remaining nzeros at end: strategy={} c={} pass={}",
strategy_code, c, pass
);
}
}
}
}
}
#[inline]
fn write_token_into(t: Token, out: &mut Vec<Token>) {
out.push(t);
}