///|
/// Integer affine transforms for controlled sprite placement.
pub(all) struct Affine2i {
a : Int
b : Int
c : Int
d : Int
tx : Int
ty : Int
scale : Int
} derive(Debug)
///|
pub fn Affine2i::identity() -> Affine2i {
{ a: 1000, b: 0, c: 0, d: 1000, tx: 0, ty: 0, scale: 1000 }
}
///|
pub fn Affine2i::translate(self : Affine2i, x : Int, y : Int) -> Affine2i {
{ ..self, tx: self.tx + x * self.scale, ty: self.ty + y * self.scale }
}
///|
pub fn Affine2i::scale(self : Affine2i, per_mille : Int) -> Affine2i {
{
..self,
a: self.a * per_mille / 1000,
b: self.b * per_mille / 1000,
c: self.c * per_mille / 1000,
d: self.d * per_mille / 1000,
}
}
///|
pub fn Affine2i::apply(self : Affine2i, x : Int, y : Int) -> (Int, Int) {
(
(self.a * x + self.b * y + self.tx) / self.scale,
(self.c * x + self.d * y + self.ty) / self.scale,
)
}
///|
pub fn Affine2i::inverse_apply(self : Affine2i, x : Int, y : Int) -> (Int, Int) {
let determinant = self.a * self.d - self.b * self.c
if determinant == 0 {
(x, y)
} else {
let px = x * self.scale - self.tx
let py = y * self.scale - self.ty
(
(self.d * px - self.b * py) / determinant,
(-self.c * px + self.a * py) / determinant,
)
}
}
///|
/// Apply an affine transform with nearest-neighbor sampling and transparent fill.
pub fn Canvas::affine(self : Canvas, transform : Affine2i) -> Canvas {
let result = Canvas::new(
width=self.width,
height=self.height,
fill=rgba(0, 0, 0, 0),
)
for y in 0.. ()
Some(color) => result.pixels[x + y * self.width] = color
}
}
}
result
}
///|
/// Rotate around the canvas center using a right-angle approximation.
pub fn Canvas::affine_rotate(
self : Canvas,
rotation : CanvasRotation,
) -> Canvas {
match rotation {
Clockwise90 => {
let result = self.rotate90()
result
}
HalfTurn => self.rotate180()
Clockwise270 => self.rotate270()
}
}
///|
/// Place a transformed canvas at a destination with clipping.
pub fn Canvas::draw_transformed(
self : Canvas,
source : Canvas,
transform : Affine2i,
) -> Int {
let transformed = source.affine(transform)
self.blit(transformed, destination_x=0, destination_y=0)
}
///|
/// Fit a source into an output rectangle using a center translation.
pub fn Canvas::fit_affine(self : Canvas, width : Int, height : Int) -> Canvas {
if self.width == 0 || self.height == 0 {
Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
} else {
let horizontal = width * 1000 / self.width
let vertical = height * 1000 / self.height
let scale = if horizontal < vertical { horizontal } else { vertical }
let scaled = self.resize_nearest(
width=self.width * scale / 1000,
height=self.height * scale / 1000,
)
let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
ignore(
result.blit(
scaled,
destination_x=(width - scaled.width) / 2,
destination_y=(height - scaled.height) / 2,
),
)
result
}
}