standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
//! The graphics model: RGBA8 pixels, rows top to bottom.

use core::cell::RefMut;

use super::{Inner, Pixels, Surface, as_bytes_mut};
use crate::colour::Rgba;
use crate::geometry::Rect;

/// Pixel writes the drawing helpers share: bounds-checked, recording
/// dirty rectangles.
pub(crate) struct Canvas<'a> {
    pub(crate) slot: &'a mut [u32],
    pub(crate) width: u32,
    pub(crate) height: u32,
    pub(crate) dirty: &'a mut super::DirtyRects,
}

impl Canvas<'_> {
    /// Blends `colour` at `(x, y)` with `coverage` (0 to 255); outside the
    /// surface nothing happens. Records no dirty rectangle: callers record
    /// their bounding box once.
    #[inline]
    pub(crate) fn blend(&mut self, x: i64, y: i64, colour: Rgba, coverage: u8) {
        if x < 0 || y < 0 || x >= i64::from(self.width) || y >= i64::from(self.height) {
            return;
        }
        let index = y as usize * self.width as usize + x as usize;
        let below = Rgba::from_word(self.slot[index]);
        self.slot[index] = colour.over(below, coverage).to_word();
    }

    /// Records the clipped box from `(x0, y0)` to `(x1, y1)`, exclusive.
    pub(crate) fn touch(&mut self, x0: i64, y0: i64, x1: i64, y1: i64) {
        let clamp_x = |v: i64| v.clamp(0, i64::from(self.width)) as u32;
        let clamp_y = |v: i64| v.clamp(0, i64::from(self.height)) as u32;
        let (left, right) = (clamp_x(x0), clamp_x(x1));
        let (top, bottom) = (clamp_y(y0), clamp_y(y1));
        if left < right && top < bottom {
            self.dirty.add(
                Rect::new(left, top, right - left, bottom - top),
                self.width,
                self.height,
            );
        }
    }

    /// Fills the clipped box with `colour` (blended when not opaque).
    pub(crate) fn fill(&mut self, x0: i64, y0: i64, x1: i64, y1: i64, colour: Rgba) {
        let left = x0.clamp(0, i64::from(self.width)) as usize;
        let right = x1.clamp(0, i64::from(self.width)) as usize;
        let top = y0.clamp(0, i64::from(self.height)) as usize;
        let bottom = y1.clamp(0, i64::from(self.height)) as usize;
        if left >= right || top >= bottom {
            return;
        }
        let stride = self.width as usize;
        for y in top..bottom {
            let row = &mut self.slot[y * stride + left..y * stride + right];
            if colour.a == 255 {
                row.fill(colour.to_word());
            } else {
                for word in row {
                    *word = colour.over(Rgba::from_word(*word), 255).to_word();
                }
            }
        }
        self.touch(x0, y0, x1, y1);
    }
}

impl Inner {
    pub(crate) fn canvas(&mut self) -> Option<Canvas<'_>> {
        let (slot, layout, dirty) = self.back()?;
        Some(Canvas {
            slot,
            width: layout.geometry.px_w,
            height: layout.geometry.px_h,
            dirty,
        })
    }
}

impl Surface<Pixels> {
    /// Runs `draw` with the drawing canvas, when the surface has pixels.
    pub(crate) fn with_canvas<R>(&mut self, draw: impl FnOnce(&mut Canvas<'_>) -> R) -> Option<R> {
        let mut inner = self.inner.borrow_mut();
        let mut canvas = inner.canvas()?;
        if canvas.width == 0 || canvas.height == 0 {
            return None;
        }
        Some(draw(&mut canvas))
    }

    /// Sets one pixel (no blending). Outside the surface nothing happens.
    pub fn set_pixel(&mut self, x: u32, y: u32, colour: Rgba) {
        self.with_canvas(|canvas| {
            if x < canvas.width && y < canvas.height {
                canvas.slot[y as usize * canvas.width as usize + x as usize] = colour.to_word();
                canvas.touch(
                    i64::from(x),
                    i64::from(y),
                    i64::from(x) + 1,
                    i64::from(y) + 1,
                );
            }
        });
    }

    /// Draws `colour` over one pixel with its alpha.
    pub fn blend_pixel(&mut self, x: i32, y: i32, colour: Rgba) {
        self.with_canvas(|canvas| {
            let (x, y) = (i64::from(x), i64::from(y));
            canvas.blend(x, y, colour, 255);
            canvas.touch(x, y, x + 1, y + 1);
        });
    }

    /// The pixel at `(x, y)` as it will be committed next.
    pub fn pixel(&self, x: u32, y: u32) -> Option<Rgba> {
        let mut inner = self.inner.borrow_mut();
        let canvas = inner.canvas()?;
        (x < canvas.width && y < canvas.height)
            .then(|| Rgba::from_word(canvas.slot[y as usize * canvas.width as usize + x as usize]))
    }

    /// Fills the whole surface with `colour`. The previous frame is not
    /// copied forward.
    pub fn clear(&mut self, colour: Rgba) {
        let mut inner = self.inner.borrow_mut();
        if let Some((slot, _)) = inner.back_discard() {
            slot.fill(colour.to_word());
        }
    }

    /// Copies an RGBA8 image of `width` by `height` pixels (`width * 4`
    /// bytes per row) to `(x, y)`, clipped to the surface; no blending. A
    /// short `rgba` copies the rows it holds. [`Surface::blit_clipped`]
    /// clips to a rectangle too and can blend.
    pub fn blit(&mut self, x: i32, y: i32, width: u32, height: u32, rgba: &[u8]) {
        let everything = crate::draw::Area::new(i32::MIN, i32::MIN, u32::MAX, u32::MAX);
        self.blit_clipped(
            x,
            y,
            crate::draw::Image::new(width, height, rgba),
            everything,
            false,
        );
    }

    /// The whole frame being painted, as RGBA8 bytes, holding what was
    /// last committed. The whole surface is marked dirty; for a small
    /// change prefer the drawing methods or [`Surface::mark_dirty`] after
    /// [`Surface::pixels_untracked`]. Empty while the surface has no pixels.
    pub fn pixels_mut(&mut self) -> RefMut<'_, [u8]> {
        let mut inner = self.inner.borrow_mut();
        let (width, height) = inner.extent();
        inner.mark_dirty(Rect::new(0, 0, width, height));
        RefMut::map(inner, |inner| match inner.back() {
            Some((slot, _, _)) => as_bytes_mut(slot),
            None => &mut [],
        })
    }

    /// As [`Surface::pixels_mut`], recording nothing: mark what you change
    /// with [`Surface::mark_dirty`].
    pub fn pixels_untracked(&mut self) -> RefMut<'_, [u8]> {
        RefMut::map(self.inner.borrow_mut(), |inner| match inner.back() {
            Some((slot, _, _)) => as_bytes_mut(slot),
            None => &mut [],
        })
    }

    /// The whole frame for a full repaint: its contents are stale (what
    /// was committed two frames ago), nothing is copied forward, and the
    /// whole surface is dirty. The cheapest way to draw an animation that
    /// repaints every pixel.
    pub fn repaint_all(&mut self) -> RefMut<'_, [u8]> {
        RefMut::map(self.inner.borrow_mut(), |inner| {
            match inner.back_discard() {
                Some((slot, _)) => as_bytes_mut(slot),
                None => &mut [],
            }
        })
    }
}