use super::ac_group::ac_strategy_info;
use super::ac_strategy::AcStrategyMap;
use super::common::{DCT_BLOCK_SIZE, ceil_log2_nonzero, floor_log2_nonzero};
use crate::bit_writer::BitWriter;
use crate::entropy_coding::encode::{
build_entropy_code_ans_with_options, build_entropy_code_with_options, write_entropy_code_ans,
write_tokens, write_tokens_ans,
};
use crate::entropy_coding::token::Token;
use crate::error::Result;
pub const NUM_ORDER_BUCKETS: usize = 13;
pub const NUM_PERMUTATION_CONTEXTS: usize = 8;
pub const STRATEGY_TO_BUCKET: [u8; 27] = [
0, 1, 1, 1, 2, 3, 4, 4, 5, 5, 6, 6, 1, 1, 1, 1, 1, 1, 7, 8, 8, 9, 10, 10, 11, 12, 12,
];
#[inline]
pub fn strategy_bucket(raw_strategy: u8) -> u8 {
STRATEGY_TO_BUCKET[raw_strategy as usize]
}
pub fn natural_coeff_order(cx: usize, cy: usize) -> Vec<u32> {
let (cx, cy) = if cx >= cy { (cx, cy) } else { (cy, cx) };
let xsize = cx * 8;
let total = cx * cy * DCT_BLOCK_SIZE;
let xs = cx / cy;
let xsm = xs - 1;
let xss = if xs <= 1 {
0
} else {
ceil_log2_nonzero(xs) as usize
};
let mut out = vec![0u32; total];
let mut cur = cx * cy; for i in 0..xsize {
for j in 0..=i {
let (x, mut y) = if i % 2 != 0 { (i - j, j) } else { (j, i - j) };
if (y & xsm) != 0 {
continue;
}
y >>= xss;
let val = if x < cx && y < cy {
y * cx + x
} else {
let v = cur;
cur += 1;
v
};
if val < total {
out[val] = (y * xsize + x) as u32;
}
}
}
for ir in 1..xsize {
let ip = xsize - ir;
let i = ip - 1;
for j in 0..=i {
let (x, mut y) = if i % 2 != 0 {
(xsize - 1 - j, xsize - 1 - (i - j))
} else {
(xsize - 1 - (i - j), xsize - 1 - j)
};
if (y & xsm) != 0 {
continue;
}
y >>= xss;
if cur < total {
out[cur] = (y * xsize + x) as u32;
}
cur += 1;
}
}
out
}
pub fn natural_coeff_order_lut(cx: usize, cy: usize) -> Vec<u32> {
let order = natural_coeff_order(cx, cy);
let mut lut = vec![0u32; order.len()];
for (i, &pos) in order.iter().enumerate() {
lut[pos as usize] = i as u32;
}
lut
}
#[inline]
pub fn coeff_order_context(val: u32) -> usize {
if val == 0 {
0
} else {
((1 + floor_log2_nonzero(val)) as usize).min(NUM_PERMUTATION_CONTEXTS - 1)
}
}
pub fn compute_lehmer_code(permutation: &[u32]) -> Vec<u32> {
let n = permutation.len();
let mut code = vec![0u32; n];
let mut temp = vec![0u32; n + 1];
for idx in 0..n {
let s = permutation[idx];
let mut penalty = 0u32;
let mut i = s as usize + 1;
while i != 0 {
penalty += temp[i];
i &= i - 1; }
debug_assert!(s >= penalty, "Lehmer code: s={} < penalty={}", s, penalty);
code[idx] = s - penalty;
i = s as usize + 1;
while i < n + 1 {
temp[i] += 1;
i += i & i.wrapping_neg(); }
}
code
}
pub fn count_zero_coefficients(
quant_ac: &[Vec<Vec<[i32; DCT_BLOCK_SIZE]>>; 3],
ac_strategy: &AcStrategyMap,
xsize_blocks: usize,
ysize_blocks: usize,
) -> Vec<Vec<Vec<i64>>> {
let mut counts: Vec<Vec<Vec<i64>>> = (0..NUM_ORDER_BUCKETS)
.map(|_| vec![Vec::new(); 3])
.collect();
for ch in &mut counts[0] {
*ch = vec![0i64; 64];
}
for ch in &mut counts[2] {
*ch = vec![0i64; 256];
}
for ch in &mut counts[3] {
*ch = vec![0i64; 1024];
}
for ch in &mut counts[4] {
*ch = vec![0i64; 128];
}
for ch in &mut counts[5] {
*ch = vec![0i64; 256];
}
for ch in &mut counts[6] {
*ch = vec![0i64; 512];
}
for ch in &mut counts[7] {
*ch = vec![0i64; 4096];
}
for ch in &mut counts[8] {
*ch = vec![0i64; 2048];
}
for by in 0..ysize_blocks {
for bx in 0..xsize_blocks {
if !ac_strategy.is_first(bx, by) {
continue;
}
let raw_strategy = ac_strategy.raw_strategy(bx, by);
let strategy_code = super::ac_strategy::STRATEGY_CODE_LUT[raw_strategy as usize];
let bucket = strategy_bucket(strategy_code) as usize;
let (_, _, covered_blocks, _, _) = ac_strategy_info(raw_strategy);
let size = covered_blocks * DCT_BLOCK_SIZE;
for ch in &mut counts[bucket] {
if ch.len() < size {
ch.resize(size, 0);
}
}
for c in 0..3 {
let cnt = &mut counts[bucket][c];
if covered_blocks == 1 {
let block = &quant_ac[c][by][bx];
let cnt_slice = &mut cnt[..DCT_BLOCK_SIZE];
for k in 0..DCT_BLOCK_SIZE {
if block[k] == 0 {
cnt_slice[k] += 1;
}
}
} else {
let covered_x_local = super::ac_strategy::COVERED_X[raw_strategy as usize];
let covered_y_local = super::ac_strategy::COVERED_Y[raw_strategy as usize];
let mut base_idx = 0;
for slot_dy in 0..covered_y_local {
for slot_dx in 0..covered_x_local {
let block = &quant_ac[c][by + slot_dy][bx + slot_dx];
let cnt_slice = &mut cnt[base_idx..base_idx + DCT_BLOCK_SIZE];
for k in 0..DCT_BLOCK_SIZE {
if block[k] == 0 {
cnt_slice[k] += 1;
}
}
base_idx += DCT_BLOCK_SIZE;
}
}
}
}
}
}
for (bucket, bucket_counts) in counts.iter_mut().enumerate().take(NUM_ORDER_BUCKETS) {
let size = match bucket {
0 => 64, 2 => 256, 3 => 1024, 4 => 128, 6 => 512, 7 => 4096, 8 => 2048, _ => continue,
};
if bucket_counts[0].len() < size {
continue; }
let (cx, cy) = bucket_to_cx_cy(bucket);
let (layout_cx, layout_cy) = if cx >= cy { (cx, cy) } else { (cy, cx) };
for channel_counts in bucket_counts.iter_mut().take(3) {
for iy in 0..layout_cy {
for ix in 0..layout_cx {
channel_counts[iy * layout_cx * 8 + ix] = -1;
}
}
}
}
counts
}
pub fn compute_custom_orders(zero_counts: &[Vec<Vec<i64>>]) -> (Vec<Vec<Vec<u32>>>, u32) {
let mut orders: Vec<Vec<Vec<u32>>> = (0..NUM_ORDER_BUCKETS)
.map(|_| vec![Vec::new(); 3])
.collect();
let mut used_orders = 0u32;
for bucket in 0..NUM_ORDER_BUCKETS {
if bucket > 6 {
continue;
}
if zero_counts[bucket][0].is_empty() {
continue;
}
let size = zero_counts[bucket][0].len();
let inv_sqrt_sz = 1.0 / (size as f64).sqrt();
let (cx, cy) = bucket_to_cx_cy(bucket);
if cx == 0 {
continue;
}
let natural = natural_coeff_order(cx, cy);
let mut bucket_has_custom = false;
for c in 0..3 {
let counts = &zero_counts[bucket][c];
if counts.is_empty() {
continue;
}
let mut pos_and_count: Vec<(u32, u64)> = Vec::with_capacity(size);
for (i, &pos) in natural.iter().enumerate().take(size) {
let raw_count = counts[pos as usize];
let quantized = if raw_count < 0 {
0u64 } else {
(raw_count as f64 * inv_sqrt_sz + 0.1) as u64
};
pos_and_count.push((pos, (quantized << 16) | (i as u64)));
}
pos_and_count.sort_by_key(|&(_, key)| key);
let order: Vec<u32> = pos_and_count.iter().map(|&(pos, _)| pos).collect();
let is_different = order.iter().zip(natural.iter()).any(|(a, b)| a != b);
if is_different {
bucket_has_custom = true;
}
orders[bucket][c] = order;
}
if bucket_has_custom {
let (cx_n, cy_n) = if cx >= cy { (cx, cy) } else { (cy, cx) };
let llf = cx_n * cy_n;
let natural_lut = natural_coeff_order_lut(cx, cy);
let mut total_lehmer_cost = 0.0f64;
let mut total_savings_bits = 0.0f64;
for c in 0..3 {
let order = &orders[bucket][c];
let counts = &zero_counts[bucket][c];
if order.is_empty() || counts.len() < size {
continue;
}
let order_zigzag: Vec<u32> =
order.iter().map(|&pos| natural_lut[pos as usize]).collect();
let lehmer = compute_lehmer_code(&order_zigzag);
let end = lehmer[llf..size]
.iter()
.rposition(|&v| v != 0)
.map_or(llf, |p| llf + p + 1);
total_lehmer_cost += (size as f64).log2();
for &val in &lehmer[llf..end] {
if val == 0 {
total_lehmer_cost += 0.5;
} else {
total_lehmer_cost += 1.5 + (val as f64 + 1.0).log2();
}
}
let max_count = counts.iter().copied().max().unwrap_or(0);
if max_count <= 0 {
continue;
}
let zero_rates: Vec<f64> = counts
.iter()
.map(|&c| {
if c < 0 {
0.0
} else {
c as f64 / max_count as f64
}
})
.collect();
let nzeros_custom = expected_trailing_zeros(order, &zero_rates, llf, size);
let nzeros_natural = expected_trailing_zeros(&natural, &zero_rates, llf, size);
total_savings_bits += (nzeros_custom - nzeros_natural) * max_count as f64;
}
if total_savings_bits > total_lehmer_cost {
used_orders |= 1 << bucket;
}
}
}
(orders, used_orders)
}
fn expected_trailing_zeros(order: &[u32], zero_rates: &[f64], llf: usize, size: usize) -> f64 {
let mut expected = 0.0;
let mut prob_all_zero = 1.0;
for i in (llf..size).rev() {
let pos = order[i] as usize;
prob_all_zero *= zero_rates[pos];
expected += prob_all_zero;
if prob_all_zero < 1e-10 {
break; }
}
expected
}
fn bucket_to_cx_cy(bucket: usize) -> (usize, usize) {
match bucket {
0 => (1, 1), 2 => (2, 2), 3 => (4, 4), 4 => (2, 1), 5 => (4, 1), 6 => (4, 2), 7 => (8, 8), 8 => (8, 4), _ => (0, 0), }
}
pub fn tokenize_coeff_orders(orders: &[Vec<Vec<u32>>], used_orders: u32) -> Vec<Token> {
let mut tokens = Vec::new();
for (bucket, bucket_orders) in orders.iter().enumerate().take(NUM_ORDER_BUCKETS) {
if used_orders & (1 << bucket) == 0 {
continue;
}
let (cx, cy) = bucket_to_cx_cy(bucket);
if cx == 0 {
continue;
}
let natural_lut = natural_coeff_order_lut(cx, cy);
let llf = cx * cy; let (cx_norm, cy_norm) = if cx >= cy { (cx, cy) } else { (cy, cx) };
let llf_norm = cx_norm * cy_norm;
let size = llf_norm * DCT_BLOCK_SIZE;
for order in bucket_orders {
if order.is_empty() {
continue;
}
let order_zigzag: Vec<u32> =
order.iter().map(|&pos| natural_lut[pos as usize]).collect();
let lehmer = compute_lehmer_code(&order_zigzag);
let mut end = size;
while end > llf && lehmer[end - 1] == 0 {
end -= 1;
}
tokens.push(Token::new(
coeff_order_context(size as u32) as u32,
(end - llf) as u32,
));
let mut last = 0u32;
for &val in &lehmer[llf..end] {
tokens.push(Token::new(coeff_order_context(last) as u32, val));
last = val;
}
}
}
tokens
}
pub fn build_and_write_coeff_orders(
tokens: &[Token],
use_ans: bool,
writer: &mut BitWriter,
) -> Result<()> {
if tokens.is_empty() {
return Ok(());
}
writer.write(1, 0)?;
if use_ans {
let code = build_entropy_code_ans_with_options(
tokens,
NUM_PERMUTATION_CONTEXTS,
false, true, None, None, );
write_entropy_code_ans(&code, writer)?;
write_tokens_ans(tokens, &code, None, writer)?;
} else {
let code = build_entropy_code_with_options(tokens, NUM_PERMUTATION_CONTEXTS, false, None);
let ec = code.as_entropy_code();
crate::entropy_coding::encode::write_entropy_code(&ec, writer)?;
write_tokens(tokens, &ec, None, writer)?;
}
Ok(())
}
pub fn get_custom_order(
orders: &[Vec<Vec<u32>>],
used_orders: u32,
raw_strategy: u8,
channel: usize,
) -> Option<&[u32]> {
let bucket = strategy_bucket(raw_strategy) as usize;
if used_orders & (1 << bucket) == 0 {
return None;
}
let order = &orders[bucket][channel];
if order.is_empty() {
return None;
}
Some(order)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_natural_coeff_order_8x8() {
let order = natural_coeff_order(1, 1);
assert_eq!(order.len(), 64);
let expected: [u32; 64] = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37,
44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
];
assert_eq!(order, expected);
let existing = super::super::ac_group::COEFF_ORDER_8X8;
assert_eq!(order, &existing[..]);
}
#[test]
fn test_natural_coeff_order_8x16() {
let order = natural_coeff_order(2, 1);
assert_eq!(order.len(), 128);
let expected: [u32; 128] = [
0, 1, 16, 2, 3, 17, 32, 18, 4, 5, 19, 33, 48, 34, 20, 6, 7, 21, 35, 49, 64, 50, 36, 22,
8, 9, 23, 37, 51, 65, 80, 66, 52, 38, 24, 10, 11, 25, 39, 53, 67, 81, 96, 82, 68, 54,
40, 26, 12, 13, 27, 41, 55, 69, 83, 97, 112, 98, 84, 70, 56, 42, 28, 14, 15, 29, 43,
57, 71, 85, 99, 113, 114, 100, 86, 72, 58, 44, 30, 31, 45, 59, 73, 87, 101, 115, 116,
102, 88, 74, 60, 46, 47, 61, 75, 89, 103, 117, 118, 104, 90, 76, 62, 63, 77, 91, 105,
119, 120, 106, 92, 78, 79, 93, 107, 121, 122, 108, 94, 95, 109, 123, 124, 110, 111,
125, 126, 127,
];
assert_eq!(order, expected);
let existing = super::super::ac_group::COEFF_ORDER_8X16;
assert_eq!(order, &existing[..]);
}
#[test]
fn test_natural_coeff_order_16x16() {
let order = natural_coeff_order(2, 2);
assert_eq!(order.len(), 256);
let existing = super::super::ac_group::COEFF_ORDER_16X16;
assert_eq!(order, &existing[..]);
}
#[test]
fn test_natural_coeff_order_lut_inverse() {
for &(cx, cy) in &[(1, 1), (2, 1), (2, 2)] {
let order = natural_coeff_order(cx, cy);
let lut = natural_coeff_order_lut(cx, cy);
assert_eq!(order.len(), lut.len());
for (i, &pos) in order.iter().enumerate() {
assert_eq!(
lut[pos as usize], i as u32,
"LUT inverse failed at i={}, pos={} for {}x{}",
i, pos, cx, cy
);
}
}
}
#[test]
fn test_coeff_order_context() {
assert_eq!(coeff_order_context(0), 0);
assert_eq!(coeff_order_context(1), 1); assert_eq!(coeff_order_context(2), 2); assert_eq!(coeff_order_context(3), 2); assert_eq!(coeff_order_context(4), 3); assert_eq!(coeff_order_context(7), 3);
assert_eq!(coeff_order_context(8), 4);
assert_eq!(coeff_order_context(15), 4);
assert_eq!(coeff_order_context(16), 5);
assert_eq!(coeff_order_context(31), 5);
assert_eq!(coeff_order_context(32), 6);
assert_eq!(coeff_order_context(63), 6);
assert_eq!(coeff_order_context(64), 7);
assert_eq!(coeff_order_context(128), 7); assert_eq!(coeff_order_context(1000), 7);
}
#[test]
fn test_lehmer_code_identity() {
let perm: Vec<u32> = (0..5).collect();
let code = compute_lehmer_code(&perm);
assert_eq!(code, vec![0, 0, 0, 0, 0]);
}
#[test]
fn test_lehmer_code_reverse() {
let perm = vec![4u32, 3, 2, 1, 0];
let code = compute_lehmer_code(&perm);
assert_eq!(code, vec![4, 3, 2, 1, 0]);
}
#[test]
fn test_lehmer_code_known() {
let perm = vec![2u32, 4, 0, 1, 3];
let code = compute_lehmer_code(&perm);
assert_eq!(code, vec![2, 3, 0, 0, 0]);
}
#[test]
fn test_lehmer_code_jxl_rs_golden() {
let full_perm = vec![0u32, 1, 2, 3, 5, 6, 8, 10, 11, 15, 4, 9, 7, 12, 13, 14];
let code = compute_lehmer_code(&full_perm);
assert_eq!(code[0], 0);
assert_eq!(code[1], 0);
assert_eq!(code[2], 0);
assert_eq!(code[3], 0);
assert_eq!(&code[4..12], &[1, 1, 2, 3, 3, 6, 0, 1]);
}
#[test]
fn test_tokenize_natural_order_produces_minimal_tokens() {
let natural = natural_coeff_order(1, 1);
let mut orders = vec![vec![Vec::new(); 3]; NUM_ORDER_BUCKETS];
for order in &mut orders[0] {
*order = natural.clone();
}
let used_orders = 1u32;
let tokens = tokenize_coeff_orders(&orders, used_orders);
assert_eq!(tokens.len(), 3);
for t in &tokens {
assert_eq!(t.value, 0, "Natural order should produce end=0");
}
}
#[test]
fn test_strategy_bucket() {
assert_eq!(strategy_bucket(0), 0); assert_eq!(strategy_bucket(4), 2); assert_eq!(strategy_bucket(6), 4); assert_eq!(strategy_bucket(7), 4); }
#[test]
fn test_compute_custom_orders_all_zero_image() {
let xsize_blocks = 8;
let ysize_blocks = 8;
let quant_ac: [Vec<Vec<[i32; DCT_BLOCK_SIZE]>>; 3] = [
vec![vec![[0i32; DCT_BLOCK_SIZE]; xsize_blocks]; ysize_blocks],
vec![vec![[0i32; DCT_BLOCK_SIZE]; xsize_blocks]; ysize_blocks],
vec![vec![[0i32; DCT_BLOCK_SIZE]; xsize_blocks]; ysize_blocks],
];
let ac_strategy = AcStrategyMap::new_dct8(xsize_blocks, ysize_blocks);
let counts = count_zero_coefficients(&quant_ac, &ac_strategy, xsize_blocks, ysize_blocks);
let (_, used_orders) = compute_custom_orders(&counts);
assert_eq!(
used_orders, 0,
"All-zero image should not need custom orders"
);
}
}