use rlvgl_platform::blit::{PixelFmt, Surface};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum JumboOrientation {
Horizontal,
Vertical,
}
pub struct JumboBuffer<'a> {
pub buf: &'a mut [u8],
pub stride: usize,
pub visible_w: u32,
pub visible_h: u32,
pub scale: u8,
pub orientation: JumboOrientation,
pub format: PixelFmt,
}
impl<'a> JumboBuffer<'a> {
#[inline]
pub fn new(
buf: &'a mut [u8],
stride: usize,
visible_w: u32,
visible_h: u32,
scale: u8,
orientation: JumboOrientation,
format: PixelFmt,
) -> Self {
Self {
buf,
stride,
visible_w,
visible_h,
scale,
orientation,
format,
}
}
#[inline]
pub const fn long_axis_pixels(&self) -> u32 {
let base = match self.orientation {
JumboOrientation::Horizontal => self.visible_w,
JumboOrientation::Vertical => self.visible_h,
};
base.saturating_mul(self.scale as u32)
}
#[inline]
pub const fn short_axis_pixels(&self) -> u32 {
match self.orientation {
JumboOrientation::Horizontal => self.visible_h,
JumboOrientation::Vertical => self.visible_w,
}
}
pub fn as_surface(&mut self) -> Surface<'_> {
let (w, h) = match self.orientation {
JumboOrientation::Horizontal => (self.visible_w * self.scale as u32, self.visible_h),
JumboOrientation::Vertical => (self.visible_w, self.visible_h * self.scale as u32),
};
Surface::new(self.buf, self.stride, self.format, w, h)
}
pub fn window_range(&self, start: u32, len: u32) -> ((u32, u32), (u32, u32)) {
let total = self.long_axis_pixels();
if total == 0 {
return ((0, 0), (0, 0));
}
let start = start % total;
let end = start.saturating_add(len);
if end <= total {
((start, len), (0, 0))
} else {
let first_len = total - start;
let second_len = len - first_len;
((start, first_len), (0, second_len))
}
}
pub fn mirror_tail(&mut self) {
if self.scale < 2 {
return;
}
let base = match self.orientation {
JumboOrientation::Horizontal => self.visible_w as usize,
JumboOrientation::Vertical => self.visible_h as usize,
};
if base == 0 {
return;
}
match self.orientation {
JumboOrientation::Vertical => {
let head_bytes = base * self.stride;
let total = base * self.scale as usize * self.stride;
if head_bytes == 0 || total <= head_bytes {
return;
}
let mut written = head_bytes;
while written < total {
let take = (total - written).min(head_bytes);
self.buf.copy_within(0..take, written);
written += take;
}
}
JumboOrientation::Horizontal => {
let bpp = match self.format {
PixelFmt::Argb8888 => 4,
PixelFmt::Rgb565 => 2,
PixelFmt::L8 | PixelFmt::A8 => 1,
PixelFmt::A4 => 1, };
let head_bytes_per_row = base * bpp;
let row_count = self.visible_h as usize;
for y in 0..row_count {
let row_start = y * self.stride;
let row_end = row_start + self.stride;
if row_end > self.buf.len() {
break;
}
let row = &mut self.buf[row_start..row_end];
let mut written = head_bytes_per_row;
while written < row.len() {
let take = (row.len() - written).min(head_bytes_per_row);
row.copy_within(0..take, written);
written += take;
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn long_axis_vertical_doubles_height() {
let mut buf = vec![0u8; 16 * 8 * 4];
let j = JumboBuffer::new(
&mut buf,
16 * 4,
16,
4,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
assert_eq!(j.long_axis_pixels(), 8);
assert_eq!(j.short_axis_pixels(), 16);
}
#[test]
fn long_axis_horizontal_doubles_width() {
let mut buf = vec![0u8; 32 * 4 * 4];
let j = JumboBuffer::new(
&mut buf,
32 * 4,
16,
4,
2,
JumboOrientation::Horizontal,
PixelFmt::Argb8888,
);
assert_eq!(j.long_axis_pixels(), 32);
assert_eq!(j.short_axis_pixels(), 4);
}
#[test]
fn window_range_does_not_wrap() {
let mut buf = vec![0u8; 4 * 8 * 4];
let j = JumboBuffer::new(
&mut buf,
4 * 4,
4,
4,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
let (a, b) = j.window_range(0, 4);
assert_eq!(a, (0, 4));
assert_eq!(b, (0, 0));
}
#[test]
fn window_range_wraps_at_end() {
let mut buf = vec![0u8; 4 * 8 * 4];
let j = JumboBuffer::new(
&mut buf,
4 * 4,
4,
4,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
let (a, b) = j.window_range(6, 4);
assert_eq!(a, (6, 2));
assert_eq!(b, (0, 2));
}
#[test]
fn window_range_modulo_start() {
let mut buf = vec![0u8; 4 * 4 * 4];
let j = JumboBuffer::new(
&mut buf,
4 * 4,
4,
4,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
let (a, b) = j.window_range(8, 4);
assert_eq!(a, (0, 4));
assert_eq!(b, (0, 0));
}
#[test]
fn as_surface_reports_full_jumbo_extent() {
let mut buf = vec![0u8; 4 * 8 * 4];
let mut j = JumboBuffer::new(
&mut buf,
4 * 4,
4,
4,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
let s = j.as_surface();
assert_eq!(s.width, 4);
assert_eq!(s.height, 8);
assert_eq!(s.stride, 16);
}
#[test]
fn mirror_tail_vertical_copies_head_to_tail() {
let stride = 2 * 4;
let mut buf = vec![0u8; stride * 4];
for b in &mut buf[0..stride] {
*b = 0xAA;
}
for b in &mut buf[stride..stride * 2] {
*b = 0xBB;
}
let mut j = JumboBuffer::new(
&mut buf,
stride,
2,
2,
2,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
j.mirror_tail();
assert!(buf[stride * 2..stride * 3].iter().all(|&b| b == 0xAA));
assert!(buf[stride * 3..stride * 4].iter().all(|&b| b == 0xBB));
}
#[test]
fn mirror_tail_horizontal_copies_head_to_tail_per_row() {
let stride = 8 * 4;
let visible_w = 4usize;
let mut buf = vec![0u8; stride * 2];
for y in 0..2 {
let row_start = y * stride;
for x in 0..visible_w {
let off = row_start + x * 4;
for k in 0..4 {
buf[off + k] = if y == 0 { 0xCC } else { 0xDD };
}
}
}
let mut j = JumboBuffer::new(
&mut buf,
stride,
4,
2,
2,
JumboOrientation::Horizontal,
PixelFmt::Argb8888,
);
j.mirror_tail();
for y in 0..2 {
let row_start = y * stride;
let expected = if y == 0 { 0xCC } else { 0xDD };
for x in visible_w..(visible_w * 2) {
let off = row_start + x * 4;
for k in 0..4 {
assert_eq!(buf[off + k], expected, "row {y} col {x} byte {k}");
}
}
}
}
#[test]
fn mirror_tail_scale_one_is_no_op() {
let stride = 4 * 4;
let mut buf = vec![0xAA; stride * 4];
let mut j = JumboBuffer::new(
&mut buf,
stride,
4,
4,
1,
JumboOrientation::Vertical,
PixelFmt::Argb8888,
);
j.mirror_tail();
assert!(buf.iter().all(|&b| b == 0xAA));
}
}