use oxideav_core::{Error, Result};
use super::VP8L_SIGNATURE;
const MAX_CODE_LENGTH: u8 = 15;
const LZ_WINDOW: usize = 4096;
const MIN_MATCH: usize = 3;
const MAX_MATCH: usize = 4096;
const COLOR_CACHE_BITS: u32 = 8;
const PREDICTOR_TILE_BITS: u32 = 4;
const COLOR_TRANSFORM_TILE_BITS: u32 = 5;
const COLOR_COEFF_GRID: [i8; 16] = [
-24, -21, -18, -15, -12, -9, -6, -3, 0, 3, 6, 9, 12, 15, 18, 21,
];
const COLOR_R2B_GRID: [i8; 5] = [-12, -6, 0, 6, 12];
const PREDICTOR_MODES: &[u32] = &[0, 1, 2, 11];
struct BitWriter {
out: Vec<u8>,
cur: u64,
nbits: u32,
}
impl BitWriter {
fn new() -> Self {
Self {
out: Vec::new(),
cur: 0,
nbits: 0,
}
}
fn write(&mut self, value: u32, n: u32) {
debug_assert!(n <= 32);
let mask = if n == 0 {
0u64
} else if n == 32 {
0xFFFF_FFFFu64
} else {
(1u64 << n) - 1
};
self.cur |= ((value as u64) & mask) << self.nbits;
self.nbits += n;
while self.nbits >= 8 {
self.out.push((self.cur & 0xff) as u8);
self.cur >>= 8;
self.nbits -= 8;
}
}
fn finish(mut self) -> Vec<u8> {
if self.nbits > 0 {
self.out.push((self.cur & 0xff) as u8);
}
self.out
}
}
pub fn encode_vp8l_argb(
width: u32,
height: u32,
pixels: &[u32],
has_alpha: bool,
) -> Result<Vec<u8>> {
encode_vp8l_argb_rdo(width, height, pixels, has_alpha)
}
fn strip_transparent_rgb(pixels: &mut [u32]) {
for p in pixels.iter_mut() {
if (*p >> 24) & 0xff == 0 {
*p &= 0xff00_0000;
}
}
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct EncoderOptions {
pub use_subtract_green: bool,
pub use_color_transform: bool,
pub use_predictor: bool,
pub use_color_cache: bool,
pub cache_bits: u32,
pub strip_transparent_color: bool,
}
impl Default for EncoderOptions {
fn default() -> Self {
Self {
use_subtract_green: true,
use_color_transform: true,
use_predictor: true,
use_color_cache: true,
cache_bits: COLOR_CACHE_BITS,
strip_transparent_color: true,
}
}
}
impl EncoderOptions {
#[doc(hidden)]
pub fn bare() -> Self {
Self {
use_subtract_green: false,
use_color_transform: false,
use_predictor: false,
use_color_cache: false,
cache_bits: COLOR_CACHE_BITS,
strip_transparent_color: true,
}
}
#[doc(hidden)]
pub fn subtract_green_only() -> Self {
Self {
use_subtract_green: true,
use_color_transform: false,
use_predictor: false,
use_color_cache: false,
cache_bits: COLOR_CACHE_BITS,
strip_transparent_color: true,
}
}
}
#[doc(hidden)]
pub fn encode_vp8l_argb_with(
width: u32,
height: u32,
pixels: &[u32],
has_alpha: bool,
opts: EncoderOptions,
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(Error::invalid("VP8L encoder: zero-size image"));
}
if width > 16384 || height > 16384 {
return Err(Error::invalid("VP8L encoder: max dimension 16384"));
}
if (pixels.len() as u64) != (width as u64) * (height as u64) {
return Err(Error::invalid("VP8L encoder: pixel count mismatch"));
}
let mut bw = BitWriter::new();
bw.write(VP8L_SIGNATURE as u32, 8);
bw.write(width - 1, 14);
bw.write(height - 1, 14);
bw.write(if has_alpha { 1 } else { 0 }, 1);
bw.write(0, 3);
let mut working = pixels.to_vec();
if opts.strip_transparent_color {
strip_transparent_rgb(&mut working);
}
if opts.use_subtract_green {
bw.write(1, 1);
bw.write(2, 2);
apply_subtract_green_forward(&mut working);
}
if opts.use_color_transform {
let tile_bits = COLOR_TRANSFORM_TILE_BITS;
let tile_side = 1u32 << tile_bits;
let sub_w = (width + tile_side - 1) / tile_side;
let sub_h = (height + tile_side - 1) / tile_side;
let coeffs = choose_color_transform(&working, width, height, tile_bits, sub_w, sub_h);
bw.write(1, 1);
bw.write(1, 2);
bw.write(tile_bits - 2, 3);
let sub_pixels: Vec<u32> = coeffs
.iter()
.map(|c| {
let g2r = (c.g2r as u8) as u32;
let g2b = (c.g2b as u8) as u32;
let r2b = (c.r2b as u8) as u32;
0xff00_0000 | (r2b << 16) | (g2b << 8) | g2r
})
.collect();
encode_image_stream(&mut bw, &sub_pixels, sub_w, sub_h, false, 0)?;
working = apply_color_transform_forward(&working, width, height, tile_bits, &coeffs, sub_w);
}
if opts.use_predictor {
let tile_bits = PREDICTOR_TILE_BITS;
let tile_side = 1u32 << tile_bits;
let sub_w = (width + tile_side - 1) / tile_side;
let sub_h = (height + tile_side - 1) / tile_side;
let modes = choose_predictor_modes(&working, width, height, tile_bits, sub_w, sub_h);
bw.write(1, 1);
bw.write(0, 2);
bw.write(tile_bits - 2, 3);
let sub_pixels: Vec<u32> = modes
.iter()
.map(|&m| 0xff00_0000 | ((m & 0xff) << 8))
.collect();
encode_image_stream(&mut bw, &sub_pixels, sub_w, sub_h, false, 0)?;
working = apply_predictor_forward(&working, width, height, tile_bits, &modes, sub_w);
}
bw.write(0, 1);
let cache_bits = if opts.use_color_cache {
if (1..=11).contains(&opts.cache_bits) {
opts.cache_bits
} else {
COLOR_CACHE_BITS
}
} else {
0
};
encode_image_stream(&mut bw, &working, width, height, true, cache_bits)?;
Ok(bw.finish())
}
fn encode_vp8l_argb_rdo(
width: u32,
height: u32,
pixels: &[u32],
has_alpha: bool,
) -> Result<Vec<u8>> {
const CACHE_BITS_GRID: [u32; 4] = [0, 6, 8, 10];
let mut stripped: Vec<u32> = pixels.to_vec();
strip_transparent_rgb(&mut stripped);
let mut best: Option<Vec<u8>> = None;
for &use_sg in &[true, false] {
for &use_ct in &[true, false] {
for &use_pr in &[true, false] {
for &cb in CACHE_BITS_GRID.iter() {
let opts = EncoderOptions {
use_subtract_green: use_sg,
use_color_transform: use_ct,
use_predictor: use_pr,
use_color_cache: cb > 0,
cache_bits: if cb > 0 { cb } else { COLOR_CACHE_BITS },
strip_transparent_color: false,
};
let bytes = encode_vp8l_argb_with(width, height, &stripped, has_alpha, opts)?;
if best.as_ref().map(|b| bytes.len() < b.len()).unwrap_or(true) {
best = Some(bytes);
}
}
}
}
}
Ok(best.expect("RDO produced at least one candidate"))
}
fn encode_image_stream(
bw: &mut BitWriter,
pixels: &[u32],
width: u32,
height: u32,
main_image: bool,
cache_bits: u32,
) -> Result<()> {
if cache_bits > 0 {
bw.write(1, 1);
bw.write(cache_bits, 4);
} else {
bw.write(0, 1);
}
if main_image {
bw.write(0, 1);
}
let cache_size = if cache_bits == 0 {
0u32
} else {
1u32 << cache_bits
};
let stream = build_symbol_stream(pixels, width, height, cache_bits);
let green_alpha = 256 + 24 + cache_size as usize;
let mut green_freq = vec![0u32; green_alpha];
let mut red_freq = vec![0u32; 256];
let mut blue_freq = vec![0u32; 256];
let mut alpha_freq = vec![0u32; 256];
let mut dist_freq = vec![0u32; 40];
for sym in &stream {
match *sym {
StreamSym::Literal { a, r, g, b } => {
green_freq[g as usize] += 1;
red_freq[r as usize] += 1;
blue_freq[b as usize] += 1;
alpha_freq[a as usize] += 1;
}
StreamSym::Backref {
len_sym, dist_sym, ..
} => {
green_freq[256 + len_sym as usize] += 1;
dist_freq[dist_sym as usize] += 1;
}
StreamSym::CacheRef { index } => {
green_freq[256 + 24 + index as usize] += 1;
}
}
}
let green_lens = build_limited_lengths(&green_freq, MAX_CODE_LENGTH)?;
let red_lens = build_limited_lengths(&red_freq, MAX_CODE_LENGTH)?;
let blue_lens = build_limited_lengths(&blue_freq, MAX_CODE_LENGTH)?;
let alpha_lens = build_limited_lengths(&alpha_freq, MAX_CODE_LENGTH)?;
let dist_lens = build_limited_lengths(&dist_freq, MAX_CODE_LENGTH)?;
let green_codes = canonical_codes(&green_lens);
let red_codes = canonical_codes(&red_lens);
let blue_codes = canonical_codes(&blue_lens);
let alpha_codes = canonical_codes(&alpha_lens);
let dist_codes = canonical_codes(&dist_lens);
emit_huffman_tree(bw, &green_lens)?;
emit_huffman_tree(bw, &red_lens)?;
emit_huffman_tree(bw, &blue_lens)?;
emit_huffman_tree(bw, &alpha_lens)?;
emit_huffman_tree(bw, &dist_lens)?;
for sym in &stream {
match *sym {
StreamSym::Literal { a, r, g, b } => {
write_code(bw, &green_codes, &green_lens, g as usize);
write_code(bw, &red_codes, &red_lens, r as usize);
write_code(bw, &blue_codes, &blue_lens, b as usize);
write_code(bw, &alpha_codes, &alpha_lens, a as usize);
}
StreamSym::Backref {
len_sym,
len_extra_bits,
len_extra,
dist_sym,
dist_extra_bits,
dist_extra,
} => {
write_code(bw, &green_codes, &green_lens, 256 + len_sym as usize);
if len_extra_bits > 0 {
bw.write(len_extra, len_extra_bits);
}
write_code(bw, &dist_codes, &dist_lens, dist_sym as usize);
if dist_extra_bits > 0 {
bw.write(dist_extra, dist_extra_bits);
}
}
StreamSym::CacheRef { index } => {
write_code(bw, &green_codes, &green_lens, 256 + 24 + index as usize);
}
}
}
Ok(())
}
#[derive(Clone, Copy)]
enum StreamSym {
Literal {
a: u8,
r: u8,
g: u8,
b: u8,
},
Backref {
len_sym: u32,
len_extra_bits: u32,
len_extra: u32,
dist_sym: u32,
dist_extra_bits: u32,
dist_extra: u32,
},
CacheRef {
index: u32,
},
}
fn encode_len_or_dist_value(value: u32) -> (u32, u32, u32) {
debug_assert!(value >= 1);
if value <= 4 {
return (value - 1, 0, 0);
}
let v = value - 1; let msb = 31 - v.leading_zeros(); let extra_bits = msb - 1;
let sym_sub = (v >> extra_bits) & 1; let symbol = 2 * extra_bits + 2 + sym_sub;
let offset = (2 + sym_sub) << extra_bits;
let extra = v - offset;
(symbol, extra_bits, extra)
}
fn build_symbol_stream(
pixels: &[u32],
_width: u32,
_height: u32,
cache_bits: u32,
) -> Vec<StreamSym> {
let mut out: Vec<StreamSym> = Vec::with_capacity(pixels.len());
let n = pixels.len();
const HASH_BITS: u32 = 12;
const HASH_SIZE: usize = 1 << HASH_BITS;
let mut head: Vec<usize> = vec![usize::MAX; HASH_SIZE];
let mut next: Vec<usize> = vec![usize::MAX; n];
let hash3 = |p0: u32, p1: u32, p2: u32| -> usize {
let k = p0
.wrapping_mul(0x9E3779B9)
.wrapping_add(p1.wrapping_mul(0x85EBCA77))
.wrapping_add(p2.wrapping_mul(0xC2B2AE3D));
(k >> (32 - HASH_BITS)) as usize
};
let cache_size = if cache_bits == 0 {
0usize
} else {
1usize << cache_bits
};
let mut cache: Vec<u32> = vec![0u32; cache_size];
let mut i = 0usize;
while i < n {
let mut best_len = 0usize;
let mut best_dist = 0usize;
if i + MIN_MATCH <= n {
let h = hash3(pixels[i], pixels[i + 1], pixels[i + 2]);
let mut candidate = head[h];
let mut tries = 64usize;
while candidate != usize::MAX && tries > 0 {
let dist = i - candidate;
if dist == 0 || dist > LZ_WINDOW {
break;
}
let max_len = (n - i).min(MAX_MATCH);
let mut l = 0usize;
while l < max_len && pixels[candidate + l] == pixels[i + l] {
l += 1;
}
if l >= MIN_MATCH && l > best_len {
best_len = l;
best_dist = dist;
if l >= 64 {
break;
}
}
candidate = next[candidate];
tries -= 1;
}
}
if best_len >= MIN_MATCH {
let (len_sym, len_eb, len_ex) = encode_len_or_dist_value(best_len as u32);
let (dist_sym, dist_eb, dist_ex) = encode_len_or_dist_value((best_dist as u32) + 120);
out.push(StreamSym::Backref {
len_sym,
len_extra_bits: len_eb,
len_extra: len_ex,
dist_sym,
dist_extra_bits: dist_eb,
dist_extra: dist_ex,
});
for k in 0..best_len {
let pos = i + k;
if pos + 2 < n {
let h = hash3(pixels[pos], pixels[pos + 1], pixels[pos + 2]);
next[pos] = head[h];
head[h] = pos;
}
if cache_size > 0 {
cache_add(&mut cache, cache_bits, pixels[pos]);
}
}
i += best_len;
} else {
let p = pixels[i];
let mut emitted_cache = false;
if cache_size > 0 {
let idx = (0x1e35_a7bd_u32.wrapping_mul(p) >> (32 - cache_bits)) as usize;
if idx < cache.len() && cache[idx] == p {
out.push(StreamSym::CacheRef { index: idx as u32 });
emitted_cache = true;
}
}
if !emitted_cache {
out.push(StreamSym::Literal {
a: ((p >> 24) & 0xff) as u8,
r: ((p >> 16) & 0xff) as u8,
g: ((p >> 8) & 0xff) as u8,
b: (p & 0xff) as u8,
});
}
if cache_size > 0 {
cache_add(&mut cache, cache_bits, p);
}
if i + 2 < n {
let h = hash3(pixels[i], pixels[i + 1], pixels[i + 2]);
next[i] = head[h];
head[h] = i;
}
i += 1;
}
}
out
}
fn cache_add(cache: &mut [u32], cache_bits: u32, argb: u32) {
if cache.is_empty() {
return;
}
let idx = (0x1e35_a7bd_u32.wrapping_mul(argb) >> (32 - cache_bits)) as usize;
if idx < cache.len() {
cache[idx] = argb;
}
}
fn apply_subtract_green_forward(pixels: &mut [u32]) {
for p in pixels.iter_mut() {
let a = (*p >> 24) & 0xff;
let r = (*p >> 16) & 0xff;
let g = (*p >> 8) & 0xff;
let b = *p & 0xff;
let nr = r.wrapping_sub(g) & 0xff;
let nb = b.wrapping_sub(g) & 0xff;
*p = (a << 24) | (nr << 16) | (g << 8) | nb;
}
}
fn predict_argb(out: &[u32], w: usize, x: usize, y: usize, mode: u32) -> u32 {
let l = out[y * w + x - 1];
let t = out[(y - 1) * w + x];
let tl = out[(y - 1) * w + x - 1];
let tr = if x + 1 < w {
out[(y - 1) * w + x + 1]
} else {
out[y * w]
};
match mode {
0 => 0xff00_0000,
1 => l,
2 => t,
3 => tr,
4 => tl,
5 => avg3(l, tr, t),
6 => avg2(l, tl),
7 => avg2(l, t),
8 => avg2(tl, t),
9 => avg2(t, tr),
10 => avg2(avg2(l, tl), avg2(t, tr)),
11 => select_argb(l, t, tl),
12 => clamp_add_sub_argb(l, t, tl),
13 => clamp_add_sub_half_argb(avg2(l, t), tl),
_ => 0xff00_0000,
}
}
fn avg2(a: u32, b: u32) -> u32 {
let mut out = 0u32;
for c in 0..4 {
let sh = c * 8;
let av = (a >> sh) & 0xff;
let bv = (b >> sh) & 0xff;
out |= ((av + bv) >> 1) << sh;
}
out
}
fn avg3(a: u32, b: u32, c: u32) -> u32 {
avg2(a, avg2(b, c))
}
fn select_argb(l: u32, t: u32, tl: u32) -> u32 {
let mut out = 0u32;
let mut dl = 0i32;
let mut dt = 0i32;
for c in 0..4 {
let sh = c * 8;
let lv = ((l >> sh) & 0xff) as i32;
let tv = ((t >> sh) & 0xff) as i32;
let tlv = ((tl >> sh) & 0xff) as i32;
dl += (tv - tlv).abs();
dt += (lv - tlv).abs();
}
for c in 0..4 {
let sh = c * 8;
let lv = (l >> sh) & 0xff;
let tv = (t >> sh) & 0xff;
let v = if dl < dt { lv } else { tv };
out |= v << sh;
}
out
}
fn clamp_add_sub_argb(l: u32, t: u32, tl: u32) -> u32 {
let mut out = 0u32;
for c in 0..4 {
let sh = c * 8;
let lv = ((l >> sh) & 0xff) as i32;
let tv = ((t >> sh) & 0xff) as i32;
let tlv = ((tl >> sh) & 0xff) as i32;
let v = (lv + tv - tlv).clamp(0, 255) as u32;
out |= v << sh;
}
out
}
fn clamp_add_sub_half_argb(a: u32, b: u32) -> u32 {
let mut out = 0u32;
for c in 0..4 {
let sh = c * 8;
let av = ((a >> sh) & 0xff) as i32;
let bv = ((b >> sh) & 0xff) as i32;
let v = (av + (av - bv) / 2).clamp(0, 255) as u32;
out |= v << sh;
}
out
}
fn sub_argb(a: u32, b: u32) -> u32 {
let aa = (a >> 24) & 0xff;
let ar = (a >> 16) & 0xff;
let ag = (a >> 8) & 0xff;
let ab = a & 0xff;
let ba = (b >> 24) & 0xff;
let br = (b >> 16) & 0xff;
let bg = (b >> 8) & 0xff;
let bb = b & 0xff;
(((aa.wrapping_sub(ba)) & 0xff) << 24)
| (((ar.wrapping_sub(br)) & 0xff) << 16)
| (((ag.wrapping_sub(bg)) & 0xff) << 8)
| ((ab.wrapping_sub(bb)) & 0xff)
}
fn score_predictor_on_tile(
originals: &[u32],
decoded: &[u32],
width: usize,
tile_x0: usize,
tile_y0: usize,
tile_x1: usize,
tile_y1: usize,
mode: u32,
) -> u64 {
let mut cost = 0u64;
for y in tile_y0..tile_y1 {
for x in tile_x0..tile_x1 {
let idx = y * width + x;
let pred = if x == 0 && y == 0 {
0xff00_0000
} else if y == 0 {
decoded[idx - 1]
} else if x == 0 {
decoded[idx - width]
} else {
predict_argb(decoded, width, x, y, mode)
};
let p = originals[idx];
for c in 0..4 {
let sh = c * 8;
let pv = ((p >> sh) & 0xff) as i32;
let prv = ((pred >> sh) & 0xff) as i32;
let d = (pv - prv).unsigned_abs() as u64;
cost += d.min(256 - d);
}
}
}
cost
}
fn choose_predictor_modes(
pixels: &[u32],
width: u32,
height: u32,
tile_bits: u32,
sub_w: u32,
sub_h: u32,
) -> Vec<u32> {
let tile_side = 1usize << tile_bits;
let w = width as usize;
let h = height as usize;
let mut modes = Vec::with_capacity((sub_w * sub_h) as usize);
for ty in 0..sub_h as usize {
for tx in 0..sub_w as usize {
let x0 = tx * tile_side;
let y0 = ty * tile_side;
let x1 = (x0 + tile_side).min(w);
let y1 = (y0 + tile_side).min(h);
let mut best = PREDICTOR_MODES[0];
let mut best_cost = u64::MAX;
for &m in PREDICTOR_MODES {
let c = score_predictor_on_tile(pixels, pixels, w, x0, y0, x1, y1, m);
if c < best_cost {
best_cost = c;
best = m;
}
}
modes.push(best);
}
}
modes
}
#[derive(Clone, Copy, Default)]
struct ColorCoeffs {
g2r: i8,
g2b: i8,
r2b: i8,
}
fn residual_cost(r: i32, g: i32, b: i32, c: ColorCoeffs) -> u64 {
let nr = r - (((c.g2r as i32) * sign_extend_i8(g)) >> 5);
let nb1 = b - (((c.g2b as i32) * sign_extend_i8(g)) >> 5);
let nb2 = nb1 - (((c.r2b as i32) * sign_extend_i8(r)) >> 5);
let rr = nr.rem_euclid(256) as u64;
let bb = nb2.rem_euclid(256) as u64;
rr.min(256 - rr) + bb.min(256 - bb)
}
fn sign_extend_i8(v: i32) -> i32 {
((v & 0xff) as i8) as i32
}
fn choose_color_transform(
pixels: &[u32],
width: u32,
height: u32,
tile_bits: u32,
sub_w: u32,
sub_h: u32,
) -> Vec<ColorCoeffs> {
let tile_side = 1usize << tile_bits;
let w = width as usize;
let h = height as usize;
let mut out = Vec::with_capacity((sub_w * sub_h) as usize);
for ty in 0..sub_h as usize {
for tx in 0..sub_w as usize {
let x0 = tx * tile_side;
let y0 = ty * tile_side;
let x1 = (x0 + tile_side).min(w);
let y1 = (y0 + tile_side).min(h);
let mut tile: Vec<(i32, i32, i32)> = Vec::with_capacity((x1 - x0) * (y1 - y0));
for y in y0..y1 {
for x in x0..x1 {
let p = pixels[y * w + x];
let r = ((p >> 16) & 0xff) as i32;
let g = ((p >> 8) & 0xff) as i32;
let b = (p & 0xff) as i32;
tile.push((r, g, b));
}
}
let baseline_score = tile.iter().fold(0u64, |acc, &(r, _g, b)| {
let rr = r.rem_euclid(256) as u64;
let bb = b.rem_euclid(256) as u64;
acc + rr.min(256 - rr) + bb.min(256 - bb)
});
let mut best = ColorCoeffs::default();
let mut best_cost = baseline_score;
for &g2r in COLOR_COEFF_GRID.iter() {
for &g2b in COLOR_COEFF_GRID.iter() {
let c = ColorCoeffs { g2r, g2b, r2b: 0 };
let mut total = 0u64;
for &(r, g, b) in &tile {
total += residual_cost(r, g, b, c);
if total >= best_cost {
break;
}
}
if total < best_cost {
best_cost = total;
best = c;
}
}
}
for &r2b in COLOR_R2B_GRID.iter() {
let c = ColorCoeffs {
g2r: best.g2r,
g2b: best.g2b,
r2b,
};
let mut total = 0u64;
for &(r, g, b) in &tile {
total += residual_cost(r, g, b, c);
if total >= best_cost {
break;
}
}
if total < best_cost {
best_cost = total;
best = c;
}
}
out.push(best);
}
}
out
}
fn apply_color_transform_forward(
pixels: &[u32],
width: u32,
height: u32,
tile_bits: u32,
coeffs: &[ColorCoeffs],
sub_w: u32,
) -> Vec<u32> {
let w = width as usize;
let h = height as usize;
let mut out = vec![0u32; w * h];
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let p = pixels[idx];
let tx = x >> tile_bits;
let ty = y >> tile_bits;
let c = coeffs[ty * sub_w as usize + tx];
let a = (p >> 24) & 0xff;
let r_orig = ((p >> 16) & 0xff) as i32;
let g = ((p >> 8) & 0xff) as i32;
let b_orig = (p & 0xff) as i32;
let new_r = (r_orig - (((c.g2r as i32) * sign_extend_i8(g)) >> 5)).rem_euclid(256);
let b_after_g = (b_orig - (((c.g2b as i32) * sign_extend_i8(g)) >> 5)).rem_euclid(256);
let new_b =
(b_after_g - (((c.r2b as i32) * sign_extend_i8(r_orig)) >> 5)).rem_euclid(256);
out[idx] = (a << 24) | ((new_r as u32) << 16) | ((g as u32) << 8) | (new_b as u32);
}
}
out
}
fn apply_predictor_forward(
pixels: &[u32],
width: u32,
height: u32,
tile_bits: u32,
modes: &[u32],
sub_w: u32,
) -> Vec<u32> {
let w = width as usize;
let h = height as usize;
let mut out = vec![0u32; w * h];
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let pred = if x == 0 && y == 0 {
0xff00_0000
} else if y == 0 {
pixels[idx - 1]
} else if x == 0 {
pixels[idx - w]
} else {
let tx = x >> tile_bits;
let ty = y >> tile_bits;
let mode = modes[ty * sub_w as usize + tx];
predict_argb(pixels, w, x, y, mode)
};
out[idx] = sub_argb(pixels[idx], pred);
}
}
out
}
fn build_limited_lengths(freqs: &[u32], max_len: u8) -> Result<Vec<u8>> {
let n = freqs.len();
let mut lens = vec![0u8; n];
let nonzero: Vec<usize> = (0..n).filter(|&i| freqs[i] > 0).collect();
if nonzero.is_empty() {
if n >= 2 {
lens[0] = 1;
lens[1] = 1;
} else {
lens[0] = 1;
}
return Ok(lens);
}
if nonzero.len() == 1 {
let s = nonzero[0];
lens[s] = 1;
let d = if s == 0 { 1.min(n - 1) } else { 0 };
lens[d] = 1;
return Ok(lens);
}
#[derive(Clone)]
struct Node {
freq: u64,
left: i32,
right: i32,
symbol: i32,
}
let mut nodes: Vec<Node> = Vec::with_capacity(n * 2);
for (i, &f) in freqs.iter().enumerate() {
nodes.push(Node {
freq: f as u64,
left: -1,
right: -1,
symbol: i as i32,
});
}
let mut heap: std::collections::BinaryHeap<std::cmp::Reverse<(u64, usize)>> =
std::collections::BinaryHeap::new();
for &i in &nonzero {
heap.push(std::cmp::Reverse((nodes[i].freq, i)));
}
while heap.len() > 1 {
let std::cmp::Reverse((fa, a)) = heap.pop().unwrap();
let std::cmp::Reverse((fb, b)) = heap.pop().unwrap();
let idx = nodes.len();
nodes.push(Node {
freq: fa + fb,
left: a as i32,
right: b as i32,
symbol: -1,
});
heap.push(std::cmp::Reverse((fa + fb, idx)));
}
let root = heap.pop().unwrap().0 .1;
fn walk(nodes: &[Node], idx: usize, depth: u8, lens: &mut [u8]) {
let n = &nodes[idx];
if n.symbol >= 0 {
lens[n.symbol as usize] = depth.max(1);
} else {
walk(nodes, n.left as usize, depth + 1, lens);
walk(nodes, n.right as usize, depth + 1, lens);
}
}
walk(&nodes, root, 0, &mut lens);
limit_code_lengths(&mut lens, max_len);
Ok(lens)
}
fn limit_code_lengths(lens: &mut [u8], max_len: u8) {
let max_observed = *lens.iter().max().unwrap_or(&0);
if max_observed <= max_len {
return;
}
let mut bl_count: Vec<u32> = vec![0; (max_observed as usize + 1).max(1)];
for &l in lens.iter() {
if l > 0 {
bl_count[l as usize] += 1;
}
}
let mut overflow: u64 = 0;
for l in (max_len as usize + 1)..bl_count.len() {
overflow += (bl_count[l] as u64) * ((1u64 << (l - max_len as usize)) - 1);
bl_count[max_len as usize] += bl_count[l];
bl_count[l] = 0;
}
while overflow > 0 {
let mut d = max_len as i32 - 1;
while d > 0 && bl_count[d as usize] == 0 {
d -= 1;
}
if d <= 0 {
break;
}
bl_count[d as usize] -= 1;
bl_count[(d + 1) as usize] += 1;
let freed = 1u64 << ((max_len as i32 - d - 1).max(0) as u32);
if freed >= overflow {
overflow = 0;
} else {
overflow -= freed;
}
}
let mut by_depth: Vec<(u8, usize)> = lens
.iter()
.enumerate()
.filter(|(_, &l)| l > 0)
.map(|(i, &l)| (l, i))
.collect();
by_depth.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
for l in lens.iter_mut() {
*l = 0;
}
let mut idx = 0usize;
for l in (1..=max_len as usize).rev() {
let cnt = bl_count[l] as usize;
for _ in 0..cnt {
if idx >= by_depth.len() {
break;
}
let (_, sym) = by_depth[idx];
lens[sym] = l as u8;
idx += 1;
}
}
while idx < by_depth.len() {
let (_, sym) = by_depth[idx];
lens[sym] = max_len;
idx += 1;
}
}
fn canonical_codes(lens: &[u8]) -> Vec<u32> {
let max_len = *lens.iter().max().unwrap_or(&0);
let mut codes = vec![0u32; lens.len()];
if max_len == 0 {
return codes;
}
let mut bl_count = vec![0u32; max_len as usize + 1];
for &l in lens {
if l > 0 {
bl_count[l as usize] += 1;
}
}
let mut next_code = vec![0u32; max_len as usize + 1];
let mut code = 0u32;
for bits in 1..=max_len as usize {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
for (sym, &l) in lens.iter().enumerate() {
if l > 0 {
codes[sym] = next_code[l as usize];
next_code[l as usize] += 1;
}
}
codes
}
fn write_code(bw: &mut BitWriter, codes: &[u32], lens: &[u8], sym: usize) {
let l = lens[sym];
let code = codes[sym];
let mut rev = 0u32;
for i in 0..l {
if (code >> i) & 1 != 0 {
rev |= 1 << (l - 1 - i);
}
}
bw.write(rev, l as u32);
}
fn emit_huffman_tree(bw: &mut BitWriter, lens: &[u8]) -> Result<()> {
bw.write(0, 1);
const CODE_LENGTH_ORDER: [usize; 19] = [
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
let meta_stream = compress_lengths(lens);
let mut meta_freq = vec![0u32; 19];
for (code, _extra) in &meta_stream {
meta_freq[*code as usize] += 1;
}
let meta_lens = build_limited_lengths(&meta_freq, 7)?;
let meta_codes = canonical_codes(&meta_lens);
let mut last_used = 0usize;
for i in 0..19 {
let sym = CODE_LENGTH_ORDER[i];
if meta_lens[sym] != 0 {
last_used = i + 1;
}
}
let num_code_lengths = last_used.max(4);
bw.write((num_code_lengths - 4) as u32, 4);
for i in 0..num_code_lengths {
let sym = CODE_LENGTH_ORDER[i];
bw.write(meta_lens[sym] as u32, 3);
}
bw.write(0, 1);
for (code, extra) in &meta_stream {
write_code(bw, &meta_codes, &meta_lens, *code as usize);
match *code {
16 => bw.write(*extra, 2),
17 => bw.write(*extra, 3),
18 => bw.write(*extra, 7),
_ => {}
}
}
Ok(())
}
fn compress_lengths(lens: &[u8]) -> Vec<(u8, u32)> {
let mut out: Vec<(u8, u32)> = Vec::new();
let mut i = 0usize;
while i < lens.len() {
let v = lens[i];
if v == 0 {
let mut j = i;
while j < lens.len() && lens[j] == 0 {
j += 1;
}
let mut run = j - i;
while run >= 11 {
let take = run.min(138);
out.push((18, (take - 11) as u32));
run -= take;
}
while run >= 3 {
let take = run.min(10);
out.push((17, (take - 3) as u32));
run -= take;
}
for _ in 0..run {
out.push((0, 0));
}
i = j;
} else {
out.push((v, 0));
i += 1;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_len_dist_roundtrip_small() {
for value in 1u32..=200 {
let (sym, eb, extra) = encode_len_or_dist_value(value);
let decoded = if sym < 4 {
sym + 1
} else {
let eb_d = (sym - 2) >> 1;
let off = (2 + (sym & 1)) << eb_d;
off + extra + 1
};
assert_eq!(decoded, value, "round-trip failed for value {value}");
assert!(eb <= 14, "extra_bits too big for value {value}");
let _ = extra;
let _ = eb;
}
}
#[test]
fn canonical_codes_match_decoder_shape() {
let lens = [2u8, 1u8, 3u8, 3u8];
let codes = canonical_codes(&lens);
assert_eq!(codes[1], 0b0);
assert_eq!(codes[0], 0b10);
assert_eq!(codes[2], 0b110);
assert_eq!(codes[3], 0b111);
}
}