use crate::bitmap::Bitmap;
use crate::zp_impl::encoder::ZpEncoder;
struct NumContext {
ctx: Vec<u8>,
left: Vec<u32>,
right: Vec<u32>,
}
impl NumContext {
fn new() -> Self {
NumContext {
ctx: vec![0, 0],
left: vec![0, 0],
right: vec![0, 0],
}
}
fn root(&self) -> usize {
1
}
fn get_left(&mut self, node: usize) -> usize {
if self.left[node] == 0 {
let idx = self.ctx.len() as u32;
self.ctx.push(0);
self.left.push(0);
self.right.push(0);
self.left[node] = idx;
}
self.left[node] as usize
}
fn get_right(&mut self, node: usize) -> usize {
if self.right[node] == 0 {
let idx = self.ctx.len() as u32;
self.ctx.push(0);
self.left.push(0);
self.right.push(0);
self.right[node] = idx;
}
self.right[node] as usize
}
}
fn encode_num(zp: &mut ZpEncoder, ctx: &mut NumContext, low: i32, high: i32, val: i32) {
let mut low = low;
let mut high = high;
let mut val_inner = val;
let mut cutoff: i32 = 0;
let mut phase: u32 = 1;
let mut range: u32 = 0xffff_ffff;
let mut node = ctx.root();
while range != 1 {
let decision = if low >= cutoff {
let child = ctx.get_right(node);
node = child;
true
} else if high >= cutoff {
let bit = val_inner >= cutoff;
let child = if bit {
ctx.get_right(node)
} else {
ctx.get_left(node)
};
zp.encode_bit(&mut ctx.ctx[node], bit);
node = child;
bit
} else {
let child = ctx.get_left(node);
node = child;
false
};
match phase {
1 => {
let negative = !decision;
if negative {
let temp = -low - 1;
low = -high - 1;
high = temp;
val_inner = -val_inner - 1;
}
phase = 2;
cutoff = 1;
}
2 => {
if !decision {
phase = 3;
range = ((cutoff + 1) / 2) as u32;
if range <= 1 {
range = 1;
cutoff = 0;
} else {
cutoff -= (range / 2) as i32;
}
} else {
cutoff = cutoff * 2 + 1;
}
}
3 => {
range /= 2;
if range == 0 {
range = 1;
}
if range != 1 {
if !decision {
cutoff -= (range / 2) as i32;
} else {
cutoff += (range / 2) as i32;
}
} else if !decision {
cutoff -= 1;
}
}
_ => unreachable!(),
}
}
}
#[inline(always)]
fn pix_bm(bm: &Bitmap, col: i32, y: i32) -> u32 {
if col < 0 || y < 0 || col >= bm.width as i32 || y >= bm.height as i32 {
return 0;
}
bm.get(col as u32, y as u32) as u32
}
fn encode_bitmap_direct(zp: &mut ZpEncoder, ctx: &mut [u8], bm: &Bitmap) {
debug_assert_eq!(ctx.len(), 1024);
let w = bm.width as i32;
let h = bm.height as i32;
for jbm_row in (0..h).rev() {
let bm_y = h - 1 - jbm_row; let bm_y_p1 = bm_y - 1; let bm_y_p2 = bm_y - 2;
let mut r2 = pix_bm(bm, 0, bm_y_p2) << 1 | pix_bm(bm, 1, bm_y_p2);
let mut r1 =
pix_bm(bm, 0, bm_y_p1) << 2 | pix_bm(bm, 1, bm_y_p1) << 1 | pix_bm(bm, 2, bm_y_p1);
let mut r0: u32 = 0;
for col in 0..w {
let idx = ((r2 << 7) | (r1 << 2) | r0) as usize;
let bit = pix_bm(bm, col, bm_y) != 0;
zp.encode_bit(&mut ctx[idx], bit);
r2 = ((r2 << 1) & 0b111) | pix_bm(bm, col + 2, bm_y_p2);
r1 = ((r1 << 1) & 0b11111) | pix_bm(bm, col + 3, bm_y_p1);
r0 = ((r0 << 1) & 0b11) | bit as u32;
}
}
}
pub fn encode_jb2(bitmap: &Bitmap) -> Vec<u8> {
let w = bitmap.width as i32;
let h = bitmap.height as i32;
if w == 0 || h == 0 {
return Vec::new();
}
let mut zp = ZpEncoder::new();
let mut record_type_ctx = NumContext::new();
let mut image_size_ctx = NumContext::new();
let mut symbol_width_ctx = NumContext::new();
let mut symbol_height_ctx = NumContext::new();
let mut hoff_ctx = NumContext::new();
let mut voff_ctx = NumContext::new();
let mut direct_bitmap_ctx = vec![0u8; 1024];
let mut offset_type_ctx: u8 = 0;
let mut flag_ctx: u8 = 0;
encode_num(&mut zp, &mut record_type_ctx, 0, 11, 0);
encode_num(&mut zp, &mut image_size_ctx, 0, 262142, w);
encode_num(&mut zp, &mut image_size_ctx, 0, 262142, h);
zp.encode_bit(&mut flag_ctx, false);
encode_num(&mut zp, &mut record_type_ctx, 0, 11, 3);
encode_num(&mut zp, &mut symbol_width_ctx, 0, 262142, w);
encode_num(&mut zp, &mut symbol_height_ctx, 0, 262142, h);
encode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, bitmap);
zp.encode_bit(&mut offset_type_ctx, true); encode_num(&mut zp, &mut hoff_ctx, -262143, 262142, 1);
encode_num(&mut zp, &mut voff_ctx, -262143, 262142, 0);
encode_num(&mut zp, &mut record_type_ctx, 0, 11, 11);
zp.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bitmap::Bitmap;
use crate::jb2;
fn make_bitmap(w: u32, h: u32, f: impl Fn(u32, u32) -> bool) -> Bitmap {
let mut bm = Bitmap::new(w, h);
for y in 0..h {
for x in 0..w {
bm.set(x, y, f(x, y));
}
}
bm
}
fn roundtrip(bm: &Bitmap) -> Bitmap {
let encoded = encode_jb2(bm);
jb2::decode(&encoded, None).expect("decode failed")
}
#[test]
fn all_white_roundtrip() {
let src = Bitmap::new(32, 32);
let decoded = roundtrip(&src);
assert_eq!(decoded.width, 32);
assert_eq!(decoded.height, 32);
for y in 0..32u32 {
for x in 0..32u32 {
assert!(!decoded.get(x, y), "expected white at ({x},{y})");
}
}
}
#[test]
fn all_black_roundtrip() {
let src = make_bitmap(32, 32, |_, _| true);
let decoded = roundtrip(&src);
for y in 0..32u32 {
for x in 0..32u32 {
assert!(decoded.get(x, y), "expected black at ({x},{y})");
}
}
}
#[test]
fn checkerboard_roundtrip() {
let src = make_bitmap(16, 16, |x, y| (x + y) % 2 == 0);
let decoded = roundtrip(&src);
for y in 0..16u32 {
for x in 0..16u32 {
assert_eq!(decoded.get(x, y), (x + y) % 2 == 0, "mismatch at ({x},{y})");
}
}
}
#[test]
fn single_pixel_roundtrip() {
let src = make_bitmap(1, 1, |_, _| true);
let decoded = roundtrip(&src);
assert_eq!(decoded.width, 1);
assert_eq!(decoded.height, 1);
assert!(decoded.get(0, 0));
}
#[test]
fn larger_image_roundtrip() {
let src = make_bitmap(64, 64, |x, y| (x * 17 + y * 31) % 5 != 0);
let decoded = roundtrip(&src);
assert_eq!(decoded.width, 64);
assert_eq!(decoded.height, 64);
let mut mismatches = 0u32;
for y in 0..64u32 {
for x in 0..64u32 {
if decoded.get(x, y) != src.get(x, y) {
mismatches += 1;
}
}
}
assert_eq!(
mismatches, 0,
"{mismatches} pixel mismatches in 64×64 roundtrip"
);
}
#[test]
fn encoded_is_nonempty() {
let src = Bitmap::new(8, 8);
let encoded = encode_jb2(&src);
assert!(!encoded.is_empty());
}
#[test]
fn zero_dimension_returns_empty() {
assert!(encode_jb2(&Bitmap::new(0, 0)).is_empty());
assert!(encode_jb2(&Bitmap::new(8, 0)).is_empty());
assert!(encode_jb2(&Bitmap::new(0, 8)).is_empty());
}
}