///|
pub fn contour_points(mask : BinaryMask) -> Array[Point] {
let result : Array[Point] = []
let boundary = mask.boundary()
for y in 0.. Array[Array[Point]] {
let loops : Array[Array[Point]] = []
let boundary = mask.boundary()
let visited = BinaryMask(width=mask.width, height=mask.height)
for y in 0.. Double {
if points.length() < 3 {
0.0
} else {
let mut total = 0.0
for i in 0.. Double {
if points.length() < 2 {
0.0
} else {
let mut total = 0.0
for i in 0.. Point {
if points.is_empty() {
Point::Point(x=0.0, y=0.0)
} else {
let signed_area = polygon_area(points)
if signed_area.abs() <= 0.0000000001 {
let mut x = 0.0
let mut y = 0.0
for point in points {
x += point.x
y += point.y
}
Point::Point(
x=x / Double::from_int(points.length()),
y=y / Double::from_int(points.length()),
)
} else {
let mut x = 0.0
let mut y = 0.0
for i in 0.. Bounds {
if points.is_empty() {
{ min_x: 0, min_y: 0, max_x: -1, max_y: -1 }
} else {
let mut min_x = points[0].x.floor().to_int()
let mut min_y = points[0].y.floor().to_int()
let mut max_x = points[0].x.ceil().to_int()
let mut max_y = points[0].y.ceil().to_int()
for point in points[1:] {
min_x = min_x.min(point.x.floor().to_int())
min_y = min_y.min(point.y.floor().to_int())
max_x = max_x.max(point.x.ceil().to_int())
max_y = max_y.max(point.y.ceil().to_int())
}
{ min_x, min_y, max_x, max_y }
}
}
///|
pub fn polygon_metrics(points : Array[Point]) -> PolygonMetrics {
let area = polygon_area(points).abs()
let perimeter = polygon_perimeter(points)
let bounds = polygon_bounds(points)
let box_area = Double::from_int(
(bounds.max_x - bounds.min_x + 1).max(0) *
(bounds.max_y - bounds.min_y + 1).max(0),
)
{
area,
perimeter,
centroid: polygon_centroid(points),
circularity: if perimeter <= 0.0 {
0.0
} else {
4.0 * 3.141592653589793 * area / (perimeter * perimeter)
},
rectangularity: if box_area <= 0.0 {
0.0
} else {
area / box_area
},
}
}
///|
fn lexicographic_before(a : Point, b : Point) -> Bool {
a.x < b.x || (a.x == b.x && a.y < b.y)
}
///|
fn cross_at(origin : Point, a : Point, b : Point) -> Double {
(a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x)
}
///|
pub fn convex_hull(points : Array[Point]) -> Array[Point] {
if points.length() < 3 {
points
} else {
let ordered : Array[Point] = []
for point in points {
let mut index = 0
while index < ordered.length() &&
lexicographic_before(ordered[index], point) {
index += 1
}
ordered.insert(index, point)
}
let lower : Array[Point] = []
for point in ordered {
while lower.length() >= 2 &&
cross_at(
lower[lower.length() - 2],
lower[lower.length() - 1],
point,
) <=
0.0 {
ignore(lower.pop())
}
lower.push(point)
}
let upper : Array[Point] = []
let mut reverse_index = ordered.length() - 1
while reverse_index >= 0 {
let point = ordered[reverse_index]
while upper.length() >= 2 &&
cross_at(
upper[upper.length() - 2],
upper[upper.length() - 1],
point,
) <=
0.0 {
ignore(upper.pop())
}
upper.push(point)
reverse_index -= 1
}
ignore(lower.pop())
ignore(upper.pop())
lower.append(upper)
lower
}
}
///|
pub fn simplify_polygon(
points : Array[Point],
tolerance : Double,
) -> Array[Point] {
if points.length() <= 2 {
points
} else {
let result : Array[Point] = [points[0]]
for point in points[1:] {
if distance(result[result.length() - 1], point) >= tolerance.max(0.0) {
result.push(point)
}
}
if result.length() > 1 &&
distance(result[0], result[result.length() - 1]) < tolerance.max(0.0) {
ignore(result.pop())
}
result
}
}
///|
pub fn contour_curvature(points : Array[Point]) -> Array[Double] {
let result : Array[Double] = []
if points.length() < 3 {
result
} else {
for i in 0.. Double {
let metrics = polygon_metrics(points)
if metrics.perimeter <= 0.0 {
0.0
} else {
metrics.area / metrics.perimeter
}
}