use crate::dc_group_data::{
AcStrategyImage, STRATEGY_DCT, STRATEGY_DCT8X16, STRATEGY_DCT16X8, STRATEGY_DCT16X16,
};
use crate::dct::{dct8x8, dct8x16, dct16x8, dct16x16};
use crate::image::Image3F;
use crate::quant_weights::DequantMatrices;
fn estimate_entropy(
raw_strategy: u8,
opsin: &Image3F,
bx: usize,
by: usize,
cx: usize,
cy: usize,
distance: f32,
matrices: &DequantMatrices,
quant_per_block: &[u8], qf_stride: usize,
) -> f32 {
let cov_x = AcStrategyImage::covered_blocks_x_of(raw_strategy);
let cov_y = AcStrategyImage::covered_blocks_y_of(raw_strategy);
let num_blocks = cov_x * cov_y;
let size = num_blocks * 64;
let bx_pix = (bx + cx) * 8;
let by_pix = (by + cy) * 8;
if by_pix + 8 * cov_y > opsin.ysize() || bx_pix + 8 * cov_x > opsin.xsize() {
return f32::INFINITY;
}
let mut block = [0.0f32; 3 * 256];
for c in 0..3 {
let plane = opsin.plane(c);
match raw_strategy {
STRATEGY_DCT => {
let mut tmp = [0.0f32; 64];
for yy in 0..8 {
let row = plane.row(by_pix + yy);
tmp[yy * 8..yy * 8 + 8].copy_from_slice(&row[bx_pix..bx_pix + 8]);
}
let dst: &mut [f32; 64] = (&mut block[c * size..c * size + 64]).try_into().unwrap();
dct8x8(&tmp, dst);
}
STRATEGY_DCT16X8 => {
let mut tmp = [0.0f32; 128];
for yy in 0..16 {
let row = plane.row(by_pix + yy);
tmp[yy * 8..yy * 8 + 8].copy_from_slice(&row[bx_pix..bx_pix + 8]);
}
let dst: &mut [f32; 128] =
(&mut block[c * size..c * size + 128]).try_into().unwrap();
dct16x8(&tmp, dst);
}
STRATEGY_DCT8X16 => {
let mut tmp = [0.0f32; 128];
for yy in 0..8 {
let row = plane.row(by_pix + yy);
tmp[yy * 16..yy * 16 + 16].copy_from_slice(&row[bx_pix..bx_pix + 16]);
}
let dst: &mut [f32; 128] =
(&mut block[c * size..c * size + 128]).try_into().unwrap();
dct8x16(&tmp, dst);
}
STRATEGY_DCT16X16 => {
let mut tmp = [0.0f32; 256];
for yy in 0..16 {
let row = plane.row(by_pix + yy);
tmp[yy * 16..yy * 16 + 16].copy_from_slice(&row[bx_pix..bx_pix + 16]);
}
let dst: &mut [f32; 256] =
(&mut block[c * size..c * size + 256]).try_into().unwrap();
dct16x16(&tmp, dst);
}
_ => unreachable!(),
}
}
let mut max_quant: u8 = 1;
for iy in 0..cov_y {
for ix in 0..cov_x {
let q = quant_per_block[(cy + iy) * qf_stride + (cx + ix)];
if q > max_quant {
max_quant = q;
}
}
}
let quant = max_quant as f32;
let masking = 1.0f32;
const K_INFO_LOSS_MULTIPLIER: f32 = 138.0;
const K_INFO_LOSS_MULTIPLIER2: f32 = 50.468_4;
const K_COST_DELTA: f32 = 5.335_918_5;
const K_COST2: f32 = 4.462_815;
const K_ZEROS_MUL: f32 = 7.565_053;
let slope = (distance * (1.0 / 3.0)).min(1.0);
let cost1 = 1.0 + slope * 8.870_325;
let cmap_factors = [0.0f32, 0.0, 1.0];
let mut entropy = 0.0f32;
let mut info_loss = 0.0f32;
let mut info_loss2 = 0.0f32;
for c in 0..3 {
let inv_matrix: &[f32] = match raw_strategy {
STRATEGY_DCT => &matrices.inv_matrix(c)[..],
STRATEGY_DCT16X16 => &matrices.inv_matrix_16x16(c)[..],
_ => &matrices.inv_matrix_16x8(c)[..],
};
let cmap_factor = cmap_factors[c];
let mut entropy_acc = 0.0f32;
let mut nzeros = 0usize;
for i in 0..size {
let in_x = block[c * size + i];
let in_y = block[size + i]; let im = inv_matrix[i];
let val = (in_x - cmap_factor * in_y) * im * quant;
let rval = val.round();
let diff = (val - rval).abs();
info_loss += diff;
info_loss2 += diff * diff;
let q = rval.abs();
if q >= 1.5 {
entropy_acc += K_COST2;
}
entropy_acc += q.sqrt() * K_COST_DELTA;
if q > 0.0 {
nzeros += 1;
}
}
entropy_acc += nzeros as f32 * cost1;
entropy += entropy_acc;
let nbits = if nzeros + 1 > 1 {
(32 - (nzeros as u32 + 1).leading_zeros()) as usize
} else {
1
};
let nbits = nbits.max(1);
let log_nb = if nbits + 17 > 1 {
(32 - (nbits as u32 + 17).leading_zeros()) as usize
} else {
1
};
let log_nb = log_nb.max(1);
entropy += K_ZEROS_MUL * (log_nb as f32 + nbits as f32);
}
let infoloss_score = K_INFO_LOSS_MULTIPLIER * info_loss
+ K_INFO_LOSS_MULTIPLIER2 * (num_blocks as f32 * info_loss2).sqrt();
entropy + masking * infoloss_score
}
pub fn find_best_16x16_transform(
opsin: &Image3F,
bx0: usize,
by0: usize,
distance: f32,
matrices: &DequantMatrices,
quant_per_block: &[u8],
qf_stride: usize,
ac_strategy: &mut AcStrategyImage,
) {
let k8x8_base = 1.4;
let k8x8_mul1 = -0.55 * 0.75;
let k8x8_mul2 = 1.073_575_8 * 0.75;
let mul8x8 = k8x8_mul2 + k8x8_mul1 / (distance + k8x8_base);
let acs_bias = 1.0 + 0.5 * ((0.7 - distance) / 0.7).clamp(0.0, 1.0);
let k_mul16x8_tuning: f32 = 1.5 * acs_bias;
let k8x16_base = 1.6;
let k8x16_mul1 = -0.55 * k_mul16x8_tuning;
let k8x16_mul2 = 0.901_958_8 * k_mul16x8_tuning;
let mul16x8 = k8x16_mul2 + k8x16_mul1 / (distance + k8x16_base);
let k_mul16x16_tuning: f32 = 1.8 * acs_bias;
let k16x16_base = 1.6;
let k16x16_mul1 = -0.55 * k_mul16x16_tuning;
let k16x16_mul2 = 0.901_958_8 * k_mul16x16_tuning;
let mul16x16 = k16x16_mul2 + k16x16_mul1 / (distance + k16x16_base);
let mut qf_local = [0u8; 4];
for iy in 0..2 {
for ix in 0..2 {
let by = by0 + iy;
let bx = bx0 + ix;
qf_local[iy * 2 + ix] = quant_per_block[by * qf_stride + bx];
}
}
let mut entropy = [[0.0f32; 2]; 2];
for dy in 0..2 {
for dx in 0..2 {
let e = estimate_entropy(
STRATEGY_DCT,
opsin,
bx0,
by0,
dx,
dy,
distance,
matrices,
&qf_local,
2,
);
entropy[dy][dx] = mul8x8 * (3.0 + e);
}
}
let entropy_16x8_left = mul16x8
* estimate_entropy(
STRATEGY_DCT16X8,
opsin,
bx0,
by0,
0,
0,
distance,
matrices,
&qf_local,
2,
);
let entropy_16x8_right = mul16x8
* estimate_entropy(
STRATEGY_DCT16X8,
opsin,
bx0,
by0,
1,
0,
distance,
matrices,
&qf_local,
2,
);
let entropy_8x16_top = mul16x8
* estimate_entropy(
STRATEGY_DCT8X16,
opsin,
bx0,
by0,
0,
0,
distance,
matrices,
&qf_local,
2,
);
let entropy_8x16_bottom = mul16x8
* estimate_entropy(
STRATEGY_DCT8X16,
opsin,
bx0,
by0,
0,
1,
distance,
matrices,
&qf_local,
2,
);
let entropy_16x16 = mul16x16
* estimate_entropy(
STRATEGY_DCT16X16,
opsin,
bx0,
by0,
0,
0,
distance,
matrices,
&qf_local,
2,
);
let cost16x8 = entropy_16x8_left.min(entropy[0][0] + entropy[1][0])
+ entropy_16x8_right.min(entropy[0][1] + entropy[1][1]);
let cost8x16 = entropy_8x16_top.min(entropy[0][0] + entropy[0][1])
+ entropy_8x16_bottom.min(entropy[1][0] + entropy[1][1]);
let cost16x16 = entropy_16x16;
let total_dct8 = entropy[0][0] + entropy[0][1] + entropy[1][0] + entropy[1][1];
let best_rect = cost16x8.min(cost8x16);
if cost16x16 < best_rect
&& cost16x16 < total_dct8
&& ac_strategy.can_place_strategy(bx0, by0, STRATEGY_DCT16X16)
{
ac_strategy.set_first(bx0, by0, STRATEGY_DCT16X16);
} else if cost16x8 < cost8x16 {
if entropy_16x8_left < entropy[0][0] + entropy[1][0]
&& ac_strategy.can_place_strategy(bx0, by0, STRATEGY_DCT16X8)
{
ac_strategy.set_first(bx0, by0, STRATEGY_DCT16X8);
}
if entropy_16x8_right < entropy[0][1] + entropy[1][1]
&& ac_strategy.can_place_strategy(bx0 + 1, by0, STRATEGY_DCT16X8)
{
ac_strategy.set_first(bx0 + 1, by0, STRATEGY_DCT16X8);
}
} else {
if entropy_8x16_top < entropy[0][0] + entropy[0][1]
&& ac_strategy.can_place_strategy(bx0, by0, STRATEGY_DCT8X16)
{
ac_strategy.set_first(bx0, by0, STRATEGY_DCT8X16);
}
if entropy_8x16_bottom < entropy[1][0] + entropy[1][1]
&& ac_strategy.can_place_strategy(bx0, by0 + 1, STRATEGY_DCT8X16)
{
ac_strategy.set_first(bx0, by0 + 1, STRATEGY_DCT8X16);
}
}
}
pub fn adjust_quant_field(ac_strategy: &AcStrategyImage, quant_field: &mut crate::image::ImageB) {
let xsize = ac_strategy.xsize();
let ysize = ac_strategy.ysize();
for (x, y, raw_strategy) in ac_strategy.iter_first_blocks() {
let cov_x = AcStrategyImage::covered_blocks_x_of(raw_strategy);
let cov_y = AcStrategyImage::covered_blocks_y_of(raw_strategy);
if cov_x == 1 && cov_y == 1 {
continue;
}
let mut max_q: u8 = 0;
for iy in 0..cov_y {
for ix in 0..cov_x {
let q = quant_field.row(y + iy)[x + ix];
if q > max_q {
max_q = q;
}
}
}
for iy in 0..cov_y {
for ix in 0..cov_x {
quant_field.row_mut(y + iy)[x + ix] = max_q;
}
}
}
let _ = (xsize, ysize);
}
pub(crate) fn fill_ac_strategy(
opsin: &Image3F,
distance: f32,
matrices: &DequantMatrices,
quant_field: &mut crate::image::ImageB,
ac_strategy: &mut AcStrategyImage,
) {
let xsize = ac_strategy.xsize();
let ysize = ac_strategy.ysize();
let qf_stride = xsize;
let mut qf_flat = vec![0u8; xsize * ysize];
for y in 0..ysize {
let r = quant_field.row(y);
qf_flat[y * xsize..(y + 1) * xsize].copy_from_slice(&r[..xsize]);
}
let mut by = 0;
while by + 1 < ysize {
let mut bx = 0;
while bx + 1 < xsize {
find_best_16x16_transform(
opsin,
bx,
by,
distance,
matrices,
&qf_flat,
qf_stride,
ac_strategy,
);
bx += 2;
}
by += 2;
}
adjust_quant_field(ac_strategy, quant_field);
}