///|
/// Alpha-composite a color onto pixel `(y, x)` in place (no-op if OOB).
fn blend_at(
img : Image,
y : Int,
x : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Unit {
if y < 0 || x < 0 || y >= img.h || x >= img.w {
return
}
let o = img.offset(y, x)
let af = a.to_double() / 255.0
let inv = 1.0 - af
img.data[o] = round_byte(img.data[o].to_double() * inv + r.to_double() * af)
img.data[o + 1] = round_byte(
img.data[o + 1].to_double() * inv + g.to_double() * af,
)
img.data[o + 2] = round_byte(
img.data[o + 2].to_double() * inv + b.to_double() * af,
)
let ea = img.data[o + 3].to_double()
img.data[o + 3] = round_byte(a.to_double() + ea * inv)
}
///|
/// Stamp a filled `width x width` block centered at `(y, x)`.
fn stamp(
img : Image,
y : Int,
x : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
width : Int,
) -> Unit {
let hw = if width < 1 { 0 } else { (width - 1) / 2 }
for dy = -hw; dy <= hw; dy = dy + 1 {
for dx = -hw; dx <= hw; dx = dx + 1 {
blend_at(img, y + dy, x + dx, r, g, b, a)
}
}
}
///|
/// Draw a single pixel (alpha blended). Returns a new image.
///
/// Sets the pixel at `(y, x)` to `(r, g, b)` alpha-composited with the
/// existing colour using source-over blending. Out-of-bounds coordinates are
/// ignored. The input image is not modified.
pub fn draw_pixel(
img : Image,
y : Int,
x : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Image {
let out = img.clone()
blend_at(out, y, x, r, g, b, a)
out
}
///|
/// Draw a line with the Bresenham algorithm and the given stroke width.
///
/// Connects `(y0, x0)` to `(y1, x1)` inclusive, stamping a `width x width`
/// block at each rasterized pixel using source-over alpha blending. The input
/// image is not modified.
pub fn draw_line(
img : Image,
y0 : Int,
x0 : Int,
y1 : Int,
x1 : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
width : Int,
) -> Image {
let out = img.clone()
let dx = (x1 - x0).abs()
let dy = (y1 - y0).abs()
let sx = if x0 < x1 { 1 } else { -1 }
let sy = if y0 < y1 { 1 } else { -1 }
let mut err = dx - dy
let mut cx = x0
let mut cy = y0
for ;; {
stamp(out, cy, cx, r, g, b, a, width)
if cx == x1 && cy == y1 {
break
}
let e2 = 2 * err
if e2 > -dy {
err = err - dy
cx = cx + sx
}
if e2 < dx {
err = err + dx
cy = cy + sy
}
}
out
}