bitmapfont_creator/
packer.rs1use image::{DynamicImage, GenericImageView, ImageBuffer, Rgba};
4use rectangle_pack::{
5 GroupedRectsToPlace, RectToInsert, TargetBin,
6};
7
8use crate::error::{BitmapFontError, Result};
9use crate::image_loader::CharImage;
10
11#[derive(Debug, Clone)]
13pub struct PackedChar {
14 pub char: String,
16 pub x: u32,
18 pub y: u32,
20 pub width: u32,
22 pub height: u32,
24 pub original_width: u32,
26 pub original_height: u32,
28 pub padding: u32,
30}
31
32#[derive(Debug)]
34pub struct AtlasResult {
35 pub image: DynamicImage,
37 pub chars: Vec<PackedChar>,
39 pub width: u32,
41 pub height: u32,
43}
44
45pub fn pack_images(images: Vec<CharImage>, max_size: u32) -> Result<AtlasResult> {
47 if images.is_empty() {
48 return Err(BitmapFontError::Packing("No images to pack".to_string()));
49 }
50
51 let mut rects: GroupedRectsToPlace<String, ()> = GroupedRectsToPlace::new();
53
54 for img in &images {
55 let rect = RectToInsert::new(img.width, img.height, 1);
56 rects.push_rect(img.char.clone(), None, rect);
57 }
58
59 let mut target_bins = std::collections::BTreeMap::new();
61 target_bins.insert((), TargetBin::new(max_size, max_size, 1));
62
63 let result = rectangle_pack::pack_rects(
65 &rects,
66 &mut target_bins,
67 &rectangle_pack::volume_heuristic,
68 &rectangle_pack::contains_smallest_box,
69 );
70
71 let placements = result.map_err(|e| BitmapFontError::Packing(
72 format!("Failed to pack images: {:?}", e)
73 ))?;
74
75 let mut max_width = 0u32;
77 let mut max_height = 0u32;
78
79 let mut packed_positions: std::collections::HashMap<String, (u32, u32)> = std::collections::HashMap::new();
80
81 for (char_key, (_, location)) in placements.packed_locations() {
82 max_width = max_width.max(location.x() + location.width());
83 max_height = max_height.max(location.y() + location.height());
84 packed_positions.insert(char_key.clone(), (location.x(), location.y()));
85 }
86
87 let atlas_width = next_power_of_two(max_width);
89 let atlas_height = next_power_of_two(max_height);
90
91 let mut atlas: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(atlas_width, atlas_height);
93
94 let mut packed_chars = Vec::new();
96
97 for img in images {
98 let (x, y) = packed_positions
99 .get(&img.char)
100 .copied()
101 .unwrap_or((0, 0));
102
103 for py in 0..img.height {
105 for px in 0..img.width {
106 let pixel = img.image.get_pixel(px, py);
107 atlas.put_pixel(x + px, y + py, pixel);
108 }
109 }
110
111 packed_chars.push(PackedChar {
112 char: img.char,
113 x,
114 y,
115 width: img.width,
116 height: img.height,
117 original_width: img.original_width,
118 original_height: img.original_height,
119 padding: img.padding,
120 });
121 }
122
123 Ok(AtlasResult {
124 image: DynamicImage::ImageRgba8(atlas),
125 chars: packed_chars,
126 width: atlas_width,
127 height: atlas_height,
128 })
129}
130
131fn next_power_of_two(n: u32) -> u32 {
133 if n == 0 {
134 return 1;
135 }
136 let mut power = 1;
137 while power < n {
138 power *= 2;
139 }
140 power
141}