rlvgl-platform 0.1.7

Platform backends, blitters, and hardware integration for rlvgl.
Documentation
//! CPU-based fallback blitter.
//!
//! Provides a pure software implementation of the [`Blitter`] trait used for
//! testing and as a baseline on platforms lacking acceleration.

use crate::blit::{BlitCaps, Blitter, PixelFmt, Rect, Surface};

/// Blitter that performs all operations on the CPU using scalar loops.
pub struct CpuBlitter;

impl CpuBlitter {
    fn pixel_size(fmt: PixelFmt) -> usize {
        match fmt {
            PixelFmt::Argb8888 => 4,
            PixelFmt::Rgb565 => 2,
            PixelFmt::L8 | PixelFmt::A8 => 1,
            PixelFmt::A4 => 1,
        }
    }

    fn argb8888_to_rgb565(c: u32) -> u16 {
        let r = ((c >> 16) & 0xff) as u16;
        let g = ((c >> 8) & 0xff) as u16;
        let b = (c & 0xff) as u16;
        ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
    }

    fn rgb565_to_argb8888(c: u16) -> u32 {
        let r = ((c >> 11) & 0x1f) as u32;
        let g = ((c >> 5) & 0x3f) as u32;
        let b = (c & 0x1f) as u32;
        0xff00_0000 | ((r << 3) << 16) | ((g << 2) << 8) | (b << 3)
    }

    fn read_pixel(surf: &Surface, x: i32, y: i32) -> u32 {
        let bpp = Self::pixel_size(surf.format);
        let offset = match surf.format {
            PixelFmt::A4 => (y as usize * surf.stride) + (x as usize / 2),
            _ => (y as usize * surf.stride) + (x as usize * bpp),
        };
        match surf.format {
            PixelFmt::Argb8888 => {
                let bytes: [u8; 4] = surf.buf[offset..offset + 4].try_into().unwrap();
                u32::from_le_bytes(bytes)
            }
            PixelFmt::Rgb565 => {
                let bytes: [u8; 2] = surf.buf[offset..offset + 2].try_into().unwrap();
                Self::rgb565_to_argb8888(u16::from_le_bytes(bytes))
            }
            PixelFmt::L8 => {
                let v = surf.buf[offset] as u32;
                0xff00_0000 | (v << 16) | (v << 8) | v
            }
            PixelFmt::A8 => {
                let a = surf.buf[offset] as u32;
                a << 24
            }
            PixelFmt::A4 => {
                let byte = surf.buf[offset];
                let nib = if x & 1 == 0 { byte >> 4 } else { byte & 0x0f } as u32;
                let a = (nib << 4) | nib;
                a << 24
            }
        }
    }

    fn write_pixel(surf: &mut Surface, x: i32, y: i32, color: u32) {
        let bpp = Self::pixel_size(surf.format);
        let offset = match surf.format {
            PixelFmt::A4 => (y as usize * surf.stride) + (x as usize / 2),
            _ => (y as usize * surf.stride) + (x as usize * bpp),
        };
        match surf.format {
            PixelFmt::Argb8888 => {
                surf.buf[offset..offset + 4].copy_from_slice(&color.to_le_bytes());
            }
            PixelFmt::Rgb565 => {
                let c = Self::argb8888_to_rgb565(color);
                surf.buf[offset..offset + 2].copy_from_slice(&c.to_le_bytes());
            }
            PixelFmt::L8 => {
                let r = (color >> 16) & 0xff;
                let g = (color >> 8) & 0xff;
                let b = color & 0xff;
                surf.buf[offset] = ((r + g + b) / 3) as u8;
            }
            PixelFmt::A8 => {
                surf.buf[offset] = (color >> 24) as u8;
            }
            PixelFmt::A4 => {
                let a = (color >> 24) as u8 >> 4;
                let byte = &mut surf.buf[offset];
                if x & 1 == 0 {
                    *byte = (*byte & 0x0f) | (a << 4);
                } else {
                    *byte = (*byte & 0xf0) | a;
                }
            }
        }
    }

