///|
/// Raw axis-aligned image blit. Samples per destination pixel via nearest or
/// bilinear interpolation, applies the matrix to map dest rect coordinates,
/// and composites via source-over `blend_over`.
///
/// This is the low-level primitive used by `Context::draw_image`. It doesn't
/// handle rotation or shear cleanly (the destination rect is computed from
/// the matrix-transformed axis-aligned corners and sampled as an axis-aligned
/// rectangle — acceptable for v0).
pub fn draw_image_raw(
pixels : FixedArray[Byte],
dst_w : Int,
dst_h : Int,
src : @image.ImageData,
dst_x : Double,
dst_y : Double,
scale_x : Double,
scale_y : Double,
src_x : Double,
src_y : Double,
src_w : Double,
src_h : Double,
matrix : Matrix2D,
global_alpha : Double,
smoothing : Bool,
) -> Unit {
let out_w = src_w * scale_x
let out_h = src_h * scale_y
let (tl_x, tl_y) = matrix.transform_point(dst_x, dst_y)
let (br_x, br_y) = matrix.transform_point(dst_x + out_w, dst_y + out_h)
let x_min = if tl_x < br_x { tl_x } else { br_x }
let x_max = if tl_x < br_x { br_x } else { tl_x }
let y_min = if tl_y < br_y { tl_y } else { br_y }
let y_max = if tl_y < br_y { br_y } else { tl_y }
let ix0 = x_min.floor().to_int()
let ix1 = x_max.ceil().to_int()
let iy0 = y_min.floor().to_int()
let iy1 = y_max.ceil().to_int()
let width_d = x_max - x_min
let height_d = y_max - y_min
if width_d <= 0.0 || height_d <= 0.0 {
return
}
let scale_u = src_w / width_d
let scale_v = src_h / height_d
for py in iy0..= 0 && py < dst_h {
for px in ix0..= 0 && px < dst_w {
let u = (px.to_double() + 0.5 - x_min) * scale_u + src_x
let v = (py.to_double() + 0.5 - y_min) * scale_v + src_y
let color = if smoothing {
sample_bilinear(src, u, v)
} else {
sample_nearest(src, u, v)
}
let offset = (py * dst_w + px) * 4
blend_over(pixels, offset, color, 256, global_alpha)
}
}
}
}
}
///|
fn sample_nearest(img : @image.ImageData, u : Double, v : Double) -> Color {
let x = u.floor().to_int()
let y = v.floor().to_int()
if x < 0 || x >= img.width || y < 0 || y >= img.height {
Color::transparent()
} else {
let o = (y * img.width + x) * 4
let r = img.data[o].to_int()
let g = img.data[o + 1].to_int()
let b = img.data[o + 2].to_int()
let a = img.data[o + 3].to_int().to_double() / 255.0
{ r, g, b, a }
}
}
///|
fn sample_bilinear(img : @image.ImageData, u : Double, v : Double) -> Color {
let x0 = u.floor().to_int()
let y0 = v.floor().to_int()
let fx = u - x0.to_double()
let fy = v - y0.to_double()
let c00 = sample_nearest(img, x0.to_double() + 0.5, y0.to_double() + 0.5)
let c10 = sample_nearest(
img,
(x0 + 1).to_double() + 0.5,
y0.to_double() + 0.5,
)
let c01 = sample_nearest(
img,
x0.to_double() + 0.5,
(y0 + 1).to_double() + 0.5,
)
let c11 = sample_nearest(
img,
(x0 + 1).to_double() + 0.5,
(y0 + 1).to_double() + 0.5,
)
let w00 = (1.0 - fx) * (1.0 - fy)
let w10 = fx * (1.0 - fy)
let w01 = (1.0 - fx) * fy
let w11 = fx * fy
let r = (c00.r.to_double() * w00 +
c10.r.to_double() * w10 +
c01.r.to_double() * w01 +
c11.r.to_double() * w11).to_int()
let g = (c00.g.to_double() * w00 +
c10.g.to_double() * w10 +
c01.g.to_double() * w01 +
c11.g.to_double() * w11).to_int()
let b = (c00.b.to_double() * w00 +
c10.b.to_double() * w10 +
c01.b.to_double() * w01 +
c11.b.to_double() * w11).to_int()
let a = c00.a * w00 + c10.a * w10 + c01.a * w01 + c11.a * w11
{ r, g, b, a }
}