Skip to main content

standard_plugin/surface/
pixels.rs

1//! The graphics model: RGBA8 pixels, rows top to bottom.
2
3use core::cell::RefMut;
4
5use super::{Inner, Pixels, Surface, as_bytes_mut};
6use crate::colour::Rgba;
7use crate::geometry::Rect;
8
9/// Pixel writes the drawing helpers share: bounds-checked, recording
10/// dirty rectangles.
11pub(crate) struct Canvas<'a> {
12    pub(crate) slot: &'a mut [u32],
13    pub(crate) width: u32,
14    pub(crate) height: u32,
15    pub(crate) dirty: &'a mut super::DirtyRects,
16}
17
18impl Canvas<'_> {
19    /// Blends `colour` at `(x, y)` with `coverage` (0 to 255); outside the
20    /// surface nothing happens. Records no dirty rectangle: callers record
21    /// their bounding box once.
22    #[inline]
23    pub(crate) fn blend(&mut self, x: i64, y: i64, colour: Rgba, coverage: u8) {
24        if x < 0 || y < 0 || x >= i64::from(self.width) || y >= i64::from(self.height) {
25            return;
26        }
27        let index = y as usize * self.width as usize + x as usize;
28        let below = Rgba::from_word(self.slot[index]);
29        self.slot[index] = colour.over(below, coverage).to_word();
30    }
31
32    /// Records the clipped box from `(x0, y0)` to `(x1, y1)`, exclusive.
33    pub(crate) fn touch(&mut self, x0: i64, y0: i64, x1: i64, y1: i64) {
34        let clamp_x = |v: i64| v.clamp(0, i64::from(self.width)) as u32;
35        let clamp_y = |v: i64| v.clamp(0, i64::from(self.height)) as u32;
36        let (left, right) = (clamp_x(x0), clamp_x(x1));
37        let (top, bottom) = (clamp_y(y0), clamp_y(y1));
38        if left < right && top < bottom {
39            self.dirty.add(
40                Rect::new(left, top, right - left, bottom - top),
41                self.width,
42                self.height,
43            );
44        }
45    }
46
47    /// Fills the clipped box with `colour` (blended when not opaque).
48    pub(crate) fn fill(&mut self, x0: i64, y0: i64, x1: i64, y1: i64, colour: Rgba) {
49        let left = x0.clamp(0, i64::from(self.width)) as usize;
50        let right = x1.clamp(0, i64::from(self.width)) as usize;
51        let top = y0.clamp(0, i64::from(self.height)) as usize;
52        let bottom = y1.clamp(0, i64::from(self.height)) as usize;
53        if left >= right || top >= bottom {
54            return;
55        }
56        let stride = self.width as usize;
57        for y in top..bottom {
58            let row = &mut self.slot[y * stride + left..y * stride + right];
59            if colour.a == 255 {
60                row.fill(colour.to_word());
61            } else {
62                for word in row {
63                    *word = colour.over(Rgba::from_word(*word), 255).to_word();
64                }
65            }
66        }
67        self.touch(x0, y0, x1, y1);
68    }
69}
70
71impl Inner {
72    pub(crate) fn canvas(&mut self) -> Option<Canvas<'_>> {
73        let (slot, layout, dirty) = self.back()?;
74        Some(Canvas {
75            slot,
76            width: layout.geometry.px_w,
77            height: layout.geometry.px_h,
78            dirty,
79        })
80    }
81}
82
83impl Surface<Pixels> {
84    /// Runs `draw` with the drawing canvas, when the surface has pixels.
85    pub(crate) fn with_canvas<R>(&mut self, draw: impl FnOnce(&mut Canvas<'_>) -> R) -> Option<R> {
86        let mut inner = self.inner.borrow_mut();
87        let mut canvas = inner.canvas()?;
88        if canvas.width == 0 || canvas.height == 0 {
89            return None;
90        }
91        Some(draw(&mut canvas))
92    }
93
94    /// Sets one pixel (no blending). Outside the surface nothing happens.
95    pub fn set_pixel(&mut self, x: u32, y: u32, colour: Rgba) {
96        self.with_canvas(|canvas| {
97            if x < canvas.width && y < canvas.height {
98                canvas.slot[y as usize * canvas.width as usize + x as usize] = colour.to_word();
99                canvas.touch(
100                    i64::from(x),
101                    i64::from(y),
102                    i64::from(x) + 1,
103                    i64::from(y) + 1,
104                );
105            }
106        });
107    }
108
109    /// Draws `colour` over one pixel with its alpha.
110    pub fn blend_pixel(&mut self, x: i32, y: i32, colour: Rgba) {
111        self.with_canvas(|canvas| {
112            let (x, y) = (i64::from(x), i64::from(y));
113            canvas.blend(x, y, colour, 255);
114            canvas.touch(x, y, x + 1, y + 1);
115        });
116    }
117
118    /// The pixel at `(x, y)` as it will be committed next.
119    pub fn pixel(&self, x: u32, y: u32) -> Option<Rgba> {
120        let mut inner = self.inner.borrow_mut();
121        let canvas = inner.canvas()?;
122        (x < canvas.width && y < canvas.height)
123            .then(|| Rgba::from_word(canvas.slot[y as usize * canvas.width as usize + x as usize]))
124    }
125
126    /// Fills the whole surface with `colour`. The previous frame is not
127    /// copied forward.
128    pub fn clear(&mut self, colour: Rgba) {
129        let mut inner = self.inner.borrow_mut();
130        if let Some((slot, _)) = inner.back_discard() {
131            slot.fill(colour.to_word());
132        }
133    }
134
135    /// Copies an RGBA8 image of `width` by `height` pixels (`width * 4`
136    /// bytes per row) to `(x, y)`, clipped to the surface; no blending. A
137    /// short `rgba` copies the rows it holds. [`Surface::blit_clipped`]
138    /// clips to a rectangle too and can blend.
139    pub fn blit(&mut self, x: i32, y: i32, width: u32, height: u32, rgba: &[u8]) {
140        let everything = crate::draw::Area::new(i32::MIN, i32::MIN, u32::MAX, u32::MAX);
141        self.blit_clipped(
142            x,
143            y,
144            crate::draw::Image::new(width, height, rgba),
145            everything,
146            false,
147        );
148    }
149
150    /// The whole frame being painted, as RGBA8 bytes, holding what was
151    /// last committed. The whole surface is marked dirty; for a small
152    /// change prefer the drawing methods or [`Surface::mark_dirty`] after
153    /// [`Surface::pixels_untracked`]. Empty while the surface has no pixels.
154    pub fn pixels_mut(&mut self) -> RefMut<'_, [u8]> {
155        let mut inner = self.inner.borrow_mut();
156        let (width, height) = inner.extent();
157        inner.mark_dirty(Rect::new(0, 0, width, height));
158        RefMut::map(inner, |inner| match inner.back() {
159            Some((slot, _, _)) => as_bytes_mut(slot),
160            None => &mut [],
161        })
162    }
163
164    /// As [`Surface::pixels_mut`], recording nothing: mark what you change
165    /// with [`Surface::mark_dirty`].
166    pub fn pixels_untracked(&mut self) -> RefMut<'_, [u8]> {
167        RefMut::map(self.inner.borrow_mut(), |inner| match inner.back() {
168            Some((slot, _, _)) => as_bytes_mut(slot),
169            None => &mut [],
170        })
171    }
172
173    /// The whole frame for a full repaint: its contents are stale (what
174    /// was committed two frames ago), nothing is copied forward, and the
175    /// whole surface is dirty. The cheapest way to draw an animation that
176    /// repaints every pixel.
177    pub fn repaint_all(&mut self) -> RefMut<'_, [u8]> {
178        RefMut::map(self.inner.borrow_mut(), |inner| {
179            match inner.back_discard() {
180                Some((slot, _)) => as_bytes_mut(slot),
181                None => &mut [],
182            }
183        })
184    }
185}