1use crate::config::PackerConfig;
2use crate::error::{Result, TexPackerError};
3use crate::model::Frame;
4use crate::runtime::{AtlasSession, RuntimeStats, RuntimeStrategy};
5use image::{Rgba, RgbaImage};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct UpdateRegion {
10 pub page_id: usize,
12 pub x: u32,
14 pub y: u32,
16 pub width: u32,
18 pub height: u32,
20}
21
22impl UpdateRegion {
23 pub fn empty() -> Self {
25 Self {
26 page_id: 0,
27 x: 0,
28 y: 0,
29 width: 0,
30 height: 0,
31 }
32 }
33
34 pub fn is_empty(&self) -> bool {
36 self.width == 0 || self.height == 0
37 }
38
39 pub fn area(&self) -> u64 {
41 (self.width as u64) * (self.height as u64)
42 }
43}
44
45pub struct RuntimeAtlas {
50 session: AtlasSession,
51 pages: Vec<RgbaImage>,
52 background_color: Rgba<u8>,
53}
54
55impl RuntimeAtlas {
56 pub fn new(cfg: PackerConfig, strategy: RuntimeStrategy) -> Self {
58 Self {
59 session: AtlasSession::new(cfg, strategy),
60 pages: Vec::new(),
61 background_color: Rgba([0, 0, 0, 0]), }
63 }
64
65 pub fn with_background_color(mut self, color: Rgba<u8>) -> Self {
67 self.background_color = color;
68 self
69 }
70
71 pub fn append_with_image(
74 &mut self,
75 key: String,
76 image: &RgbaImage,
77 ) -> Result<(usize, Frame<String>, UpdateRegion)> {
78 let (w, h) = image.dimensions();
79 let (page_id, frame) = self.session.append(key, w, h)?;
80
81 self.ensure_page(page_id);
83
84 let update_region = self.blit_to_page(page_id, &frame, image)?;
86
87 Ok((page_id, frame, update_region))
88 }
89
90 pub fn append(&mut self, key: String, w: u32, h: u32) -> Result<(usize, Frame<String>)> {
93 self.session.append(key, w, h)
94 }
95
96 pub fn evict_with_clear(
99 &mut self,
100 page_id: usize,
101 key: &str,
102 clear: bool,
103 ) -> Option<UpdateRegion> {
104 let slot_region = if clear {
106 self.session
107 .get_reserved_slot(key)
108 .map(|(pid, slot)| UpdateRegion {
109 page_id: pid,
110 x: slot.x,
111 y: slot.y,
112 width: slot.w,
113 height: slot.h,
114 })
115 } else {
116 None
117 };
118
119 if self.session.evict(page_id, key) {
121 if clear && let Some(region) = slot_region {
123 self.clear_region(region);
124 return Some(region);
125 }
126 Some(UpdateRegion::empty())
127 } else {
128 None
129 }
130 }
131
132 pub fn evict_by_key_with_clear(&mut self, key: &str, clear: bool) -> Option<UpdateRegion> {
134 let slot_region = if clear {
136 self.session
137 .get_reserved_slot(key)
138 .map(|(page_id, slot)| UpdateRegion {
139 page_id,
140 x: slot.x,
141 y: slot.y,
142 width: slot.w,
143 height: slot.h,
144 })
145 } else {
146 None
147 };
148
149 if self.session.evict_by_key(key) {
150 if clear && let Some(region) = slot_region {
151 self.clear_region(region);
152 return Some(region);
153 }
154 Some(UpdateRegion::empty())
155 } else {
156 None
157 }
158 }
159
160 pub fn get_page_image(&self, page_id: usize) -> Option<&RgbaImage> {
162 self.pages.get(page_id)
163 }
164
165 pub fn get_page_image_mut(&mut self, page_id: usize) -> Option<&mut RgbaImage> {
167 self.pages.get_mut(page_id)
168 }
169
170 pub fn num_pages(&self) -> usize {
172 self.pages.len()
173 }
174
175 pub fn get_frame(&self, key: &str) -> Option<(usize, &Frame<String>)> {
177 self.session.get_frame(key)
178 }
179
180 pub fn contains(&self, key: &str) -> bool {
181 self.session.contains(key)
182 }
183
184 pub fn keys(&self) -> Vec<&str> {
185 self.session.keys()
186 }
187
188 pub fn texture_count(&self) -> usize {
189 self.session.texture_count()
190 }
191
192 pub fn stats(&self) -> RuntimeStats {
193 self.session.stats()
194 }
195
196 pub fn snapshot_atlas(&self) -> crate::model::Atlas<String> {
197 self.session.snapshot_atlas()
198 }
199
200 fn ensure_page(&mut self, page_id: usize) {
202 while self.pages.len() <= page_id {
203 let page_img = RgbaImage::from_pixel(
204 self.session.cfg.max_width,
205 self.session.cfg.max_height,
206 self.background_color,
207 );
208 self.pages.push(page_img);
209 }
210 }
211
212 fn blit_to_page(
214 &mut self,
215 page_id: usize,
216 frame: &Frame<String>,
217 image: &RgbaImage,
218 ) -> Result<UpdateRegion> {
219 let page = self
220 .pages
221 .get_mut(page_id)
222 .ok_or_else(|| TexPackerError::InvalidConfig("Page not found".into()))?;
223
224 let (src_w, src_h) = image.dimensions();
225 let dst_x = frame.frame.x;
226 let dst_y = frame.frame.y;
227
228 let extrude = self.session.cfg.texture_extrusion;
230 let outlines = self.session.cfg.texture_outlines;
231 let dst = crate::compositing::BlitRect::new(dst_x, dst_y, frame.frame.w, frame.frame.h);
232 let src = crate::compositing::BlitRect::new(0, 0, src_w, src_h);
233 let options = crate::compositing::BlitOptions {
234 rotated: frame.rotated,
235 extrude,
236 outlines,
237 };
238 crate::compositing::blit_rgba(image, page, dst, src, options);
239
240 let start_x = dst_x.saturating_sub(extrude);
242 let start_y = dst_y.saturating_sub(extrude);
243 let mut width = frame.frame.w + extrude.saturating_mul(2);
244 let mut height = frame.frame.h + extrude.saturating_mul(2);
245 if start_x + width > page.width() {
247 width = page.width() - start_x;
248 }
249 if start_y + height > page.height() {
250 height = page.height() - start_y;
251 }
252
253 Ok(UpdateRegion {
254 page_id,
255 x: start_x,
256 y: start_y,
257 width,
258 height,
259 })
260 }
261
262 fn clear_region(&mut self, region: UpdateRegion) {
264 if let Some(page) = self.pages.get_mut(region.page_id) {
265 for y in region.y..(region.y + region.height).min(page.height()) {
266 for x in region.x..(region.x + region.width).min(page.width()) {
267 page.put_pixel(x, y, self.background_color);
268 }
269 }
270 }
271 }
272}