use rusty_esp_core::error::{Error, Result};
use rusty_esp_core::frame::{Geometry, PixelFormat};
use rusty_esp_core::pcm::{as_u16, as_u16_mut};
use crate::expect_len;
#[must_use]
pub const fn pack_rgb565(r: u8, g: u8, b: u8) -> u16 {
((r as u16 & 0xF8) << 8) | ((g as u16 & 0xFC) << 3) | (b as u16 >> 3)
}
#[must_use]
pub const fn unpack_rgb565(p: u16) -> [u8; 3] {
let r5 = ((p >> 11) & 0x1F) as u8;
let g6 = ((p >> 5) & 0x3F) as u8;
let b5 = (p & 0x1F) as u8;
[
(r5 << 3) | (r5 >> 2),
(g6 << 2) | (g6 >> 4),
(b5 << 3) | (b5 >> 2),
]
}
#[inline]
const fn r5to8(p: u16) -> u32 {
let v = ((p >> 11) & 0x1F) as u32;
(v << 3) | (v >> 2)
}
#[inline]
const fn g6to8(p: u16) -> u32 {
let v = ((p >> 5) & 0x3F) as u32;
(v << 2) | (v >> 4)
}
#[inline]
const fn b5to8(p: u16) -> u32 {
let v = (p & 0x1F) as u32;
(v << 3) | (v >> 2)
}
#[must_use]
pub fn yuv_to_rgb(y: u8, u: u8, v: u8) -> [u8; 3] {
let y = i32::from(y);
let cb = i32::from(u) - 128;
let cr = i32::from(v) - 128;
let r = y + ((359 * cr) >> 8);
let g = y - ((88 * cb + 183 * cr) >> 8);
let b = y + ((454 * cb) >> 8);
[clamp(r), clamp(g), clamp(b)]
}
fn clamp(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
pub fn rgb565_to_rgb888(src: &[u8], dst: &mut [u8]) -> Result<usize> {
if src.len() % 2 != 0 {
return Err(Error::InvalidGeometry);
}
let pixels = src.len() / 2;
expect_len(dst, pixels * 3)?;
if let Some(sv) = as_u16(src) {
let dv = &mut dst[..pixels * 3];
let body = pixels / 8 * 8;
let (svb, svt) = sv.split_at(body);
let (dvb, dvt) = dv.split_at_mut(body * 3);
for (s, d) in svb.chunks_exact(8).zip(dvb.chunks_exact_mut(24)) {
for k in 0..8 {
d[k * 3..k * 3 + 3].copy_from_slice(&unpack_rgb565(s[k]));
}
}
for (s, d) in svt.iter().zip(dvt.chunks_exact_mut(3)) {
d.copy_from_slice(&unpack_rgb565(*s));
}
return Ok(pixels);
}
let body = pixels / 8 * 8;
let (sb, st) = src.split_at(body * 2);
let (db, dt) = dst[..pixels * 3].split_at_mut(body * 3);
for (s, d) in sb.chunks_exact(16).zip(db.chunks_exact_mut(24)) {
d[0..3].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[0], s[1]])));
d[3..6].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[2], s[3]])));
d[6..9].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[4], s[5]])));
d[9..12].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[6], s[7]])));
d[12..15].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[8], s[9]])));
d[15..18].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[10], s[11]])));
d[18..21].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[12], s[13]])));
d[21..24].copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[14], s[15]])));
}
for (s, d) in st.chunks_exact(2).zip(dt.chunks_exact_mut(3)) {
d.copy_from_slice(&unpack_rgb565(u16::from_le_bytes([s[0], s[1]])));
}
Ok(pixels)
}
pub fn rgb888_to_rgb565(src: &[u8], dst: &mut [u8]) -> Result<usize> {
if src.len() % 3 != 0 {
return Err(Error::InvalidGeometry);
}
let pixels = src.len() / 3;
expect_len(dst, pixels * 2)?;
if let Some(dv) = as_u16_mut(&mut dst[..pixels * 2]) {
let body = pixels / 16 * 16;
let (sb, st) = src.split_at(body * 3);
let (dvb, dvt) = dv.split_at_mut(body);
for (s, d) in sb.chunks_exact(48).zip(dvb.chunks_exact_mut(16)) {
for k in 0..16 {
d[k] = pack_rgb565(s[k * 3], s[k * 3 + 1], s[k * 3 + 2]);
}
}
for (s, d) in st.chunks_exact(3).zip(dvt.iter_mut()) {
*d = pack_rgb565(s[0], s[1], s[2]);
}
return Ok(pixels);
}
let body = pixels / 8 * 8;
let (sb, st) = src.split_at(body * 3);
let (db, dt) = dst[..pixels * 2].split_at_mut(body * 2);
for (s, d) in sb.chunks_exact(24).zip(db.chunks_exact_mut(16)) {
d[0..2].copy_from_slice(&pack_rgb565(s[0], s[1], s[2]).to_le_bytes());
d[2..4].copy_from_slice(&pack_rgb565(s[3], s[4], s[5]).to_le_bytes());
d[4..6].copy_from_slice(&pack_rgb565(s[6], s[7], s[8]).to_le_bytes());
d[6..8].copy_from_slice(&pack_rgb565(s[9], s[10], s[11]).to_le_bytes());
d[8..10].copy_from_slice(&pack_rgb565(s[12], s[13], s[14]).to_le_bytes());
d[10..12].copy_from_slice(&pack_rgb565(s[15], s[16], s[17]).to_le_bytes());
d[12..14].copy_from_slice(&pack_rgb565(s[18], s[19], s[20]).to_le_bytes());
d[14..16].copy_from_slice(&pack_rgb565(s[21], s[22], s[23]).to_le_bytes());
}
for (s, d) in st.chunks_exact(3).zip(dt.chunks_exact_mut(2)) {
d.copy_from_slice(&pack_rgb565(s[0], s[1], s[2]).to_le_bytes());
}
Ok(pixels)
}
pub fn yuyv_to_rgb888(src: &[u8], dst: &mut [u8]) -> Result<usize> {
if src.len() % 4 != 0 {
return Err(Error::InvalidGeometry);
}
let pixels = src.len() / 2;
expect_len(dst, pixels * 3)?;
let body = pixels / 4 * 4;
let (sb, st) = src.split_at(body * 2);
let (db, dt) = dst[..pixels * 3].split_at_mut(body * 3);
for (s, d) in sb.chunks_exact(8).zip(db.chunks_exact_mut(12)) {
d[0..3].copy_from_slice(&yuv_to_rgb(s[0], s[1], s[3]));
d[3..6].copy_from_slice(&yuv_to_rgb(s[2], s[1], s[3]));
d[6..9].copy_from_slice(&yuv_to_rgb(s[4], s[5], s[7]));
d[9..12].copy_from_slice(&yuv_to_rgb(s[6], s[5], s[7]));
}
for (s, d) in st.chunks_exact(4).zip(dt.chunks_exact_mut(6)) {
d[..3].copy_from_slice(&yuv_to_rgb(s[0], s[1], s[3]));
d[3..].copy_from_slice(&yuv_to_rgb(s[2], s[1], s[3]));
}
Ok(pixels)
}
pub fn yuyv_to_rgb565(src: &[u8], dst: &mut [u8]) -> Result<usize> {
if src.len() % 4 != 0 {
return Err(Error::InvalidGeometry);
}
let pixels = src.len() / 2;
expect_len(dst, pixels * 2)?;
if let Some(dv) = as_u16_mut(&mut dst[..pixels * 2]) {
let body = pixels / 8 * 8;
let (sb, st) = src.split_at(body * 2);
let (dvb, dvt) = dv.split_at_mut(body);
for (s, d) in sb.chunks_exact(16).zip(dvb.chunks_exact_mut(8)) {
for k in 0..4 {
let (i, o) = (k * 4, k * 2);
let [r0, g0, b0] = yuv_to_rgb(s[i], s[i + 1], s[i + 3]);
let [r1, g1, b1] = yuv_to_rgb(s[i + 2], s[i + 1], s[i + 3]);
d[o] = pack_rgb565(r0, g0, b0);
d[o + 1] = pack_rgb565(r1, g1, b1);
}
}
for (s, d) in st.chunks_exact(4).zip(dvt.chunks_exact_mut(2)) {
let [r0, g0, b0] = yuv_to_rgb(s[0], s[1], s[3]);
let [r1, g1, b1] = yuv_to_rgb(s[2], s[1], s[3]);
d[0] = pack_rgb565(r0, g0, b0);
d[1] = pack_rgb565(r1, g1, b1);
}
return Ok(pixels);
}
let body = pixels / 8 * 8;
let (sb, st) = src.split_at(body * 2);
let (db, dt) = dst[..pixels * 2].split_at_mut(body * 2);
for (s, d) in sb.chunks_exact(16).zip(db.chunks_exact_mut(16)) {
for (i, o) in [(0usize, 0usize), (4, 4), (8, 8), (12, 12)] {
let [r0, g0, b0] = yuv_to_rgb(s[i], s[i + 1], s[i + 3]);
let [r1, g1, b1] = yuv_to_rgb(s[i + 2], s[i + 1], s[i + 3]);
d[o..o + 2].copy_from_slice(&pack_rgb565(r0, g0, b0).to_le_bytes());
d[o + 2..o + 4].copy_from_slice(&pack_rgb565(r1, g1, b1).to_le_bytes());
}
}
for (s, d) in st.chunks_exact(4).zip(dt.chunks_exact_mut(4)) {
let [r0, g0, b0] = yuv_to_rgb(s[0], s[1], s[3]);
let [r1, g1, b1] = yuv_to_rgb(s[2], s[1], s[3]);
d[..2].copy_from_slice(&pack_rgb565(r0, g0, b0).to_le_bytes());
d[2..].copy_from_slice(&pack_rgb565(r1, g1, b1).to_le_bytes());
}
Ok(pixels)
}
pub fn yuyv_to_gray8(src: &[u8], dst: &mut [u8]) -> Result<usize> {
if src.len() % 2 != 0 {
return Err(Error::InvalidGeometry);
}
let pixels = src.len() / 2;
expect_len(dst, pixels)?;
let body = pixels / 16 * 16;
let (sb, st) = src.split_at(body * 2);
let (db, dt) = dst[..pixels].split_at_mut(body);
for (s, d) in sb.chunks_exact(32).zip(db.chunks_exact_mut(16)) {
for k in 0..16 {
d[k] = s[k * 2];
}
}
for (s, d) in st.chunks_exact(2).zip(dt.iter_mut()) {
*d = s[0];
}
Ok(pixels)
}
pub fn downscale2x_gray8(src: &[u8], width: u32, height: u32, dst: &mut [u8]) -> Result<Geometry> {
let (w, h) = (width as usize, height as usize);
expect_len(src, w * h)?;
let (ow, oh) = (w / 2, h / 2);
let out = Geometry::new(ow as u32, oh as u32, PixelFormat::Gray8)?;
expect_len(dst, ow * oh)?;
let span = ow * 2;
for oy in 0..oh {
let r0 = &src[(2 * oy) * w..(2 * oy) * w + span];
let r1 = &src[(2 * oy + 1) * w..(2 * oy + 1) * w + span];
let drow = &mut dst[oy * ow..oy * ow + ow];
let pairs = ow / 2 * 2;
let (r0b, r0t) = r0.split_at(pairs * 2);
let (r1b, r1t) = r1.split_at(pairs * 2);
let (db, dt) = drow.split_at_mut(pairs);
for ((a, b), d) in r0b
.chunks_exact(4)
.zip(r1b.chunks_exact(4))
.zip(db.chunks_exact_mut(2))
{
let s0 = u32::from(a[0]) + u32::from(a[1]) + u32::from(b[0]) + u32::from(b[1]);
let s1 = u32::from(a[2]) + u32::from(a[3]) + u32::from(b[2]) + u32::from(b[3]);
d[0] = ((s0 + 2) / 4) as u8;
d[1] = ((s1 + 2) / 4) as u8;
}
for ((a, b), d) in r0t
.chunks_exact(2)
.zip(r1t.chunks_exact(2))
.zip(dt.iter_mut())
{
let sum = u32::from(a[0]) + u32::from(a[1]) + u32::from(b[0]) + u32::from(b[1]);
*d = ((sum + 2) / 4) as u8;
}
}
Ok(out)
}
pub fn downscale2x_rgb565(src: &[u8], width: u32, height: u32, dst: &mut [u8]) -> Result<Geometry> {
let (w, h) = (width as usize, height as usize);
expect_len(src, w * h * 2)?;
let (ow, oh) = (w / 2, h / 2);
let out = Geometry::new(ow as u32, oh as u32, PixelFormat::Rgb565)?;
expect_len(dst, ow * oh * 2)?;
if let (Some(sv), Some(dvv)) = (as_u16(src), as_u16_mut(&mut dst[..ow * oh * 2])) {
for oy in 0..oh {
let r0 = &sv[(2 * oy) * w..(2 * oy) * w + ow * 2];
let r1 = &sv[(2 * oy + 1) * w..(2 * oy + 1) * w + ow * 2];
let drow = &mut dvv[oy * ow..oy * ow + ow];
let mut c0 = r0.chunks_exact(8);
let mut c1 = r1.chunks_exact(8);
let mut cd = drow.chunks_exact_mut(4);
for ((s0, s1), d) in c0.by_ref().zip(c1.by_ref()).zip(cd.by_ref()) {
for k in 0..4 {
let (q0, q1) = (s0[k * 2], s0[k * 2 + 1]);
let (q2, q3) = (s1[k * 2], s1[k * 2 + 1]);
let r = ((r5to8(q0) + r5to8(q1) + r5to8(q2) + r5to8(q3) + 2) / 4) as u8;
let g = ((g6to8(q0) + g6to8(q1) + g6to8(q2) + g6to8(q3) + 2) / 4) as u8;
let b = ((b5to8(q0) + b5to8(q1) + b5to8(q2) + b5to8(q3) + 2) / 4) as u8;
d[k] = pack_rgb565(r, g, b);
}
}
for ((s0, s1), d) in c0
.remainder()
.chunks_exact(2)
.zip(c1.remainder().chunks_exact(2))
.zip(cd.into_remainder().iter_mut())
{
let (q0, q1, q2, q3) = (s0[0], s0[1], s1[0], s1[1]);
let r = ((r5to8(q0) + r5to8(q1) + r5to8(q2) + r5to8(q3) + 2) / 4) as u8;
let g = ((g6to8(q0) + g6to8(q1) + g6to8(q2) + g6to8(q3) + 2) / 4) as u8;
let b = ((b5to8(q0) + b5to8(q1) + b5to8(q2) + b5to8(q3) + 2) / 4) as u8;
*d = pack_rgb565(r, g, b);
}
}
return Ok(out);
}
let stride = w * 2;
let span = ow * 4; for oy in 0..oh {
let r0 = &src[(2 * oy) * stride..(2 * oy) * stride + span];
let r1 = &src[(2 * oy + 1) * stride..(2 * oy + 1) * stride + span];
let drow = &mut dst[oy * ow * 2..oy * ow * 2 + ow * 2];
for ((s0, s1), d) in r0
.chunks_exact(4)
.zip(r1.chunks_exact(4))
.zip(drow.chunks_exact_mut(2))
{
let q0 = u16::from_le_bytes([s0[0], s0[1]]);
let q1 = u16::from_le_bytes([s0[2], s0[3]]);
let q2 = u16::from_le_bytes([s1[0], s1[1]]);
let q3 = u16::from_le_bytes([s1[2], s1[3]]);
let r = ((r5to8(q0) + r5to8(q1) + r5to8(q2) + r5to8(q3) + 2) / 4) as u8;
let g = ((g6to8(q0) + g6to8(q1) + g6to8(q2) + g6to8(q3) + 2) / 4) as u8;
let b = ((b5to8(q0) + b5to8(q1) + b5to8(q2) + b5to8(q3) + 2) / 4) as u8;
d.copy_from_slice(&pack_rgb565(r, g, b).to_le_bytes());
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rgb565_pack_unpack_extremes_and_round_trip() {
assert_eq!(pack_rgb565(255, 255, 255), 0xFFFF);
assert_eq!(pack_rgb565(0, 0, 0), 0);
assert_eq!(unpack_rgb565(0xFFFF), [255, 255, 255]);
assert_eq!(unpack_rgb565(0xF800), [255, 0, 0]);
assert_eq!(unpack_rgb565(0x07E0), [0, 255, 0]);
assert_eq!(unpack_rgb565(0x001F), [0, 0, 255]);
for &(r, g, b) in &[(10u8, 200u8, 77u8), (128, 128, 128), (1, 2, 3)] {
let [r2, g2, b2] = unpack_rgb565(pack_rgb565(r, g, b));
assert!((i32::from(r) - i32::from(r2)).abs() <= 7);
assert!((i32::from(g) - i32::from(g2)).abs() <= 3);
assert!((i32::from(b) - i32::from(b2)).abs() <= 7);
}
}
#[test]
fn yuv_known_points() {
assert_eq!(yuv_to_rgb(128, 128, 128), [128, 128, 128]);
assert_eq!(yuv_to_rgb(255, 128, 128), [255, 255, 255]);
assert_eq!(yuv_to_rgb(0, 128, 128), [0, 0, 0]);
let [r, g, b] = yuv_to_rgb(76, 85, 255);
assert!(r >= 250 && g <= 3 && b <= 3, "{r} {g} {b}");
let [r, g, b] = yuv_to_rgb(29, 255, 107);
assert!(b >= 250 && r <= 3 && g <= 3, "{r} {g} {b}");
}
#[test]
fn conversions_and_sizes() {
let rgb = [255u8, 0, 0, 0, 255, 0];
let mut p = [0u8; 4];
assert_eq!(rgb888_to_rgb565(&rgb, &mut p).unwrap(), 2);
assert_eq!(p, [0x00, 0xF8, 0xE0, 0x07]);
let mut back = [0u8; 6];
assert_eq!(rgb565_to_rgb888(&p, &mut back).unwrap(), 2);
assert_eq!(back, rgb);
let mut small = [0u8; 3];
assert_eq!(
rgb565_to_rgb888(&p, &mut small),
Err(Error::BufferTooSmall { needed: 6 })
);
assert_eq!(
rgb565_to_rgb888(&p[..3], &mut back),
Err(Error::InvalidGeometry)
);
let yuyv = [128u8, 128, 255, 128];
let mut rgb = [0u8; 6];
assert_eq!(yuyv_to_rgb888(&yuyv, &mut rgb).unwrap(), 2);
assert_eq!(rgb, [128, 128, 128, 255, 255, 255]);
let mut g = [0u8; 2];
yuyv_to_gray8(&yuyv, &mut g).unwrap();
assert_eq!(g, [128, 255]);
let mut p = [0u8; 4];
yuyv_to_rgb565(&yuyv, &mut p).unwrap();
assert_eq!(&p[2..], &[0xFF, 0xFF]);
}
#[test]
fn downscale_drops_odd_edges_and_rounds_to_nearest() {
let src = [0u8, 4, 8, 12, 4, 8, 12, 16];
let mut dst = [0u8; 2];
let g = downscale2x_gray8(&src, 4, 2, &mut dst).unwrap();
assert_eq!((g.width, g.height), (2, 1));
assert_eq!(dst, [4, 12]);
let src = [10u8; 15];
let mut dst = [0u8; 2];
let g = downscale2x_gray8(&src, 5, 3, &mut dst).unwrap();
assert_eq!((g.width, g.height), (2, 1));
assert_eq!(dst, [10, 10]);
let src = [1u8, 2, 3, 4];
let mut dst = [0u8; 1];
downscale2x_gray8(&src, 2, 2, &mut dst).unwrap();
assert_eq!(dst, [3]);
let c = pack_rgb565(200, 100, 50).to_le_bytes();
let src: std::vec::Vec<u8> = c.iter().copied().cycle().take(8).collect();
let mut dst = [0u8; 2];
downscale2x_rgb565(&src, 2, 2, &mut dst).unwrap();
assert_eq!(dst, c);
let mut tiny = [0u8; 1];
assert_eq!(
downscale2x_rgb565(&src, 2, 2, &mut tiny),
Err(Error::BufferTooSmall { needed: 2 })
);
}
}