use tiny_skia::{Paint, PathBuilder, PixmapMut, Rect as SkRect, Stroke, Transform};
use crate::color::Color;
use crate::geometry::Rect;
pub struct Scene<'a> {
pub background: Color,
pub border: Color,
pub selection: Color,
pub choice: Color,
pub border_weight: f32,
pub display_dimensions: bool,
pub font: Option<&'a fontdue::Font>,
pub logical: Rect,
pub scale: f32,
pub choice_boxes: &'a [Rect],
pub selection_rect: Option<Rect>,
pub crosshair: Option<(i32, i32)>,
}
impl Scene<'_> {
fn transform(&self) -> Transform {
Transform::from_row(
self.scale,
0.0,
0.0,
self.scale,
-self.scale * self.logical.x as f32,
-self.scale * self.logical.y as f32,
)
}
}
fn solid(color: Color) -> Paint<'static> {
let mut paint = Paint::default();
paint.set_color(color.to_skia());
paint.anti_alias = false;
paint
}
fn sk_rect(r: Rect) -> Option<SkRect> {
SkRect::from_xywh(r.x as f32, r.y as f32, r.width as f32, r.height as f32)
}
pub fn render(pixmap: &mut PixmapMut, scene: &Scene) {
let transform = scene.transform();
pixmap.fill(scene.background.to_skia());
let choice_paint = solid(scene.choice);
for rect in scene.choice_boxes {
if rect.intersects(&scene.logical) {
if let Some(r) = sk_rect(*rect) {
pixmap.fill_rect(r, &choice_paint, transform, None);
}
}
}
if let Some((cx, cy)) = scene.crosshair {
if scene.logical.contains(cx, cy) {
let paint = solid(scene.border);
if let Some(h) = sk_rect(Rect::new(scene.logical.x, cy, scene.logical.width, 1)) {
pixmap.fill_rect(h, &paint, transform, None);
}
if let Some(v) = sk_rect(Rect::new(cx, scene.logical.y, 1, scene.logical.height)) {
pixmap.fill_rect(v, &paint, transform, None);
}
}
}
if let Some(sel) = scene.selection_rect {
if sel.intersects(&scene.logical) {
if let Some(r) = sk_rect(sel) {
pixmap.fill_rect(r, &solid(scene.selection), transform, None);
let path = PathBuilder::from_rect(r);
let stroke = Stroke {
width: scene.border_weight,
..Stroke::default()
};
pixmap.stroke_path(&path, &solid(scene.border), &stroke, transform, None);
}
if scene.display_dimensions {
if let Some(font) = scene.font {
let text = format!("{}x{}", sel.width, sel.height);
let anchor_x =
((sel.x + sel.width + 10 - scene.logical.x) as f32) * scene.scale;
let baseline =
((sel.y + sel.height + 20 - scene.logical.y) as f32) * scene.scale;
blit_text(
pixmap,
&text,
anchor_x,
baseline,
14.0 * scene.scale,
font,
scene.border,
);
}
}
}
}
}
fn blit_text(
pixmap: &mut PixmapMut,
text: &str,
x0: f32,
baseline: f32,
px: f32,
font: &fontdue::Font,
color: Color,
) {
let width = pixmap.width() as i32;
let height = pixmap.height() as i32;
let data = pixmap.data_mut();
let mut pen_x = x0;
for ch in text.chars() {
let (metrics, coverage) = font.rasterize(ch, px);
let gx = pen_x + metrics.xmin as f32;
let gy = baseline - metrics.height as f32 - metrics.ymin as f32;
for row in 0..metrics.height {
for col in 0..metrics.width {
let cov = coverage[row * metrics.width + col];
if cov == 0 {
continue;
}
let px_x = (gx + col as f32) as i32;
let px_y = (gy + row as f32) as i32;
if px_x < 0 || px_y < 0 || px_x >= width || px_y >= height {
continue;
}
let idx = ((px_y * width + px_x) * 4) as usize;
let alpha = (cov as u16 * color.a as u16 / 255) as u8;
blend_over(&mut data[idx..idx + 4], color, alpha);
}
}
pen_x += metrics.advance_width;
}
}
fn blend_over(dst: &mut [u8], color: Color, alpha: u8) {
let sa = alpha as u16;
let inv = 255 - sa;
let sr = color.r as u16 * sa / 255;
let sg = color.g as u16 * sa / 255;
let sb = color.b as u16 * sa / 255;
dst[0] = (sr + dst[0] as u16 * inv / 255) as u8;
dst[1] = (sg + dst[1] as u16 * inv / 255) as u8;
dst[2] = (sb + dst[2] as u16 * inv / 255) as u8;
dst[3] = (sa + dst[3] as u16 * inv / 255) as u8;
}