yatp_cli/
packer.rs

1use image::{DynamicImage, RgbaImage};
2
3use crate::{format, Color, Point, Rect, Size};
4
5/// Binary bin packer
6#[derive(Debug)]
7pub struct Packer {
8    image: RgbaImage,
9    bins: Bin,
10}
11
12#[derive(Debug)]
13struct Bin {
14    occupied: Option<Size>,
15    area: Rect,
16
17    //                   (top, bottom)
18    children: Option<Box<(Bin, Bin)>>,
19}
20
21impl Packer {
22    /// Creates a new packer with given `size` and a transparent background
23    pub fn new(size: Size) -> Self {
24        let mut image = RgbaImage::new(size.width, size.height);
25        image
26            .pixels_mut()
27            .for_each(|v| *v = Color::from([255, 0, 255, 0]));
28
29        Self {
30            image,
31            bins: Bin {
32                occupied: None,
33                children: None,
34                area: Rect::new(Point::new(0, 0), size),
35            },
36        }
37    }
38
39    /// Packs a new `image` with a given `gap` and if successful returns the region it was packed
40    /// in
41    pub fn pack(&mut self, image: &DynamicImage, gap: u32) -> Option<Rect> {
42        let size = Size::new(image.width(), image.height());
43        let gapped_size = Size::new(image.width() + gap * 2, image.height() + gap * 2);
44
45        match self.bins.insert(gapped_size) {
46            Some(rect) => {
47                let rect = Rect::new(Point::new(rect.min_x() + gap, rect.min_y() + gap), size);
48
49                let image = image.to_rgba8();
50                image.enumerate_pixels().for_each(|(x, y, p)| {
51                    self.image.put_pixel(x + rect.min_x(), y + rect.min_y(), *p)
52                });
53
54                Some(rect)
55            }
56            None => None,
57        }
58    }
59
60    /// Saves this packer's image to a file with given `name` and `img` format
61    pub fn save(&self, name: &str, img: format::ImageFormat) -> anyhow::Result<()> {
62        self.image
63            .save_with_format(format!("{}.{}", name, img.ext()), img.as_image_format())?;
64        Ok(())
65    }
66}
67
68impl Bin {
69    pub fn fits(&self, size: Size) -> bool {
70        if self.area.width() < size.width || self.area.height() < size.height {
71            return false;
72        }
73
74        if let Some(children) = self.children.as_ref() {
75            return children.0.fits(size) || children.1.fits(size);
76        }
77
78        true
79    }
80
81    pub fn insert(&mut self, size: Size) -> Option<Rect> {
82        if !self.fits(size) {
83            return None;
84        }
85
86        if let Some(ref mut children) = self.children {
87            if let Some(rect) = children.0.insert(size) {
88                return Some(rect);
89            }
90
91            if let Some(rect) = children.1.insert(size) {
92                return Some(rect);
93            }
94
95            return None;
96        }
97
98        self.occupied = Some(size);
99        self.children = Some(Box::new((
100            Bin {
101                occupied: None,
102                area: Rect::new(
103                    Point::new(self.area.min_x() + size.width, self.area.min_y()),
104                    Size::new(self.area.width() - size.width, size.height),
105                ),
106                children: None,
107            },
108            Bin {
109                occupied: None,
110                area: Rect::new(
111                    Point::new(self.area.min_x(), self.area.min_y() + size.height),
112                    Size::new(self.area.width(), self.area.height() - size.height),
113                ),
114                children: None,
115            },
116        )));
117
118        Some(Rect::new(self.area.origin, size))
119    }
120}