summaryrefslogtreecommitdiffstats
path: root/src/frontier/image.rs
blob: 18bf620b04eafb1324382ab4b72e81763933a180 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! Frontier that targets an image.

use super::{Frontier, Pixel, Target};

use crate::color::{ColorSpace, Rgb8};
use crate::soft::SoftKdTree;

use acap::NearestNeighbors;

use image::RgbImage;

/// A [Frontier] that places colors on the closest pixel of a target image.
#[derive(Debug)]
pub struct ImageFrontier<C> {
    nodes: SoftKdTree<Pixel<C>>,
    width: u32,
    height: u32,
    len: usize,
    deleted: usize,
}

impl<C: ColorSpace> ImageFrontier<C> {
    /// Create an ImageFrontier from an image.
    pub fn new(img: &RgbImage) -> Self {
        let width = img.width();
        let height = img.height();
        let len = (width as usize) * (height as usize);

        Self {
            nodes: img
                .enumerate_pixels()
                .map(|(x, y, p)| Pixel::new(x, y, C::from(*p)))
                .collect(),
            width,
            height,
            len,
            deleted: 0,
        }
    }
}

impl<C: ColorSpace> Frontier for ImageFrontier<C> {
    fn width(&self) -> u32 {
        self.width
    }

    fn height(&self) -> u32 {
        self.height
    }

    fn len(&self) -> usize {
        self.len - self.deleted
    }

    fn is_empty(&self) -> bool {
        self.len() == 0
    }

    fn place(&mut self, rgb8: Rgb8) -> Option<(u32, u32)> {
        let color = C::from(rgb8);

        if let Some(node) = self.nodes.nearest(&Target(color)).map(|n| n.item) {
            let pos = node.pos;

            node.delete();
            self.deleted += 1;

            if 32 * self.deleted >= self.len {
                self.nodes.rebuild();
                self.len -= self.deleted;
                self.deleted = 0;
            }

            Some(pos)
        } else {
            None
        }
    }
}