///|
/// A gradient interpolation strategy for procedural canvas backgrounds.
pub(all) enum GradientMode {
LinearHorizontal
LinearVertical
LinearDiagonal
Radial
Diamond
} derive(Eq, Debug)
///|
pub(all) struct GradientStop {
position : Int
color : Rgba
} derive(Debug)
///|
pub fn GradientStop::new(position : Int, color : Rgba) -> GradientStop {
{ position, color }
}
///|
pub fn GradientStop::position(self : GradientStop) -> Int {
self.position
}
///|
pub fn GradientStop::color(self : GradientStop) -> Rgba {
self.color
}
///|
/// Fill a canvas with a two-color deterministic gradient.
pub fn Canvas::gradient(
self : Canvas,
mode : GradientMode,
first : Rgba,
second : Rgba,
) -> Canvas {
let result = self.copy()
for y in 0.. x
LinearVertical => y
LinearDiagonal => x + y
Radial => {
let dx = x - self.width / 2
let dy = y - self.height / 2
int_sqrt(dx * dx + dy * dy)
}
Diamond => {
let dx = if x > self.width / 2 {
x - self.width / 2
} else {
self.width / 2 - x
}
let dy = if y > self.height / 2 {
y - self.height / 2
} else {
self.height / 2 - y
}
dx + dy
}
}
let denominator = match mode {
LinearHorizontal => if self.width <= 1 { 1 } else { self.width - 1 }
LinearVertical => if self.height <= 1 { 1 } else { self.height - 1 }
LinearDiagonal =>
if self.width + self.height <= 2 {
1
} else {
self.width + self.height - 2
}
Radial =>
if self.width > self.height {
self.width / 2
} else {
self.height / 2
}
Diamond => (self.width + self.height) / 2
}
result.pixels[x + y * self.width] = mix_color(
first, second, numerator, denominator,
)
}
}
result
}
///|
/// Fill using multiple stops. Positions are integer percentages 0..1000.
pub fn Canvas::gradient_stops(
self : Canvas,
mode : GradientMode,
stops : ArrayView[GradientStop],
) -> Canvas {
if stops.length() == 0 {
self.copy()
} else if stops.length() == 1 {
Canvas::new(width=self.width, height=self.height, fill=stops[0].color())
} else {
let result = self.copy()
for y in 0..
x * 1000 / (if self.width <= 1 { 1 } else { self.width - 1 })
LinearVertical =>
y * 1000 / (if self.height <= 1 { 1 } else { self.height - 1 })
LinearDiagonal =>
(x + y) *
1000 /
(if self.width + self.height <= 2 {
1
} else {
self.width + self.height - 2
})
Radial =>
int_sqrt(
(x - self.width / 2) * (x - self.width / 2) +
(y - self.height / 2) * (y - self.height / 2),
) *
1000 /
(if self.width > self.height { self.width } else { self.height })
Diamond => {
let dx = if x > self.width / 2 {
x - self.width / 2
} else {
self.width / 2 - x
}
let dy = if y > self.height / 2 {
y - self.height / 2
} else {
self.height / 2 - y
}
(dx + dy) *
1000 /
(if self.width + self.height <= 2 {
1
} else {
self.width + self.height
})
}
}
let position = if raw < 0 { 0 } else if raw > 1000 { 1000 } else { raw }
result.pixels[x + y * self.width] = interpolate_stops(stops, position)
}
}
result
}
}
///|
fn interpolate_stops(stops : ArrayView[GradientStop], position : Int) -> Rgba {
if position <= stops[0].position() {
stops[0].color()
} else if position >= stops[stops.length() - 1].position() {
stops[stops.length() - 1].color()
} else {
let mut result = stops[0].color()
for i in 1..= left.position() && position <= right.position() {
result = mix_color(
left.color(),
right.color(),
position - left.position(),
right.position() - left.position(),
)
break
}
}
result
}
}
///|
/// Fill a clipped polygon using an integer scanline test.
pub fn Canvas::fill_polygon(
self : Canvas,
points : ArrayView[(Int, Int)],
color : Rgba,
) -> Int {
if points.length() < 3 {
0
} else {
let mut min_y = self.height
let mut max_y = 0
for point in points {
let (_, y) = point
if y < min_y {
min_y = y
}
if y > max_y {
max_y = y
}
}
let first_y = if min_y < 0 { 0 } else { min_y }
let last_y = if max_y >= self.height { self.height - 1 } else { max_y }
let mut written = 0
for y in first_y..<(last_y + 1) {
for x in 0.. Bool {
let mut inside = false
let mut j = points.length() - 1
for i in 0.. y) != (yj > y) {
let denominator = yj - yi
if denominator != 0 {
let crossing = (xj - xi) * (y - yi) / denominator + xi
if x < crossing {
inside = !inside
}
}
}
j = i
}
inside
}
///|
/// Fill a rectangle with rounded corners using a distance test.
pub fn Canvas::fill_rounded_rect(
self : Canvas,
x~ : Int,
y~ : Int,
width~ : Int,
height~ : Int,
radius~ : Int,
color~ : Rgba,
) -> Int {
if width <= 0 || height <= 0 {
0
} else {
let safe_radius = if radius < 0 {
0
} else if radius * 2 > width {
width / 2
} else if radius * 2 > height {
height / 2
} else {
radius
}
let mut written = 0
for row in y..<(y + height) {
for column in x..<(x + width) {
let near_left = column < x + safe_radius
let near_right = column >= x + width - safe_radius
let near_top = row < y + safe_radius
let near_bottom = row >= y + height - safe_radius
let corner_x = if near_left {
x + safe_radius
} else if near_right {
x + width - safe_radius - 1
} else {
column
}
let corner_y = if near_top {
y + safe_radius
} else if near_bottom {
y + height - safe_radius - 1
} else {
row
}
let dx = column - corner_x
let dy = row - corner_y
if (!near_left && !near_right) ||
(!near_top && !near_bottom) ||
dx * dx + dy * dy <= safe_radius * safe_radius {
if self.set(x=column, y=row, color~) {
written += 1
}
}
}
}
written
}
}
///|
/// Draw a regular polygon outline with a deterministic integer approximation.
pub fn Canvas::draw_polygon(
self : Canvas,
points : ArrayView[(Int, Int)],
color : Rgba,
) -> Int {
if points.length() < 2 {
0
} else {
let mut count = 0
for i in 0.. Array[(Int, Int)] {
if points < 2 || outer_radius < 0 || inner_radius < 0 {
[]
} else {
let result : Array[(Int, Int)] = []
let count = points * 2
for i in 0.. (Int, Int) {
let direction = (degrees % 360 + 360) % 360
if direction < 23 {
(1000, 0)
} else if direction < 68 {
(707, 707)
} else if direction < 113 {
(0, 1000)
} else if direction < 158 {
(-707, 707)
} else if direction < 203 {
(-1000, 0)
} else if direction < 248 {
(-707, -707)
} else if direction < 293 {
(0, -1000)
} else if direction < 338 {
(707, -707)
} else {
(1000, 0)
}
}
///|
/// Paint a repeating checker or stripe pattern into a copy of a canvas.
pub fn Canvas::pattern(
self : Canvas,
cell_size : Int,
first : Rgba,
second : Rgba,
stripes : Bool,
) -> Canvas {
if cell_size <= 0 {
self.copy()
} else {
let result = self.copy()
for y in 0.. Canvas {
let safe_amount = if amount < 0 {
0
} else if amount > 255 {
255
} else {
amount
}
let result = self.copy()
let mut state = seed
for i in 0.. Canvas {
let safe_levels = if levels < 2 {
2
} else if levels > 16 {
16
} else {
levels
}
let matrix = [[0, 2], [3, 1]]
let result = self.copy()
for y in 0.. Int {
if width <= 0 || height <= 0 {
0
} else {
let inner = if thickness < 0 { 0 } else { thickness }
let mut count = self.fill_rect(x~, y~, width~, height~, color=fill)
for layer in 0..