Skip to main content

bitmapfont_creator/
packer.rs

1//! Texture packing module
2
3use 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/// A packed character with its position in the atlas
12#[derive(Debug, Clone)]
13pub struct PackedChar {
14    /// The character identifier
15    pub char: String,
16    /// X position in the atlas
17    pub x: u32,
18    /// Y position in the atlas
19    pub y: u32,
20    /// Width in pixels (including padding)
21    pub width: u32,
22    /// Height in pixels (including padding)
23    pub height: u32,
24    /// Original width (without padding)
25    pub original_width: u32,
26    /// Original height (without padding)
27    pub original_height: u32,
28    /// Padding applied
29    pub padding: u32,
30}
31
32/// Result of the packing operation
33#[derive(Debug)]
34pub struct AtlasResult {
35    /// The generated atlas image
36    pub image: DynamicImage,
37    /// Information about packed characters
38    pub chars: Vec<PackedChar>,
39    /// Atlas width
40    pub width: u32,
41    /// Atlas height
42    pub height: u32,
43}
44
45/// Pack character images into a single atlas
46pub 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    // Create rectangles for packing
52    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    // Create target bins (try different sizes)
60    let mut target_bins = std::collections::BTreeMap::new();
61    target_bins.insert((), TargetBin::new(max_size, max_size, 1));
62
63    // Perform packing
64    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    // Calculate actual atlas size needed
76    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    // Round up to power of 2 for better GPU compatibility
88    let atlas_width = next_power_of_two(max_width);
89    let atlas_height = next_power_of_two(max_height);
90
91    // Create the atlas image
92    let mut atlas: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(atlas_width, atlas_height);
93
94    // Place images on the atlas
95    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        // Copy image to atlas
104        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
131/// Calculate the next power of two
132fn 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}