use std::f64::consts::PI;
use std::sync::Arc;
use agg_rust::arc::Arc as AggArc;
use agg_rust::basics::PATH_FLAGS_NONE;
use agg_rust::comp_op::{CompOp, PixfmtRgba32CompOp};
use agg_rust::conv_curve::ConvCurve;
use agg_rust::conv_stroke::ConvStroke;
use agg_rust::conv_transform::ConvTransform;
use agg_rust::gsv_text::GsvText;
use agg_rust::math_stroke::{LineCap, LineJoin};
use agg_rust::path_storage::PathStorage;
use agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa;
use agg_rust::renderer_base::RendererBase;
use agg_rust::renderer_scanline::render_scanlines_aa_solid;
use agg_rust::rendering_buffer::RowAccessor;
use agg_rust::rounded_rect::RoundedRect;
use agg_rust::scanline_u::ScanlineU8;
use agg_rust::trans_affine::TransAffine;
use crate::color::Color;
use crate::framebuffer::Framebuffer;
use crate::text::{shape_text, measure_advance, Font, TextMetrics};
struct LayerEntry {
fb: Framebuffer,
saved_state: GfxState,
saved_stack: Vec<GfxState>,
origin_x: f64,
origin_y: f64,
}
pub use agg_rust::comp_op::CompOp as BlendMode;
#[derive(Clone)]
struct GfxState {
transform: TransAffine,
fill_color: Color,
stroke_color: Color,
line_width: f64,
line_join: LineJoin,
line_cap: LineCap,
blend_mode: CompOp,
clip: Option<(f64, f64, f64, f64)>,
global_alpha: f64,
font: Option<Arc<Font>>,
font_size: f64,
}
impl Default for GfxState {
fn default() -> Self {
Self {
transform: TransAffine::new(),
fill_color: Color::black(),
stroke_color: Color::black(),
line_width: 1.0,
line_join: LineJoin::Round,
line_cap: LineCap::Round,
blend_mode: CompOp::SrcOver,
clip: None,
global_alpha: 1.0,
font: None,
font_size: 16.0,
}
}
}
pub struct GfxCtx<'a> {
base_fb: &'a mut Framebuffer,
layer_stack: Vec<LayerEntry>,
state: GfxState,
state_stack: Vec<GfxState>,
path: PathStorage,
lcd_mode: bool,
}
impl<'a> GfxCtx<'a> {
pub fn new(fb: &'a mut Framebuffer) -> Self {
Self {
base_fb: fb,
layer_stack: Vec::new(),
state: GfxState::default(),
state_stack: Vec::new(),
path: PathStorage::new(),
lcd_mode: false,
}
}
pub fn push_layer(&mut self, width: f64, height: f64) {
let origin_x = self.state.transform.tx;
let origin_y = self.state.transform.ty;
let saved_state = self.state.clone();
let saved_stack = std::mem::take(&mut self.state_stack);
let layer_fb = Framebuffer::new(width.ceil() as u32, height.ceil() as u32);
self.layer_stack.push(LayerEntry {
fb: layer_fb,
saved_state,
saved_stack,
origin_x,
origin_y,
});
self.state.transform = TransAffine::new();
self.state.clip = None;
}
pub fn pop_layer(&mut self) {
let Some(layer) = self.layer_stack.pop() else { return; };
let ox = layer.origin_x as i32;
let oy = layer.origin_y as i32;
self.state = layer.saved_state;
self.state_stack = layer.saved_stack;
if let Some(top) = self.layer_stack.last_mut() {
composite_framebuffers(&mut top.fb, &layer.fb, ox, oy);
} else {
composite_framebuffers(self.base_fb, &layer.fb, ox, oy);
}
}
pub fn save(&mut self) {
self.state_stack.push(self.state.clone());
}
pub fn restore(&mut self) {
if let Some(state) = self.state_stack.pop() {
self.state = state;
}
}
pub fn translate(&mut self, tx: f64, ty: f64) {
self.state.transform.premultiply(&TransAffine::new_translation(tx, ty));
}
pub fn rotate(&mut self, radians: f64) {
self.state.transform.premultiply(&TransAffine::new_rotation(radians));
}
pub fn scale(&mut self, sx: f64, sy: f64) {
self.state.transform.premultiply(&TransAffine::new_scaling(sx, sy));
}
pub fn set_transform(&mut self, m: TransAffine) { self.state.transform = m; }
pub fn reset_transform(&mut self) { self.state.transform = TransAffine::new(); }
pub fn transform(&self) -> TransAffine { self.state.transform }
pub fn set_fill_color(&mut self, color: Color) { self.state.fill_color = color; }
pub fn set_stroke_color(&mut self, color: Color) { self.state.stroke_color = color; }
pub fn set_line_width(&mut self, w: f64) { self.state.line_width = w; }
pub fn set_line_join(&mut self, join: LineJoin) { self.state.line_join = join; }
pub fn set_line_cap(&mut self, cap: LineCap) { self.state.line_cap = cap; }
pub fn set_blend_mode(&mut self, mode: CompOp) { self.state.blend_mode = mode; }
pub fn set_global_alpha(&mut self, alpha: f64) {
self.state.global_alpha = alpha.clamp(0.0, 1.0);
}
pub fn set_font(&mut self, font: Arc<Font>) {
self.state.font = Some(font);
}
pub fn set_font_size(&mut self, size: f64) {
self.state.font_size = size.max(1.0);
}
pub fn set_lcd_mode(&mut self, on: bool) { self.lcd_mode = on; }
pub fn lcd_mode(&self) -> bool { self.lcd_mode }
pub fn clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
let t = &self.state.transform;
let corners = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)];
let mut sx_min = f64::INFINITY;
let mut sy_min = f64::INFINITY;
let mut sx_max = f64::NEG_INFINITY;
let mut sy_max = f64::NEG_INFINITY;
for (lx, ly) in corners {
let mut sx = lx;
let mut sy = ly;
t.transform(&mut sx, &mut sy);
if sx < sx_min { sx_min = sx; }
if sx > sx_max { sx_max = sx; }
if sy < sy_min { sy_min = sy; }
if sy > sy_max { sy_max = sy; }
}
let sw = (sx_max - sx_min).max(0.0);
let sh = (sy_max - sy_min).max(0.0);
if let Some((cx, cy, cw, ch)) = self.state.clip {
let x1 = sx_min.max(cx);
let y1 = sy_min.max(cy);
let x2 = sx_max.min(cx + cw);
let y2 = sy_max.min(cy + ch);
self.state.clip = Some((x1, y1, (x2 - x1).max(0.0), (y2 - y1).max(0.0)));
} else {
self.state.clip = Some((sx_min, sy_min, sw, sh));
}
}
pub fn reset_clip(&mut self) { self.state.clip = None; }
pub fn clear(&mut self, color: Color) {
let rgba = color.to_rgba8();
for chunk in active_fb(&mut self.base_fb, &mut self.layer_stack).pixels_mut().chunks_exact_mut(4) {
chunk[0] = rgba.r as u8;
chunk[1] = rgba.g as u8;
chunk[2] = rgba.b as u8;
chunk[3] = rgba.a as u8;
}
}
pub fn begin_path(&mut self) { self.path = PathStorage::new(); }
pub fn move_to(&mut self, x: f64, y: f64) { self.path.move_to(x, y); }
pub fn line_to(&mut self, x: f64, y: f64) { self.path.line_to(x, y); }
pub fn cubic_to(&mut self, cx1: f64, cy1: f64, cx2: f64, cy2: f64, x: f64, y: f64) {
self.path.curve4(cx1, cy1, cx2, cy2, x, y);
}
pub fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) {
self.path.curve3(cx, cy, x, y);
}
pub fn arc_to(&mut self, cx: f64, cy: f64, r: f64, start_angle: f64, end_angle: f64, ccw: bool) {
let mut arc = AggArc::new(cx, cy, r, r, start_angle, end_angle, ccw);
self.path.concat_path(&mut arc, 0);
}
pub fn circle(&mut self, cx: f64, cy: f64, r: f64) {
self.arc_to(cx, cy, r, 0.0, 2.0 * PI, true);
self.path.close_polygon(PATH_FLAGS_NONE);
}
pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
self.path.move_to(x, y);
self.path.line_to(x + w, y);
self.path.line_to(x + w, y + h);
self.path.line_to(x, y + h);
self.path.close_polygon(PATH_FLAGS_NONE);
}
pub fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
let r = r.min(w * 0.5).min(h * 0.5).max(0.0);
let mut rr = RoundedRect::new(x, y, x + w, y + h, r);
rr.normalize_radius();
self.path.concat_path(&mut rr, 0);
}
pub fn close_path(&mut self) { self.path.close_polygon(PATH_FLAGS_NONE); }
pub fn fill(&mut self) {
let mut color = self.state.fill_color;
color.a *= self.state.global_alpha as f32;
let rgba = color.to_rgba8();
let mode = self.state.blend_mode;
let clip = self.state.clip;
let transform = self.state.transform.clone();
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
rasterize_fill(fb, &mut self.path, &rgba, mode, clip, &transform);
}
pub fn stroke(&mut self) {
let mut color = self.state.stroke_color;
color.a *= self.state.global_alpha as f32;
let rgba = color.to_rgba8();
let width = self.state.line_width;
let join = self.state.line_join;
let cap = self.state.line_cap;
let mode = self.state.blend_mode;
let clip = self.state.clip;
let transform = self.state.transform.clone();
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
rasterize_stroke(fb, &mut self.path, &rgba, width, join, cap, mode, clip, &transform);
}
pub fn fill_and_stroke(&mut self) {
self.fill();
self.stroke();
}
pub fn fill_text(&mut self, text: &str, x: f64, y: f64) {
let font = match self.state.font.clone() {
Some(f) => f,
None => return,
};
let font_size = self.state.font_size;
let mut color = self.state.fill_color;
color.a *= self.state.global_alpha as f32;
if self.lcd_mode {
let t = &self.state.transform;
let ctm_scale = (t.sx * t.sx + t.shy * t.shy).sqrt().max(1e-6);
let phys_size = font_size * ctm_scale;
let cached = crate::lcd_coverage::rasterize_text_lcd_cached(
&font, text, phys_size,
);
let dst_x = x - cached.baseline_x_in_mask / ctm_scale;
let dst_y = y - cached.baseline_y_in_mask / ctm_scale;
<Self as crate::DrawCtx>::draw_lcd_mask_arc(
self,
&cached.pixels, cached.width, cached.height,
color, dst_x, dst_y,
);
return;
}
let rgba = color.to_rgba8();
let mode = self.state.blend_mode;
let clip = self.state.clip;
let transform = self.state.transform.clone();
let (glyph_paths, _) = shape_text(&font, text, font_size, x, y);
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
for mut path in glyph_paths {
rasterize_fill(fb, &mut path, &rgba, mode, clip, &transform);
}
}
pub fn measure_text(&self, text: &str) -> Option<TextMetrics> {
let font = self.state.font.as_ref()?;
let size = self.state.font_size;
Some(TextMetrics {
width: measure_advance(font, text, size),
ascent: font.ascender_px(size),
descent: font.descender_px(size),
line_height: font.line_height_px(size),
})
}
pub fn fill_text_gsv(&mut self, text: &str, x: f64, y: f64, size: f64) {
let mut color = self.state.fill_color;
color.a *= self.state.global_alpha as f32;
let rgba = color.to_rgba8();
let mode = self.state.blend_mode;
let clip = self.state.clip;
let transform = self.state.transform.clone();
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
let w = fb.width();
let h = fb.height();
let stride = (w * 4) as i32;
let mut ra = RowAccessor::new();
unsafe { ra.attach(fb.pixels_mut().as_mut_ptr(), w, h, stride) };
let pf = PixfmtRgba32CompOp::new_with_op(&mut ra, mode);
let mut rb = RendererBase::new(pf);
apply_clip(&mut rb, clip);
let mut ras = RasterizerScanlineAa::new();
let mut sl = ScanlineU8::new();
let mut gsv = GsvText::new();
gsv.size(size, 0.0);
gsv.start_point(x, y);
gsv.text(text);
let mut stroke = ConvStroke::new(&mut gsv);
stroke.set_width(size * 0.1);
let mut transformed = ConvTransform::new(&mut stroke, transform);
ras.add_path(&mut transformed, 0);
render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &rgba);
}
}
#[inline]
fn active_fb<'a>(
base_fb: &'a mut Framebuffer,
layer_stack: &'a mut Vec<LayerEntry>,
) -> &'a mut Framebuffer {
if let Some(top) = layer_stack.last_mut() {
&mut top.fb
} else {
base_fb
}
}
fn composite_framebuffers(dst: &mut Framebuffer, src: &Framebuffer, dest_x: i32, dest_y: i32) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width() as i32;
let dst_h = dst.height() as i32;
let src_px = src.pixels();
let dst_px = dst.pixels_mut();
for sy in 0..src_h {
let dy = dest_y + sy;
if dy < 0 || dy >= dst_h { continue; }
for sx in 0..src_w {
let dx = dest_x + sx;
if dx < 0 || dx >= dst_w { continue; }
let si = ((sy * src_w + sx) * 4) as usize;
let di = ((dy * dst_w + dx) * 4) as usize;
let sa = src_px[si + 3] as f32 / 255.0;
if sa < 1e-4 { continue; } let inv_sa = 1.0 - sa;
for k in 0..4 {
let s = src_px[si + k] as f32;
let d = dst_px[di + k] as f32;
dst_px[di + k] = (s + d * inv_sa).round().clamp(0.0, 255.0) as u8;
}
}
}
}
pub(crate) fn rasterize_fill(
fb: &mut Framebuffer,
path: &mut PathStorage,
color: &agg_rust::color::Rgba8,
mode: CompOp,
clip: Option<(f64, f64, f64, f64)>,
transform: &TransAffine,
) {
let w = fb.width();
let h = fb.height();
let stride = (w * 4) as i32;
let mut ra = RowAccessor::new();
unsafe { ra.attach(fb.pixels_mut().as_mut_ptr(), w, h, stride) };
let pf = PixfmtRgba32CompOp::new_with_op(&mut ra, mode);
let mut rb = RendererBase::new(pf);
apply_clip(&mut rb, clip);
let mut ras = RasterizerScanlineAa::new();
let mut sl = ScanlineU8::new();
let mut curves = ConvCurve::new(path);
let mut transformed = ConvTransform::new(&mut curves, transform.clone());
ras.add_path(&mut transformed, 0);
render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, color);
}
pub(crate) fn rasterize_stroke(
fb: &mut Framebuffer,
path: &mut PathStorage,
color: &agg_rust::color::Rgba8,
width: f64,
join: LineJoin,
cap: LineCap,
mode: CompOp,
clip: Option<(f64, f64, f64, f64)>,
transform: &TransAffine,
) {
let w = fb.width();
let h = fb.height();
let stride = (w * 4) as i32;
let mut ra = RowAccessor::new();
unsafe { ra.attach(fb.pixels_mut().as_mut_ptr(), w, h, stride) };
let pf = PixfmtRgba32CompOp::new_with_op(&mut ra, mode);
let mut rb = RendererBase::new(pf);
apply_clip(&mut rb, clip);
let mut ras = RasterizerScanlineAa::new();
let mut sl = ScanlineU8::new();
let mut curves = ConvCurve::new(path);
let mut stroke = ConvStroke::new(&mut curves);
stroke.set_width(width);
stroke.set_line_join(join);
stroke.set_line_cap(cap);
let mut transformed = ConvTransform::new(&mut stroke, transform.clone());
ras.add_path(&mut transformed, 0);
render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, color);
}
impl crate::draw_ctx::DrawCtx for GfxCtx<'_> {
fn set_fill_color(&mut self, c: crate::color::Color) { self.set_fill_color(c) }
fn set_stroke_color(&mut self, c: crate::color::Color) { self.set_stroke_color(c) }
fn set_line_width(&mut self, w: f64) { self.set_line_width(w) }
fn set_line_join(&mut self, j: agg_rust::math_stroke::LineJoin) { self.set_line_join(j) }
fn set_line_cap(&mut self, c: agg_rust::math_stroke::LineCap) { self.set_line_cap(c) }
fn set_blend_mode(&mut self, m: agg_rust::comp_op::CompOp) { self.set_blend_mode(m) }
fn set_global_alpha(&mut self, a: f64) { self.set_global_alpha(a) }
fn set_font(&mut self, f: Arc<crate::text::Font>) { self.set_font(f) }
fn set_font_size(&mut self, s: f64) { self.set_font_size(s) }
fn clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64) { self.clip_rect(x, y, w, h) }
fn reset_clip(&mut self) { self.reset_clip() }
fn clear(&mut self, c: crate::color::Color) { self.clear(c) }
fn begin_path(&mut self) { self.begin_path() }
fn move_to(&mut self, x: f64, y: f64) { self.move_to(x, y) }
fn line_to(&mut self, x: f64, y: f64) { self.line_to(x, y) }
fn cubic_to(&mut self, cx1: f64, cy1: f64, cx2: f64, cy2: f64, x: f64, y: f64) {
self.cubic_to(cx1, cy1, cx2, cy2, x, y)
}
fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) { self.quad_to(cx, cy, x, y) }
fn arc_to(&mut self, cx: f64, cy: f64, r: f64, a1: f64, a2: f64, ccw: bool) {
self.arc_to(cx, cy, r, a1, a2, ccw)
}
fn circle(&mut self, cx: f64, cy: f64, r: f64) { self.circle(cx, cy, r) }
fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) { self.rect(x, y, w, h) }
fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
self.rounded_rect(x, y, w, h, r)
}
fn close_path(&mut self) { self.close_path() }
fn fill(&mut self) { self.fill() }
fn stroke(&mut self) { self.stroke() }
fn fill_and_stroke(&mut self) { self.fill_and_stroke() }
fn draw_triangles_aa(
&mut self,
vertices: &[[f32; 3]],
indices: &[u32],
color: crate::color::Color,
) {
let saved_fill = self.state.fill_color;
self.set_fill_color(color);
let n_tris = indices.len() / 3;
for t in 0..n_tris {
let i0 = indices[t * 3 ] as usize;
let i1 = indices[t * 3 + 1] as usize;
let i2 = indices[t * 3 + 2] as usize;
if i0 >= vertices.len() || i1 >= vertices.len() || i2 >= vertices.len() { continue; }
let v0 = vertices[i0];
let v1 = vertices[i1];
let v2 = vertices[i2];
self.begin_path();
self.move_to(v0[0] as f64, v0[1] as f64);
self.line_to(v1[0] as f64, v1[1] as f64);
self.line_to(v2[0] as f64, v2[1] as f64);
self.close_path();
self.fill();
}
self.set_fill_color(saved_fill);
}
fn fill_text(&mut self, t: &str, x: f64, y: f64) { self.fill_text(t, x, y) }
fn fill_text_gsv(&mut self, t: &str, x: f64, y: f64, s: f64) { self.fill_text_gsv(t, x, y, s) }
fn measure_text(&self, t: &str) -> Option<crate::text::TextMetrics> { self.measure_text(t) }
fn transform(&self) -> agg_rust::trans_affine::TransAffine { self.transform() }
fn save(&mut self) { self.save() }
fn restore(&mut self) { self.restore() }
fn translate(&mut self, tx: f64, ty: f64) { self.translate(tx, ty) }
fn rotate(&mut self, r: f64) { self.rotate(r) }
fn scale(&mut self, sx: f64, sy: f64) { self.scale(sx, sy) }
fn set_transform(&mut self, m: agg_rust::trans_affine::TransAffine) { self.set_transform(m) }
fn reset_transform(&mut self) { self.reset_transform() }
fn push_layer(&mut self, w: f64, h: f64) { self.push_layer(w, h) }
fn pop_layer(&mut self) { self.pop_layer() }
fn has_image_blit(&self) -> bool { true }
fn draw_image_rgba_arc(
&mut self,
data: &Arc<Vec<u8>>,
img_w: u32,
img_h: u32,
dst_x: f64,
dst_y: f64,
dst_w: f64,
dst_h: f64,
) {
self.draw_image_rgba(data.as_slice(), img_w, img_h, dst_x, dst_y, dst_w, dst_h);
}
fn draw_lcd_backbuffer_arc(
&mut self,
color: &Arc<Vec<u8>>,
alpha: &Arc<Vec<u8>>,
w: u32,
h: u32,
dst_x: f64,
dst_y: f64,
_dst_w: f64,
_dst_h: f64,
) {
if w == 0 || h == 0 { return; }
let w_u = w as usize;
let h_u = h as usize;
if color.len() < w_u * h_u * 3 || alpha.len() < w_u * h_u * 3 { return; }
let t = &self.state.transform;
let sx = (dst_x * t.sx + dst_y * t.shx + t.tx).round() as i32;
let sy = (dst_x * t.shy + dst_y * t.sy + t.ty).round() as i32;
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
let fw = fb.width() as i32;
let fh = fb.height() as i32;
let fw_u = fw as usize;
let pixels = fb.pixels_mut();
for src_y in 0..h_u {
let dy = sy + (h_u - 1 - src_y) as i32;
if dy < 0 || dy >= fh { continue; }
let dy_u = dy as usize;
for src_x in 0..w_u {
let dx = sx + src_x as i32;
if dx < 0 || dx >= fw { continue; }
let ci = (src_y * w_u + src_x) * 3;
let sa_r = alpha[ci] as f32 / 255.0;
let sa_g = alpha[ci + 1] as f32 / 255.0;
let sa_b = alpha[ci + 2] as f32 / 255.0;
if sa_r == 0.0 && sa_g == 0.0 && sa_b == 0.0 { continue; }
let sc_r = color[ci] as f32 / 255.0;
let sc_g = color[ci + 1] as f32 / 255.0;
let sc_b = color[ci + 2] as f32 / 255.0;
let di = (dy_u * fw_u + dx as usize) * 4;
let dc_r = pixels[di] as f32 / 255.0;
let dc_g = pixels[di + 1] as f32 / 255.0;
let dc_b = pixels[di + 2] as f32 / 255.0;
let da = pixels[di + 3] as f32 / 255.0;
let rc_r = sc_r + dc_r * (1.0 - sa_r);
let rc_g = sc_g + dc_g * (1.0 - sa_g);
let rc_b = sc_b + dc_b * (1.0 - sa_b);
let src_a_max = sa_r.max(sa_g).max(sa_b);
let ra = src_a_max + da * (1.0 - src_a_max);
pixels[di] = (rc_r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
pixels[di + 1] = (rc_g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
pixels[di + 2] = (rc_b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
pixels[di + 3] = (ra * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}
fn has_lcd_mask_composite(&self) -> bool { true }
fn draw_lcd_mask(
&mut self,
mask: &[u8],
mask_w: u32,
mask_h: u32,
src_color: Color,
dst_x: f64,
dst_y: f64,
) {
if mask.len() < (mask_w as usize) * (mask_h as usize) * 3 { return; }
let t = &self.state.transform;
let sx = dst_x * t.sx + dst_y * t.shx + t.tx;
let sy = dst_x * t.shy + dst_y * t.sy + t.ty;
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
let fw = fb.width();
let fh = fb.height();
let origin_x = sx.round() as i32;
let origin_y = sy.round() as i32;
let sa = src_color.a.clamp(0.0, 1.0);
let sr = src_color.r.clamp(0.0, 1.0);
let sg = src_color.g.clamp(0.0, 1.0);
let sb = src_color.b.clamp(0.0, 1.0);
let fw_i = fw as i32;
let fh_i = fh as i32;
let mw_i = mask_w as i32;
let mh_i = mask_h as i32;
let pixels = fb.pixels_mut();
for my in 0..mh_i {
let dy = origin_y + my;
if dy < 0 || dy >= fh_i { continue; }
for mx in 0..mw_i {
let dx = origin_x + mx;
if dx < 0 || dx >= fw_i { continue; }
let mi = ((my * mw_i + mx) * 3) as usize;
let cr = (mask[mi] as f32 / 255.0) * sa;
let cg = (mask[mi + 1] as f32 / 255.0) * sa;
let cb = (mask[mi + 2] as f32 / 255.0) * sa;
if cr == 0.0 && cg == 0.0 && cb == 0.0 { continue; }
let di = ((dy * fw_i + dx) * 4) as usize;
let dr = pixels[di] as f32 / 255.0;
let dg = pixels[di + 1] as f32 / 255.0;
let db = pixels[di + 2] as f32 / 255.0;
let rr = sr * cr + dr * (1.0 - cr);
let rg = sg * cg + dg * (1.0 - cg);
let rbb = sb * cb + db * (1.0 - cb);
pixels[di] = (rr * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
pixels[di + 1] = (rg * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
pixels[di + 2] = (rbb * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}
fn draw_image_rgba(
&mut self,
data: &[u8],
img_w: u32,
img_h: u32,
dst_x: f64,
dst_y: f64,
dst_w: f64,
dst_h: f64,
) {
if img_w == 0 || img_h == 0 || dst_w < 1.0 || dst_h < 1.0 { return; }
let out_w = dst_w.round() as u32;
let out_h = dst_h.round() as u32;
let mut scaled = crate::framebuffer::Framebuffer::new(out_w, out_h);
let px = scaled.pixels_mut();
for dy in 0..out_h {
for dx in 0..out_w {
let sx = (dx as f64 / out_w as f64 * img_w as f64) as u32;
let sy_img = ((1.0 - (dy as f64 + 0.5) / out_h as f64) * img_h as f64)
.floor()
.clamp(0.0, (img_h - 1) as f64) as u32;
let si = ((sy_img * img_w + sx) * 4) as usize;
let di = ((dy * out_w + dx) * 4) as usize;
if si + 3 < data.len() && di + 3 < px.len() {
let a = data[si + 3] as u32;
if a == 255 {
px[di] = data[si];
px[di + 1] = data[si + 1];
px[di + 2] = data[si + 2];
px[di + 3] = 255;
} else {
px[di] = (((data[si] as u32) * a + 127) / 255) as u8;
px[di + 1] = (((data[si + 1] as u32) * a + 127) / 255) as u8;
px[di + 2] = (((data[si + 2] as u32) * a + 127) / 255) as u8;
px[di + 3] = a as u8;
}
}
}
}
let (tx, ty) = { let t = self.transform(); (t.tx, t.ty) };
let screen_x = (tx + dst_x).round() as i32;
let screen_y = (ty + dst_y).round() as i32;
let fb = active_fb(&mut self.base_fb, &mut self.layer_stack);
composite_framebuffers(fb, &scaled, screen_x, screen_y);
}
}
pub(crate) fn apply_clip<PF: agg_rust::pixfmt_rgba::PixelFormat>(
rb: &mut RendererBase<PF>,
clip: Option<(f64, f64, f64, f64)>,
) {
if let Some((x, y, w, h)) = clip {
let x1 = x.floor() as i32;
let y1 = y.floor() as i32;
let x2 = (x + w).ceil() as i32 - 1;
let y2 = (y + h).ceil() as i32 - 1;
rb.clip_box_i(x1, y1, x2, y2);
}
}