]> luflow.net public git repositories - flow-texpack.git/blob - src/texpack/packer.rs
Initial commit.
[flow-texpack.git] / src / texpack / packer.rs
1 // flow-texpack: A program that will allow you to generate texture atlas.
2 // zlib License (see LICENSE)
3
4 use log::info;
5 use std::collections::HashMap;
6 use std::fs::File;
7 use std::io::Write;
8 use std::path::PathBuf;
9 use std::sync::{Arc, Mutex};
10
11 use flow_rbp::FreeRectHeuristic;
12 use flow_rbp::RectsBinPack;
13
14 use crate::Texture;
15 use crate::texpack::app::AtlasImage;
16
17 /// is the minimum allowed size.
18 pub const MIN_SIZE: u32 = 64;
19 /// is the maximum allowed size.
20 pub const MAX_SIZE: u32 = 8192;
21
22 /// Specifies the different error types that can occur.
23 #[derive(PartialEq, Clone, Debug)]
24 pub enum PackerError {
25 /// Invalid argument
26 InvalidArg,
27 }
28
29 /// Specifies the properties of a `Point`.
30 #[derive(Clone, Debug)]
31 pub struct Point {
32 /// is the x offset.
33 pub x: i32,
34 /// is the y offset.
35 pub y: i32,
36 /// is the duplicate ID.
37 pub duplicate_id: usize,
38 /// is the flag determining whether rotated or not.
39 pub rotate: bool,
40 }
41
42 impl Point {
43 /// Instantiates a new `Point` instance.
44 pub fn new() -> Self {
45 Self {
46 x: 0,
47 y: 0,
48 duplicate_id: 0,
49 rotate: false,
50 }
51 }
52 }
53
54 /// Specifies the properties of a `PackerState`.
55 #[derive(Clone, Debug)]
56 pub struct PackerState {
57 /// is the width.
58 pub width: u32,
59 /// is the height.
60 pub height: u32,
61 /// is the padding.
62 pub padding: i32,
63 /// is the flag determining whether mip maps should be generated (rendering hint).
64 pub generate_mipmaps: bool,
65 /// is the vector holding the textures to pack.
66 pub textures: Vec<Texture>,
67 /// is the vector holding the points used for `unique` lookups.
68 pub points: Vec<Point>,
69 /// is the hash map holding texture hash and duplicate_id.
70 pub duplicates: HashMap<u64, usize>,
71 }
72
73 impl PackerState {
74 /// Instantiates a new `PackerState` instance.
75 ///
76 /// # Arguments
77 ///
78 /// * `width` - is the width.
79 /// * `height` - is the height.
80 /// * `padding` - is the padding.
81 /// * `generate_mipmaps` - is the flag determining whether to generate mip maps (rendering hint)
82 /// or not.
83 pub fn new(width: u32, height: u32, padding: i32, generate_mipmaps: bool) -> Self {
84 Self {
85 width,
86 height,
87 padding,
88 generate_mipmaps,
89 textures: Vec::new(),
90 points: Vec::new(),
91 duplicates: HashMap::new(),
92 }
93 }
94 }
95
96 /// Specifies the properties of a `Packer`.
97 #[derive(Debug)]
98 pub struct Packer {
99 /// is the `PackerState` protected by a `Mutex`.
100 pub state: Mutex<PackerState>,
101 }
102
103 impl Packer {
104 /// Instantiates a new `Packer` instance.
105 ///
106 /// # Arguments
107 ///
108 /// * `width` - is the width.
109 /// * `height` - is the height.
110 /// * `padding` - is the padding.
111 /// * `generate_mipmaps` - is the flag determining whether to generate mip maps (rendering hint)
112 /// or not.
113 ///
114 /// # Errors
115 ///
116 /// [`InvalidArg`](crate::texpack::packer::PackerError) error is returned if:
117 /// `width != SIZE_IN_POWER_OF_TWO || height != SIZE_IN_POWER_OF_TWO ||
118 /// width < 64 || width > 8192 ||
119 /// height < 64 || height > 8192`.
120 pub fn new(
121 width: u32,
122 height: u32,
123 padding: i32,
124 generate_mipmaps: bool,
125 ) -> Result<Self, PackerError> {
126 // make sure width/height is power-of-two and 64 - 8192 in size:
127 if width >= MIN_SIZE
128 && width <= MAX_SIZE
129 && height >= MIN_SIZE
130 && height <= MAX_SIZE
131 && (width & (width - 1)) == 0
132 && (height & (height - 1)) == 0
133 {
134 Ok(Self {
135 state: Mutex::new(PackerState::new(width, height, padding, generate_mipmaps)),
136 })
137 } else {
138 Err(PackerError::InvalidArg)
139 }
140 }
141
142 /// Returns a shared `Arc` `Packer` instance.
143 pub fn shared(self) -> Arc<Self> {
144 Arc::new(self)
145 }
146
147 /// Packs all textures that will fit from given `textures` and pops each packed from vector.
148 ///
149 /// # Arguments
150 ///
151 /// * `textures` - is the vector holding the textures to pack.
152 /// * `unique` - is a flag determining whether to include only unique textures or not (a
153 /// unique texture is determined by its combined hash value of `width`, `height` and `buffer`).
154 /// * `rotate` - is a flag determining whether rotation of textures is allowed or not.
155 /// * `square` - is a flag determining whether packers size must be POWER OF TWO in size for both width
156 /// and height.
157 /// * `adjust_fit` - is a flag determining whether to adjust fit automatically or not.
158 /// * `heuristic` - is the heuristic method to use for determining where to place the texture.
159 ///
160 /// # Panics
161 ///
162 /// If packing fails.
163 pub fn pack(
164 &mut self,
165 textures: &mut Vec<Texture>,
166 unique: bool,
167 rotate: bool,
168 square: bool,
169 adjust_size: bool,
170 heuristic: FreeRectHeuristic,
171 ) {
172 assert!(textures.len() > 0);
173
174 let mut exists_larger = false;
175 if !square {
176 exists_larger = self.exists_larger_texture(textures);
177 }
178
179 // make sure each texture fit within size:
180 if adjust_size {
181 self.adjust_size_to_fit(textures);
182 }
183
184 let mut lock = self.state.lock().unwrap();
185 let mut rbp = RectsBinPack::new(
186 lock.width.try_into().unwrap(),
187 lock.height.try_into().unwrap(),
188 rotate,
189 )
190 .unwrap();
191
192 let mut ww: u32 = 0;
193 let mut hh: u32 = 0;
194 while !textures.is_empty() {
195 if let Some(texture) = textures.last() {
196 if unique {
197 if let Some(value) = lock.duplicates.get(&texture.hash_value) {
198 if let Some(point) = lock.points.get(*value) {
199 info!(
200 "Texture '{}' with hash: {} is not unique (not packed but will be added in descriptor)",
201 texture.file_name, texture.hash_value
202 );
203 let mut p = point.clone();
204 p.duplicate_id = *value;
205 lock.points.push(p);
206 lock.textures.push(texture.clone());
207 textures.pop();
208 continue;
209 }
210 }
211 }
212
213 {
214 let tw: i32 = texture.width.try_into().unwrap();
215 let th: i32 = texture.height.try_into().unwrap();
216 let width: i32 = tw + lock.padding;
217 let height: i32 = th + lock.padding;
218 if let Some(rect) = rbp.insert(width, height, heuristic.clone()) {
219 if unique {
220 let num_points = lock.points.len();
221 lock.duplicates.insert(texture.hash_value, num_points);
222 }
223
224 // check if we rotated:
225 let mut p = Point::new();
226 p.x = rect.x;
227 p.y = rect.y;
228 p.duplicate_id = std::usize::MAX;
229 p.rotate = rotate && tw != rect.width - lock.padding;
230
231 info!(
232 "Packed '{}' w: {} h: {} rotated: {} hash: {}",
233 texture.file_name,
234 texture.width,
235 texture.height,
236 p.rotate,
237 texture.hash_value
238 );
239 lock.points.push(p);
240 lock.textures.push(texture.clone());
241 textures.pop();
242
243 ww = std::cmp::max((rect.x + rect.width).try_into().unwrap(), ww);
244 hh = std::cmp::max((rect.y + rect.height).try_into().unwrap(), hh);
245 } else {
246 break;
247 }
248 }
249 } else {
250 panic!("texture.last() failed!");
251 }
252 }
253
254 // tweak power-of-two size so that it's optimized for largest found width/height:
255 if !square && !exists_larger {
256 while lock.width / 2 >= ww {
257 lock.width /= 2;
258 }
259
260 while lock.height / 2 >= hh {
261 lock.height /= 2;
262 }
263 }
264 }
265
266 /// Saves the packed textures to disk.
267 ///
268 /// # Arguments
269 ///
270 /// * `file_path` - is the output file path.
271 /// * `image_type` - is the output image type.
272 ///
273 /// # Panics
274 ///
275 /// If save fails.
276 pub fn save_image(&self, file_path: &PathBuf, image_type: AtlasImage) {
277 let lock = self.state.lock().unwrap();
278 let mut texture = Texture::with_details(lock.width, lock.height).unwrap();
279
280 for i in 0..lock.textures.len() {
281 if let Some(src) = lock.textures.get(i)
282 && let Some(point) = lock.points.get(i)
283 {
284 if point.duplicate_id == std::usize::MAX {
285 if point.rotate {
286 texture.copy_pixels_rot_90cw(
287 src,
288 point.x.try_into().unwrap(),
289 point.y.try_into().unwrap(),
290 );
291 } else {
292 texture.copy_pixels(
293 src,
294 point.x.try_into().unwrap(),
295 point.y.try_into().unwrap(),
296 );
297 }
298 }
299 }
300 }
301
302 texture.save(file_path, image_type);
303 }
304
305 /// Saves the atlas descriptor to disk in JSON format.
306 ///
307 /// # Arguments
308 ///
309 /// * `file` - is a reference to already opened for write `File` to use when writing.
310 /// * `file_name` - is the name of the atlas image.
311 /// * `image_ext` - is the extension of the atlas image ("png", "tga" etc).
312 pub fn save_json(&self, file: &mut File, file_name: &str, image_ext: &str) {
313 let lock = self.state.lock().unwrap();
314 file.write(String::from("\t\t\t{\n").as_bytes()).unwrap();
315 file.write(format!("\t\t\t\t\"n\": \"{}.{}\",\n", file_name, image_ext).as_bytes())
316 .unwrap();
317 file.write(format!("\t\t\t\t\"numImages\": {},\n", lock.textures.len()).as_bytes())
318 .unwrap();
319 file.write(format!("\t\t\t\t\"width\": {},\n", lock.width).as_bytes())
320 .unwrap();
321 file.write(format!("\t\t\t\t\"height\": {},\n", lock.height).as_bytes())
322 .unwrap();
323 file.write(
324 format!(
325 "\t\t\t\t\"generateMipMaps\": {},\n",
326 lock.generate_mipmaps as u8
327 )
328 .as_bytes(),
329 )
330 .unwrap();
331 file.write(String::from("\t\t\t\t\"img\":\n").as_bytes())
332 .unwrap();
333 file.write(String::from("\t\t\t\t[\n").as_bytes()).unwrap();
334
335 for i in 0..lock.textures.len() {
336 if let Some(texture) = lock.textures.get(i)
337 && let Some(point) = lock.points.get(i)
338 {
339 let mut trimmed = false;
340 if texture.frame_w != texture.width || texture.frame_h != texture.height {
341 trimmed = true;
342 }
343
344 if i > 0 {
345 file.write(String::from(",\n").as_bytes()).unwrap();
346 }
347
348 file.write(String::from("\t\t\t\t\t{\n").as_bytes())
349 .unwrap();
350 file.write(format!("\t\t\t\t\t\t\"n\": \"{}\", ", texture.file_name).as_bytes())
351 .unwrap();
352 file.write(format!("\"x\": {}, ", point.x).as_bytes())
353 .unwrap();
354 file.write(format!("\"y\": {}, ", point.y).as_bytes())
355 .unwrap();
356 file.write(format!("\"w\": {}, ", texture.width).as_bytes())
357 .unwrap();
358 file.write(format!("\"h\": {}, ", texture.height).as_bytes())
359 .unwrap();
360 file.write(format!("\"trimmed\": {}, ", trimmed as u8).as_bytes())
361 .unwrap();
362 file.write(format!("\"rotated\": {}, ", point.rotate as u8).as_bytes())
363 .unwrap();
364 file.write(format!("\"fx\": {}, ", texture.frame_x).as_bytes())
365 .unwrap();
366 file.write(format!("\"fy\": {}, ", texture.frame_y).as_bytes())
367 .unwrap();
368 file.write(format!("\"fw\": {}, ", texture.frame_w).as_bytes())
369 .unwrap();
370 file.write(format!("\"fh\": {}\n", texture.frame_h).as_bytes())
371 .unwrap();
372 file.write(String::from("\t\t\t\t\t}").as_bytes()).unwrap();
373 }
374 }
375 file.write(String::from("\n\t\t\t\t]\n").as_bytes())
376 .unwrap();
377 file.write(String::from("\t\t\t}").as_bytes()).unwrap();
378 }
379
380 /// Saves the atlas descriptor to disk in plain TXT format.
381 ///
382 /// # Arguments
383 ///
384 /// * `file` - is a reference to already opened for write `File` to use when writing.
385 /// * `file_name` - is the name of the atlas image.
386 /// * `image_ext` - is the extension of the atlas image ("png", "tga" etc).
387 pub fn save_txt(&self, file: &mut File, file_name: &str, image_ext: &str) {
388 let lock = self.state.lock().unwrap();
389 file.write(format!("{}.{}", file_name, image_ext).as_bytes())
390 .unwrap();
391 file.write(format!(",{}", lock.textures.len()).as_bytes())
392 .unwrap();
393 file.write(format!(",{}", lock.width).as_bytes()).unwrap();
394 file.write(format!(",{}", lock.height).as_bytes()).unwrap();
395 file.write(format!(",{}\n", lock.generate_mipmaps as u8).as_bytes())
396 .unwrap();
397
398 for i in 0..lock.textures.len() {
399 if let Some(texture) = lock.textures.get(i)
400 && let Some(point) = lock.points.get(i)
401 {
402 let mut trimmed = false;
403 if texture.frame_w != texture.width || texture.frame_h != texture.height {
404 trimmed = true;
405 }
406
407 file.write(format!("{}", texture.file_name).as_bytes())
408 .unwrap();
409 file.write(format!(",{}", point.x).as_bytes()).unwrap();
410 file.write(format!(",{}", point.y).as_bytes()).unwrap();
411 file.write(format!(",{}", texture.width).as_bytes())
412 .unwrap();
413 file.write(format!(",{}", texture.height).as_bytes())
414 .unwrap();
415 file.write(format!(",{}", trimmed as u8).as_bytes())
416 .unwrap();
417 file.write(format!(",{}", point.rotate as u8).as_bytes())
418 .unwrap();
419 file.write(format!(",{}", texture.frame_x).as_bytes())
420 .unwrap();
421 file.write(format!(",{}", texture.frame_y).as_bytes())
422 .unwrap();
423 file.write(format!(",{}", texture.frame_w).as_bytes())
424 .unwrap();
425 file.write(format!(",{}\n", texture.frame_h).as_bytes())
426 .unwrap();
427 }
428 }
429 }
430
431 /// Adjusts the packer width and height so that given `textures` will fit.
432 ///
433 /// # Arguments
434 ///
435 /// * `textures` - is the vector holding the textures.
436 ///
437 /// # Panics
438 ///
439 /// If new adjusted width / height is > 8192.
440 fn adjust_size_to_fit(&mut self, textures: &Vec<Texture>) -> bool {
441 let mut lock = self.state.lock().unwrap();
442 let mut adjusted_size = false;
443 let padding: u32 = lock.padding.try_into().unwrap();
444
445 for i in 0..textures.len() {
446 if let Some(texture) = textures.get(i) {
447 if texture.width + padding > lock.width {
448 lock.width *= 2;
449 lock.height = lock.width;
450 adjusted_size = true;
451 }
452
453 if texture.height + padding > lock.height {
454 lock.height *= 2;
455 lock.width = lock.height;
456 adjusted_size = true;
457 }
458
459 if lock.width > MAX_SIZE || lock.height > MAX_SIZE {
460 panic!(
461 "adjust_size_to_fit failed. Maximum allowed width / height is {}",
462 MAX_SIZE
463 );
464 }
465
466 // make sure width is at least minimum size:
467 if lock.width < MIN_SIZE {
468 lock.width = MIN_SIZE;
469 }
470
471 // make sure height is at least minimum size:
472 if lock.height < MIN_SIZE {
473 lock.height = MIN_SIZE;
474 }
475 }
476 }
477
478 if adjusted_size {
479 info!(
480 "Packer: Adjusted size to {}x{} to fit textures.",
481 lock.width, lock.height
482 );
483 }
484
485 return adjusted_size;
486 }
487
488 /// Determines whether there exists a texture in given `textures` which size is larger then
489 /// that of packer width and height.
490 ///
491 /// # Arguments
492 ///
493 /// * `textures` - is the vector holding the textures.
494 fn exists_larger_texture(&self, textures: &Vec<Texture>) -> bool {
495 let lock = self.state.lock().unwrap();
496 let padding: u32 = lock.padding.try_into().unwrap();
497
498 for i in 0..textures.len() {
499 if let Some(texture) = textures.get(i) {
500 if texture.width + padding > lock.width || texture.height + padding > lock.height {
501 return true;
502 }
503 }
504 }
505
506 return false;
507 }
508 }
509
510 // unit tests:
511 #[cfg(test)]
512 mod tests {
513 use super::*;
514 use crate::texpack::app::{exists_file, get_atlas_image_extension, remove_file};
515
516 #[test]
517 fn point_basics() {
518 let p = Point::new();
519
520 assert_eq!(p.x, 0);
521 assert_eq!(p.y, 0);
522 assert_eq!(p.duplicate_id, 0);
523 assert_eq!(p.rotate, false);
524 }
525
526 #[test]
527 fn packer_error() {
528 assert_eq!(
529 Packer::new(0, 0, 1, false).unwrap_err(),
530 PackerError::InvalidArg
531 );
532
533 assert_eq!(
534 Packer::new(32, 32, 1, false).unwrap_err(),
535 PackerError::InvalidArg
536 );
537
538 assert_eq!(
539 Packer::new(8192, 8193, 1, false).unwrap_err(),
540 PackerError::InvalidArg
541 );
542
543 assert_eq!(
544 Packer::new(32, 64, 1, false).unwrap_err(),
545 PackerError::InvalidArg
546 );
547
548 assert_eq!(
549 Packer::new(64, 32, 1, false).unwrap_err(),
550 PackerError::InvalidArg
551 );
552 }
553
554 fn load_textures() -> Vec<Texture> {
555 let mut textures: Vec<Texture> = Vec::new();
556 let mut t1 = Texture::new();
557 t1.load(
558 &PathBuf::from("test_data/white_32x32.png"),
559 false,
560 false,
561 false,
562 0,
563 64,
564 );
565 textures.push(t1);
566
567 let mut t2 = Texture::new();
568 t2.load(
569 &PathBuf::from("test_data/red_32x32.png"),
570 false,
571 false,
572 false,
573 0,
574 64,
575 );
576 textures.push(t2);
577
578 let mut t3 = Texture::new();
579 t3.load(
580 &PathBuf::from("test_data/green_32x32.png"),
581 false,
582 false,
583 false,
584 0,
585 64,
586 );
587 textures.push(t3);
588
589 let mut t4 = Texture::new();
590 t4.load(
591 &PathBuf::from("test_data/blue_32x32.png"),
592 false,
593 false,
594 false,
595 0,
596 64,
597 );
598 textures.push(t4);
599
600 return textures;
601 }
602
603 #[test]
604 fn packer_basics_short_side_fit() {
605 let mut textures = load_textures();
606 assert_eq!(textures.len(), 4);
607
608 let mut packer = Packer::new(64, 64, 0, true).unwrap();
609 packer.pack(
610 &mut textures,
611 true,
612 false,
613 false,
614 false,
615 FreeRectHeuristic::ShortSideFit,
616 );
617 assert_eq!(textures.len() == 0, true);
618
619 let file_path = PathBuf::from("test_data/atlas_short_side_fit.png");
620
621 packer.save_image(&file_path, AtlasImage::Png);
622 assert_eq!(exists_file(&file_path), true);
623 remove_file(&file_path);
624 assert_eq!(exists_file(&file_path), false);
625 }
626
627 #[test]
628 fn packer_basics_long_side_fit() {
629 let mut textures = load_textures();
630 assert_eq!(textures.len(), 4);
631
632 let mut packer = Packer::new(64, 64, 0, true).unwrap();
633 packer.pack(
634 &mut textures,
635 true,
636 false,
637 false,
638 false,
639 FreeRectHeuristic::LongSideFit,
640 );
641 assert_eq!(textures.len() == 0, true);
642
643 let file_path = PathBuf::from("test_data/atlas_long_side_fit.png");
644
645 packer.save_image(&file_path, AtlasImage::Png);
646 assert_eq!(exists_file(&file_path), true);
647 remove_file(&file_path);
648 assert_eq!(exists_file(&file_path), false);
649 }
650
651 #[test]
652 fn packer_basics_area_fit() {
653 let mut textures = load_textures();
654 assert_eq!(textures.len(), 4);
655
656 let mut packer = Packer::new(64, 64, 0, true).unwrap();
657 packer.pack(
658 &mut textures,
659 true,
660 false,
661 false,
662 false,
663 FreeRectHeuristic::AreaFit,
664 );
665 assert_eq!(textures.len() == 0, true);
666
667 let file_path = PathBuf::from("test_data/atlas_area_fit.png");
668
669 packer.save_image(&file_path, AtlasImage::Png);
670 assert_eq!(exists_file(&file_path), true);
671 remove_file(&file_path);
672 assert_eq!(exists_file(&file_path), false);
673 }
674
675 #[test]
676 fn packer_basics_bottom_left() {
677 let mut textures = load_textures();
678 assert_eq!(textures.len(), 4);
679
680 let mut packer = Packer::new(64, 64, 0, true).unwrap();
681 packer.pack(
682 &mut textures,
683 true,
684 false,
685 false,
686 false,
687 FreeRectHeuristic::BottomLeft,
688 );
689 assert_eq!(textures.len() == 0, true);
690
691 let file_path = PathBuf::from("test_data/atlas_bottom_left.png");
692
693 packer.save_image(&file_path, AtlasImage::Png);
694 assert_eq!(exists_file(&file_path), true);
695 remove_file(&file_path);
696 assert_eq!(exists_file(&file_path), false);
697 }
698
699 #[test]
700 fn packer_basics_contact_point() {
701 let mut textures = load_textures();
702 assert_eq!(textures.len(), 4);
703
704 let mut packer = Packer::new(64, 64, 0, true).unwrap();
705 packer.pack(
706 &mut textures,
707 true,
708 false,
709 false,
710 false,
711 FreeRectHeuristic::ContactPoint,
712 );
713 assert_eq!(textures.len() == 0, true);
714
715 let file_path = PathBuf::from("test_data/atlas_contact_point.png");
716
717 packer.save_image(&file_path, AtlasImage::Png);
718 assert_eq!(exists_file(&file_path), true);
719 remove_file(&file_path);
720 assert_eq!(exists_file(&file_path), false);
721 }
722
723 #[test]
724 fn packer_adjust_size_to_fit() {
725 let mut textures: Vec<Texture> = Vec::new();
726 let mut t1 = Texture::new();
727 t1.load(
728 &PathBuf::from("test_data/white_128x128.png"),
729 false,
730 false,
731 true,
732 0,
733 64,
734 );
735 textures.push(t1);
736
737 let mut packer = Packer::new(64, 64, 0, true).unwrap();
738 packer.pack(
739 &mut textures,
740 true,
741 false,
742 false,
743 false,
744 FreeRectHeuristic::ContactPoint,
745 );
746 assert_eq!(textures.len() == 0, true);
747
748 let file_path = PathBuf::from("test_data/atlas_adjust_size_to_fit.png");
749
750 packer.save_image(&file_path, AtlasImage::Png);
751 assert_eq!(exists_file(&file_path), true);
752
753 let mut output = Texture::new();
754 output.load(
755 &PathBuf::from("test_data/atlas_adjust_size_to_fit.png"),
756 false,
757 false,
758 false,
759 0,
760 64,
761 );
762 assert_eq!(output.width, 64);
763 assert_eq!(output.height, 64);
764
765 remove_file(&file_path);
766 assert_eq!(exists_file(&file_path), false);
767 }
768
769 #[test]
770 fn packer_trim() {
771 let mut textures: Vec<Texture> = Vec::new();
772 let mut t1 = Texture::new();
773 t1.load(
774 &PathBuf::from("test_data/blue_trimmable_128x128.png"),
775 false,
776 true,
777 false,
778 0,
779 128,
780 );
781 textures.push(t1);
782
783 let mut packer = Packer::new(128, 128, 0, true).unwrap();
784 packer.pack(
785 &mut textures,
786 true,
787 false,
788 false,
789 false,
790 FreeRectHeuristic::BottomLeft,
791 );
792 assert_eq!(textures.len() == 0, true);
793
794 let file_path = PathBuf::from("test_data/atlas_trimmed.png");
795
796 packer.save_image(&file_path, AtlasImage::Png);
797 assert_eq!(exists_file(&file_path), true);
798
799 let mut output = Texture::new();
800 output.load(
801 &PathBuf::from("test_data/atlas_trimmed.png"),
802 false,
803 false,
804 false,
805 0,
806 64,
807 );
808 assert_eq!(output.width, 32);
809 assert_eq!(output.height, 32);
810
811 remove_file(&file_path);
812 assert_eq!(exists_file(&file_path), false);
813 }
814
815 #[test]
816 fn packer_rotated() {
817 let mut textures: Vec<Texture> = Vec::new();
818 let mut t1 = Texture::new();
819 t1.load(
820 &PathBuf::from("test_data/white_128x64.png"),
821 false,
822 false,
823 false,
824 0,
825 64,
826 );
827 textures.push(t1);
828 assert_eq!(textures.len(), 1);
829
830 let mut packer = Packer::new(64, 128, 0, true).unwrap();
831 packer.pack(
832 &mut textures,
833 true,
834 true,
835 false,
836 false,
837 FreeRectHeuristic::LongSideFit,
838 );
839 assert_eq!(textures.len() == 0, true);
840
841 let file_path = PathBuf::from("test_data/atlas_rotated.png");
842
843 packer.save_image(&file_path, AtlasImage::Png);
844 assert_eq!(exists_file(&file_path), true);
845
846 let mut output = Texture::new();
847 output.load(
848 &PathBuf::from("test_data/atlas_rotated.png"),
849 false,
850 false,
851 false,
852 0,
853 64,
854 );
855 assert_eq!(output.width, 64);
856 assert_eq!(output.height, 128);
857
858 remove_file(&file_path);
859 assert_eq!(exists_file(&file_path), false);
860 }
861
862 #[test]
863 fn packer_save_all_supported_types() {
864 let mut textures = load_textures();
865 assert_eq!(textures.len(), 4);
866
867 let mut packer = Packer::new(64, 64, 0, true).unwrap();
868 packer.pack(
869 &mut textures,
870 true,
871 false,
872 false,
873 false,
874 FreeRectHeuristic::ShortSideFit,
875 );
876 assert_eq!(textures.len() == 0, true);
877
878 let base_file_path = PathBuf::from("test_data/atlas_save");
879 let image_types = vec![
880 AtlasImage::Png,
881 AtlasImage::Tga,
882 AtlasImage::Tiff,
883 AtlasImage::Webp,
884 ];
885
886 for image_type in image_types {
887 let file_path = PathBuf::from(format!(
888 "{}.{}",
889 base_file_path.display(),
890 get_atlas_image_extension(image_type.clone())
891 ));
892
893 println!("{}", file_path.display());
894
895 packer.save_image(&file_path, image_type.clone());
896 assert_eq!(exists_file(&file_path), true);
897 remove_file(&file_path);
898 assert_eq!(exists_file(&file_path), false);
899 }
900 }
901 }