1 // flow-texpack: A program that will allow you to generate texture atlas.
2 // zlib License (see LICENSE)
5 use std::collections::HashMap;
8 use std::path::PathBuf;
9 use std::sync::{Arc, Mutex};
11 use flow_rbp::FreeRectHeuristic;
12 use flow_rbp::RectsBinPack;
15 use crate::texpack::app::AtlasImage;
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;
22 /// Specifies the different error types that can occur.
23 #[derive(PartialEq, Clone, Debug)]
24 pub enum PackerError {
29 /// Specifies the properties of a `Point`.
30 #[derive(Clone, Debug)]
36 /// is the duplicate ID.
37 pub duplicate_id: usize,
38 /// is the flag determining whether rotated or not.
43 /// Instantiates a new `Point` instance.
44 pub fn new() -> Self {
54 /// Specifies the properties of a `PackerState`.
55 #[derive(Clone, Debug)]
56 pub struct PackerState {
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>,
74 /// Instantiates a new `PackerState` instance.
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)
83 pub fn new(width: u32, height: u32, padding: i32, generate_mipmaps: bool) -> Self {
91 duplicates: HashMap::new(),
96 /// Specifies the properties of a `Packer`.
99 /// is the `PackerState` protected by a `Mutex`.
100 pub state: Mutex<PackerState>,
104 /// Instantiates a new `Packer` instance.
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)
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`.
124 generate_mipmaps: bool,
125 ) -> Result<Self, PackerError> {
126 // make sure width/height is power-of-two and 64 - 8192 in size:
129 && height >= MIN_SIZE
130 && height <= MAX_SIZE
131 && (width & (width - 1)) == 0
132 && (height & (height - 1)) == 0
135 state: Mutex::new(PackerState::new(width, height, padding, generate_mipmaps)),
138 Err(PackerError::InvalidArg)
142 /// Returns a shared `Arc` `Packer` instance.
143 pub fn shared(self) -> Arc<Self> {
147 /// Packs all textures that will fit from given `textures` and pops each packed from vector.
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
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.
162 /// If packing fails.
165 textures: &mut Vec<Texture>,
170 heuristic: FreeRectHeuristic,
172 assert!(textures.len() > 0);
174 let mut exists_larger = false;
176 exists_larger = self.exists_larger_texture(textures);
179 // make sure each texture fit within size:
181 self.adjust_size_to_fit(textures);
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(),
194 while !textures.is_empty() {
195 if let Some(texture) = textures.last() {
197 if let Some(value) = lock.duplicates.get(&texture.hash_value) {
198 if let Some(point) = lock.points.get(*value) {
200 "Texture '{}' with hash: {} is not unique (not packed but will be added in descriptor)",
201 texture.file_name, texture.hash_value
203 let mut p = point.clone();
204 p.duplicate_id = *value;
206 lock.textures.push(texture.clone());
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()) {
220 let num_points = lock.points.len();
221 lock.duplicates.insert(texture.hash_value, num_points);
224 // check if we rotated:
225 let mut p = Point::new();
228 p.duplicate_id = std::usize::MAX;
229 p.rotate = rotate && tw != rect.width - lock.padding;
232 "Packed '{}' w: {} h: {} rotated: {} hash: {}",
240 lock.textures.push(texture.clone());
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);
250 panic!("texture.last() failed!");
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 {
260 while lock.height / 2 >= hh {
266 /// Saves the packed textures to disk.
270 /// * `file_path` - is the output file path.
271 /// * `image_type` - is the output image type.
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();
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)
284 if point.duplicate_id == std::usize::MAX {
286 texture.copy_pixels_rot_90cw(
288 point.x.try_into().unwrap(),
289 point.y.try_into().unwrap(),
294 point.x.try_into().unwrap(),
295 point.y.try_into().unwrap(),
302 texture.save(file_path, image_type);
305 /// Saves the atlas descriptor to disk in JSON format.
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())
317 file.write(format!("\t\t\t\t\"numImages\": {},\n", lock.textures.len()).as_bytes())
319 file.write(format!("\t\t\t\t\"width\": {},\n", lock.width).as_bytes())
321 file.write(format!("\t\t\t\t\"height\": {},\n", lock.height).as_bytes())
325 "\t\t\t\t\"generateMipMaps\": {},\n",
326 lock.generate_mipmaps as u8
331 file.write(String::from("\t\t\t\t\"img\":\n").as_bytes())
333 file.write(String::from("\t\t\t\t[\n").as_bytes()).unwrap();
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)
339 let mut trimmed = false;
340 if texture.frame_w != texture.width || texture.frame_h != texture.height {
345 file.write(String::from(",\n").as_bytes()).unwrap();
348 file.write(String::from("\t\t\t\t\t{\n").as_bytes())
350 file.write(format!("\t\t\t\t\t\t\"n\": \"{}\", ", texture.file_name).as_bytes())
352 file.write(format!("\"x\": {}, ", point.x).as_bytes())
354 file.write(format!("\"y\": {}, ", point.y).as_bytes())
356 file.write(format!("\"w\": {}, ", texture.width).as_bytes())
358 file.write(format!("\"h\": {}, ", texture.height).as_bytes())
360 file.write(format!("\"trimmed\": {}, ", trimmed as u8).as_bytes())
362 file.write(format!("\"rotated\": {}, ", point.rotate as u8).as_bytes())
364 file.write(format!("\"fx\": {}, ", texture.frame_x).as_bytes())
366 file.write(format!("\"fy\": {}, ", texture.frame_y).as_bytes())
368 file.write(format!("\"fw\": {}, ", texture.frame_w).as_bytes())
370 file.write(format!("\"fh\": {}\n", texture.frame_h).as_bytes())
372 file.write(String::from("\t\t\t\t\t}").as_bytes()).unwrap();
375 file.write(String::from("\n\t\t\t\t]\n").as_bytes())
377 file.write(String::from("\t\t\t}").as_bytes()).unwrap();
380 /// Saves the atlas descriptor to disk in plain TXT format.
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())
391 file.write(format!(",{}", lock.textures.len()).as_bytes())
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())
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)
402 let mut trimmed = false;
403 if texture.frame_w != texture.width || texture.frame_h != texture.height {
407 file.write(format!("{}", texture.file_name).as_bytes())
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())
413 file.write(format!(",{}", texture.height).as_bytes())
415 file.write(format!(",{}", trimmed as u8).as_bytes())
417 file.write(format!(",{}", point.rotate as u8).as_bytes())
419 file.write(format!(",{}", texture.frame_x).as_bytes())
421 file.write(format!(",{}", texture.frame_y).as_bytes())
423 file.write(format!(",{}", texture.frame_w).as_bytes())
425 file.write(format!(",{}\n", texture.frame_h).as_bytes())
431 /// Adjusts the packer width and height so that given `textures` will fit.
435 /// * `textures` - is the vector holding the textures.
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();
445 for i in 0..textures.len() {
446 if let Some(texture) = textures.get(i) {
447 if texture.width + padding > lock.width {
449 lock.height = lock.width;
450 adjusted_size = true;
453 if texture.height + padding > lock.height {
455 lock.width = lock.height;
456 adjusted_size = true;
459 if lock.width > MAX_SIZE || lock.height > MAX_SIZE {
461 "adjust_size_to_fit failed. Maximum allowed width / height is {}",
466 // make sure width is at least minimum size:
467 if lock.width < MIN_SIZE {
468 lock.width = MIN_SIZE;
471 // make sure height is at least minimum size:
472 if lock.height < MIN_SIZE {
473 lock.height = MIN_SIZE;
480 "Packer: Adjusted size to {}x{} to fit textures.",
481 lock.width, lock.height
485 return adjusted_size;
488 /// Determines whether there exists a texture in given `textures` which size is larger then
489 /// that of packer width and height.
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();
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 {
514 use crate::texpack::app::{exists_file, get_atlas_image_extension, remove_file};
518 let p = Point::new();
522 assert_eq!(p.duplicate_id, 0);
523 assert_eq!(p.rotate, false);
529 Packer::new(0, 0, 1, false).unwrap_err(),
530 PackerError::InvalidArg
534 Packer::new(32, 32, 1, false).unwrap_err(),
535 PackerError::InvalidArg
539 Packer::new(8192, 8193, 1, false).unwrap_err(),
540 PackerError::InvalidArg
544 Packer::new(32, 64, 1, false).unwrap_err(),
545 PackerError::InvalidArg
549 Packer::new(64, 32, 1, false).unwrap_err(),
550 PackerError::InvalidArg
554 fn load_textures() -> Vec<Texture> {
555 let mut textures: Vec<Texture> = Vec::new();
556 let mut t1 = Texture::new();
558 &PathBuf::from("test_data/white_32x32.png"),
567 let mut t2 = Texture::new();
569 &PathBuf::from("test_data/red_32x32.png"),
578 let mut t3 = Texture::new();
580 &PathBuf::from("test_data/green_32x32.png"),
589 let mut t4 = Texture::new();
591 &PathBuf::from("test_data/blue_32x32.png"),
604 fn packer_basics_short_side_fit() {
605 let mut textures = load_textures();
606 assert_eq!(textures.len(), 4);
608 let mut packer = Packer::new(64, 64, 0, true).unwrap();
615 FreeRectHeuristic::ShortSideFit,
617 assert_eq!(textures.len() == 0, true);
619 let file_path = PathBuf::from("test_data/atlas_short_side_fit.png");
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);
628 fn packer_basics_long_side_fit() {
629 let mut textures = load_textures();
630 assert_eq!(textures.len(), 4);
632 let mut packer = Packer::new(64, 64, 0, true).unwrap();
639 FreeRectHeuristic::LongSideFit,
641 assert_eq!(textures.len() == 0, true);
643 let file_path = PathBuf::from("test_data/atlas_long_side_fit.png");
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);
652 fn packer_basics_area_fit() {
653 let mut textures = load_textures();
654 assert_eq!(textures.len(), 4);
656 let mut packer = Packer::new(64, 64, 0, true).unwrap();
663 FreeRectHeuristic::AreaFit,
665 assert_eq!(textures.len() == 0, true);
667 let file_path = PathBuf::from("test_data/atlas_area_fit.png");
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);
676 fn packer_basics_bottom_left() {
677 let mut textures = load_textures();
678 assert_eq!(textures.len(), 4);
680 let mut packer = Packer::new(64, 64, 0, true).unwrap();
687 FreeRectHeuristic::BottomLeft,
689 assert_eq!(textures.len() == 0, true);
691 let file_path = PathBuf::from("test_data/atlas_bottom_left.png");
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);
700 fn packer_basics_contact_point() {
701 let mut textures = load_textures();
702 assert_eq!(textures.len(), 4);
704 let mut packer = Packer::new(64, 64, 0, true).unwrap();
711 FreeRectHeuristic::ContactPoint,
713 assert_eq!(textures.len() == 0, true);
715 let file_path = PathBuf::from("test_data/atlas_contact_point.png");
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);
724 fn packer_adjust_size_to_fit() {
725 let mut textures: Vec<Texture> = Vec::new();
726 let mut t1 = Texture::new();
728 &PathBuf::from("test_data/white_128x128.png"),
737 let mut packer = Packer::new(64, 64, 0, true).unwrap();
744 FreeRectHeuristic::ContactPoint,
746 assert_eq!(textures.len() == 0, true);
748 let file_path = PathBuf::from("test_data/atlas_adjust_size_to_fit.png");
750 packer.save_image(&file_path, AtlasImage::Png);
751 assert_eq!(exists_file(&file_path), true);
753 let mut output = Texture::new();
755 &PathBuf::from("test_data/atlas_adjust_size_to_fit.png"),
762 assert_eq!(output.width, 64);
763 assert_eq!(output.height, 64);
765 remove_file(&file_path);
766 assert_eq!(exists_file(&file_path), false);
771 let mut textures: Vec<Texture> = Vec::new();
772 let mut t1 = Texture::new();
774 &PathBuf::from("test_data/blue_trimmable_128x128.png"),
783 let mut packer = Packer::new(128, 128, 0, true).unwrap();
790 FreeRectHeuristic::BottomLeft,
792 assert_eq!(textures.len() == 0, true);
794 let file_path = PathBuf::from("test_data/atlas_trimmed.png");
796 packer.save_image(&file_path, AtlasImage::Png);
797 assert_eq!(exists_file(&file_path), true);
799 let mut output = Texture::new();
801 &PathBuf::from("test_data/atlas_trimmed.png"),
808 assert_eq!(output.width, 32);
809 assert_eq!(output.height, 32);
811 remove_file(&file_path);
812 assert_eq!(exists_file(&file_path), false);
816 fn packer_rotated() {
817 let mut textures: Vec<Texture> = Vec::new();
818 let mut t1 = Texture::new();
820 &PathBuf::from("test_data/white_128x64.png"),
828 assert_eq!(textures.len(), 1);
830 let mut packer = Packer::new(64, 128, 0, true).unwrap();
837 FreeRectHeuristic::LongSideFit,
839 assert_eq!(textures.len() == 0, true);
841 let file_path = PathBuf::from("test_data/atlas_rotated.png");
843 packer.save_image(&file_path, AtlasImage::Png);
844 assert_eq!(exists_file(&file_path), true);
846 let mut output = Texture::new();
848 &PathBuf::from("test_data/atlas_rotated.png"),
855 assert_eq!(output.width, 64);
856 assert_eq!(output.height, 128);
858 remove_file(&file_path);
859 assert_eq!(exists_file(&file_path), false);
863 fn packer_save_all_supported_types() {
864 let mut textures = load_textures();
865 assert_eq!(textures.len(), 4);
867 let mut packer = Packer::new(64, 64, 0, true).unwrap();
874 FreeRectHeuristic::ShortSideFit,
876 assert_eq!(textures.len() == 0, true);
878 let base_file_path = PathBuf::from("test_data/atlas_save");
879 let image_types = vec![
886 for image_type in image_types {
887 let file_path = PathBuf::from(format!(
889 base_file_path.display(),
890 get_atlas_image_extension(image_type.clone())
893 println!("{}", file_path.display());
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);