use crate::bit_writer::BitWriter;
use crate::encode_image::AlphaPlane;
use crate::entropy::{
EntropyCode, OwnedEntropyCode, Token, optimize_entropy_code, pack_signed, write_entropy_code,
write_prefix_codes, write_token,
};
use crate::static_entropy_codes::{K_CONTEXT_TREE_TOKENS, K_DC_CONTEXT_MAP, K_DC_PREFIX_CODES};
const TREE_CTX_PROPERTY: u32 = 1;
const TREE_CTX_PREDICTOR: u32 = 2;
const TREE_CTX_OFFSET: u32 = 3;
const TREE_CTX_MULTIPLIER_LOG: u32 = 4;
const TREE_CTX_MULTIPLIER_BITS: u32 = 5;
const NUM_TREE_CONTEXTS: usize = 6;
const PREDICTOR_GRADIENT: u32 = 5;
const GROUP_DIM: usize = 256;
pub fn write_lfglobal_alpha_section(
alpha: &AlphaPlane,
xsize: usize,
ysize: usize,
w: &mut BitWriter,
) {
assert_eq!(alpha.len(), xsize * ysize);
if xsize <= GROUP_DIM && ysize <= GROUP_DIM {
write_group_header_local_tree(w);
let tokens = tokenize_channel(alpha, xsize, ysize, 0, 0, xsize, ysize, xsize);
let pixel_code = build_pixel_code(&tokens);
write_tree_and_pixel_histograms(&pixel_code, w);
let code_ref = pixel_code.as_ref();
for tok in &tokens {
write_token(*tok, &code_ref, w);
}
} else {
write_group_header_global_tree(w);
}
}
#[inline]
pub fn write_global_alpha_modular(
alpha: &AlphaPlane,
xsize: usize,
ysize: usize,
w: &mut BitWriter,
) {
write_lfglobal_alpha_section(alpha, xsize, ysize, w);
}
pub fn write_ac_group_alpha(
alpha: &AlphaPlane,
full_xsize: usize,
full_ysize: usize,
x0: usize,
y0: usize,
gw: usize,
gh: usize,
group_index: usize, num_lf_groups: usize,
_num_groups: usize,
w: &mut BitWriter,
) {
if full_xsize <= GROUP_DIM && full_ysize <= GROUP_DIM {
return;
}
assert_eq!(alpha.len(), full_xsize * full_ysize);
const NUM_QUANT_TABLES: usize = 17;
let stream_id = 1 + num_lf_groups * 3 + NUM_QUANT_TABLES + group_index;
write_group_header_global_tree(w);
let dc_code = EntropyCode::new(&K_DC_CONTEXT_MAP, &K_DC_PREFIX_CODES);
for gy in 0..gh {
let img_y = y0 + gy;
for gx in 0..gw {
let img_x = x0 + gx;
let v = alpha.get_i32(img_y * full_xsize + img_x);
let w_ = if gx > 0 {
alpha.get_i32(img_y * full_xsize + img_x - 1)
} else {
0
};
let n_ = if gy > 0 {
alpha.get_i32((img_y - 1) * full_xsize + img_x)
} else {
0
};
let nw_ = if gx > 0 && gy > 0 {
alpha.get_i32((img_y - 1) * full_xsize + img_x - 1)
} else {
0
};
let pred = gradient(w_, n_, nw_);
let residual = v - pred;
let context_id = alpha_context_id(gx, gy, w_, n_, nw_, n_ + w_ - nw_, stream_id);
let tok = Token::new(context_id as u32, pack_signed(residual));
write_token(tok, &dc_code, w);
}
}
}
fn write_group_header_local_tree(w: &mut BitWriter) {
w.write(1, 0); w.write(1, 1); w.write(2, 0); }
fn write_group_header_global_tree(w: &mut BitWriter) {
w.write(1, 1); w.write(1, 1); w.write(2, 0); }
fn write_tree_and_pixel_histograms(pixel_code: &OwnedEntropyCode, w: &mut BitWriter) {
let tree_tokens = [
Token::new(TREE_CTX_PROPERTY, 0),
Token::new(TREE_CTX_PREDICTOR, PREDICTOR_GRADIENT),
Token::new(TREE_CTX_OFFSET, pack_signed(0)),
Token::new(TREE_CTX_MULTIPLIER_LOG, 0),
Token::new(TREE_CTX_MULTIPLIER_BITS, 0),
];
let tree_code = optimize_entropy_code(&tree_tokens, NUM_TREE_CONTEXTS);
let tree_code_ref = tree_code.as_ref();
w.write(1, 0); write_entropy_code(&tree_code_ref, w);
for tok in &tree_tokens {
write_token(*tok, &tree_code_ref, w);
}
w.write(1, 0); write_prefix_codes(&pixel_code.prefix_codes, w);
}
fn build_pixel_code(tokens: &[Token]) -> OwnedEntropyCode {
let mut code = optimize_entropy_code(tokens, 1);
for pc in &mut code.prefix_codes {
let mut nonzero = 0;
let mut idx = 0;
for (i, &d) in pc.depths.iter().enumerate() {
if d != 0 {
nonzero += 1;
idx = i;
if nonzero > 1 {
break;
}
}
}
if nonzero == 1 {
if idx == 0 {
pc.depths[idx] = 0;
pc.bits[idx] = 0;
} else {
pc.depths[0] = 1;
pc.bits[0] = 0;
pc.depths[idx] = 1;
pc.bits[idx] = 1;
}
}
}
code
}
fn alpha_context_id(
x: usize,
y: usize,
w_px: i32,
n_px: i32,
_nw_px: i32,
grad_raw: i32,
group_id: usize,
) -> usize {
let props: [i32; 16] = [
0, group_id as i32, y as i32, x as i32, n_px.abs(), w_px.abs(), n_px, w_px, grad_raw, grad_raw.abs(), 0,
0,
0,
0,
0,
0, ];
static TREE: std::sync::OnceLock<Vec<TreeNode>> = std::sync::OnceLock::new();
let tree = TREE.get_or_init(build_global_tree);
traverse_tree(tree, &props)
}
#[derive(Clone)]
enum TreeNode {
Leaf {
id: usize,
},
Split {
property: usize,
splitval: i32,
left: usize,
right: usize,
},
}
fn unpack_signed(u: u32) -> i32 {
((u >> 1) ^ ((!u) & 1).wrapping_sub(1)) as i32
}
fn build_global_tree() -> Vec<TreeNode> {
let mut nodes: Vec<TreeNode> = Vec::with_capacity(89);
let mut to_decode = 1usize;
let mut idx = 0usize;
let mut leaf_id = 0usize;
while to_decode > 0 {
to_decode -= 1;
let (_, val) = K_CONTEXT_TREE_TOKENS[idx];
idx += 1;
if val == 0 {
idx += 4;
nodes.push(TreeNode::Leaf { id: leaf_id });
leaf_id += 1;
} else {
let property = (val - 1) as usize;
let (_, sv) = K_CONTEXT_TREE_TOKENS[idx];
idx += 1;
let splitval = unpack_signed(sv);
let left = nodes.len() + to_decode + 1;
let right = nodes.len() + to_decode + 2;
nodes.push(TreeNode::Split {
property,
splitval,
left,
right,
});
to_decode += 2;
}
}
nodes
}
fn traverse_tree(tree: &[TreeNode], props: &[i32]) -> usize {
let mut idx = 0;
loop {
match &tree[idx] {
TreeNode::Leaf { id } => return *id,
TreeNode::Split {
property,
splitval,
left,
right,
} => {
let v = props.get(*property).copied().unwrap_or(0);
idx = if v > *splitval { *left } else { *right };
}
}
}
}
#[inline]
pub fn gradient(w: i32, n: i32, nw: i32) -> i32 {
let lo = w.min(n);
let hi = w.max(n);
(w + n - nw).clamp(lo, hi)
}
fn tokenize_channel(
alpha: &AlphaPlane,
_full_xsize: usize,
_full_ysize: usize,
x0: usize,
y0: usize,
gw: usize,
gh: usize,
stride: usize,
) -> Vec<Token> {
let _ = _full_ysize;
let mut tokens = Vec::with_capacity(gw * gh);
for y in y0..y0 + gh {
for x in x0..x0 + gw {
let v = alpha.get_i32(y * stride + x);
let w_ = if x > 0 {
alpha.get_i32(y * stride + x - 1)
} else {
0
};
let n_ = if y > 0 {
alpha.get_i32((y - 1) * stride + x)
} else {
0
};
let nw_ = if x > 0 && y > 0 {
alpha.get_i32((y - 1) * stride + x - 1)
} else {
0
};
let pred = gradient(w_, n_, nw_);
tokens.push(Token::new(0, pack_signed(v - pred)));
}
}
tokens
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encode_image::AlphaPlane;
#[test]
fn gradient_basic() {
assert_eq!(gradient(50, 50, 0), 50);
assert_eq!(gradient(50, 50, 100), 50);
assert_eq!(gradient(10, 20, 5), 20);
assert_eq!(gradient(10, 20, 25), 10);
assert_eq!(gradient(10, 20, 15), 15);
}
#[test]
fn tokenize_constant_zero() {
let chan = vec![0u8; 16 * 16];
let alpha = AlphaPlane::from_u8(chan);
let toks = tokenize_channel(&alpha, 16, 16, 0, 0, 16, 16, 16);
for t in &toks {
assert_eq!(t.value, 0);
}
}
#[test]
fn write_alpha_small_emits_bytes() {
let mut w = BitWriter::new();
let chan = vec![128u8; 8 * 8];
let alpha = AlphaPlane::from_u8(chan);
write_global_alpha_modular(&alpha, 8, 8, &mut w);
let bits = w.bits_written();
w.zero_pad_to_byte();
assert!(w.into_bytes().len() > 0);
assert!(bits > 0);
}
#[test]
fn write_alpha_large_emits_header_only() {
let mut w = BitWriter::new();
let chan = vec![200u8; 512 * 400];
let alpha = AlphaPlane::from_u8(chan);
write_lfglobal_alpha_section(&alpha, 512, 400, &mut w);
assert_eq!(w.bits_written(), 4);
}
#[test]
fn global_tree_all_gradient_for_alpha() {
for group_id in 0..30 {
for abs_n in [0i32, 50, 128, 200] {
for abs_w in [0i32, 50, 128, 200] {
for grad in [-200i32, 0, 200] {
let cid = alpha_context_id(1, 1, abs_w, abs_n, 0, grad, group_id);
assert!(cid < 45, "context_id={cid} out of range");
}
}
}
}
}
}