use crate::iw44_encode::{Iw44EncodeOptions, encode_iw44_color, encode_iw44_gray};
use crate::pixmap::{GrayPixmap, Pixmap};
pub const THUMBNAIL_MAX_SIDE: u32 = 128;
pub fn encode_th44_color(src: &Pixmap) -> Vec<Vec<u8>> {
let (tw, th) = thumbnail_dimensions(src.width, src.height);
let thumb = crate::pixmap::scale_lanczos3(src, tw, th);
let opts = Iw44EncodeOptions {
slices_per_chunk: 100,
total_slices: 100,
chroma_delay: 0,
chroma_half: false,
..Iw44EncodeOptions::default()
};
encode_iw44_color(&thumb, &opts)
}
pub fn encode_th44_gray_from_bitmap(src: &crate::Bitmap) -> Vec<Vec<u8>> {
let w = src.width;
let h = src.height;
let mut data = Vec::with_capacity((w * h) as usize);
for y in 0..h {
for x in 0..w {
data.push(if src.get(x, y) { 0u8 } else { 255u8 });
}
}
let gray = GrayPixmap {
width: w,
height: h,
data,
};
let (tw, th) = thumbnail_dimensions(w, h);
let scaled = scale_gray_bilinear(&gray, tw, th);
let opts = Iw44EncodeOptions {
slices_per_chunk: 100,
total_slices: 100,
chroma_delay: 0,
chroma_half: false,
..Iw44EncodeOptions::default()
};
encode_iw44_gray(&scaled, &opts)
}
pub(crate) fn thumbnail_dimensions(w: u32, h: u32) -> (u32, u32) {
if w == 0 || h == 0 {
return (1, 1);
}
let max = THUMBNAIL_MAX_SIDE;
if w <= max && h <= max {
return (w.max(1), h.max(1));
}
if w >= h {
let tw = max;
let th = ((h as u64 * max as u64) / w as u64).max(1) as u32;
(tw, th)
} else {
let th = max;
let tw = ((w as u64 * max as u64) / h as u64).max(1) as u32;
(tw, th)
}
}
pub(crate) fn scale_gray_bilinear(src: &GrayPixmap, dst_w: u32, dst_h: u32) -> GrayPixmap {
let sw = src.width;
let sh = src.height;
if sw == 0 || sh == 0 || dst_w == 0 || dst_h == 0 {
return GrayPixmap {
width: dst_w.max(1),
height: dst_h.max(1),
data: vec![255u8; (dst_w.max(1) * dst_h.max(1)) as usize],
};
}
if sw == dst_w && sh == dst_h {
return src.clone();
}
let mut data = Vec::with_capacity((dst_w * dst_h) as usize);
for oy in 0..dst_h {
let sy_f = (oy as f32 + 0.5) * (sh as f32 / dst_h as f32) - 0.5;
let sy0 = (sy_f.floor() as i64).clamp(0, (sh - 1) as i64) as u32;
let sy1 = (sy0 + 1).min(sh - 1);
let dy = (sy_f - sy_f.floor()).clamp(0.0, 1.0);
for ox in 0..dst_w {
let sx_f = (ox as f32 + 0.5) * (sw as f32 / dst_w as f32) - 0.5;
let sx0 = (sx_f.floor() as i64).clamp(0, (sw - 1) as i64) as u32;
let sx1 = (sx0 + 1).min(sw - 1);
let dx = (sx_f - sx_f.floor()).clamp(0.0, 1.0);
let p00 = src.get(sx0, sy0) as f32;
let p10 = src.get(sx1, sy0) as f32;
let p01 = src.get(sx0, sy1) as f32;
let p11 = src.get(sx1, sy1) as f32;
let val = p00 * (1.0 - dx) * (1.0 - dy)
+ p10 * dx * (1.0 - dy)
+ p01 * (1.0 - dx) * dy
+ p11 * dx * dy;
data.push(val.round().clamp(0.0, 255.0) as u8);
}
}
GrayPixmap {
width: dst_w,
height: dst_h,
data,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thumbnail_dimensions_long_side_capped() {
let (w, h) = thumbnail_dimensions(800, 600);
assert_eq!(w, 128);
assert_eq!(h, 96);
}
#[test]
fn thumbnail_dimensions_portrait() {
let (w, h) = thumbnail_dimensions(600, 800);
assert_eq!(w, 96);
assert_eq!(h, 128);
}
#[test]
fn thumbnail_dimensions_small_passthrough() {
let (w, h) = thumbnail_dimensions(64, 48);
assert_eq!(w, 64);
assert_eq!(h, 48);
}
#[test]
fn thumbnail_dimensions_zero() {
assert_eq!(thumbnail_dimensions(0, 100), (1, 1));
assert_eq!(thumbnail_dimensions(100, 0), (1, 1));
}
#[test]
fn scale_gray_bilinear_size() {
let src = GrayPixmap {
width: 64,
height: 48,
data: vec![128u8; 64 * 48],
};
let dst = scale_gray_bilinear(&src, 32, 24);
assert_eq!(dst.width, 32);
assert_eq!(dst.height, 24);
assert_eq!(dst.data.len(), 32 * 24);
}
#[test]
fn encode_th44_color_round_trips_dimensions() {
use crate::iw44::Iw44Image;
let src = Pixmap::white(200, 150);
let chunks = encode_th44_color(&src);
assert!(!chunks.is_empty());
let mut img = Iw44Image::new();
for c in &chunks {
img.decode_chunk(c).expect("decode TH44 chunk");
}
let decoded = img.to_rgb().expect("to_rgb");
let (tw, th) = thumbnail_dimensions(200, 150);
assert_eq!(
decoded.width, tw,
"decoded width matches thumbnail_dimensions"
);
assert_eq!(
decoded.height, th,
"decoded height matches thumbnail_dimensions"
);
}
#[test]
fn encode_th44_gray_from_bitmap_round_trips_dimensions() {
use crate::Bitmap;
use crate::iw44::Iw44Image;
let mut bm = Bitmap::new(200, 300);
for x in 0..50 {
for y in 0..50 {
bm.set(x, y, true);
}
}
let chunks = encode_th44_gray_from_bitmap(&bm);
assert!(!chunks.is_empty());
let mut img = Iw44Image::new();
for c in &chunks {
img.decode_chunk(c).expect("decode TH44 gray chunk");
}
let decoded = img.to_rgb().expect("to_rgb");
let (tw, th) = thumbnail_dimensions(200, 300);
assert_eq!(decoded.width, tw);
assert_eq!(decoded.height, th);
}
}