Skip to main content

bitmapfont_creator/
image_loader.rs

1//! Image loading module
2
3use std::path::Path;
4use image::{DynamicImage, GenericImageView, ImageBuffer, Rgba};
5
6use crate::error::{BitmapFontError, Result};
7use crate::config::FontConfig;
8
9/// A loaded character image with its metadata
10#[derive(Debug)]
11pub struct CharImage {
12    /// The character identifier
13    pub char: String,
14    /// The loaded image (with padding applied)
15    pub image: DynamicImage,
16    /// Image width in pixels (including padding)
17    pub width: u32,
18    /// Image height in pixels (including padding)
19    pub height: u32,
20    /// Original width (without padding)
21    pub original_width: u32,
22    /// Original height (without padding)
23    pub original_height: u32,
24    /// Padding applied
25    pub padding: u32,
26}
27
28/// Load all character images from a font configuration
29pub fn load_images(config: &FontConfig, base_path: &Path) -> Result<Vec<CharImage>> {
30    let mut images = Vec::new();
31    // Cache for loaded sprite sheets
32    let mut sheet_cache: std::collections::HashMap<std::path::PathBuf, DynamicImage> = std::collections::HashMap::new();
33
34    for (char_key, char_config) in &config.chars {
35        let full_path = if char_config.path.is_absolute() {
36            char_config.path.clone()
37        } else {
38            base_path.join(&char_config.path)
39        };
40
41        // Load or get cached sprite sheet
42        let sheet = if let Some(cached) = sheet_cache.get(&full_path) {
43            cached.clone()
44        } else {
45            let img = image::open(&full_path)
46                .map_err(|e| BitmapFontError::ImageLoad(
47                    format!("Failed to load image '{}': {}", full_path.display(), e)
48                ))?;
49            sheet_cache.insert(full_path.clone(), img.clone());
50            img
51        };
52
53        // Extract frame or use whole image
54        let (original_width, original_height, char_img) = if let Some(frame) = &char_config.frame {
55            // Extract specific frame from sprite sheet
56            let frame_img = extract_frame(&sheet, frame)?;
57            (frame.w, frame.h, frame_img)
58        } else {
59            // Use whole image
60            (sheet.width(), sheet.height(), sheet.clone())
61        };
62
63        // Apply padding
64        let padding = char_config.padding;
65        let padded_img = apply_padding(&char_img, padding);
66        let width = original_width + padding * 2;
67        let height = original_height + padding * 2;
68
69        images.push(CharImage {
70            char: char_key.clone(),
71            image: padded_img,
72            width,
73            height,
74            original_width,
75            original_height,
76            padding,
77        });
78    }
79
80    Ok(images)
81}
82
83/// Extract a frame from a sprite sheet
84fn extract_frame(sheet: &DynamicImage, frame: &crate::config::Frame) -> Result<DynamicImage> {
85    // Validate frame bounds
86    if frame.x + frame.w > sheet.width() || frame.y + frame.h > sheet.height() {
87        return Err(BitmapFontError::ImageLoad(
88            format!(
89                "Frame ({}, {}, {}, {}) is out of bounds for image size ({}, {})",
90                frame.x, frame.y, frame.w, frame.h,
91                sheet.width(), sheet.height()
92            )
93        ));
94    }
95
96    // Extract the frame region
97    let mut frame_img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(frame.w, frame.h);
98
99    for y in 0..frame.h {
100        for x in 0..frame.w {
101            let pixel = sheet.get_pixel(frame.x + x, frame.y + y);
102            frame_img.put_pixel(x, y, pixel);
103        }
104    }
105
106    Ok(DynamicImage::ImageRgba8(frame_img))
107}
108
109/// Apply padding around an image
110fn apply_padding(img: &DynamicImage, padding: u32) -> DynamicImage {
111    if padding == 0 {
112        return img.clone();
113    }
114
115    let width = img.width() + padding * 2;
116    let height = img.height() + padding * 2;
117
118    let mut padded: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(width, height);
119
120    // Fill with transparent pixels (padding area)
121    for pixel in padded.pixels_mut() {
122        *pixel = Rgba([0, 0, 0, 0]);
123    }
124
125    // Copy original image to the center
126    for y in 0..img.height() {
127        for x in 0..img.width() {
128            let pixel = img.get_pixel(x, y);
129            padded.put_pixel(x + padding, y + padding, pixel);
130        }
131    }
132
133    DynamicImage::ImageRgba8(padded)
134}