use crate::binary_image::BinaryImage;
#[derive(Clone, PartialEq, Debug)]
pub struct SignedDistanceField<D: DistanceStorage> {
pub width: u16,
pub height: u16,
pub distances: D,
pub distance_targets: Vec<(u16, u16)>
}
pub type F16DistanceStorage = Vec<half::f16>;
pub type F32DistanceStorage = Vec<f32>;
pub trait DistanceStorage {
fn new(length: usize) -> Self;
#[inline(always)]
fn get(&self, index: usize) -> f32;
#[inline(always)]
fn set(&mut self, index: usize, distance: f32);
}
pub struct NormalizedDistanceField<D: DistanceStorage> {
pub width: u16,
pub height: u16,
pub distances: D,
pub zero_distance: f32,
pub former_min_distance: f32,
pub former_max_distance: f32,
pub distance_targets: Vec<(u16, u16)>
}
impl<D> SignedDistanceField<D> where D: DistanceStorage {
pub fn compute(binary_image: &impl BinaryImage) -> Self {
let width = binary_image.width();
let height = binary_image.height();
let mut distance_field = SignedDistanceField {
width, height,
distances: D::new(width as usize * height as usize),
distance_targets: vec![(0, 0); width as usize * height as usize],
};
for y in 0..height {
for x in 0..width {
if is_at_edge(binary_image, x, y, -1, 0)
|| is_at_edge(binary_image, x, y, 1, 0)
|| is_at_edge(binary_image, x, y, 0, -1)
|| is_at_edge(binary_image, x, y, 0, 1)
{
distance_field.set_target_with_distance(x, y, x, y, 0.0);
}
}
}
for y in 0..height {
for x in 0..width {
let left_bottom = distance_field.distance_by_neighbour(x, y, -1, -1);
let bottom = distance_field.distance_by_neighbour(x, y, 0, -1);
let right_bottom = distance_field.distance_by_neighbour(x, y, 1, -1);
let left = distance_field.distance_by_neighbour(x, y, -1, 0);
let mut own = distance_field.get_distance(x, y);
if left_bottom < own { own = distance_field.take_neighbour_target(x, y, -1, -1); }
if bottom < own { own = distance_field.take_neighbour_target(x, y, 0, -1); }
if right_bottom < own { own = distance_field.take_neighbour_target(x, y, 1, -1); }
if left < own { distance_field.take_neighbour_target(x, y, -1, 0); }
}
}
for y in (0..height).rev() {
for x in (0..width).rev() {
let right = distance_field.distance_by_neighbour(x, y, 1, 0);
let top_left = distance_field.distance_by_neighbour(x, y, -1, 1);
let top = distance_field.distance_by_neighbour(x, y, 0, 1);
let top_right= distance_field.distance_by_neighbour(x, y, 1, 1);
let mut own = distance_field.get_distance(x, y);
if right < own { own = distance_field.take_neighbour_target(x, y, 1, 0); }
if top_left < own { own = distance_field.take_neighbour_target(x, y, -1, 1); }
if top < own { own = distance_field.take_neighbour_target(x, y, 0, 1); }
if top_right < own { distance_field.take_neighbour_target(x, y, 1, 1); }
}
}
for y in 0..height {
for x in 0..width {
if binary_image.is_inside(x, y) {
distance_field.invert_distance_sign(x, y);
}
}
}
distance_field
}
#[inline(always)]
fn distance_by_neighbour(&mut self, x: u16, y: u16, neighbour_x: i32, neighbour_y: i32, ) -> f32 {
let distance_to_neighbour = length(neighbour_x, neighbour_y);
let neighbour_x = x as i32 + neighbour_x;
let neighbour_y = y as i32 + neighbour_y;
if is_valid_index(neighbour_x, neighbour_y, self.width, self.height) {
let neighbours_distance = self.get_distance(
neighbour_x as u16, neighbour_y as u16
);
neighbours_distance + distance_to_neighbour
}
else {
std::f32::INFINITY
}
}
#[inline(always)]
pub fn get_distance(&self, x: u16, y: u16) -> f32 {
self.distances.get(self.flatten_index(x, y))
}
#[inline(always)]
pub fn get_distance_target(&self, x: u16, y: u16) -> (u16, u16) {
self.distance_targets[self.flatten_index(x, y)]
}
#[inline(always)]
fn set_target_with_distance(&mut self, x: u16, y: u16, target_x: u16, target_y: u16, distance: f32) {
let index = self.flatten_index(x, y);
self.distances.set(index, distance);
self.distance_targets[index] = (target_x, target_y);
}
#[inline(always)]
fn set_target_and_distance(&mut self, x: u16, y: u16, target_x: u16, target_y: u16) -> f32 {
let distance = distance(x, y, target_x, target_y);
self.set_target_with_distance(x, y, target_x, target_y, distance);
distance
}
#[inline(always)]
fn take_neighbour_target(&mut self, x: u16, y: u16, neighbour_x: i32, neighbour_y: i32) -> f32 {
debug_assert!(x as i32 + neighbour_x >= 0 && y as i32 + neighbour_y >= 0);
let target_of_neighbour = self.get_distance_target(
(x as i32 + neighbour_x) as u16,
(y as i32 + neighbour_y) as u16
);
self.set_target_and_distance(x, y, target_of_neighbour.0, target_of_neighbour.1)
}
#[inline(always)]
fn invert_distance_sign(&mut self, x: u16, y: u16) {
let index = self.flatten_index(x, y);
self.distances.set(index, - self.distances.get(index));
}
#[inline(always)]
pub fn flatten_index(&self, x: u16, y: u16) -> usize {
debug_assert!(
is_valid_index(x as i32, y as i32, self.width, self.height),
"Invalid pixel target index"
);
self.width as usize * y as usize + x as usize
}
pub fn normalize_distances(self) -> Option<NormalizedDistanceField<D>> {
NormalizedDistanceField::normalize(self)
}
pub fn normalize_clamped_distances(self, min: f32, max: f32) -> Option<NormalizedDistanceField<D>> {
NormalizedDistanceField::normalize_clamped(self, min, max)
}
}
#[inline(always)]
fn is_at_edge(image: &impl BinaryImage, x: u16, y: u16, neighbour_x: i32, neighbour_y: i32) -> bool {
let neighbour_x = x as i32 + neighbour_x;
let neighbour_y = y as i32 + neighbour_y;
is_valid_index(neighbour_x, neighbour_y, image.width(), image.height())
&& image.is_inside(x, y) != image.is_inside(neighbour_x as u16, neighbour_y as u16)
}
#[inline]
fn length(x: i32, y: i32) -> f32 {
((x * x + y * y) as f32).sqrt()
}
#[inline]
fn distance(x: u16, y: u16, target_x: u16, target_y: u16) -> f32 {
length(x as i32 - target_x as i32, y as i32 - target_y as i32)
}
#[inline]
fn is_valid_index(x: i32, y: i32, width: u16, height: u16) -> bool {
x >= 0 && y >= 0 && x < width as i32 && y < height as i32
}
#[inline]
fn normalize(value: f32, min: f32, max: f32) -> f32 {
(value - min) / (max - min)
}
impl<D> NormalizedDistanceField<D> where D: DistanceStorage {
pub fn normalize(distance_field: SignedDistanceField<D>) -> Option<Self> {
let mut distance_field = distance_field;
let width = distance_field.width;
let height = distance_field.height;
let (min, max) = (0..width as usize * height as usize)
.map(|index| distance_field.distances.get(index))
.fold((std::f32::INFINITY, std::f32::NEG_INFINITY), |(min, max), distance|
(min.min(distance), max.max(distance))
);
if min.is_infinite() || max.is_infinite() {
return None;
}
for index in 0..width as usize * height as usize {
let distance = distance_field.distances.get(index);
let normalized = normalize(distance, min, max);
distance_field.distances.set(index, normalized);
}
Some(NormalizedDistanceField {
width, height,
distances: distance_field.distances,
zero_distance: normalize(0.0, min, max),
former_max_distance: max, former_min_distance: min,
distance_targets: distance_field.distance_targets
})
}
pub fn normalize_clamped(distance_field: SignedDistanceField<D>, min: f32, max: f32) -> Option<Self> {
let mut normalized = NormalizedDistanceField {
width: distance_field.width,
height: distance_field.height,
distances: distance_field.distances,
former_min_distance: std::f32::INFINITY,
former_max_distance: std::f32::NEG_INFINITY,
zero_distance: normalize(0.0, min, max),
distance_targets: distance_field.distance_targets
};
for index in 0..normalized.width as usize * normalized.height as usize {
let distance = normalized.distances.get(index);
if distance.is_infinite() { return None; }
normalized.former_max_distance = normalized.former_max_distance.max(distance);
normalized.former_min_distance = normalized.former_min_distance.min(distance);
let clamped = distance.min(max).max(min);
let normalized_distance = normalize(clamped, min, max);
normalized.distances.set(index, normalized_distance);
}
Some(normalized)
}
pub fn to_u8(&self) -> Vec<u8> {
(0..self.width as usize * self.height as usize)
.map(|index| (self.distances.get(index).min(1.0).max(0.0) * std::u8::MAX as f32) as u8)
.collect()
}
pub fn to_u16(&self) -> Vec<u16> {
(0..self.width as usize * self.height as usize)
.map(|index| (self.distances.get(index).min(1.0).max(0.0) * std::u16::MAX as f32) as u16)
.collect()
}
#[cfg(feature = "piston_image")]
pub fn to_gray_u8_image(&self) -> image::GrayImage {
image::GrayImage::from_raw(self.width as u32, self.height as u32, self.to_u8())
.expect("incorrect vector length")
}
}
impl DistanceStorage for F16DistanceStorage {
fn new(length: usize) -> Self {
vec![half::consts::INFINITY; length]
}
#[inline(always)]
fn get(&self, index: usize) -> f32 {
self[index].to_f32()
}
#[inline(always)]
fn set(&mut self, index: usize, distance: f32) {
self[index] = half::f16::from_f32(distance)
}
}
impl DistanceStorage for F32DistanceStorage {
fn new(length: usize) -> Self {
vec![std::f32::INFINITY; length]
}
#[inline(always)]
fn get(&self, index: usize) -> f32 {
self[index]
}
#[inline(always)]
fn set(&mut self, index: usize, distance: f32) {
self[index] = distance
}
}