1use crate::definitions::Image;
5use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma};
6use std::cmp::min;
7use std::{f64, u8};
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub enum Norm {
23 L1,
26 LInf,
29}
30
31pub fn distance_transform(image: &GrayImage, norm: Norm) -> GrayImage {
77 let mut out = image.clone();
78 distance_transform_mut(&mut out, norm);
79 out
80}
81
82pub fn distance_transform_mut(image: &mut GrayImage, norm: Norm) {
89 distance_transform_impl(image, norm, DistanceFrom::Foreground);
90}
91
92#[derive(PartialEq, Eq, Copy, Clone)]
93pub(crate) enum DistanceFrom {
94 Foreground,
95 Background,
96}
97
98pub(crate) fn distance_transform_impl(image: &mut GrayImage, norm: Norm, from: DistanceFrom) {
99 let max_distance = Luma([min(image.width() + image.height(), 255u32) as u8]);
100
101 unsafe {
102 for y in 0..image.height() {
104 for x in 0..image.width() {
105 if from == DistanceFrom::Foreground {
106 if image.unsafe_get_pixel(x, y)[0] > 0u8 {
107 image.unsafe_put_pixel(x, y, Luma([0u8]));
108 continue;
109 }
110 } else if image.unsafe_get_pixel(x, y)[0] == 0u8 {
111 image.unsafe_put_pixel(x, y, Luma([0u8]));
112 continue;
113 }
114
115 image.unsafe_put_pixel(x, y, max_distance);
116
117 if x > 0 {
118 check(image, x, y, x - 1, y);
119 }
120
121 if y > 0 {
122 check(image, x, y, x, y - 1);
123
124 if norm == Norm::LInf {
125 if x > 0 {
126 check(image, x, y, x - 1, y - 1);
127 }
128 if x < image.width() - 1 {
129 check(image, x, y, x + 1, y - 1);
130 }
131 }
132 }
133 }
134 }
135
136 for y in (0..image.height()).rev() {
138 for x in (0..image.width()).rev() {
139 if x < image.width() - 1 {
140 check(image, x, y, x + 1, y);
141 }
142
143 if y < image.height() - 1 {
144 check(image, x, y, x, y + 1);
145
146 if norm == Norm::LInf {
147 if x < image.width() - 1 {
148 check(image, x, y, x + 1, y + 1);
149 }
150 if x > 0 {
151 check(image, x, y, x - 1, y + 1);
152 }
153 }
154 }
155 }
156 }
157 }
158}
159
160unsafe fn check(
164 image: &mut GrayImage,
165 current_x: u32,
166 current_y: u32,
167 candidate_x: u32,
168 candidate_y: u32,
169) {
170 let current = image.unsafe_get_pixel(current_x, current_y)[0] as u16;
171 let candidate_incr = image.unsafe_get_pixel(candidate_x, candidate_y)[0] as u16 + 1;
172 if candidate_incr < current {
173 image.unsafe_put_pixel(current_x, current_y, Luma([candidate_incr as u8]));
174 }
175}
176
177pub fn euclidean_squared_distance_transform(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
185 let (width, height) = image.dimensions();
186 let mut result = ImageBuffer::new(width, height);
187 let mut column_envelope = LowerEnvelope::new(height as usize);
188
189 for x in 0..width {
191 let source = Column { image, column: x };
192 let mut sink = ColumnMut {
193 image: &mut result,
194 column: x,
195 };
196 distance_transform_1d_mut(&source, &mut sink, &mut column_envelope);
197 }
198
199 let mut row_buffer = vec![0f64; width as usize];
200 let mut row_envelope = LowerEnvelope::new(width as usize);
201
202 for y in 0..height {
204 for x in 0..width {
205 row_buffer[x as usize] = result.get_pixel(x, y)[0];
206 }
207 let mut sink = Row {
208 image: &mut result,
209 row: y,
210 };
211 distance_transform_1d_mut(&row_buffer, &mut sink, &mut row_envelope);
212 }
213
214 result
215}
216
217struct LowerEnvelope {
218 locations: Vec<usize>,
220 boundaries: Vec<f64>,
225}
226
227impl LowerEnvelope {
228 fn new(image_side: usize) -> LowerEnvelope {
229 LowerEnvelope {
230 locations: vec![0; image_side],
231 boundaries: vec![f64::NAN; image_side + 1],
232 }
233 }
234}
235
236trait Sink {
237 fn put(&mut self, idx: usize, value: f64);
238 fn len(&self) -> usize;
239}
240
241trait Source {
242 fn get(&self, idx: usize) -> f64;
243 fn len(&self) -> usize;
244}
245
246struct Row<'a> {
247 image: &'a mut Image<Luma<f64>>,
248 row: u32,
249}
250
251impl<'a> Sink for Row<'a> {
252 fn put(&mut self, idx: usize, value: f64) {
253 unsafe {
254 self.image
255 .unsafe_put_pixel(idx as u32, self.row, Luma([value]));
256 }
257 }
258 fn len(&self) -> usize {
259 self.image.width() as usize
260 }
261}
262
263struct ColumnMut<'a> {
264 image: &'a mut Image<Luma<f64>>,
265 column: u32,
266}
267
268impl<'a> Sink for ColumnMut<'a> {
269 fn put(&mut self, idx: usize, value: f64) {
270 unsafe {
271 self.image
272 .unsafe_put_pixel(self.column, idx as u32, Luma([value]));
273 }
274 }
275 fn len(&self) -> usize {
276 self.image.height() as usize
277 }
278}
279
280impl Source for Vec<f64> {
281 fn get(&self, idx: usize) -> f64 {
282 self[idx]
283 }
284 fn len(&self) -> usize {
285 self.len()
286 }
287}
288
289impl Source for [f64] {
290 fn get(&self, idx: usize) -> f64 {
291 self[idx]
292 }
293 fn len(&self) -> usize {
294 self.len()
295 }
296}
297
298struct Column<'a> {
299 image: &'a Image<Luma<u8>>,
300 column: u32,
301}
302
303impl<'a> Source for Column<'a> {
304 fn get(&self, idx: usize) -> f64 {
305 let pixel = unsafe { self.image.unsafe_get_pixel(self.column, idx as u32)[0] as f64 };
306 if pixel > 0f64 {
307 0f64
308 } else {
309 f64::INFINITY
310 }
311 }
312 fn len(&self) -> usize {
313 self.image.height() as usize
314 }
315}
316
317fn distance_transform_1d_mut<S, T>(f: &S, result: &mut T, envelope: &mut LowerEnvelope)
318where
319 S: Source,
320 T: Sink,
321{
322 assert!(result.len() == f.len());
323 assert!(envelope.boundaries.len() == f.len() + 1);
324 assert!(envelope.locations.len() == f.len());
325
326 if f.len() == 0 {
327 return;
328 }
329
330 let mut k = 0;
332
333 envelope.locations[0] = 0;
336
337 envelope.boundaries[0] = f64::NEG_INFINITY;
339 envelope.boundaries[1] = f64::INFINITY;
340
341 for q in 1..f.len() {
342 if f.get(q) == f64::INFINITY {
343 continue;
344 }
345
346 if k == 0 && f.get(envelope.locations[k]) == f64::INFINITY {
347 envelope.locations[k] = q;
348 envelope.boundaries[k] = f64::NEG_INFINITY;
349 envelope.boundaries[k + 1] = f64::INFINITY;
350 continue;
351 }
352
353 let mut s = intersection(f, envelope.locations[k], q);
361
362 while s <= envelope.boundaries[k] {
363 k -= 1;
368 s = intersection(f, envelope.locations[k], q);
369 }
370
371 k += 1;
372 envelope.locations[k] = q;
373 envelope.boundaries[k] = s;
374 envelope.boundaries[k + 1] = f64::INFINITY;
375 }
376
377 let mut k = 0;
378 for q in 0..f.len() {
379 while envelope.boundaries[k + 1] < q as f64 {
380 k += 1;
381 }
382 let dist = q as f64 - envelope.locations[k] as f64;
383 result.put(q, dist * dist + f.get(envelope.locations[k]));
384 }
385}
386
387fn intersection<S: Source + ?Sized>(f: &S, p: usize, q: usize) -> f64 {
389 let fq = f.get(q);
397 let fp = f.get(p);
398 let p = p as f64;
399 let q = q as f64;
400
401 ((fq + q * q) - (fp + p * p)) / (2.0 * q - 2.0 * p)
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::definitions::Image;
408 use crate::property_testing::GrayTestImage;
409 use crate::utils::{gray_bench_image, pixel_diff_summary};
410 use image::{GrayImage, Luma};
411 use quickcheck::{quickcheck, TestResult};
412 use std::cmp::max;
413 use std::f64;
414 use test::{black_box, Bencher};
415
416 #[test]
417 fn test_distance_transform_saturation() {
418 let image = GrayImage::from_fn(300, 300, |x, y| match (x, y) {
420 (0, 0) => Luma([255u8]),
421 _ => Luma([0u8]),
422 });
423
424 let expected = GrayImage::from_fn(300, 300, |x, y| Luma([min(255, max(x, y)) as u8]));
426
427 let distances = distance_transform(&image, Norm::LInf);
428 assert_pixels_eq!(distances, expected);
429 }
430
431 impl<'a> Sink for Vec<f64> {
432 fn put(&mut self, idx: usize, value: f64) {
433 self[idx] = value;
434 }
435 fn len(&self) -> usize {
436 self.len()
437 }
438 }
439
440 fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
441 let mut r = vec![0.0; f.len()];
442 let mut e = LowerEnvelope::new(f.len());
443 distance_transform_1d_mut(f, &mut r, &mut e);
444 r
445 }
446
447 #[test]
448 fn test_distance_transform_1d_constant() {
449 let f = vec![0.0, 0.0, 0.0];
450 let dists = distance_transform_1d(&f);
451 assert_eq!(dists, &[0.0, 0.0, 0.0]);
452 }
453
454 #[test]
455 fn test_distance_transform_1d_descending_gradient() {
456 let f = vec![7.0, 5.0, 3.0, 1.0];
457 let dists = distance_transform_1d(&f);
458 assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
459 }
460
461 #[test]
462 fn test_distance_transform_1d_ascending_gradient() {
463 let f = vec![1.0, 3.0, 5.0, 7.0];
464 let dists = distance_transform_1d(&f);
465 assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
466 }
467
468 #[test]
469 fn test_distance_transform_1d_with_infinities() {
470 let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
471 let dists = distance_transform_1d(&f);
472 assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
473 }
474
475 fn distance_transform_1d_reference(f: &[f64]) -> Vec<f64> {
479 let mut ret = vec![0.0; f.len()];
480 for q in 0..f.len() {
481 ret[q] = (0..f.len())
482 .map(|p| {
483 let dist = p as f64 - q as f64;
484 dist * dist + f[p]
485 })
486 .fold(0.0 / 0.0, f64::min);
487 }
488 ret
489 }
490
491 #[test]
492 fn test_distance_transform_1d_matches_reference_implementation() {
493 fn prop(f: Vec<f64>) -> bool {
494 let expected = distance_transform_1d_reference(&f);
495 let actual = distance_transform_1d(&f);
496 expected == actual
497 }
498 quickcheck(prop as fn(Vec<f64>) -> bool);
499 }
500
501 fn euclidean_squared_distance_transform_reference(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
502 let (width, height) = image.dimensions();
503
504 let mut dists = Image::new(width, height);
505
506 for y in 0..height {
507 for x in 0..width {
508 let mut min = f64::INFINITY;
509 for yc in 0..height {
510 for xc in 0..width {
511 let pc = image.get_pixel(xc, yc)[0];
512 if pc > 0 {
513 let dx = xc as f64 - x as f64;
514 let dy = yc as f64 - y as f64;
515
516 min = f64::min(min, dx * dx + dy * dy);
517 }
518 }
519 }
520
521 dists.put_pixel(x, y, Luma([min]));
522 }
523 }
524
525 dists
526 }
527
528 #[test]
529 fn test_euclidean_squared_distance_transform_matches_reference_implementation() {
530 fn prop(image: GrayTestImage) -> TestResult {
531 let expected = euclidean_squared_distance_transform_reference(&image.0);
532 let actual = euclidean_squared_distance_transform(&image.0);
533 match pixel_diff_summary(&actual, &expected) {
534 None => TestResult::passed(),
535 Some(err) => TestResult::error(err),
536 }
537 }
538 quickcheck(prop as fn(GrayTestImage) -> TestResult);
539 }
540
541 #[test]
542 fn test_euclidean_squared_distance_transform_example() {
543 let image = gray_image!(
544 1, 0, 0, 0, 0;
545 0, 1, 0, 0, 0;
546 1, 1, 1, 0, 0;
547 0, 0, 0, 0, 0;
548 0, 0, 1, 0, 0
549 );
550
551 let expected = gray_image!(type: f64,
552 0.0, 1.0, 2.0, 5.0, 8.0;
553 1.0, 0.0, 1.0, 2.0, 5.0;
554 0.0, 0.0, 0.0, 1.0, 4.0;
555 1.0, 1.0, 1.0, 2.0, 5.0;
556 4.0, 1.0, 0.0, 1.0, 4.0
557 );
558
559 let dist = euclidean_squared_distance_transform(&image);
560 assert_pixels_eq_within!(dist, expected, 1e-6);
561 }
562
563 macro_rules! bench_euclidean_squared_distance_transform {
564 ($name:ident, side: $s:expr) => {
565 #[bench]
566 fn $name(b: &mut Bencher) {
567 let image = gray_bench_image($s, $s);
568 b.iter(|| {
569 let distance = euclidean_squared_distance_transform(&image);
570 black_box(distance);
571 })
572 }
573 };
574 }
575
576 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_10, side: 10);
577 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_100, side: 100);
578 bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_200, side: 200);
579
580 macro_rules! bench_distance_transform {
581 ($name:ident, $norm:expr, side: $s:expr) => {
582 #[bench]
583 fn $name(b: &mut Bencher) {
584 let image = gray_bench_image($s, $s);
585 b.iter(|| {
586 let distance = distance_transform(&image, $norm);
587 black_box(distance);
588 })
589 }
590 };
591 }
592
593 bench_distance_transform!(bench_distance_transform_l1_10, Norm::L1, side: 10);
594 bench_distance_transform!(bench_distance_transform_l1_100, Norm::L1, side: 100);
595 bench_distance_transform!(bench_distance_transform_l1_200, Norm::L1, side: 200);
596 bench_distance_transform!(bench_distance_transform_linf_10, Norm::LInf, side: 10);
597 bench_distance_transform!(bench_distance_transform_linf_100, Norm::LInf, side: 100);
598 bench_distance_transform!(bench_distance_transform_linf_200, Norm::LInf, side: 200);
599}