///|
/// Small vector primitives used to draw overlays without a foreign runtime.
pub(all) struct Point2i {
x : Int
y : Int
} derive(Eq, Debug)
///|
pub fn Point2i::new(x : Int, y : Int) -> Point2i {
{ x, y }
}
///|
pub fn Point2i::x(self : Point2i) -> Int {
self.x
}
///|
pub fn Point2i::y(self : Point2i) -> Int {
self.y
}
///|
pub fn Point2i::offset(self : Point2i, dx : Int, dy : Int) -> Point2i {
{ x: self.x + dx, y: self.y + dy }
}
///|
pub(all) struct Polyline {
points : Array[Point2i]
closed : Bool
} derive(Debug)
///|
pub fn Polyline::new(points : Array[Point2i], closed : Bool) -> Polyline {
{ points, closed }
}
///|
pub fn Polyline::points(self : Polyline) -> ArrayView[Point2i] {
self.points[:]
}
///|
pub fn Polyline::closed(self : Polyline) -> Bool {
self.closed
}
///|
pub fn Polyline::translated(self : Polyline, dx : Int, dy : Int) -> Polyline {
let result : Array[Point2i] = []
for point in self.points {
result.push(point.offset(dx, dy))
}
{ ..self, points: result }
}
///|
pub fn Polyline::bounds(self : Polyline) -> GifRect? {
if self.points.length() == 0 {
None
} else {
let mut left = self.points[0].x
let mut right = left
let mut top = self.points[0].y
let mut bottom = top
for point in self.points {
if point.x < left {
left = point.x
}
if point.x > right {
right = point.x
}
if point.y < top {
top = point.y
}
if point.y > bottom {
bottom = point.y
}
}
Some(
GifRect::new(left~, top~, width=right - left + 1, height=bottom - top + 1),
)
}
}
///|
/// Draw an anti-aliased-looking polyline using integer supersampling.
pub fn Canvas::stroke_polyline(
self : Canvas,
polyline : Polyline,
color : Rgba,
thickness : Int,
) -> Int {
if polyline.points.length() < 2 {
0
} else {
let mut drawn = 0
let safe_thickness = if thickness < 1 { 1 } else { thickness }
for i in 0..<(polyline.points.length() - 1) {
let start = polyline.points[i]
let end = polyline.points[i + 1]
drawn += self.draw_line(
x0=start.x,
y0=start.y,
x1=end.x,
y1=end.y,
color~,
)
if safe_thickness > 1 {
drawn += self.draw_circle(
center_x=start.x,
center_y=start.y,
radius=safe_thickness / 2,
color~,
)
drawn += self.draw_circle(
center_x=end.x,
center_y=end.y,
radius=safe_thickness / 2,
color~,
)
}
}
if polyline.closed {
let start = polyline.points[polyline.points.length() - 1]
let end = polyline.points[0]
drawn += self.draw_line(
x0=start.x,
y0=start.y,
x1=end.x,
y1=end.y,
color~,
)
}
drawn
}
}
///|
/// Sample a quadratic Bezier curve into a polyline.
pub fn quadratic_bezier(
start : Point2i,
control : Point2i,
end : Point2i,
samples : Int,
) -> Polyline {
let count = if samples < 2 { 2 } else { samples }
let points : Array[Point2i] = []
for i in 0..<=count {
let t = i * 1000 / count
let inverse = 1000 - t
let x = (
start.x * inverse * inverse +
control.x * 2 * inverse * t +
end.x * t * t
) /
1000000
let y = (
start.y * inverse * inverse +
control.y * 2 * inverse * t +
end.y * t * t
) /
1000000
points.push(Point2i::new(x, y))
}
Polyline::new(points, false)
}
///|
/// Draw a quadratic Bezier curve in one operation.
pub fn Canvas::draw_quadratic(
self : Canvas,
start : Point2i,
control : Point2i,
end : Point2i,
color : Rgba,
samples : Int,
) -> Int {
self.stroke_polyline(quadratic_bezier(start, control, end, samples), color, 1)
}
///|
/// Fill a regular polygon centered at a coordinate.
pub fn Canvas::fill_regular_polygon(
self : Canvas,
center_x : Int,
center_y : Int,
radius : Int,
sides : Int,
color : Rgba,
) -> Int {
let safe_sides = if sides < 3 { 3 } else if sides > 64 { 64 } else { sides }
let safe_radius = if radius < 0 { 0 } else { radius }
let vertices : Array[(Int, Int)] = []
for i in 0.. Int {
let safe_dash = if dash < 1 { 1 } else { dash }
let safe_gap = if gap < 0 { 0 } else { gap }
let dx = end_x - start_x
let dy = end_y - start_y
let length = if dx.abs() > dy.abs() { dx.abs() } else { dy.abs() }
let mut drawn = 0
let safe_length = if length == 0 { 1 } else { length }
for i in 0..<(length + 1) {
let period = safe_dash + safe_gap
if period == 0 || i % period < safe_dash {
let x = start_x + dx * i / safe_length
let y = start_y + dy * i / safe_length
if self.set(x~, y~, color~) {
drawn += 1
}
}
}
drawn
}
///|
/// Create an outline-only version of a polygon.
pub fn Canvas::stroke_polygon(
self : Canvas,
vertices : ArrayView[(Int, Int)],
color : Rgba,
) -> Int {
if vertices.length() < 2 {
0
} else {
let mut drawn = 0
for i in 0..