use crate::bit_writer::BitWriter;
use crate::encode_image::AlphaPlane;
use crate::entropy::{
OwnedEntropyCode, Token, optimize_entropy_code, pack_signed, write_entropy_code,
write_prefix_codes, write_token,
};
const TREE_CTX_SPLITVAL: u32 = 0;
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(crate) 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(crate) fn write_global_alpha_modular(
alpha: &AlphaPlane,
xsize: usize,
ysize: usize,
w: &mut BitWriter,
) {
write_lfglobal_alpha_section(alpha, xsize, ysize, w);
}
pub(crate) fn write_ac_group_alpha(
alpha: &AlphaPlane,
full_xsize: usize,
full_ysize: usize,
x0: usize,
y0: usize,
gw: usize,
gh: usize,
w: &mut BitWriter,
) {
if full_xsize <= GROUP_DIM && full_ysize <= GROUP_DIM {
return;
}
let mut tokens: Vec<Token> = Vec::with_capacity(gw * gh);
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 grad_raw = w_ + n_ - nw_;
let ctx = if grad_raw > 0 { 0u32 } else { 1u32 };
tokens.push(Token::new(ctx, pack_signed(v - pred)));
}
}
write_group_header_local_tree(w);
write_split_tree(w);
const NUM_CTX: usize = 2;
let lz_stream = lz77_compress_alpha(&tokens);
let plain_code = build_pixel_code_n(&tokens, NUM_CTX);
let plain_bits = estimate_plain_bits(&tokens, &plain_code);
let lz_num_ctx = NUM_CTX + 1; let lz_streams = [lz_stream];
let lz_code = crate::enc_lz77_ac::build_lz_code_no_cluster(&lz_streams, lz_num_ctx);
let lz_bits = crate::enc_lz77_ac::estimate_ac_lz_bits(&lz_streams, &lz_code, lz_num_ctx);
if lz_bits + 64 < plain_bits {
crate::enc_lz77_ac::write_ac_lz_header_and_code(&lz_code, w);
for t in &lz_streams[0] {
crate::enc_lz77_ac::write_ac_lz(*t, &lz_code, lz_num_ctx, w);
}
} else {
w.write(1, 0); write_entropy_code(&plain_code.as_ref(), w);
let code_ref = plain_code.as_ref();
for tok in &tokens {
write_token(*tok, &code_ref, w);
}
}
}
fn lz77_compress_alpha(tokens: &[Token]) -> Vec<crate::enc_lz77_ac::AcLz> {
use crate::enc_lz77_ac::{AcLz, LZ77_MIN_LENGTH};
let mut out: Vec<AcLz> = Vec::with_capacity(tokens.len());
let mut i = 0;
while i < tokens.len() {
let v = tokens[i].value;
out.push(AcLz::Lit {
context: tokens[i].context,
value: v,
});
let mut j = i + 1;
while j < tokens.len() && tokens[j].value == v {
j += 1;
}
let run_extra = (j - i - 1) as u32; if run_extra >= LZ77_MIN_LENGTH {
out.push(AcLz::Copy {
context: tokens[i + 1].context,
length_value: run_extra - LZ77_MIN_LENGTH,
});
i = j;
} else {
i += 1;
}
}
out
}
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 write_split_tree(w: &mut BitWriter) {
const PROP_GRAD: u32 = 9;
let tree_tokens = [
Token::new(TREE_CTX_PROPERTY, PROP_GRAD + 1),
Token::new(TREE_CTX_SPLITVAL, pack_signed(0)),
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),
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);
}
}
fn estimate_plain_bits(tokens: &[Token], code: &OwnedEntropyCode) -> u64 {
let code_ref = code.as_ref();
let mut bits: u64 = 0;
for t in tokens {
let (sym, nbits, _) = crate::entropy::uint_encode(t.value);
let cl = code_ref.context_map[t.context as usize] as usize;
let pc = &code_ref.prefix_codes[cl];
let d = if pc.single_symbol {
0
} else {
pc.depths[sym as usize] as u64
};
bits += d + nbits as u64;
}
bits
}
fn build_pixel_code(tokens: &[Token]) -> OwnedEntropyCode {
build_pixel_code_n(tokens, 1)
}
fn build_pixel_code_n(tokens: &[Token], num_contexts: usize) -> OwnedEntropyCode {
let mut code = if num_contexts > 1 {
crate::entropy::build_entropy_code_no_cluster(tokens, num_contexts)
} else {
optimize_entropy_code(tokens, num_contexts)
};
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;
}
}
pc.update_single_symbol();
}
code
}
#[inline]
pub(crate) 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);
}
}