use crate::error::{DecodeError, DecodedImage};
use crate::profile::Profile;
use std::sync::atomic::AtomicBool;
pub fn decode(src: &[u8], profile: &Profile, canceled: &AtomicBool) -> Result<DecodedImage, DecodeError> {
let (w, h) = crate::decoder_helpers::validate_dimensions(src, profile, "width and height must be positive", 2)?;
let le = profile.little_endian;
let swap = profile.swap_rgb_channels;
let mut dst = vec![0u8; w * h * 4];
let row_stride = src.len() / h;
for y in 0..h {
crate::pixel_utils::check_canceled(canceled, "rgb565 decode canceled")?;
let row_start = y * row_stride;
let dst_start = y * w * 4;
let row_dst = &mut dst[dst_start..dst_start + w * 4];
#[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
if le {
crate::simd::rgb565_apply_row_to_bgra(&src[row_start..row_start + w * 2], row_dst);
if swap {
for p in row_dst.chunks_exact_mut(4) {
p.swap(0, 2);
}
}
continue;
}
let src_row = &src[row_start..row_start + w * 2];
for (src_pixel, dst_pixel) in src_row.chunks_exact(2).zip(row_dst.chunks_exact_mut(4)) {
let pixel = if le {
u16::from_le_bytes([src_pixel[0], src_pixel[1]])
} else {
u16::from_be_bytes([src_pixel[0], src_pixel[1]])
};
let r5 = u32::from((pixel >> 11) & 0x1F);
let g6 = u32::from((pixel >> 5) & 0x3F);
let b5 = u32::from(pixel & 0x1F);
if swap {
dst_pixel.copy_from_slice(&[
crate::pixel_utils::msb_replicate_5(r5),
crate::pixel_utils::msb_replicate_6(g6),
crate::pixel_utils::msb_replicate_5(b5),
255,
]);
} else {
dst_pixel.copy_from_slice(&[
crate::pixel_utils::msb_replicate_5(b5),
crate::pixel_utils::msb_replicate_6(g6),
crate::pixel_utils::msb_replicate_5(r5),
255,
]);
}
}
}
#[allow(clippy::cast_possible_truncation)]
Ok(DecodedImage {
data: dst,
width: w as u32,
height: h as u32,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::{Encoding, Profile};
use std::sync::atomic::AtomicBool;
fn make_profile(w: i32, h: i32, le: bool, swap: bool) -> Profile {
Profile {
prefix: 0,
width: w,
height: h,
encoding: Encoding::Rgb565,
frame_byte_length: w * h * 2,
little_endian: le,
swap_rgb_channels: swap,
..Default::default()
}
}
#[test]
fn zero_dimensions_returns_invalid_format() {
let profile = make_profile(0, 100, true, false);
let result = decode(b"", &profile, &AtomicBool::new(false));
assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
}
#[test]
fn negative_dimensions_returns_invalid_format() {
let profile = make_profile(-1, 100, true, false);
let result = decode(b"", &profile, &AtomicBool::new(false));
assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
}
#[test]
fn too_short_returns_buffer_too_short() {
let profile = make_profile(100, 100, true, false);
let result = decode(&[0u8; 10], &profile, &AtomicBool::new(false));
assert!(matches!(
result,
Err(DecodeError::BufferTooShort {
expected: 20000,
actual: 10
})
));
}
#[test]
fn decode_black_pixel() {
let profile = make_profile(1, 1, true, false);
let img = decode(&[0x00, 0x00], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0, 0, 0, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn decode_white_pixel() {
let profile = make_profile(1, 1, true, false);
let img = decode(&[0xFF, 0xFF], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0xFF, 0xFF, 0xFF, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn decode_red_pixel() {
let profile = make_profile(1, 1, true, false);
let img = decode(&[0x00, 0xF8], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0, 0, 0xFF, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn decode_blue_pixel() {
let profile = make_profile(1, 1, true, false);
let img = decode(&[0x1F, 0x00], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0xFF, 0, 0, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn decode_big_endian() {
let profile = make_profile(1, 1, false, false);
let img = decode(&[0xF8, 0x00], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0, 0, 0xFF, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn decode_swap_rgb_channels() {
let profile = make_profile(1, 1, true, true);
let img = decode(&[0x1F, 0x00], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0, 0, 0xFF, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn multi_pixel_decode() {
let profile = make_profile(2, 1, true, false);
let img = decode(&[0xFF, 0xFF, 0xFF, 0xFF], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(&img.data, &[0xFF, 0xFF, 0xFF, 255, 0xFF, 0xFF, 0xFF, 255]);
assert_eq!(img.width, 2);
assert_eq!(img.height, 1);
}
#[test]
fn row_stride_is_data_driven() {
let w = 55;
let h = 55;
let padded_data = vec![0u8; 6400]; let profile = Profile {
width: w,
height: h,
encoding: Encoding::Rgb565,
frame_byte_length: 6400,
little_endian: true,
..Default::default()
};
let img = decode(&padded_data, &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.width, 55);
assert_eq!(img.height, 55);
assert_eq!(img.data.len(), 55 * 55 * 4);
}
}