use core::cell::RefMut;
use super::{Inner, Pixels, Surface, as_bytes_mut};
use crate::colour::Rgba;
use crate::geometry::Rect;
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<'_> {
#[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();
}
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,
);
}
}
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> {
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))
}
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,
);
}
});
}
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);
});
}
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]))
}
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());
}
}
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,
);
}
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 [],
})
}
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 [],
})
}
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 [],
}
})
}
}