add canvas_image builtin for PNG rendering with alpha blending

Registers canvas_image(path, x, y, w, h) in the type system and
implements it in the interpreter using the image crate — scales to
exact dimensions via Lanczos3 and alpha-composites onto the pixmap.
This commit is contained in:
Will Anderson
2026-04-28 14:51:24 -05:00
parent 18b60e3bf1
commit d86bbc3740
5 changed files with 106 additions and 1 deletions
+44
View File
@@ -4724,6 +4724,50 @@ fn dispatch_builtin(
unreachable!()
}
"canvas_image" => {
// canvas_image(path: String, x: Int, y: Int, w: Int, h: Int) -> Void
// Draws a PNG image scaled to w×h at (x, y) with alpha blending.
let draw_h = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 };
let draw_w = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 };
let dy = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 };
let dx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 };
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } };
if draw_w <= 0 || draw_h <= 0 { stack.push(Value::Nil); return BuiltinResult::Handled; }
use image::GenericImageView;
if let Ok(img) = image::open(&path) {
let img = img.resize_exact(draw_w as u32, draw_h as u32, image::imageops::FilterType::Lanczos3);
let rgba = img.to_rgba8();
CANVAS.with(|cv| {
let mut cv = cv.borrow_mut();
if let Some(px) = cv.pixmap.as_mut() {
let pw = px.width() as i32;
let ph = px.height() as i32;
let data = px.data_mut();
for row in 0..draw_h {
for col in 0..draw_w {
let ix = dx + col;
let iy = dy + row;
if ix < 0 || iy < 0 || ix >= pw || iy >= ph { continue; }
let p = rgba.get_pixel(col as u32, row as u32);
let sa = p[3] as u32;
if sa == 0 { continue; }
let ia = 255 - sa;
let idx = (iy as usize * pw as usize + ix as usize) * 4;
data[idx] = ((ia * data[idx] as u32 + sa * p[0] as u32) / 255) as u8;
data[idx + 1] = ((ia * data[idx+1] as u32 + sa * p[1] as u32) / 255) as u8;
data[idx + 2] = ((ia * data[idx+2] as u32 + sa * p[2] as u32) / 255) as u8;
data[idx + 3] = ((ia * data[idx+3] as u32 + sa * sa) / 255) as u8;
}
}
}
});
}
stack.push(Value::Nil);
BuiltinResult::Handled
}
_ => BuiltinResult::NotBuiltin,
}
}