    fn blend_pixel(src: u32, dst: u32) -> u32 {
        let sa = (src >> 24) & 0xff;
        let inv = 255 - sa;
        let sr = (src >> 16) & 0xff;
        let sg = (src >> 8) & 0xff;
        let sb = src & 0xff;
        let dr = (dst >> 16) & 0xff;
        let dg = (dst >> 8) & 0xff;
        let db = dst & 0xff;
        let r = (sr * sa + dr * inv) / 255;
        let g = (sg * sa + dg * inv) / 255;
        let b = (sb * sa + db * inv) / 255;
        0xff00_0000 | (r << 16) | (g << 8) | b
    }
}

impl Blitter for CpuBlitter {
    fn caps(&self) -> BlitCaps {
        BlitCaps::FILL | BlitCaps::BLIT | BlitCaps::BLEND | BlitCaps::PFC
    }

    fn fill(&mut self, dst: &mut Surface, area: Rect, color: u32) {
        // Clamp to surface bounds to prevent out-of-bounds writes.
        let sx = if area.x < 0 { 0i32 } else { area.x };
        let sy = if area.y < 0 { 0i32 } else { area.y };
        let ex = (area.x + area.w as i32).min(dst.width as i32);
        let ey = (area.y + area.h as i32).min(dst.height as i32);
        if sx >= ex || sy >= ey {
            return;
        }
        let cw = (ex - sx) as u32;

        match dst.format {
            PixelFmt::Argb8888 => {
                let bpp = 4usize;
                for row in sy..ey {
                    let start = row as usize * dst.stride + sx as usize * bpp;
                    let end = start + cw as usize * bpp;
                    if end > dst.buf.len() {
                        break;
                    }
                    let line = &mut dst.buf[start..end];
                    for px in line.chunks_exact_mut(4) {
                        px.copy_from_slice(&color.to_le_bytes());
                    }
                }
            }
            PixelFmt::Rgb565 => {
                let c = Self::argb8888_to_rgb565(color);
                let bpp = 2usize;
                for row in sy..ey {
                    let start = row as usize * dst.stride + sx as usize * bpp;
                    let end = start + cw as usize * bpp;
                    if end > dst.buf.len() {
                        break;
                    }
                    let line = &mut dst.buf[start..end];
                    for px in line.chunks_exact_mut(2) {
                        px.copy_from_slice(&c.to_le_bytes());
                    }
                }
            }
            _ => {
                for y in sy..ey {
                    for x in sx..ex {
                        Self::write_pixel(dst, x, y, color);
                    }
                }
            }
        }
    }

