1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
5pub struct Rect {
6 pub x: u32,
7 pub y: u32,
8 pub w: u32,
9 pub h: u32,
10}
11
12impl Rect {
13 pub fn new(x: u32, y: u32, w: u32, h: u32) -> Self {
14 Self { x, y, w, h }
15 }
16 pub fn right(&self) -> u32 {
18 self.x + self.w.saturating_sub(1)
19 }
20 pub fn bottom(&self) -> u32 {
22 self.y + self.h.saturating_sub(1)
23 }
24 pub fn contains(&self, r: &Rect) -> bool {
26 r.x >= self.x && r.y >= self.y && r.right() <= self.right() && r.bottom() <= self.bottom()
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Frame<K = String> {
33 pub key: K,
35 pub frame: Rect,
37 pub rotated: bool,
39 pub trimmed: bool,
41 pub source: Rect,
43 pub source_size: (u32, u32),
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Page<K = String> {
50 pub id: usize,
51 pub width: u32,
52 pub height: u32,
53 pub frames: Vec<Frame<K>>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Meta {
59 pub schema_version: String,
63 pub app: String,
64 pub version: String,
65 pub format: String,
66 pub scale: f32,
67 pub power_of_two: bool,
68 pub square: bool,
69 pub max_dim: (u32, u32),
70 pub padding: (u32, u32),
71 pub extrude: u32,
72 pub allow_rotation: bool,
73 pub trim_mode: String,
74 pub background_color: Option<[u8; 4]>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Atlas<K = String> {
80 pub pages: Vec<Page<K>>,
81 pub meta: Meta,
82}
83
84#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
86pub struct PackStats {
87 pub num_pages: usize,
89 pub num_frames: usize,
91 pub total_page_area: u64,
93 pub used_frame_area: u64,
95 pub occupancy: f64,
98 pub avg_page_width: f64,
100 pub avg_page_height: f64,
101 pub max_page_width: u32,
103 pub max_page_height: u32,
104 pub num_rotated: usize,
106 pub num_trimmed: usize,
108}
109
110impl<K> Atlas<K> {
111 pub fn stats(&self) -> PackStats {
113 let num_pages = self.pages.len();
114 let mut num_frames = 0;
115 let mut total_page_area = 0u64;
116 let mut used_frame_area = 0u64;
117 let mut max_page_width = 0u32;
118 let mut max_page_height = 0u32;
119 let mut num_rotated = 0;
120 let mut num_trimmed = 0;
121
122 for page in &self.pages {
123 let page_area = (page.width as u64) * (page.height as u64);
124 total_page_area += page_area;
125 max_page_width = max_page_width.max(page.width);
126 max_page_height = max_page_height.max(page.height);
127
128 for frame in &page.frames {
129 num_frames += 1;
130 let frame_area = (frame.frame.w as u64) * (frame.frame.h as u64);
131 used_frame_area += frame_area;
132
133 if frame.rotated {
134 num_rotated += 1;
135 }
136 if frame.trimmed {
137 num_trimmed += 1;
138 }
139 }
140 }
141
142 let occupancy = if total_page_area > 0 {
143 used_frame_area as f64 / total_page_area as f64
144 } else {
145 0.0
146 };
147
148 let (avg_page_width, avg_page_height) = if num_pages > 0 {
149 let total_width: u64 = self.pages.iter().map(|p| p.width as u64).sum();
150 let total_height: u64 = self.pages.iter().map(|p| p.height as u64).sum();
151 (
152 total_width as f64 / num_pages as f64,
153 total_height as f64 / num_pages as f64,
154 )
155 } else {
156 (0.0, 0.0)
157 };
158
159 PackStats {
160 num_pages,
161 num_frames,
162 total_page_area,
163 used_frame_area,
164 occupancy,
165 avg_page_width,
166 avg_page_height,
167 max_page_width,
168 max_page_height,
169 num_rotated,
170 num_trimmed,
171 }
172 }
173}
174
175impl PackStats {
176 pub fn summary(&self) -> String {
178 format!(
179 "Pages: {}, Frames: {}, Occupancy: {:.2}%, Total Area: {} px², Used Area: {} px², Rotated: {}, Trimmed: {}",
180 self.num_pages,
181 self.num_frames,
182 self.occupancy * 100.0,
183 self.total_page_area,
184 self.used_frame_area,
185 self.num_rotated,
186 self.num_trimmed,
187 )
188 }
189
190 pub fn wasted_area(&self) -> u64 {
192 self.total_page_area.saturating_sub(self.used_frame_area)
193 }
194
195 pub fn waste_percentage(&self) -> f64 {
197 if self.total_page_area > 0 {
198 (self.wasted_area() as f64 / self.total_page_area as f64) * 100.0
199 } else {
200 0.0
201 }
202 }
203}