simple-render 0.2.0

a simple immediate mode wayland ui renderer inspired by clay
Documentation
use super::*;
use crate::ui::Bounds;

pub struct Canvas<'a> {
    pub(in crate::wayland) pixels: &'a mut [u8],
    pub(in crate::wayland) width: u32,
    pub(in crate::wayland) height: u32,
    pub(in crate::wayland) stride: u32,
    pub(in crate::wayland) scale: u32,
    pub(in crate::wayland) damage: Option<DamageRect>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DamageRect {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}

impl<'a> Canvas<'a> {
    pub fn from_bgra_pixels(
        pixels: &'a mut [u8],
        width: u32,
        height: u32,
        stride: u32,
        scale: u32,
    ) -> Option<Self> {
        if stride < width.checked_mul(4)? {
            return None;
        }
        let required = if height == 0 {
            0
        } else {
            stride
                .checked_mul(height.saturating_sub(1))?
                .checked_add(width.checked_mul(4)?)?
        };
        if pixels.len() < required as usize {
            return None;
        }

        Some(Self {
            pixels,
            width,
            height,
            stride,
            scale: scale.max(1),
            damage: None,
        })
    }

    pub fn pixels(&self) -> &[u8] {
        self.pixels
    }

    pub fn pixels_mut(&mut self) -> &mut [u8] {
        self.damage_all();
        self.pixels
    }

    pub fn width(&self) -> u32 {
        self.width
    }

    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn stride(&self) -> u32 {
        self.stride
    }

    pub fn scale(&self) -> u32 {
        self.scale
    }

    pub fn clear(&mut self, rgba: [u8; 4]) {
        let bgra = rgba_to_bgra(rgba);
        for pixel in self.pixels.chunks_exact_mut(4) {
            pixel.copy_from_slice(&bgra);
        }
        self.damage_all();
    }

    pub fn put_pixel(&mut self, x: u32, y: u32, rgba: [u8; 4]) {
        if x >= self.width || y >= self.height {
            return;
        }

        let index = ((y * self.stride) + (x * 4)) as usize;
        self.pixels[index..index + 4].copy_from_slice(&rgba_to_bgra(rgba));
        self.damage_rect(DamageRect::new(x, y, 1, 1));
    }

    pub fn blend_pixel(&mut self, x: u32, y: u32, rgba: [u8; 4]) {
        if x >= self.width || y >= self.height || rgba[3] == 0 {
            return;
        }

        let index = ((y * self.stride) + (x * 4)) as usize;
        let background = bgra_to_rgba([
            self.pixels[index],
            self.pixels[index + 1],
            self.pixels[index + 2],
            self.pixels[index + 3],
        ]);
        self.pixels[index..index + 4].copy_from_slice(&rgba_to_bgra(blend_color(background, rgba)));
        self.damage_rect(DamageRect::new(x, y, 1, 1));
    }

    pub fn damage(&self) -> Option<DamageRect> {
        self.damage
    }

    pub fn damage_rect(&mut self, rect: DamageRect) {
        let Some(rect) = rect.clamp(self.width, self.height) else {
            return;
        };
        self.damage = Some(match self.damage {
            Some(damage) => damage.union(rect),
            None => rect,
        });
    }

    pub fn damage_all(&mut self) {
        self.damage_rect(DamageRect::new(0, 0, self.width, self.height));
    }
}

impl fmt::Debug for Canvas<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Canvas")
            .field("width", &self.width)
            .field("height", &self.height)
            .field("stride", &self.stride)
            .finish_non_exhaustive()
    }
}

impl DamageRect {
    pub const fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    pub fn from_bounds(bounds: Bounds) -> Self {
        Self::new(bounds.x, bounds.y, bounds.width, bounds.height)
    }

    pub fn from_bounds_pair(previous: Bounds, current: Bounds) -> Self {
        Self::from_bounds(previous.union(current))
    }

    pub fn include_bounds(self, bounds: Bounds) -> Self {
        self.union(Self::from_bounds(bounds))
    }

    pub fn right(self) -> u32 {
        self.x.saturating_add(self.width)
    }

    pub fn bottom(self) -> u32 {
        self.y.saturating_add(self.height)
    }

    pub fn union(self, other: Self) -> Self {
        let x = self.x.min(other.x);
        let y = self.y.min(other.y);
        let right = self.right().max(other.right());
        let bottom = self.bottom().max(other.bottom());
        Self {
            x,
            y,
            width: right.saturating_sub(x),
            height: bottom.saturating_sub(y),
        }
    }

    pub fn clamp(self, width: u32, height: u32) -> Option<Self> {
        let x = self.x.min(width);
        let y = self.y.min(height);
        let right = self.right().min(width);
        let bottom = self.bottom().min(height);
        let width = right.saturating_sub(x);
        let height = bottom.saturating_sub(y);
        (width > 0 && height > 0).then_some(Self {
            x,
            y,
            width,
            height,
        })
    }
}

fn rgba_to_bgra([red, green, blue, alpha]: [u8; 4]) -> [u8; 4] {
    [blue, green, red, alpha]
}

fn bgra_to_rgba([blue, green, red, alpha]: [u8; 4]) -> [u8; 4] {
    [red, green, blue, alpha]
}

pub(in crate::wayland) fn blend_color(background: [u8; 4], foreground: [u8; 4]) -> [u8; 4] {
    let foreground_alpha = u32::from(foreground[3]);
    if foreground_alpha == 0 {
        return background;
    }
    if foreground_alpha == 255 {
        return foreground;
    }

    let background_alpha = u32::from(background[3]);
    let inverse_foreground_alpha = 255 - foreground_alpha;
    let output_alpha = foreground_alpha + background_alpha * inverse_foreground_alpha / 255;
    if output_alpha == 0 {
        return [0, 0, 0, 0];
    }

    let blend_component = |foreground: u8, background: u8| {
        let foreground = u32::from(foreground) * foreground_alpha;
        let background = u32::from(background) * background_alpha * inverse_foreground_alpha / 255;
        ((foreground + background) / output_alpha).min(255) as u8
    };

    [
        blend_component(foreground[0], background[0]),
        blend_component(foreground[1], background[1]),
        blend_component(foreground[2], background[2]),
        output_alpha.min(255) as u8,
    ]
}