1use serde::{Deserialize, Serialize};
2use std::str::FromStr;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "lowercase")]
12pub enum AlgorithmFamily {
13 Skyline,
15 MaxRects,
17 Guillotine,
19 Auto,
21}
22
23impl FromStr for AlgorithmFamily {
24 type Err = ();
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s.to_ascii_lowercase().as_str() {
27 "skyline" => Ok(Self::Skyline),
28 "maxrects" => Ok(Self::MaxRects),
29 "guillotine" => Ok(Self::Guillotine),
30 "auto" => Ok(Self::Auto),
31 _ => Err(()),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "lowercase")]
39pub enum MaxRectsHeuristic {
40 BestAreaFit,
41 BestShortSideFit,
42 BestLongSideFit,
43 BottomLeft,
44 ContactPoint,
45}
46
47impl FromStr for MaxRectsHeuristic {
48 type Err = ();
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 match s.to_ascii_lowercase().as_str() {
51 "baf" | "bestareafit" => Ok(Self::BestAreaFit),
52 "bssf" | "bestshortsidefit" => Ok(Self::BestShortSideFit),
53 "blsf" | "bestlongsidefit" => Ok(Self::BestLongSideFit),
54 "bl" | "bottomleft" => Ok(Self::BottomLeft),
55 "cp" | "contactpoint" => Ok(Self::ContactPoint),
56 _ => Err(()),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "lowercase")]
64pub enum SkylineHeuristic {
65 BottomLeft,
66 MinWaste,
67}
68
69impl FromStr for SkylineHeuristic {
70 type Err = ();
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s.to_ascii_lowercase().as_str() {
73 "bl" | "bottomleft" => Ok(Self::BottomLeft),
74 "minwaste" | "mw" => Ok(Self::MinWaste),
75 _ => Err(()),
76 }
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82#[serde(rename_all = "lowercase")]
83pub enum GuillotineChoice {
84 BestAreaFit,
85 BestShortSideFit,
86 BestLongSideFit,
87 WorstAreaFit,
88 WorstShortSideFit,
89 WorstLongSideFit,
90}
91
92impl FromStr for GuillotineChoice {
93 type Err = ();
94 fn from_str(s: &str) -> Result<Self, Self::Err> {
95 match s.to_ascii_lowercase().as_str() {
96 "baf" | "bestareafit" => Ok(Self::BestAreaFit),
97 "bssf" | "bestshortsidefit" => Ok(Self::BestShortSideFit),
98 "blsf" | "bestlongsidefit" => Ok(Self::BestLongSideFit),
99 "waf" | "worstareafit" => Ok(Self::WorstAreaFit),
100 "wssf" | "worstshortsidefit" => Ok(Self::WorstShortSideFit),
101 "wlsf" | "worstlongsidefit" => Ok(Self::WorstLongSideFit),
102 _ => Err(()),
103 }
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(rename_all = "lowercase")]
110pub enum GuillotineSplit {
111 SplitShorterLeftoverAxis,
112 SplitLongerLeftoverAxis,
113 SplitMinimizeArea,
114 SplitMaximizeArea,
115 SplitShorterAxis,
116 SplitLongerAxis,
117}
118
119impl FromStr for GuillotineSplit {
120 type Err = ();
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 match s.to_ascii_lowercase().as_str() {
123 "slas" | "splitshorterleftoveraxis" => Ok(Self::SplitShorterLeftoverAxis),
124 "llas" | "splitlongerleftoveraxis" => Ok(Self::SplitLongerLeftoverAxis),
125 "minas" | "splitminimizearea" => Ok(Self::SplitMinimizeArea),
126 "maxas" | "splitmaximizearea" => Ok(Self::SplitMaximizeArea),
127 "sas" | "splitshorteraxis" => Ok(Self::SplitShorterAxis),
128 "las" | "splitlongeraxis" => Ok(Self::SplitLongerAxis),
129 _ => Err(()),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
136#[serde(rename_all = "lowercase")]
137pub enum AutoMode {
138 Fast,
139 Quality,
140}
141
142impl FromStr for AutoMode {
143 type Err = ();
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 match s.to_ascii_lowercase().as_str() {
146 "fast" => Ok(Self::Fast),
147 "quality" => Ok(Self::Quality),
148 _ => Err(()),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
155#[serde(rename_all = "snake_case")]
156pub enum SortOrder {
157 AreaDesc,
158 MaxSideDesc,
159 HeightDesc,
160 WidthDesc,
161 NameAsc,
162 None,
163}
164
165impl FromStr for SortOrder {
166 type Err = ();
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 match s.to_ascii_lowercase().as_str() {
169 "area_desc" => Ok(Self::AreaDesc),
170 "max_side_desc" => Ok(Self::MaxSideDesc),
171 "height_desc" => Ok(Self::HeightDesc),
172 "width_desc" => Ok(Self::WidthDesc),
173 "name_asc" => Ok(Self::NameAsc),
174 "none" => Ok(Self::None),
175 _ => Err(()),
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct PackerConfig {
182 pub max_width: u32,
184 pub max_height: u32,
186 pub allow_rotation: bool,
188 pub force_max_dimensions: bool,
190
191 pub border_padding: u32,
193 pub texture_padding: u32,
195 pub texture_extrusion: u32,
197
198 pub trim: bool,
200 pub trim_threshold: u8,
201 pub texture_outlines: bool,
203
204 pub power_of_two: bool,
206 pub square: bool,
208 pub use_waste_map: bool,
210
211 #[serde(default = "default_family")]
213 pub family: AlgorithmFamily,
214 #[serde(default = "default_mr_heuristic")]
215 pub mr_heuristic: MaxRectsHeuristic,
216 #[serde(default = "default_skyline_heuristic")]
217 pub skyline_heuristic: SkylineHeuristic,
218 #[serde(default = "default_g_choice")]
219 pub g_choice: GuillotineChoice,
220 #[serde(default = "default_g_split")]
221 pub g_split: GuillotineSplit,
222 #[serde(default = "default_auto_mode")]
223 pub auto_mode: AutoMode,
224 #[serde(default = "default_sort_order")]
225 pub sort_order: SortOrder,
226
227 #[serde(default)]
230 pub time_budget_ms: Option<u64>,
231 #[serde(default = "default_parallel")]
233 pub parallel: bool,
234
235 #[serde(default)]
238 pub mr_reference: bool,
239
240 #[serde(default)]
242 pub auto_mr_ref_time_ms_threshold: Option<u64>,
243 #[serde(default)]
245 pub auto_mr_ref_input_threshold: Option<usize>,
246
247 #[serde(default = "default_transparent_policy")]
249 pub transparent_policy: TransparentPolicy,
250}
251
252impl Default for PackerConfig {
253 fn default() -> Self {
254 Self {
255 max_width: 1024,
256 max_height: 1024,
257 allow_rotation: true,
258 force_max_dimensions: false,
259 border_padding: 0,
260 texture_padding: 2,
261 texture_extrusion: 0,
262 trim: true,
263 trim_threshold: 0,
264 texture_outlines: false,
265 power_of_two: false,
266 square: false,
267 use_waste_map: false,
268 family: default_family(),
269 mr_heuristic: default_mr_heuristic(),
270 skyline_heuristic: default_skyline_heuristic(),
271 g_choice: default_g_choice(),
272 g_split: default_g_split(),
273 auto_mode: default_auto_mode(),
274 sort_order: default_sort_order(),
275 time_budget_ms: None,
276 parallel: default_parallel(),
277 mr_reference: false,
278 auto_mr_ref_time_ms_threshold: None,
279 auto_mr_ref_input_threshold: None,
280 transparent_policy: default_transparent_policy(),
281 }
282 }
283}
284
285impl PackerConfig {
286 pub fn validate(&self) -> crate::error::Result<()> {
293 use crate::error::TexPackerError;
294
295 if self.max_width == 0 || self.max_height == 0 {
297 return Err(TexPackerError::InvalidDimensions {
298 width: self.max_width,
299 height: self.max_height,
300 });
301 }
302
303 let total_border = self.border_padding.saturating_mul(2);
305 let total_padding_per_texture = self
306 .texture_padding
307 .saturating_add(self.texture_extrusion.saturating_mul(2));
308
309 if total_border >= self.max_width || total_border >= self.max_height {
310 return Err(TexPackerError::InvalidConfig(format!(
311 "border_padding ({}) * 2 exceeds atlas dimensions ({}x{})",
312 self.border_padding, self.max_width, self.max_height
313 )));
314 }
315
316 let usable_width = self.max_width.saturating_sub(total_border);
318 let usable_height = self.max_height.saturating_sub(total_border);
319
320 if usable_width == 0 || usable_height == 0 {
321 return Err(TexPackerError::InvalidConfig(format!(
322 "No usable space after border_padding: {}x{} - {} * 2 = {}x{}",
323 self.max_width, self.max_height, self.border_padding, usable_width, usable_height
324 )));
325 }
326
327 if total_padding_per_texture > usable_width / 2
329 || total_padding_per_texture > usable_height / 2
330 {
331 }
334
335 Ok(())
338 }
339}
340
341fn default_family() -> AlgorithmFamily {
342 AlgorithmFamily::Skyline
343}
344fn default_mr_heuristic() -> MaxRectsHeuristic {
345 MaxRectsHeuristic::BestAreaFit
346}
347fn default_skyline_heuristic() -> SkylineHeuristic {
348 SkylineHeuristic::BottomLeft
349}
350fn default_g_choice() -> GuillotineChoice {
351 GuillotineChoice::BestAreaFit
352}
353fn default_g_split() -> GuillotineSplit {
354 GuillotineSplit::SplitShorterLeftoverAxis
355}
356fn default_auto_mode() -> AutoMode {
357 AutoMode::Quality
358}
359fn default_sort_order() -> SortOrder {
360 SortOrder::AreaDesc
361}
362fn default_parallel() -> bool {
363 false
364}
365fn default_transparent_policy() -> TransparentPolicy {
366 TransparentPolicy::Keep
367}
368
369#[derive(Debug, Default, Clone)]
371pub struct PackerConfigBuilder {
372 cfg: PackerConfig,
373}
374
375impl PackerConfigBuilder {
376 pub fn new() -> Self {
377 Self {
378 cfg: PackerConfig::default(),
379 }
380 }
381 pub fn with_max_dimensions(mut self, w: u32, h: u32) -> Self {
382 self.cfg.max_width = w;
383 self.cfg.max_height = h;
384 self
385 }
386 pub fn allow_rotation(mut self, v: bool) -> Self {
387 self.cfg.allow_rotation = v;
388 self
389 }
390 pub fn force_max_dimensions(mut self, v: bool) -> Self {
391 self.cfg.force_max_dimensions = v;
392 self
393 }
394 pub fn border_padding(mut self, v: u32) -> Self {
395 self.cfg.border_padding = v;
396 self
397 }
398 pub fn texture_padding(mut self, v: u32) -> Self {
399 self.cfg.texture_padding = v;
400 self
401 }
402 pub fn texture_extrusion(mut self, v: u32) -> Self {
403 self.cfg.texture_extrusion = v;
404 self
405 }
406 pub fn trim(mut self, v: bool) -> Self {
407 self.cfg.trim = v;
408 self
409 }
410 pub fn trim_threshold(mut self, v: u8) -> Self {
411 self.cfg.trim_threshold = v;
412 self
413 }
414 pub fn outlines(mut self, v: bool) -> Self {
415 self.cfg.texture_outlines = v;
416 self
417 }
418 pub fn pow2(mut self, v: bool) -> Self {
419 self.cfg.power_of_two = v;
420 self
421 }
422 pub fn square(mut self, v: bool) -> Self {
423 self.cfg.square = v;
424 self
425 }
426 pub fn family(mut self, v: AlgorithmFamily) -> Self {
427 self.cfg.family = v;
428 self
429 }
430 pub fn skyline_heuristic(mut self, v: SkylineHeuristic) -> Self {
431 self.cfg.skyline_heuristic = v;
432 self
433 }
434 pub fn mr_heuristic(mut self, v: MaxRectsHeuristic) -> Self {
435 self.cfg.mr_heuristic = v;
436 self
437 }
438 pub fn g_choice(mut self, v: GuillotineChoice) -> Self {
439 self.cfg.g_choice = v;
440 self
441 }
442 pub fn g_split(mut self, v: GuillotineSplit) -> Self {
443 self.cfg.g_split = v;
444 self
445 }
446 pub fn auto_mode(mut self, v: AutoMode) -> Self {
447 self.cfg.auto_mode = v;
448 self
449 }
450 pub fn sort_order(mut self, v: SortOrder) -> Self {
451 self.cfg.sort_order = v;
452 self
453 }
454 pub fn time_budget_ms(mut self, v: Option<u64>) -> Self {
455 self.cfg.time_budget_ms = v;
456 self
457 }
458 pub fn parallel(mut self, v: bool) -> Self {
459 self.cfg.parallel = v;
460 self
461 }
462 pub fn mr_reference(mut self, v: bool) -> Self {
463 self.cfg.mr_reference = v;
464 self
465 }
466 pub fn auto_mr_ref_time_ms_threshold(mut self, v: Option<u64>) -> Self {
467 self.cfg.auto_mr_ref_time_ms_threshold = v;
468 self
469 }
470 pub fn auto_mr_ref_input_threshold(mut self, v: Option<usize>) -> Self {
471 self.cfg.auto_mr_ref_input_threshold = v;
472 self
473 }
474 pub fn use_waste_map(mut self, v: bool) -> Self {
475 self.cfg.use_waste_map = v;
476 self
477 }
478 pub fn transparent_policy(mut self, v: TransparentPolicy) -> Self {
479 self.cfg.transparent_policy = v;
480 self
481 }
482 pub fn build(self) -> PackerConfig {
483 self.cfg
484 }
485}
486
487impl PackerConfig {
488 pub fn builder() -> PackerConfigBuilder {
490 PackerConfigBuilder::new()
491 }
492}
493#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
495#[serde(rename_all = "snake_case")]
496pub enum TransparentPolicy {
497 Keep,
499 OneByOne,
501 Skip,
503}
504
505impl FromStr for TransparentPolicy {
506 type Err = ();
507 fn from_str(s: &str) -> Result<Self, Self::Err> {
508 match s.to_ascii_lowercase().as_str() {
509 "keep" => Ok(Self::Keep),
510 "one_by_one" | "1x1" | "onebyone" => Ok(Self::OneByOne),
511 "skip" => Ok(Self::Skip),
512 _ => Err(()),
513 }
514 }
515}