packr2/
lib.rs

1//! Rectangle packing algorithm
2
3#![no_std]
4
5extern crate alloc;
6
7use alloc::{vec, vec::Vec};
8use core::cmp::Ordering;
9
10pub use skyline_packer::SkylinePacker;
11pub use split_packer::SplitPacker;
12pub use strip_packer::StripPacker;
13
14//mod optimize;
15mod skyline_packer;
16mod split_packer;
17mod strip_packer;
18
19/// Configuration for a texture packer.
20#[derive(Debug, Copy, Clone)]
21pub struct PackerConfig {
22    /// Max width of the packed image. Default value is `1024`.
23    pub max_width: u32,
24    /// Max height of the packed image. Default value is `1024`.
25    pub max_height: u32,
26    /// True to allow rotation of the input images. Default value is `true`. Images rotated will be
27    /// rotated 90 degrees clockwise.
28    pub allow_flipping: bool,
29}
30
31impl Default for PackerConfig {
32    fn default() -> PackerConfig {
33        PackerConfig {
34            max_width: 1024,
35            max_height: 1024,
36            allow_flipping: true,
37        }
38    }
39}
40
41/// Defines a rectangle in pixels with the origin at the top-left of the texture atlas.
42#[derive(Default, Copy, Clone, Debug)]
43#[repr(C)]
44pub struct Rect {
45    pub x: u32,
46    pub y: u32,
47    pub w: u32,
48    pub h: u32,
49}
50
51impl Rect {
52    /// Create a new [Rect] based on a position and its width and height.
53    pub fn new(x: u32, y: u32, w: u32, h: u32) -> Rect {
54        Rect { x, y, w, h }
55    }
56
57    pub const fn area(&self) -> u64 {
58        self.w as u64 * self.h as u64
59    }
60
61    pub const fn size(&self) -> Size {
62        Size {
63            w: self.w,
64            h: self.h,
65        }
66    }
67
68    /// Get the top coordinate of the rectangle.
69    #[inline(always)]
70    pub fn top(&self) -> u32 {
71        self.y
72    }
73
74    /// Get the bottom coordinate of the rectangle.
75    #[inline(always)]
76    pub fn bottom(&self) -> u32 {
77        // todo: badly defined, remove the -1
78        self.y + self.h - 1
79    }
80
81    /// Get the left coordinate of the rectangle.
82    #[inline(always)]
83    pub fn left(&self) -> u32 {
84        self.x
85    }
86
87    /// Get the right coordinate of the rectangle.
88    #[inline(always)]
89    pub fn right(&self) -> u32 {
90        // todo: badly defined, remove the -1
91        self.x + self.w - 1
92    }
93
94    /// Check if this rectangle contains another.
95    pub fn contains(&self, other: &Rect) -> bool {
96        self.left() <= other.left()
97            && self.right() >= other.right()
98            && self.top() <= other.top()
99            && self.bottom() >= other.bottom()
100    }
101}
102
103/// [`Rect`] that could be flipped sideway (rotated by 90 degrees clockwise)
104#[derive(Default, Clone, Copy, Debug)]
105#[repr(C)]
106pub struct Rectf {
107    pub x: u32,
108    pub y: u32,
109    pub w: u32,
110    pub h: u32,
111    pub flipped: bool,
112}
113
114impl Rectf {
115    pub fn from_rect(Rect { x, y, w, h }: Rect, flipped: bool) -> Self {
116        Self {
117            x,
118            y,
119            w,
120            h,
121            flipped,
122        }
123    }
124}
125
126impl core::ops::Deref for Rectf {
127    type Target = Rect;
128
129    fn deref(&self) -> &Self::Target {
130        // safety: the fields of `Rect` are included inside `Rectf` in the same order
131        unsafe { core::mem::transmute(self) }
132    }
133}
134
135#[derive(Default, Clone, Copy)]
136pub struct Size {
137    pub w: u32,
138    pub h: u32,
139}
140
141impl Size {
142    pub const ZERO: Size = Size::new(0, 0);
143
144    pub const fn new(w: u32, h: u32) -> Self {
145        Self { w, h }
146    }
147
148    pub fn flip(&mut self) -> &Self {
149        core::mem::swap(&mut self.w, &mut self.h);
150        self
151    }
152
153    pub const fn max_side(&self) -> u32 {
154        if self.h > self.w {
155            self.h
156        } else {
157            self.w
158        }
159    }
160
161    pub const fn min_side(&self) -> u32 {
162        if self.h < self.w {
163            self.h
164        } else {
165            self.w
166        }
167    }
168
169    pub const fn area(&self) -> u64 {
170        self.w as u64 * self.h as u64
171    }
172
173    pub const fn perimeter(&self) -> u64 {
174        2 * (self.w as u64) + 2 * (self.h as u64)
175    }
176
177    pub fn pathological_mult(&self) -> f32 {
178        self.max_side() as f32 / self.min_side() as f32 * self.area() as f32
179    }
180
181    pub fn expand_with(&mut self, r: &Rect) {
182        self.w = self.w.max(r.x + r.w);
183        self.h = self.h.max(r.y + r.h);
184    }
185}
186
187pub trait Packer {
188    fn insert(&mut self, w: u32, h: u32) -> Option<Rectf>;
189    fn reset(&mut self, resize: Option<Size>);
190    fn used_area(&self) -> Size;
191}
192
193#[derive(Clone, Copy)]
194pub struct RectInput<K> {
195    pub size: Size,
196    pub key: K,
197}
198
199#[derive(Clone, Copy)]
200pub struct RectOutput<K> {
201    pub rect: Rectf,
202    pub atlas: usize,
203    pub key: K,
204}
205
206pub const RECT_SORT_FUNCTIONS: [fn(Size, Size) -> Ordering; 6] = [
207    |a: Size, b: Size| b.area().cmp(&a.area()),
208    |a: Size, b: Size| b.perimeter().cmp(&a.perimeter()),
209    |a: Size, b: Size| (b.w.max(b.h)).cmp(&(a.w.max(a.h))),
210    |a: Size, b: Size| b.w.cmp(&a.w),
211    |a: Size, b: Size| b.h.cmp(&a.h),
212    |a: Size, b: Size| {
213        if b.pathological_mult() < a.pathological_mult() {
214            Ordering::Less
215        } else {
216            Ordering::Greater
217        }
218    },
219];
220
221/// Sorts the input data using the heuristics defined in [`RECT_SORT_FUNCTIONS`] to find the best possible packing,
222/// the results might end up been inside multiple atlases.
223///
224/// The output is sorted by atlas.
225pub fn pack<P: Packer, K: Copy>(
226    inputs: &mut Vec<RectInput<K>>,
227    mut packer: P,
228) -> Vec<RectOutput<K>> {
229    let mut output = vec![];
230    let mut output_area = core::u64::MAX;
231
232    let mut current = vec![];
233    let mut current_area;
234
235    for cmp in RECT_SORT_FUNCTIONS {
236        current.clear();
237        current_area = 0;
238
239        inputs.sort_by(|a, b| (cmp)(a.size, b.size));
240        let mut iterator = inputs.iter();
241
242        // use as many atlas as needed
243        let mut atlas = 0;
244        'atlasing: loop {
245            packer.reset(None);
246
247            loop {
248                if let Some(input) = iterator.next() {
249                    if let Some(rect) = packer.insert(input.size.w, input.size.h) {
250                        current.push(RectOutput {
251                            rect,
252                            atlas,
253                            key: input.key,
254                        });
255                    } else {
256                        // use another atlas
257                        current_area += packer.used_area().area();
258                        atlas += 1;
259                        break;
260                    }
261                } else {
262                    break 'atlasing;
263                }
264            }
265        }
266
267        current_area += packer.used_area().area();
268
269        if current_area < output_area {
270            output_area = current_area;
271            core::mem::swap(&mut current, &mut output);
272        }
273    }
274
275    output
276}