use agg_rust::path_storage::PathStorage;
use agg_rust::trans_affine::TransAffine;
use crate::color::Color;
use crate::draw_ctx::FillRule;
pub struct LcdBuffer {
color: Vec<u8>,
alpha: Vec<u8>,
width: u32,
height: u32,
}
impl LcdBuffer {
pub fn new(width: u32, height: u32) -> Self {
const MAX_BYTES: usize = 512 * 1024 * 1024; let bytes = (width as usize)
.saturating_mul(height as usize)
.saturating_mul(3);
if bytes > MAX_BYTES {
#[cfg(debug_assertions)]
eprintln!(
"[LcdBuffer] clamped pathological size ({}, {}); \
widget bounds likely skipped a size cap",
width, height,
);
return Self {
color: vec![0u8; 3],
alpha: vec![0u8; 3],
width: 1,
height: 1,
};
}
Self {
color: vec![0u8; bytes],
alpha: vec![0u8; bytes],
width,
height,
}
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn color_plane(&self) -> &[u8] {
&self.color
}
#[inline]
pub fn alpha_plane(&self) -> &[u8] {
&self.alpha
}
#[inline]
pub fn color_plane_mut(&mut self) -> &mut [u8] {
&mut self.color
}
#[inline]
pub fn alpha_plane_mut(&mut self) -> &mut [u8] {
&mut self.alpha
}
#[inline]
pub fn planes_mut(&mut self) -> (&mut [u8], &mut [u8]) {
(&mut self.color, &mut self.alpha)
}
pub fn into_planes(self) -> (Vec<u8>, Vec<u8>) {
(self.color, self.alpha)
}
pub fn color_plane_flipped(&self) -> Vec<u8> {
flip_plane(&self.color, self.width, self.height)
}
pub fn alpha_plane_flipped(&self) -> Vec<u8> {
flip_plane(&self.alpha, self.width, self.height)
}
pub fn to_rgba8_top_down_collapsed(&self) -> Vec<u8> {
let w = self.width as usize;
let h = self.height as usize;
let mut out = vec![0u8; w * h * 4];
for y in 0..h {
let src_y = h - 1 - y;
for x in 0..w {
let si = (src_y * w + x) * 3;
let di = (y * w + x) * 4;
let ra = self.alpha[si];
let ga = self.alpha[si + 1];
let ba = self.alpha[si + 2];
let a = ra.max(ga).max(ba);
if a == 0 {
continue;
} let af = a as f32 / 255.0;
let rc = self.color[si] as f32 / 255.0;
let gc = self.color[si + 1] as f32 / 255.0;
let bc = self.color[si + 2] as f32 / 255.0;
out[di] = ((rc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
out[di + 1] = ((gc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
out[di + 2] = ((bc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
out[di + 3] = a;
}
}
out
}
pub fn clear(&mut self, color: Color) {
let a = color.a.clamp(0.0, 1.0);
let r_c = ((color.r.clamp(0.0, 1.0) * a) * 255.0 + 0.5) as u8;
let g_c = ((color.g.clamp(0.0, 1.0) * a) * 255.0 + 0.5) as u8;
let b_c = ((color.b.clamp(0.0, 1.0) * a) * 255.0 + 0.5) as u8;
let a_byte = (a * 255.0 + 0.5) as u8;
for px in self.color.chunks_exact_mut(3) {
px[0] = r_c;
px[1] = g_c;
px[2] = b_c;
}
for px in self.alpha.chunks_exact_mut(3) {
px[0] = a_byte;
px[1] = a_byte;
px[2] = a_byte;
}
}
pub fn fill_path(
&mut self,
path: &mut PathStorage,
color: Color,
transform: &TransAffine,
clip: Option<(f64, f64, f64, f64)>,
fill_rule: FillRule,
) {
if self.width == 0 || self.height == 0 {
return;
}
let mut builder = LcdMaskBuilder::new(self.width, self.height)
.with_clip(clip)
.with_fill_rule(fill_rule);
builder.with_paths(transform, |add| {
add(path);
});
let mask = builder.finalize();
let clip_i = clip.map(rect_to_pixel_clip);
self.composite_mask(&mask, color, 0, 0, clip_i);
}
pub fn composite_mask(
&mut self,
mask: &LcdMask,
src: Color,
dst_x: i32,
dst_y: i32,
clip: Option<(i32, i32, i32, i32)>,
) {
if mask.width == 0 || mask.height == 0 {
return;
}
let sa = src.a.clamp(0.0, 1.0);
let sr = src.r.clamp(0.0, 1.0);
let sg = src.g.clamp(0.0, 1.0);
let sb = src.b.clamp(0.0, 1.0);
let dst_w_i = self.width as i32;
let dst_h_i = self.height as i32;
let dst_w_u = self.width as usize;
let mw = mask.width as i32;
let mh = mask.height as i32;
let (cx1, cy1, cx2, cy2) = match clip {
Some((cx1, cy1, cx2, cy2)) => {
(cx1.max(0), cy1.max(0), cx2.min(dst_w_i), cy2.min(dst_h_i))
}
None => (0, 0, dst_w_i, dst_h_i),
};
if cx1 >= cx2 || cy1 >= cy2 {
return;
}
for my in 0..mh {
let dy = dst_y + my;
if dy < cy1 || dy >= cy2 {
continue;
}
let dy_u = dy as usize;
for mx in 0..mw {
let dx = dst_x + mx;
if dx < cx1 || dx >= cx2 {
continue;
}
let mi = ((my * mw + mx) * 3) as usize;
let ea_r = sa * (mask.data[mi] as f32 / 255.0);
let ea_g = sa * (mask.data[mi + 1] as f32 / 255.0);
let ea_b = sa * (mask.data[mi + 2] as f32 / 255.0);
if ea_r == 0.0 && ea_g == 0.0 && ea_b == 0.0 {
continue;
}
let di = (dy_u * dst_w_u + (dx as usize)) * 3;
let bc_r = self.color[di] as f32 / 255.0;
let bc_g = self.color[di + 1] as f32 / 255.0;
let bc_b = self.color[di + 2] as f32 / 255.0;
let ba_r = self.alpha[di] as f32 / 255.0;
let ba_g = self.alpha[di + 1] as f32 / 255.0;
let ba_b = self.alpha[di + 2] as f32 / 255.0;
let rc_r = sr * ea_r + bc_r * (1.0 - ea_r);
let rc_g = sg * ea_g + bc_g * (1.0 - ea_g);
let rc_b = sb * ea_b + bc_b * (1.0 - ea_b);
let ra_r = ea_r + ba_r * (1.0 - ea_r);
let ra_g = ea_g + ba_g * (1.0 - ea_g);
let ra_b = ea_b + ba_b * (1.0 - ea_b);
self.color[di] = (rc_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 1] = (rc_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 2] = (rc_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di] = (ra_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 1] = (ra_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 2] = (ra_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}
pub fn composite_mask_with_color<F>(
&mut self,
mask: &LcdMask,
dst_x: i32,
dst_y: i32,
clip: Option<(i32, i32, i32, i32)>,
mut color_at: F,
) where
F: FnMut(i32, i32) -> Color,
{
if mask.width == 0 || mask.height == 0 {
return;
}
let dst_w_i = self.width as i32;
let dst_h_i = self.height as i32;
let dst_w_u = self.width as usize;
let mw = mask.width as i32;
let mh = mask.height as i32;
let (cx1, cy1, cx2, cy2) = match clip {
Some((cx1, cy1, cx2, cy2)) => {
(cx1.max(0), cy1.max(0), cx2.min(dst_w_i), cy2.min(dst_h_i))
}
None => (0, 0, dst_w_i, dst_h_i),
};
if cx1 >= cx2 || cy1 >= cy2 {
return;
}
for my in 0..mh {
let dy = dst_y + my;
if dy < cy1 || dy >= cy2 {
continue;
}
let dy_u = dy as usize;
for mx in 0..mw {
let dx = dst_x + mx;
if dx < cx1 || dx >= cx2 {
continue;
}
let mi = ((my * mw + mx) * 3) as usize;
let src = color_at(dx, dy);
let sa = src.a.clamp(0.0, 1.0);
let sr = src.r.clamp(0.0, 1.0);
let sg = src.g.clamp(0.0, 1.0);
let sb = src.b.clamp(0.0, 1.0);
let ea_r = sa * (mask.data[mi] as f32 / 255.0);
let ea_g = sa * (mask.data[mi + 1] as f32 / 255.0);
let ea_b = sa * (mask.data[mi + 2] as f32 / 255.0);
if ea_r == 0.0 && ea_g == 0.0 && ea_b == 0.0 {
continue;
}
let di = (dy_u * dst_w_u + (dx as usize)) * 3;
let bc_r = self.color[di] as f32 / 255.0;
let bc_g = self.color[di + 1] as f32 / 255.0;
let bc_b = self.color[di + 2] as f32 / 255.0;
let ba_r = self.alpha[di] as f32 / 255.0;
let ba_g = self.alpha[di + 1] as f32 / 255.0;
let ba_b = self.alpha[di + 2] as f32 / 255.0;
let rc_r = sr * ea_r + bc_r * (1.0 - ea_r);
let rc_g = sg * ea_g + bc_g * (1.0 - ea_g);
let rc_b = sb * ea_b + bc_b * (1.0 - ea_b);
let ra_r = ea_r + ba_r * (1.0 - ea_r);
let ra_g = ea_g + ba_g * (1.0 - ea_g);
let ra_b = ea_b + ba_b * (1.0 - ea_b);
self.color[di] = (rc_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 1] = (rc_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 2] = (rc_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di] = (ra_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 1] = (ra_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 2] = (ra_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}
pub fn composite_buffer(
&mut self,
src: &LcdBuffer,
dst_x: i32,
dst_y: i32,
clip: Option<(i32, i32, i32, i32)>,
) {
if src.width == 0 || src.height == 0 {
return;
}
let dst_w_i = self.width as i32;
let dst_h_i = self.height as i32;
let dst_w_u = self.width as usize;
let src_w_u = src.width as usize;
let sw = src.width as i32;
let sh = src.height as i32;
let (cx1, cy1, cx2, cy2) = match clip {
Some((x1, y1, x2, y2)) => (x1.max(0), y1.max(0), x2.min(dst_w_i), y2.min(dst_h_i)),
None => (0, 0, dst_w_i, dst_h_i),
};
if cx1 >= cx2 || cy1 >= cy2 {
return;
}
for sy in 0..sh {
let dy = dst_y + sy;
if dy < cy1 || dy >= cy2 {
continue;
}
let dy_u = dy as usize;
let sy_u = sy as usize;
for sx in 0..sw {
let dx = dst_x + sx;
if dx < cx1 || dx >= cx2 {
continue;
}
let si = (sy_u * src_w_u + sx as usize) * 3;
let di = (dy_u * dst_w_u + dx as usize) * 3;
let sa_r = src.alpha[si] as f32 / 255.0;
let sa_g = src.alpha[si + 1] as f32 / 255.0;
let sa_b = src.alpha[si + 2] as f32 / 255.0;
if sa_r == 0.0 && sa_g == 0.0 && sa_b == 0.0 {
continue;
}
let sc_r = src.color[si] as f32 / 255.0;
let sc_g = src.color[si + 1] as f32 / 255.0;
let sc_b = src.color[si + 2] as f32 / 255.0;
let bc_r = self.color[di] as f32 / 255.0;
let bc_g = self.color[di + 1] as f32 / 255.0;
let bc_b = self.color[di + 2] as f32 / 255.0;
let ba_r = self.alpha[di] as f32 / 255.0;
let ba_g = self.alpha[di + 1] as f32 / 255.0;
let ba_b = self.alpha[di + 2] as f32 / 255.0;
let rc_r = sc_r + bc_r * (1.0 - sa_r);
let rc_g = sc_g + bc_g * (1.0 - sa_g);
let rc_b = sc_b + bc_b * (1.0 - sa_b);
let ra_r = sa_r + ba_r * (1.0 - sa_r);
let ra_g = sa_g + ba_g * (1.0 - sa_g);
let ra_b = sa_b + ba_b * (1.0 - sa_b);
self.color[di] = (rc_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 1] = (rc_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.color[di + 2] = (rc_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di] = (ra_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 1] = (ra_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
self.alpha[di + 2] = (ra_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}
}
fn flip_plane(src: &[u8], width: u32, height: u32) -> Vec<u8> {
let row_bytes = (width * 3) as usize;
let mut out = vec![0u8; src.len()];
for y in 0..height as usize {
let dst_y = height as usize - 1 - y;
out[dst_y * row_bytes..(dst_y + 1) * row_bytes]
.copy_from_slice(&src[y * row_bytes..(y + 1) * row_bytes]);
}
out
}
mod mask;
#[cfg(test)]
mod tests;
pub use mask::{
composite_lcd_mask, identity_xform, rasterize_lcd_mask, rasterize_lcd_mask_multi,
rasterize_text_lcd_cached, rect_to_pixel_clip, CachedLcdText, LcdMask, LcdMaskBuilder,
};