use super::font::{GLYPH_ADVANCE, GLYPH_WIDTH, glyph_or_box};
use super::{Area, Shapes, clip_segment, unit};
use crate::colour::Rgba;
use crate::surface::{Canvas, Pixels, Surface};
fn coverage(v: f32) -> u8 {
(v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
fn rounded_box_distance(px: f32, py: f32, bx: f32, by: f32, r: f32) -> f32 {
let qx = libm::fabsf(px) - (bx - r);
let qy = libm::fabsf(py) - (by - r);
let outside = libm::sqrtf(qx.max(0.0) * qx.max(0.0) + qy.max(0.0) * qy.max(0.0));
outside + qx.max(qy).min(0.0) - r
}
fn shade(
canvas: &mut Canvas<'_>,
(x0, y0, x1, y1): (i64, i64, i64, i64),
colour: Rgba,
shade: impl Fn(f32, f32) -> f32,
) {
let (left, right) = (x0.max(0), x1.min(i64::from(canvas.width)));
let (top, bottom) = (y0.max(0), y1.min(i64::from(canvas.height)));
for y in top..bottom {
for x in left..right {
let amount = coverage(shade(x as f32 + 0.5, y as f32 + 0.5));
if amount > 0 {
canvas.blend(x, y, colour, amount);
}
}
}
canvas.touch(left, top, right, bottom);
}
fn rounded(canvas: &mut Canvas<'_>, area: Area, radius: u32, colour: Rgba, fill: bool) {
if area.w == 0 || area.h == 0 {
return;
}
let (bx, by) = (area.w as f32 / 2.0, area.h as f32 / 2.0);
let (cx, cy) = (area.left() as f32 + bx, area.top() as f32 + by);
let r = (radius as f32).min(bx).min(by);
let bounds = (area.left(), area.top(), area.right(), area.bottom());
shade(canvas, bounds, colour, |px, py| {
let d = rounded_box_distance(px - cx, py - cy, bx, by, r);
if fill {
0.5 - d
} else {
(0.5 - d).clamp(0.0, 1.0) - (-0.5 - d).clamp(0.0, 1.0)
}
});
}
fn finite(v: f32) -> f32 {
if v.is_nan() {
0.0
} else {
v.clamp(-1.0e9, 1.0e9)
}
}
fn disc(canvas: &mut Canvas<'_>, centre: (f32, f32), radius: f32, colour: Rgba, fill: bool) {
let (cx, cy) = (finite(centre.0), finite(centre.1));
let r = finite(radius).clamp(0.0, 1.0e6);
let reach = libm::ceilf(r) as i64 + 2;
let (x, y) = (libm::floorf(cx) as i64, libm::floorf(cy) as i64);
let bounds = (x - reach, y - reach, x + reach + 1, y + reach + 1);
shade(canvas, bounds, colour, |px, py| {
let d = libm::sqrtf((px - cx) * (px - cx) + (py - cy) * (py - cy)) - r;
if fill { 0.5 - d } else { 1.0 - libm::fabsf(d) }
});
}
fn wu_line(canvas: &mut Canvas<'_>, from: (i64, i64), to: (i64, i64), colour: Rgba) {
let ((x0, y0), (x1, y1)) = (from, to);
let steep = (y1 - y0).abs() > (x1 - x0).abs();
let (mut a0, mut b0, mut a1, mut b1) = if steep {
(y0, x0, y1, x1)
} else {
(x0, y0, x1, y1)
};
if a0 > a1 {
core::mem::swap(&mut a0, &mut a1);
core::mem::swap(&mut b0, &mut b1);
}
let gradient = if a1 == a0 {
0.0
} else {
(b1 - b0) as f32 / (a1 - a0) as f32
};
let mut b = b0 as f32;
for a in a0..=a1 {
let base = libm::floorf(b);
let fraction = b - base;
let (near, far) = (coverage(1.0 - fraction), coverage(fraction));
let base = base as i64;
if steep {
canvas.blend(base, a, colour, near);
canvas.blend(base + 1, a, colour, far);
} else {
canvas.blend(a, base, colour, near);
canvas.blend(a, base + 1, colour, far);
}
b += gradient;
}
let (min_x, max_x) = (x0.min(x1), x0.max(x1));
let (min_y, max_y) = (y0.min(y1), y0.max(y1));
canvas.touch(min_x - 1, min_y - 1, max_x + 2, max_y + 2);
}
#[derive(Clone, Copy, Debug)]
pub struct Image<'a> {
pub width: u32,
pub height: u32,
pub rgba: &'a [u8],
}
impl<'a> Image<'a> {
pub const fn new(width: u32, height: u32, rgba: &'a [u8]) -> Self {
Self {
width,
height,
rgba,
}
}
}
impl Surface<Pixels> {
pub fn blit_clipped(&mut self, x: i32, y: i32, image: Image<'_>, clip: Area, blend: bool) {
self.with_canvas(|canvas| {
let (x0, y0) = (i64::from(x), i64::from(y));
let left = x0.max(clip.left()).max(0);
let right = (x0 + i64::from(image.width))
.min(clip.right())
.min(i64::from(canvas.width));
let top = y0.max(clip.top()).max(0);
let bottom = (y0 + i64::from(image.height))
.min(clip.bottom())
.min(i64::from(canvas.height));
if left >= right || top >= bottom {
return;
}
let stride = image.width as usize * 4;
let columns = (right - left) as usize;
let skip = (left - x0) as usize;
let mut last_row = top;
for target_y in top..bottom {
let start = (target_y - y0) as usize * stride + skip * 4;
let Some(source) = image.rgba.get(start..start + columns * 4) else {
break;
};
let offset = target_y as usize * canvas.width as usize + left as usize;
let target = &mut canvas.slot[offset..offset + columns];
for (word, pixel) in target.iter_mut().zip(source.chunks_exact(4)) {
let colour = Rgba::new(pixel[0], pixel[1], pixel[2], pixel[3]);
*word = if blend {
colour.over(Rgba::from_word(*word), 255).to_word()
} else {
colour.to_word()
};
}
last_row = target_y + 1;
}
canvas.touch(left, top, right, last_row);
});
}
pub fn fill_rect_at(&mut self, x: f32, y: f32, w: f32, h: f32, colour: Rgba) {
let (x, y) = (finite(x), finite(y));
let (w, h) = (finite(w).max(0.0), finite(h).max(0.0));
if w == 0.0 || h == 0.0 {
return;
}
let (right, bottom) = (x + w, y + h);
let bounds = (
libm::floorf(x) as i64,
libm::floorf(y) as i64,
libm::ceilf(right) as i64,
libm::ceilf(bottom) as i64,
);
self.with_canvas(|canvas| {
shade(canvas, bounds, colour, |px, py| {
let across = (px + 0.5).min(right) - (px - 0.5).max(x);
let down = (py + 0.5).min(bottom) - (py - 0.5).max(y);
across.clamp(0.0, 1.0) * down.clamp(0.0, 1.0)
});
});
}
pub fn label_scaled(&mut self, x: i32, y: i32, text: &str, colour: Rgba, scale: u32) -> u32 {
let scale = scale.max(1);
let advance = i64::from(GLYPH_ADVANCE) * i64::from(scale);
self.with_canvas(|canvas| {
let mut pen = i64::from(x);
let top = i64::from(y);
let size = i64::from(scale);
for character in text.chars() {
if pen >= i64::from(canvas.width) {
break;
}
if pen + advance > 0 {
for (row, bits) in glyph_or_box(character).iter().enumerate() {
for column in 0..GLYPH_WIDTH {
if bits & (1 << (GLYPH_WIDTH - 1 - column)) != 0 {
let gx = pen + i64::from(column) * size;
let gy = top + row as i64 * size;
canvas.fill(gx, gy, gx + size, gy + size, colour);
}
}
}
}
pen += advance;
}
(pen - i64::from(x)).clamp(0, i64::from(u32::MAX)) as u32
})
.unwrap_or(0)
}
}
impl Shapes for Surface<Pixels> {
type Ink = Rgba;
fn stroke_rect(&mut self, area: Area, ink: Rgba) {
if area.w == 0 || area.h == 0 {
return;
}
self.with_canvas(|canvas| {
let (l, t, r, b) = (area.left(), area.top(), area.right(), area.bottom());
canvas.fill(l, t, r, t + 1, ink);
if b - t > 1 {
canvas.fill(l, b - 1, r, b, ink);
}
canvas.fill(l, t + 1, l + 1, b - 1, ink);
if r - l > 1 {
canvas.fill(r - 1, t + 1, r, b - 1, ink);
}
});
}
fn fill_rect(&mut self, area: Area, ink: Rgba) {
self.with_canvas(|canvas| {
canvas.fill(area.left(), area.top(), area.right(), area.bottom(), ink);
});
}
fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Rgba) {
self.with_canvas(|canvas| {
let from = (i64::from(from.0), i64::from(from.1));
let to = (i64::from(to.0), i64::from(to.1));
if let Some((a, b)) = clip_segment(from, to, canvas.width, canvas.height) {
wu_line(canvas, a, b, ink);
}
});
}
fn bar(&mut self, area: Area, fraction: f32, fill: Rgba, track: Rgba) {
self.with_canvas(|canvas| {
let (l, t, r, b) = (area.left(), area.top(), area.right(), area.bottom());
canvas.fill(l, t, r, b, track);
let filled = unit(fraction) * area.w as f32;
let whole = libm::floorf(filled) as i64;
canvas.fill(l, t, l + whole, b, fill);
let partial = coverage(filled - whole as f32);
if partial > 0 && whole < i64::from(area.w) {
for y in t..b {
canvas.blend(l + whole, y, fill, partial);
}
}
});
}
fn rounded_rect(&mut self, area: Area, radius: u32, ink: Rgba) {
self.with_canvas(|canvas| rounded(canvas, area, radius, ink, false));
}
fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Rgba) {
self.with_canvas(|canvas| rounded(canvas, area, radius, ink, true));
}
fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Rgba) {
self.with_canvas(|canvas| disc(canvas, centre, radius, ink, false));
}
fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Rgba) {
self.with_canvas(|canvas| disc(canvas, centre, radius, ink, true));
}
fn label(&mut self, x: i32, y: i32, text: &str, ink: Rgba) -> u32 {
self.label_scaled(x, y, text, ink, 1)
}
}