#![cfg_attr(not(test), allow(dead_code))]
#![allow(
clippy::many_single_char_names,
clippy::similar_names,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::doc_markdown,
clippy::cast_precision_loss
)]
use self::helpers::interlace_fields;
use crate::profile::{Encoding, Profile};
mod cl;
mod clcl;
mod helpers;
mod reordered;
mod rgb555;
mod rgb565;
mod uyvy;
mod ycbcr420;
pub use cl::encode_cl;
pub use clcl::encode_clcl;
pub use reordered::encode_reordered_rgb555;
pub use rgb555::encode_rgb555;
pub use rgb565::encode_rgb565;
pub use uyvy::encode_uyvy;
pub use ycbcr420::encode_ycbcr420;
#[must_use]
pub(crate) fn rotate_bgra(src: &[u8], w: i32, h: i32, rotation: i32) -> Vec<u8> {
let wu = w as usize;
let hu = h as usize;
match rotation % 360 {
90 => {
let mut dst = vec![0u8; wu * hu * 4];
for sy in 0..hu {
for sx in 0..wu {
let s_idx = (sy * wu + sx) * 4;
let dx = hu - 1 - sy;
let dy = sx;
let d_idx = (dy * hu + dx) * 4;
dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
}
}
dst
}
180 => {
let mut dst = vec![0u8; wu * hu * 4];
for sy in 0..hu {
for sx in 0..wu {
let s_idx = (sy * wu + sx) * 4;
let dx = wu - 1 - sx;
let dy = hu - 1 - sy;
let d_idx = (dy * wu + dx) * 4;
dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
}
}
dst
}
270 => {
let mut dst = vec![0u8; wu * hu * 4];
for sy in 0..hu {
for sx in 0..wu {
let s_idx = (sy * wu + sx) * 4;
let dx = sy;
let dy = wu - 1 - sx;
let d_idx = (dy * hu + dx) * 4;
dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
}
}
dst
}
_ => src.to_vec(),
}
}
#[must_use]
pub fn build_ithmb_file(bgra: &[u8], w: i32, h: i32, profile: &Profile) -> Vec<u8> {
let rotated = if profile.rotation != 0 {
rotate_bgra(bgra, w, h, profile.rotation)
} else {
bgra.to_vec()
};
let encoded = encode_bgra(&rotated, w, h, profile);
let prefix_bytes = (profile.prefix as u32).to_be_bytes();
let mut file = Vec::with_capacity(4 + encoded.len());
file.extend_from_slice(&prefix_bytes);
file.extend_from_slice(&encoded);
if profile.is_padded {
let total_min = 4 + profile.frame_byte_length as usize;
if file.len() < total_min {
file.resize(total_min, 0);
}
}
file
}
#[must_use]
pub fn encode_bgra(src: &[u8], w: i32, h: i32, profile: &Profile) -> Vec<u8> {
let encoded: Vec<u8> = if profile.clcl_chroma {
encode_clcl(src, w, h)
} else if profile.cl_chroma {
encode_cl(src, w, h)
} else {
match profile.encoding {
Encoding::Rgb565 => encode_rgb565(src, w, h, !profile.little_endian),
Encoding::Rgb555 => encode_rgb555(src, w, h, !profile.little_endian, profile.swap_rgb_channels),
Encoding::ReorderedRgb555 => encode_reordered_rgb555(src, w, h, true), Encoding::Yuv422 => encode_uyvy(src, w, h),
Encoding::Ycbcr420 => encode_ycbcr420(src, w, h, profile.swap_chroma_planes),
Encoding::Jpeg => {
Vec::new()
}
}
};
let interlaced = if profile.is_interlaced {
interlace_fields(&encoded, w, h, profile.encoding)
} else {
encoded
};
let target = profile.frame_byte_length as usize;
if interlaced.len() < target {
let mut padded = interlaced;
padded.resize(target, 0);
padded
} else {
interlaced
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::Encoding;
fn make_rgb_profile(w: i32, h: i32, enc: Encoding, le: bool, swap: bool, interlace: bool) -> Profile {
let bpp: i32 = match enc {
Encoding::Rgb565 | Encoding::Rgb555 | Encoding::ReorderedRgb555 | Encoding::Yuv422 => 2,
Encoding::Ycbcr420 => 3, Encoding::Jpeg => 0,
};
Profile {
prefix: 0,
width: w,
height: h,
encoding: enc,
frame_byte_length: w * h * bpp,
little_endian: le,
swap_rgb_channels: swap,
is_interlaced: interlace,
..Default::default()
}
}
#[test]
fn rgb565_white_1x1_le() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_rgb565(&bgra, 1, 1, false);
assert_eq!(enc, &[0xFF, 0xFF]);
}
#[test]
fn rgb565_white_1x1_be() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_rgb565(&bgra, 1, 1, true);
assert_eq!(enc, &[0xFF, 0xFF]);
}
#[test]
fn rgb565_black_1x1() {
let bgra = vec![0, 0, 0, 255];
let enc = encode_rgb565(&bgra, 1, 1, false);
assert_eq!(enc, &[0x00, 0x00]);
}
#[test]
fn rgb565_red_1x1() {
let bgra = vec![0, 0, 255, 255];
let enc = encode_rgb565(&bgra, 1, 1, false);
assert_eq!(enc, &[0x00, 0xF8]);
}
#[test]
fn rgb565_red_1x1_be() {
let bgra = vec![0, 0, 255, 255];
let enc = encode_rgb565(&bgra, 1, 1, true);
assert_eq!(enc, &[0xF8, 0x00]);
}
#[test]
fn rgb565_green_1x1() {
let bgra = vec![0, 255, 0, 255];
let enc = encode_rgb565(&bgra, 1, 1, false);
assert_eq!(enc, &[0xE0, 0x07]);
}
#[test]
fn rgb565_blue_1x1() {
let bgra = vec![255, 0, 0, 255];
let enc = encode_rgb565(&bgra, 1, 1, false);
assert_eq!(enc, &[0x1F, 0x00]);
}
#[test]
fn rgb565_2x1() {
let bgra = vec![
0, 0, 255, 255, 255, 0, 0, 255, ];
let enc = encode_rgb565(&bgra, 2, 1, false);
assert_eq!(enc, &[0x00, 0xF8, 0x1F, 0x00]);
}
#[test]
fn rgb565_2x2() {
let bgra = [0u8, 0, 255, 255].repeat(4);
let enc = encode_rgb565(&bgra, 2, 2, false);
assert_eq!(enc.len(), 2 * 2 * 2);
assert!(enc.iter().all(|&b| b == 0x00 || b == 0xF8));
for i in 0..4 {
assert_eq!(enc[i * 2], 0x00, "pixel {i} byte 0");
assert_eq!(enc[i * 2 + 1], 0xF8, "pixel {i} byte 1");
}
}
#[test]
fn rgb565_roundtrip() {
let bgra = vec![10, 20, 30, 255, 200, 180, 100, 255, 50, 60, 70, 255, 0, 0, 0, 255];
let enc = encode_rgb565(&bgra, 2, 2, true);
let dec = crate::rgb565::decode(
&enc,
&make_rgb_profile(2, 2, Encoding::Rgb565, false, false, false),
&std::sync::atomic::AtomicBool::new(false),
)
.unwrap();
assert_eq!(dec.data.len(), bgra.len());
for i in 0..4 {
assert!(
(i32::from(dec.data[i * 4]) - i32::from(bgra[i * 4])).abs() <= 8, "B channel mismatch at pixel {i}"
);
assert!(
(i32::from(dec.data[i * 4 + 1]) - i32::from(bgra[i * 4 + 1])).abs() <= 4,
"G channel mismatch at pixel {i}"
);
assert!(
(i32::from(dec.data[i * 4 + 2]) - i32::from(bgra[i * 4 + 2])).abs() <= 8,
"R channel mismatch at pixel {i}"
);
assert_eq!(dec.data[i * 4 + 3], 255, "alpha mismatch at pixel {i}");
}
}
#[test]
fn rgb555_white_1x1() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, false);
assert_eq!(enc, &[0x7F, 0xFF]);
}
#[test]
fn rgb555_white_1x1_le() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_rgb555(&bgra, 1, 1, false, false);
assert_eq!(enc, &[0xFF, 0x7F]);
}
#[test]
fn rgb555_black_1x1() {
let bgra = vec![0, 0, 0, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, false);
assert_eq!(enc, &[0x00, 0x00]);
}
#[test]
fn rgb555_red_1x1() {
let bgra = vec![0, 0, 255, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, false);
assert_eq!(enc, &[0x7C, 0x00]);
}
#[test]
fn rgb555_blue_1x1() {
let bgra = vec![255, 0, 0, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, false);
assert_eq!(enc, &[0x00, 0x1F]);
}
#[test]
fn rgb555_green_1x1() {
let bgra = vec![0, 255, 0, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, false);
assert_eq!(enc, &[0x03, 0xE0]);
}
#[test]
fn rgb555_swap_rgb_bgr15() {
let bgra = vec![255, 0, 0, 255];
let enc = encode_rgb555(&bgra, 1, 1, true, true);
assert_eq!(enc, &[0x7C, 0x00]);
}
#[test]
fn rgb555_roundtrip() {
let bgra = vec![10, 20, 30, 255, 200, 180, 100, 255];
let enc = encode_rgb555(&bgra, 2, 1, true, false);
let dec = crate::rgb555::decode(
&enc,
&make_rgb_profile(2, 1, Encoding::Rgb555, false, false, false),
&std::sync::atomic::AtomicBool::new(false),
)
.unwrap();
assert_eq!(dec.data.len(), bgra.len());
for i in 0..2 {
for c in 0..3 {
let diff = (i32::from(dec.data[i * 4 + c]) - i32::from(bgra[i * 4 + c])).abs();
assert!(diff <= 8, "channel {c} pixel {i} diff {diff}");
}
assert_eq!(dec.data[i * 4 + 3], 255);
}
}
#[test]
fn reordered_rgb555_white_1x1() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_reordered_rgb555(&bgra, 1, 1, true);
assert_eq!(enc, &[0x7F, 0xFF]);
}
#[test]
fn reordered_rgb555_red_1x1() {
let bgra = vec![0, 0, 255, 255];
let enc = encode_reordered_rgb555(&bgra, 1, 1, true);
assert_eq!(enc, &[0x7C, 0x00]);
}
#[test]
fn uyvy_white_2x1() {
let bgra = vec![255u8; 2 * 4];
let enc = encode_uyvy(&bgra, 2, 1);
assert_eq!(enc, &[128, 255, 128, 255]);
}
#[test]
fn uyvy_black_2x1() {
let bgra = [0u8, 0, 0, 255].repeat(2);
let enc = encode_uyvy(&bgra, 2, 1);
assert_eq!(enc, &[128, 0, 128, 0]);
}
#[test]
fn uyvy_red_blue_2x1() {
let bgra = vec![
0, 0, 255, 255, 255, 0, 0, 255, ];
let enc = encode_uyvy(&bgra, 2, 1);
assert_eq!(enc.len(), 4);
assert_eq!(enc[1], 76); assert_eq!(enc[3], 28); assert_eq!(enc[0], 170); assert_eq!(enc[2], 181); }
#[test]
fn uyvy_roundtrip_2x2() {
let bgra = vec![128u8; 2 * 2 * 4];
let enc = encode_uyvy(&bgra, 2, 2);
assert_eq!(enc.len(), 2 * 2 * 2);
for block in 0..2 {
let off = block * 4;
assert_eq!(enc[off + 1], 128); assert_eq!(enc[off + 3], 128); }
}
#[test]
fn ycbcr420_white_2x2() {
let bgra = vec![255u8; 4 * 4];
let enc = encode_ycbcr420(&bgra, 2, 2, false);
assert_eq!(enc.len(), 4 + 1 + 1); assert_eq!(&enc[0..4], &[255, 255, 255, 255]);
assert_eq!(enc[4], 128); assert_eq!(enc[5], 128); }
#[test]
fn ycbcr420_black_2x2() {
let bgra = [0u8, 0, 0, 255].repeat(4);
let enc = encode_ycbcr420(&bgra, 2, 2, false);
assert_eq!(enc.len(), 6);
assert_eq!(&enc[0..4], &[0, 0, 0, 0]);
assert_eq!(enc[4], 128);
assert_eq!(enc[5], 128);
}
#[test]
fn ycbcr420_swap_chroma() {
let bgra = vec![255u8; 4 * 4];
let enc = encode_ycbcr420(&bgra, 2, 2, true);
assert_eq!(enc.len(), 6);
assert_eq!(&enc[0..4], &[255, 255, 255, 255]);
assert_eq!(enc[4], 128); assert_eq!(enc[5], 128); }
#[test]
fn ycbcr420_2x2_red_green_blue_gray() {
let bgra = vec![0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, 128, 128, 128, 255];
let enc = encode_ycbcr420(&bgra, 2, 2, false);
assert_eq!(enc.len(), 6);
assert_eq!(enc[0], 76);
assert_eq!(enc[1], 149);
assert_eq!(enc[2], 28);
assert_eq!(enc[3], 128);
assert!(enc[4] > 0); assert!(enc[5] > 0); }
#[test]
fn clcl_white_2x1() {
let bgra = vec![255u8; 2 * 4];
let enc = encode_clcl(&bgra, 2, 1);
assert_eq!(enc.len(), 4);
assert_eq!(enc[0], 255); assert_eq!(enc[1], 255); assert_eq!(enc[2], 0x88);
assert_eq!(enc[3], 0x88);
}
#[test]
fn clcl_black_2x1() {
let bgra = [0u8, 0, 0, 255].repeat(2);
let enc = encode_clcl(&bgra, 2, 1);
assert_eq!(enc.len(), 4);
assert_eq!(enc[0], 0);
assert_eq!(enc[1], 0);
assert_eq!(enc[2], 0x88);
assert_eq!(enc[3], 0x88);
}
#[test]
fn clcl_2x2() {
let bgra = [128u8, 128, 128, 255].repeat(4);
let enc = encode_clcl(&bgra, 2, 2);
assert_eq!(enc.len(), 8);
assert_eq!(enc[0], 128);
assert_eq!(enc[1], 128);
assert_eq!(enc[2], 128);
assert_eq!(enc[3], 128);
assert_eq!(enc[4], 0x88);
assert_eq!(enc[5], 0x88);
assert_eq!(enc[6], 0x88);
assert_eq!(enc[7], 0x88);
}
#[test]
fn cl_white_1x1() {
let bgra = vec![255, 255, 255, 255];
let enc = encode_cl(&bgra, 1, 1);
assert_eq!(enc.len(), 2);
assert_eq!(enc[0], 255);
assert_eq!(enc[1], 0x88);
}
#[test]
fn cl_black_1x1() {
let bgra = vec![0, 0, 0, 255];
let enc = encode_cl(&bgra, 1, 1);
assert_eq!(enc.len(), 2);
assert_eq!(enc[0], 0);
assert_eq!(enc[1], 0x88);
}
#[test]
fn cl_2x1() {
let bgra = vec![255u8; 2 * 4];
let enc = encode_cl(&bgra, 2, 1);
assert_eq!(enc.len(), 4);
assert_eq!(enc[0], 255);
assert_eq!(enc[1], 255);
assert_eq!(enc[2], 0x88);
assert_eq!(enc[3], 0x88);
}
#[test]
fn clamp_test() {
assert_eq!(crate::pixel_utils::clamp_u8(0), 0);
assert_eq!(crate::pixel_utils::clamp_u8(255), 255);
assert_eq!(crate::pixel_utils::clamp_u8(-10), 0);
assert_eq!(crate::pixel_utils::clamp_u8(300), 255);
assert_eq!(crate::pixel_utils::clamp_u8(128), 128);
}
#[test]
fn rotate_180_2x2() {
let bgra = vec![0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255];
let rotated = rotate_bgra(&bgra, 2, 2, 180);
assert_eq!(&rotated[0..4], &[255, 255, 255, 255]); assert_eq!(&rotated[4..8], &[255, 0, 0, 255]); assert_eq!(&rotated[8..12], &[0, 255, 0, 255]); assert_eq!(&rotated[12..16], &[0, 0, 255, 255]); }
#[test]
fn rotate_90_2x1() {
let bgra = vec![
0, 0, 255, 255, 255, 0, 0, 255, ];
let rotated = rotate_bgra(&bgra, 2, 1, 90);
assert_eq!(rotated.len(), 8); assert_eq!(rotated.len(), 8);
}
#[test]
fn encode_bgra_rgb565_le() {
let profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
let bgra = vec![255, 255, 255, 255];
let enc = encode_bgra(&bgra, 1, 1, &profile);
assert_eq!(enc, &[0xFF, 0xFF]);
}
#[test]
fn encode_bgra_clcl() {
let mut profile = make_rgb_profile(2, 1, Encoding::Rgb565, true, false, false);
profile.clcl_chroma = true;
profile.frame_byte_length = 4;
let bgra = vec![255u8; 2 * 4];
let enc = encode_bgra(&bgra, 2, 1, &profile);
assert_eq!(enc.len(), 4);
}
#[test]
fn encode_bgra_cl() {
let mut profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
profile.cl_chroma = true;
profile.frame_byte_length = 2;
let bgra = vec![255, 255, 255, 255];
let enc = encode_bgra(&bgra, 1, 1, &profile);
assert_eq!(enc.len(), 2);
}
#[test]
fn encode_bgra_ycbcr420() {
let bgra = vec![255u8; 4 * 4];
let enc = encode_ycbcr420(&bgra, 2, 2, false);
assert_eq!(enc.len(), 6);
}
#[test]
fn encode_bgra_padding() {
let mut profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
profile.frame_byte_length = 10; let bgra = vec![255, 255, 255, 255];
let enc = encode_bgra(&bgra, 1, 1, &profile);
assert_eq!(enc.len(), 10);
assert_eq!(&enc[0..2], &[0xFF, 0xFF]);
assert_eq!(&enc[2..], &[0, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn encode_bgra_interlace() {
let mut profile = make_rgb_profile(2, 2, Encoding::Rgb565, true, false, true);
profile.frame_byte_length = 8;
let bgra = vec![0, 0, 255, 255, 0, 0, 128, 255, 0, 0, 255, 255, 0, 0, 128, 255];
let enc = encode_bgra(&bgra, 2, 2, &profile);
assert_eq!(enc.len(), 8);
assert_ne!(enc, &[0u8; 8]);
}
#[test]
fn build_ithmb_file_rgb565() {
let profile = Profile {
prefix: 0x1234_5678,
width: 1,
height: 1,
encoding: Encoding::Rgb565,
frame_byte_length: 2,
..Default::default()
};
let bgra = vec![255, 255, 255, 255];
let file = build_ithmb_file(&bgra, 1, 1, &profile);
assert_eq!(file.len(), 6);
assert_eq!(&file[0..4], &[0x12, 0x34, 0x56, 0x78]);
assert_eq!(&file[4..6], &[0xFF, 0xFF]);
}
#[test]
fn build_ithmb_file_padded() {
let profile = Profile {
prefix: 0x0000_1001,
width: 1,
height: 1,
encoding: Encoding::Rgb565,
frame_byte_length: 100,
is_padded: true,
slot_size: 100,
..Default::default()
};
let bgra = vec![255, 255, 255, 255];
let file = build_ithmb_file(&bgra, 1, 1, &profile);
assert_eq!(file.len(), 104);
assert_eq!(&file[0..4], &[0x00, 0x00, 0x10, 0x01]);
assert_eq!(&file[4..6], &[0xFF, 0xFF]);
}
}