use crate::types::{Color, Fixed, Point, Rect};
use super::command::DrawCommand;
use super::renderer::Renderer;
pub struct SwRenderer<'a> {
buf: &'a mut [u8],
width: u32,
height: u32,
pub scale: Fixed,
}
impl<'a> SwRenderer<'a> {
pub fn new(buf: &'a mut [u8], width: u32, height: u32) -> Self {
Self {
buf,
width,
height,
scale: Fixed::ONE,
}
}
fn put_pixel(&mut self, x: i32, y: i32, color: &Color, opa: u8) {
if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
return;
}
let idx = ((y as u32 * self.width + x as u32) * 4) as usize;
if idx + 3 >= self.buf.len() {
return;
}
let a = ((color.a as u16) * (opa as u16) / 255) as u8;
if a == 255 {
self.buf[idx] = color.r;
self.buf[idx + 1] = color.g;
self.buf[idx + 2] = color.b;
self.buf[idx + 3] = 255;
} else if a > 0 {
let inv = 255 - a as u16;
self.buf[idx] = ((color.r as u16 * a as u16 + self.buf[idx] as u16 * inv) / 255) as u8;
self.buf[idx + 1] =
((color.g as u16 * a as u16 + self.buf[idx + 1] as u16 * inv) / 255) as u8;
self.buf[idx + 2] =
((color.b as u16 * a as u16 + self.buf[idx + 2] as u16 * inv) / 255) as u8;
self.buf[idx + 3] = 255;
}
}
fn rounded_rect_coverage(px: Fixed, py: Fixed, w: Fixed, h: Fixed, r: Fixed) -> Fixed {
if r == Fixed::ZERO {
return Fixed::ONE;
}
let (cx, cy) = if px < r && py < r {
(r, r) } else if px >= w - r && py < r {
(w - r, r) } else if px < r && py >= h - r {
(r, h - r) } else if px >= w - r && py >= h - r {
(w - r, h - r) } else {
return Fixed::ONE; };
let dx = px - cx + Fixed::from_raw(128); let dy = py - cy + Fixed::from_raw(128);
let dist_sq = dx * dx + dy * dy;
let r_sq = r * r;
if dist_sq <= r_sq {
Fixed::ONE
} else {
let dist = dist_sq.sqrt();
let overshoot = dist - r;
if overshoot >= Fixed::ONE {
Fixed::ZERO
} else {
Fixed::ONE - overshoot
}
}
}
fn fill_rect(&mut self, area: &Rect, clip: &Rect, color: &Color, opa: u8, radius: Fixed) {
let screen = Rect::new(0, 0, self.width, self.height);
let Some(draw_area) = area.intersect(clip) else {
return;
};
let Some(draw_area) = draw_area.intersect(&screen) else {
return;
};
let r = radius.min(area.w / 2).min(area.h / 2);
let is_aligned = area.is_aligned() && r == Fixed::ZERO;
let (px_x0, px_y0, px_x1, px_y1) = draw_area.pixel_bounds();
if is_aligned {
for py in px_y0..px_y1 {
for px in px_x0..px_x1 {
self.put_pixel(px, py, color, opa);
}
}
return;
}
for py in px_y0..px_y1 {
let pixel_top = Fixed::from_int(py);
let pixel_bot = Fixed::from_int(py + 1);
let cov_y_top = pixel_top.max(area.y);
let cov_y_bot = pixel_bot.min(area.y + area.h);
let cov_y = ((cov_y_bot - cov_y_top).raw().clamp(0, 256)) as u16;
for px in px_x0..px_x1 {
let pixel_left = Fixed::from_int(px);
let pixel_right = Fixed::from_int(px + 1);
let cov_x_left = pixel_left.max(area.x);
let cov_x_right = pixel_right.min(area.x + area.w);
let cov_x = ((cov_x_right - cov_x_left).raw().clamp(0, 256)) as u16;
let edge_cov = (cov_x as u32 * cov_y as u32 / 256).min(255) as u8;
let corner_cov = if r > Fixed::ZERO {
let rel_x = Fixed::from_int(px) - area.x;
let rel_y = Fixed::from_int(py) - area.y;
Self::rounded_rect_coverage(rel_x, rel_y, area.w, area.h, r)
} else {
Fixed::ONE
};
let cov = (edge_cov as u16 * (corner_cov.raw().clamp(0, 256) as u16) / 256) as u8;
let final_opa = (opa as u16 * cov as u16 / 255) as u8;
if final_opa > 0 {
self.put_pixel(px, py, color, final_opa);
}
}
}
}
fn draw_border(
&mut self,
area: &Rect,
clip: &Rect,
color: &Color,
width: Fixed,
radius: Fixed,
opa: u8,
) {
let screen = Rect::new(0, 0, self.width, self.height);
let Some(draw_area) = area.intersect(clip) else {
return;
};
let Some(draw_area) = draw_area.intersect(&screen) else {
return;
};
let r = radius.min(area.w / 2).min(area.h / 2);
let bw = width;
let (px_x0, px_y0, px_x1, px_y1) = draw_area.pixel_bounds();
let inner_r = (r - bw).max(Fixed::ZERO);
let inner_w = (area.w - bw * 2).max(Fixed::ZERO);
let inner_h = (area.h - bw * 2).max(Fixed::ZERO);
for py in px_y0..px_y1 {
for px in px_x0..px_x1 {
let rel_x = Fixed::from_int(px) - area.x;
let rel_y = Fixed::from_int(py) - area.y;
let outer_cov = Self::rounded_rect_coverage(rel_x, rel_y, area.w, area.h, r);
if outer_cov == Fixed::ZERO {
continue;
}
let inner_rel_x = rel_x - bw;
let inner_rel_y = rel_y - bw;
let inner_cov = if inner_rel_x >= Fixed::ZERO
&& inner_rel_y >= Fixed::ZERO
&& inner_rel_x < inner_w
&& inner_rel_y < inner_h
{
Self::rounded_rect_coverage(inner_rel_x, inner_rel_y, inner_w, inner_h, inner_r)
} else {
Fixed::ZERO
};
let border_cov = (outer_cov - inner_cov).max(Fixed::ZERO);
let cov = (border_cov.raw().clamp(0, 256)) as u8;
let final_opa = (opa as u16 * cov as u16 / 255) as u8;
if final_opa > 0 {
self.put_pixel(px, py, color, final_opa);
}
}
}
}
fn draw_label(
&mut self,
pos: &crate::types::Point,
text: &[u8],
clip: &Rect,
color: &Color,
opa: u8,
) {
use super::font::{CHAR_H, CHAR_W, glyph};
let s = self.scale.to_int();
let (clip_x, clip_y, clip_x2, clip_y2) = clip.pixel_bounds();
let (mut cx, cy) = pos.floor();
for &ch in text {
let bitmap = glyph(ch);
for row in 0..CHAR_H as i32 {
let byte = bitmap[row as usize];
for col in 0..CHAR_W as i32 {
if byte & (0x80 >> col) != 0 {
for dy in 0..s {
for dx in 0..s {
let px = cx + col * s + dx;
let py = cy + row * s + dy;
if px >= clip_x && px < clip_x2 && py >= clip_y && py < clip_y2 {
self.put_pixel(px, py, color, opa);
}
}
}
}
}
}
cx += CHAR_W as i32 * s;
}
}
fn blit_rgba(&mut self, pos: &Point, data: &[u8], width: u16, height: u16, clip: &Rect) {
let s = self.scale.to_int();
let (clip_x, clip_y, clip_x2, clip_y2) = clip.pixel_bounds();
let (base_x, base_y) = pos.floor();
for row in 0..height as i32 {
for col in 0..width as i32 {
let src_idx = ((row * width as i32 + col) * 4) as usize;
if src_idx + 3 >= data.len() {
break;
}
let a = data[src_idx + 3] as u16;
if a == 0 {
continue;
}
for dy in 0..s {
for dx in 0..s {
let px = base_x + col * s + dx;
let py = base_y + row * s + dy;
if px < clip_x || px >= clip_x2 || py < clip_y || py >= clip_y2 {
continue;
}
let dst_idx = ((py as u32 * self.width + px as u32) * 4) as usize;
if dst_idx + 3 >= self.buf.len() {
continue;
}
if a == 255 {
self.buf[dst_idx] = data[src_idx];
self.buf[dst_idx + 1] = data[src_idx + 1];
self.buf[dst_idx + 2] = data[src_idx + 2];
self.buf[dst_idx + 3] = 255;
} else {
let inv = 255 - a;
self.buf[dst_idx] = ((data[src_idx] as u16 * a
+ self.buf[dst_idx] as u16 * inv)
/ 255) as u8;
self.buf[dst_idx + 1] = ((data[src_idx + 1] as u16 * a
+ self.buf[dst_idx + 1] as u16 * inv)
/ 255) as u8;
self.buf[dst_idx + 2] = ((data[src_idx + 2] as u16 * a
+ self.buf[dst_idx + 2] as u16 * inv)
/ 255) as u8;
self.buf[dst_idx + 3] = 255;
}
}
}
}
}
}
}
impl Renderer for SwRenderer<'_> {
fn draw(&mut self, cmd: &DrawCommand, clip: &Rect) {
match cmd {
DrawCommand::Fill {
area,
color,
opa,
radius,
} => {
self.fill_rect(area, clip, color, *opa, *radius);
}
DrawCommand::Border {
area,
color,
width,
radius,
opa,
} => {
self.draw_border(area, clip, color, *width, *radius, *opa);
}
DrawCommand::Label {
pos,
text,
color,
opa,
} => {
self.draw_label(pos, text, clip, color, *opa);
}
DrawCommand::Blit {
pos,
data,
width,
height,
} => {
self.blit_rgba(pos, data, *width, *height, clip);
}
_ => {}
}
}
fn flush(&mut self) {}
}