    fn blit(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32)) {
        // Clip source rect to destination bounds.
        let clip_x = if dst_pos.0 < 0 { -dst_pos.0 } else { 0 };
        let clip_y = if dst_pos.1 < 0 { -dst_pos.1 } else { 0 };
        let dst_x = dst_pos.0.max(0);
        let dst_y = dst_pos.1.max(0);
        let w = (src_area.w as i32 - clip_x).min(dst.width as i32 - dst_x);
        let h = (src_area.h as i32 - clip_y).min(dst.height as i32 - dst_y);
        if w <= 0 || h <= 0 {
            return;
        }

        let src_x0 = src_area.x + clip_x;
        let src_y0 = src_area.y + clip_y;

        if src.format == dst.format {
            let bpp = Self::pixel_size(src.format);
            for row in 0..h {
                let src_start = ((src_y0 + row) as usize * src.stride) + (src_x0 as usize * bpp);
                let dst_start = ((dst_y + row) as usize * dst.stride) + (dst_x as usize * bpp);
                let len = w as usize * bpp;
                dst.buf[dst_start..dst_start + len]
                    .copy_from_slice(&src.buf[src_start..src_start + len]);
            }
            return;
        }

        for row in 0..h {
            for col in 0..w {
                let px = Self::read_pixel(src, src_x0 + col, src_y0 + row);
                Self::write_pixel(dst, dst_x + col, dst_y + row, px);
            }
        }
    }

    fn blend(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32)) {
        // Clip source rect to destination bounds.
        let clip_x = if dst_pos.0 < 0 { -dst_pos.0 } else { 0 };
        let clip_y = if dst_pos.1 < 0 { -dst_pos.1 } else { 0 };
        let dst_x = dst_pos.0.max(0);
        let dst_y = dst_pos.1.max(0);
        let w = (src_area.w as i32 - clip_x).min(dst.width as i32 - dst_x);
        let h = (src_area.h as i32 - clip_y).min(dst.height as i32 - dst_y);
        if w <= 0 || h <= 0 {
            return;
        }

        let src_x0 = src_area.x + clip_x;
        let src_y0 = src_area.y + clip_y;

        for row in 0..h {
            for col in 0..w {
                let s = Self::read_pixel(src, src_x0 + col, src_y0 + row);
                let d = Self::read_pixel(dst, dst_x + col, dst_y + row);
                let out = Self::blend_pixel(s, d);
                Self::write_pixel(dst, dst_x + col, dst_y + row, out);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use heapless::Vec;

    #[test]
    fn fill_argb8888() {
        let mut buf = [0u8; 64];
        let mut surf = Surface::new(&mut buf, 16, PixelFmt::Argb8888, 4, 4);
        let mut blit = CpuBlitter;
        blit.fill(
            &mut surf,
            Rect {
                x: 0,
                y: 0,
                w: 4,
                h: 4,
            },
            0x11223344,
        );
        for px in buf.chunks_exact(4) {
            assert_eq!(u32::from_le_bytes(px.try_into().unwrap()), 0x11223344);
        }
    }

    #[test]
    fn blit_argb8888_to_rgb565() {
        let mut src_buf = [0u8; 16];
        let mut dst_buf = [0u8; 32];
        let src_colors = [0xff0000ffu32, 0xff00ff00, 0xffff0000, 0xffffffff];
        for (i, chunk) in src_buf.chunks_exact_mut(4).enumerate() {
            chunk.copy_from_slice(&src_colors[i].to_le_bytes());
        }
        let src = Surface::new(&mut src_buf, 8, PixelFmt::Argb8888, 2, 2);
        let mut dst = Surface::new(&mut dst_buf, 8, PixelFmt::Rgb565, 4, 4);
        let mut blit = CpuBlitter;
        blit.blit(
            &src,
            Rect {
                x: 0,
                y: 0,
                w: 2,
                h: 2,
            },
            &mut dst,
            (1, 1),
        );
        let expected: Vec<u16, 4> = Vec::from_slice(&[
            CpuBlitter::argb8888_to_rgb565(0xff0000ff),
            CpuBlitter::argb8888_to_rgb565(0xff00ff00),
            CpuBlitter::argb8888_to_rgb565(0xffff0000),
            CpuBlitter::argb8888_to_rgb565(0xffffffff),
        ])
        .unwrap();
        for (i, row) in (1..3).enumerate() {
            for (j, col) in (1..3).enumerate() {
                let idx = row * 8 + col * 2;
                let val = u16::from_le_bytes([dst_buf[idx], dst_buf[idx + 1]]);
                assert_eq!(val, expected[i * 2 + j]);
            }
        }
    }

    #[test]
    fn blend_argb8888() {
        let mut src_buf = [0u8; 16];
        let mut dst_buf = [0u8; 16];
        for chunk in src_buf.chunks_exact_mut(4) {
            chunk.copy_from_slice(&0x80ff0000u32.to_le_bytes());
        }
        for chunk in dst_buf.chunks_exact_mut(4) {
            chunk.copy_from_slice(&0xff000000u32.to_le_bytes());
        }
        let src = Surface::new(&mut src_buf, 8, PixelFmt::Argb8888, 2, 2);
        let mut dst = Surface::new(&mut dst_buf, 8, PixelFmt::Argb8888, 2, 2);
        let mut blit = CpuBlitter;
        blit.blend(
            &src,
            Rect {
                x: 0,
                y: 0,
                w: 2,
                h: 2,
            },
            &mut dst,
            (0, 0),
        );
        for chunk in dst_buf.chunks_exact(4) {
            assert_eq!(u32::from_le_bytes(chunk.try_into().unwrap()), 0xff800000);
        }
    }
}