1use crate::definitions::Image;
5use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma};
6use std::cmp::min;
7
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum Norm {
12 L1,
16 L2,
25 LInf,
29}
30
31pub fn distance_transform(image: &GrayImage, norm: Norm) -> GrayImage {
92 let mut out = image.clone();
93 distance_transform_mut(&mut out, norm);
94 out
95}
96
97pub fn distance_transform_mut(image: &mut GrayImage, norm: Norm) {
107 distance_transform_impl(image, norm, DistanceFrom::Foreground);
108}
109
110#[derive(PartialEq, Eq, Copy, Clone)]
111pub(crate) enum DistanceFrom {
112 Foreground,
113 Background,
114}
115
116pub(crate) fn distance_transform_impl(image: &mut GrayImage, norm: Norm, from: DistanceFrom) {
117 match norm {
118 Norm::LInf => distance_transform_impl_linf_or_l1::<true>(image, from),
119 Norm::L1 => distance_transform_impl_linf_or_l1::<false>(image, from),
120 Norm::L2 => {
121 match from {
122 DistanceFrom::Foreground => (),
123 DistanceFrom::Background => image
124 .iter_mut()
125 .for_each(|p| *p = if *p == 0 { 1 } else { 0 }),
126 }
127 let float_dist: ImageBuffer<Luma<f64>, Vec<f64>> =
128 euclidean_squared_distance_transform(image);
129 image
130 .iter_mut()
131 .zip(float_dist.iter())
132 .for_each(|(u, v)| *u = v.sqrt().clamp(0.0, 255.0).ceil() as u8);
133 }
134 }
135}
136
137fn distance_transform_impl_linf_or_l1<const IS_LINF: bool>(
138 image: &mut GrayImage,
139 from: DistanceFrom,
140) {
141 let max_distance = Luma([min(image.width() + image.height(), 255u32) as u8]);
142
143 unsafe {
150 for y in 0..image.height() {
152 for x in 0..image.width() {
153 if from == DistanceFrom::Foreground {
154 if image.unsafe_get_pixel(x, y)[0] > 0u8 {
155 image.unsafe_put_pixel(x, y, Luma([0u8]));
156 continue;
157 }
158 } else if image.unsafe_get_pixel(x, y)[0] == 0u8 {
159 image.unsafe_put_pixel(x, y, Luma([0u8]));
160 continue;
161 }
162
163 image.unsafe_put_pixel(x, y, max_distance);
164
165 if x > 0 {
166 check(image, x, y, x - 1, y);
167 }
168
169 if y > 0 {
170 check(image, x, y, x, y - 1);
171
172 if IS_LINF {
173 if x > 0 {
174 check(image, x, y, x - 1, y - 1);
175 }
176 if x < image.width() - 1 {
177 check(image, x, y, x + 1, y - 1);
178 }
179 }
180 }
181 }
182 }
183
184 for y in (0..image.height()).rev() {
186 for x in (0..image.width()).rev() {
187 if x < image.width() - 1 {
188 check(image, x, y, x + 1, y);
189 }
190
191 if y < image.height() - 1 {
192 check(image, x, y, x, y + 1);
193
194 if IS_LINF {
195 if x < image.width() - 1 {
196 check(image, x, y, x + 1, y + 1);
197 }
198 if x > 0 {
199 check(image, x, y, x - 1, y + 1);
200 }
201 }
202 }
203 }
204 }
205 }
206}
207
208unsafe fn check(
212 image: &mut GrayImage,
213 current_x: u32,
214 current_y: u32,
215 candidate_x: u32,
216 candidate_y: u32,
217) {
218 let current = image.unsafe_get_pixel(current_x, current_y)[0] as u16;
219 let candidate_incr = image.unsafe_get_pixel(candidate_x, candidate_y)[0] as u16 + 1;
220 if candidate_incr < current {
221 image.unsafe_put_pixel(current_x, current_y, Luma([candidate_incr as u8]));
222 }
223}
224
225pub fn euclidean_squared_distance_transform(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
233 let (width, height) = image.dimensions();
234 let mut result = ImageBuffer::new(width, height);
235 let mut column_envelope = LowerEnvelope::new(height as usize);
236
237 for x in 0..width {
239 let source = Column { image, column: x };
240 let mut sink = ColumnMut {
241 image: &mut result,
242 column: x,
243 };
244 distance_transform_1d_mut(&source, &mut sink, &mut column_envelope);
245 }
246
247 let mut row_buffer = vec![0f64; width as usize];
248 let mut row_envelope = LowerEnvelope::new(width as usize);
249
250 for y in 0..height {
252 for x in 0..width {
253 row_buffer[x as usize] = result.get_pixel(x, y)[0];
254 }
255 let mut sink = Row {
256 image: &mut result,
257 row: y,
258 };
259 distance_transform_1d_mut(&row_buffer, &mut sink, &mut row_envelope);
260 }
261
262 result
263}
264
265struct LowerEnvelope {
266 locations: Vec<usize>,
268 boundaries: Vec<f64>,
273}
274
275impl LowerEnvelope {
276 fn new(image_side: usize) -> LowerEnvelope {
277 LowerEnvelope {
278 locations: vec![0; image_side],
279 boundaries: vec![f64::NAN; image_side + 1],
280 }
281 }
282}
283
284trait Sink {
285 fn put(&mut self, idx: usize, value: f64);
286 fn len(&self) -> usize;
287}
288
289trait Source {
290 fn get(&self, idx: usize) -> f64;
291 fn len(&self) -> usize;
292}
293
294struct Row<'a> {
295 image: &'a mut Image<Luma<f64>>,
296 row: u32,
297}
298
299impl<'a> Sink for Row<'a> {
300 fn put(&mut self, idx: usize, value: f64) {
301 unsafe {
302 self.image
303 .unsafe_put_pixel(idx as u32, self.row, Luma([value]));
304 }
305 }
306 fn len(&self) -> usize {
307 self.image.width() as usize
308 }
309}
310
311struct ColumnMut<'a> {
312 image: &'a mut Image<Luma<f64>>,
313 column: u32,
314}
315
316impl<'a> Sink for ColumnMut<'a> {
317 fn put(&mut self, idx: usize, value: f64) {
318 unsafe {
319 self.image
320 .unsafe_put_pixel(self.column, idx as u32, Luma([value]));
321 }
322 }
323 fn len(&self) -> usize {
324 self.image.height() as usize
325 }
326}
327
328impl Source for Vec<f64> {
329 fn get(&self, idx: usize) -> f64 {
330 self[idx]
331 }
332 fn len(&self) -> usize {
333 self.len()
334 }
335}
336
337impl Source for [f64] {
338 fn get(&self, idx: usize) -> f64 {
339 self[idx]
340 }
341 fn len(&self) -> usize {
342 self.len()
343 }
344}
345
346struct Column<'a> {
347 image: &'a Image<Luma<u8>>,
348 column: u32,
349}
350
351impl<'a> Source for Column<'a> {
352 fn get(&self, idx: usize) -> f64 {
353 let pixel = unsafe { self.image.unsafe_get_pixel(self.column, idx as u32)[0] as f64 };
354 if pixel > 0f64 {
355 0f64
356 } else {
357 f64::INFINITY
358 }
359 }
360 fn len(&self) -> usize {
361 self.image.height() as usize
362 }
363}
364
365fn distance_transform_1d_mut<S, T>(f: &S, result: &mut T, envelope: &mut LowerEnvelope)
366where
367 S: Source,
368 T: Sink,
369{
370 assert!(result.len() == f.len());
371 assert!(envelope.boundaries.len() == f.len() + 1);
372 assert!(envelope.locations.len() == f.len());
373
374 if f.len() == 0 {
375 return;
376 }
377
378 let mut k = 0;
380
381 envelope.locations[0] = 0;
384
385 envelope.boundaries[0] = f64::NEG_INFINITY;
387 envelope.boundaries[1] = f64::INFINITY;
388
389 for q in 1..f.len() {
390 if f.get(q) == f64::INFINITY {
391 continue;
392 }
393
394 if k == 0 && f.get(envelope.locations[k]) == f64::INFINITY {
395 envelope.locations[k] = q;
396 envelope.boundaries[k] = f64::NEG_INFINITY;
397 envelope.boundaries[k + 1] = f64::INFINITY;
398 continue;
399 }
400
401 let mut s = intersection(f, envelope.locations[k], q);
409
410 while s <= envelope.boundaries[k] {
411 k -= 1;
416 s = intersection(f, envelope.locations[k], q);
417 }
418
419 k += 1;
420 envelope.locations[k] = q;
421 envelope.boundaries[k] = s;
422 envelope.boundaries[k + 1] = f64::INFINITY;
423 }
424
425 let mut k = 0;
426 for q in 0..f.len() {
427 while envelope.boundaries[k + 1] < q as f64 {
428 k += 1;
429 }
430 let dist = q as f64 - envelope.locations[k] as f64;
431 result.put(q, dist * dist + f.get(envelope.locations[k]));
432 }
433}
434
435fn intersection<S: Source + ?Sized>(f: &S, p: usize, q: usize) -> f64 {
437 let fq = f.get(q);
445 let fp = f.get(p);
446 let p = p as f64;
447 let q = q as f64;
448
449 ((fq + q * q) - (fp + p * p)) / (2.0 * q - 2.0 * p)
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455 use crate::definitions::Image;
456 use crate::property_testing::GrayTestImage;
457 use crate::utils::pixel_diff_summary;
458 use image::{GrayImage, Luma};
459 use quickcheck::{quickcheck, Arbitrary, Gen, TestResult};
460 use std::cmp::max;
461 use std::f64;
462
463 #[derive(Debug, Clone)]
465 struct BoundedFloat(f64);
466
467 impl Arbitrary for BoundedFloat {
468 fn arbitrary(g: &mut Gen) -> Self {
469 let mut f;
470
471 loop {
472 f = f64::arbitrary(g);
473
474 if f.is_normal() {
475 f = f.clamp(-1_000_000.0, 1_000_000.0);
476 break;
477 }
478 }
479
480 BoundedFloat(f)
481 }
482 }
483
484 #[test]
485 fn test_distance_transform_saturation() {
486 let image = GrayImage::from_fn(300, 3, |x, y| match (x, y) {
488 (0, 0) => Luma([255u8]),
489 _ => Luma([0u8]),
490 });
491
492 let expected = GrayImage::from_fn(300, 3, |x, y| Luma([min(255, max(x, y)) as u8]));
494
495 let distances = distance_transform(&image, Norm::LInf);
496 assert_pixels_eq!(distances, expected);
497 }
498
499 impl Sink for Vec<f64> {
500 fn put(&mut self, idx: usize, value: f64) {
501 self[idx] = value;
502 }
503 fn len(&self) -> usize {
504 self.len()
505 }
506 }
507
508 fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
509 let mut r = vec![0.0; f.len()];
510 let mut e = LowerEnvelope::new(f.len());
511 distance_transform_1d_mut(f, &mut r, &mut e);
512 r
513 }
514
515 #[test]
516 fn test_distance_transform_1d_constant() {
517 let f = vec![0.0, 0.0, 0.0];
518 let dists = distance_transform_1d(&f);
519 assert_eq!(dists, &[0.0, 0.0, 0.0]);
520 }
521
522 #[test]
523 fn test_distance_transform_1d_descending_gradient() {
524 let f = vec![7.0, 5.0, 3.0, 1.0];
525 let dists = distance_transform_1d(&f);
526 assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
527 }
528
529 #[test]
530 fn test_distance_transform_1d_ascending_gradient() {
531 let f = vec![1.0, 3.0, 5.0, 7.0];
532 let dists = distance_transform_1d(&f);
533 assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
534 }
535
536 #[test]
537 fn test_distance_transform_1d_with_infinities() {
538 let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
539 let dists = distance_transform_1d(&f);
540 assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
541 }
542
543 fn distance_transform_1d_reference(f: &[f64]) -> Vec<f64> {
547 let mut ret = vec![0.0; f.len()];
548 for q in 0..f.len() {
549 ret[q] = (0..f.len())
550 .map(|p| {
551 let dist = p as f64 - q as f64;
552 dist * dist + f[p]
553 })
554 .fold(f64::NAN, f64::min);
555 }
556 ret
557 }
558
559 #[cfg_attr(miri, ignore = "slow")]
560 #[test]
561 fn test_distance_transform_1d_matches_reference_implementation() {
562 fn prop(f: Vec<BoundedFloat>) -> bool {
563 let v: Vec<f64> = f.into_iter().map(|n| n.0).collect();
564 let expected = distance_transform_1d_reference(&v);
565 let actual = distance_transform_1d(&v);
566 expected == actual
567 }
568
569 quickcheck(prop as fn(Vec<BoundedFloat>) -> bool);
570 }
571
572 fn euclidean_squared_distance_transform_reference(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
573 let (width, height) = image.dimensions();
574
575 let mut dists = Image::new(width, height);
576
577 for y in 0..height {
578 for x in 0..width {
579 let mut min = f64::INFINITY;
580 for yc in 0..height {
581 for xc in 0..width {
582 let pc = image.get_pixel(xc, yc)[0];
583 if pc > 0 {
584 let dx = xc as f64 - x as f64;
585 let dy = yc as f64 - y as f64;
586
587 min = f64::min(min, dx * dx + dy * dy);
588 }
589 }
590 }
591
592 dists.put_pixel(x, y, Luma([min]));
593 }
594 }
595
596 dists
597 }
598
599 #[cfg_attr(miri, ignore = "slow")]
600 #[test]
601 fn test_euclidean_squared_distance_transform_matches_reference_implementation() {
602 fn prop(image: GrayTestImage) -> TestResult {
603 let expected = euclidean_squared_distance_transform_reference(&image.0);
604 let actual = euclidean_squared_distance_transform(&image.0);
605 match pixel_diff_summary(&actual, &expected) {
606 None => TestResult::passed(),
607 Some(err) => TestResult::error(err),
608 }
609 }
610 quickcheck(prop as fn(GrayTestImage) -> TestResult);
611 }
612
613 #[test]
614 fn test_euclidean_squared_distance_transform_example() {
615 let image = gray_image!(
616 1, 0, 0, 0, 0;
617 0, 1, 0, 0, 0;
618 1, 1, 1, 0, 0;
619 0, 0, 0, 0, 0;
620 0, 0, 1, 0, 0
621 );
622
623 let expected = gray_image!(type: f64,
624 0.0, 1.0, 2.0, 5.0, 8.0;
625 1.0, 0.0, 1.0, 2.0, 5.0;
626 0.0, 0.0, 0.0, 1.0, 4.0;
627 1.0, 1.0, 1.0, 2.0, 5.0;
628 4.0, 1.0, 0.0, 1.0, 4.0
629 );
630
631 let dist = euclidean_squared_distance_transform(&image);
632 assert_pixels_eq_within!(dist, expected, 1e-6);
633 }
634}
635
636#[cfg(not(miri))]
637#[cfg(test)]
638mod benches {
639 use super::*;
640 use crate::utils::gray_bench_image;
641 use test::{black_box, Bencher};
642
643 macro_rules! bench_euclidean_squared_distance_transform {
644 ($name:ident, side: $s:expr) => {
645 #[bench]
646 fn $name(b: &mut Bencher) {
647 let image = gray_bench_image($s, $s);
648 b.iter(|| {
649 let distance = euclidean_squared_distance_transform(&image);
650 black_box(distance);
651 })
652 }
653 };
654 }
655
656 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_10, side: 10);
657 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_100, side: 100);
658 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_200, side: 200);
659
660 macro_rules! bench_distance_transform {
661 ($name:ident, $norm:expr, side: $s:expr) => {
662 #[bench]
663 fn $name(b: &mut Bencher) {
664 let image = gray_bench_image($s, $s);
665 b.iter(|| {
666 let distance = distance_transform(&image, $norm);
667 black_box(distance);
668 })
669 }
670 };
671 }
672
673 bench_distance_transform!(bench_distance_transform_l1_10, Norm::L1, side: 10);
674 bench_distance_transform!(bench_distance_transform_l1_100, Norm::L1, side: 100);
675 bench_distance_transform!(bench_distance_transform_l1_200, Norm::L1, side: 200);
676 bench_distance_transform!(bench_distance_transform_l2_10, Norm::L2, side: 10);
677 bench_distance_transform!(bench_distance_transform_l2_100, Norm::L2, side: 100);
678 bench_distance_transform!(bench_distance_transform_l2_200, Norm::L2, side: 200);
679 bench_distance_transform!(bench_distance_transform_linf_10, Norm::LInf, side: 10);
680 bench_distance_transform!(bench_distance_transform_linf_100, Norm::LInf, side: 100);
681 bench_distance_transform!(bench_distance_transform_linf_200, Norm::LInf, side: 200);
682}