1use std::collections::HashMap;
2
3use crate::{math::{Point2, RectF}, utils::ArcRef};
4
5use super::{
6 super::GPUInner,
7 Texture,
8 TextureError,
9 TextureBuilder,
10 TextureUsage,
11 TextureFormat,
12};
13
14#[derive(Debug, Clone)]
17pub struct TextureAtlas {
18 pub(crate) texture: Texture,
19 pub(crate) items: HashMap<String, TextureAtlasCoord>,
20}
21
22#[derive(Debug, Clone)]
23pub(crate) struct TextureAtlasCoord {
24 pub rect_uv: RectF,
25 pub size: Point2,
26}
27
28impl TextureAtlas {
29 pub(crate) fn new(texture: Texture, items: HashMap<String, TextureAtlasCoord>) -> Self {
30 Self { texture, items }
31 }
32
33 pub fn get_id(&self, id: &str) -> Option<(RectF, Point2)> {
35 self.items.get(id).map(|coord| (coord.rect_uv, coord.size))
36 }
37
38 pub fn get_texture(&self) -> &Texture {
40 &self.texture
41 }
42
43 pub fn get_texture_size(&self) -> Point2 {
45 let inner = self.texture.inner.borrow();
46
47 Point2::new(inner.size.x as i32, inner.size.y as i32)
48 }
49}
50
51const MAX_WIDTH_SIZE: i32 = 2048;
52
53#[derive(Debug, Clone)]
54pub struct TextureAtlasBuilder {
55 pub(crate) gpu: ArcRef<GPUInner>,
56 pub(crate) items: HashMap<String, ItemQueue>,
57}
58
59#[derive(Debug, Clone)]
60pub(crate) enum ItemQueue {
61 File(String), Memory(Vec<u8>), Raw(Vec<u8>, u32, u32), }
65
66#[derive(Debug, Clone)]
67pub enum TextureAtlasBuilderError {
68 EmptyAtlas,
69 ExceedsMaxSize(i32, i32),
70 FileNotFound(String),
71 InvalidData(String),
72 TextureCreationError(TextureError),
73}
74
75impl std::fmt::Display for TextureAtlasBuilderError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 TextureAtlasBuilderError::EmptyAtlas => write!(f, "Texture atlas cannot be empty"),
79 TextureAtlasBuilderError::ExceedsMaxSize(width, height) => write!(
80 f,
81 "Texture atlas exceeds maximum size: {}x{}",
82 width, height
83 ),
84 TextureAtlasBuilderError::FileNotFound(file) => {
85 write!(f, "Texture file not found: {}", file)
86 }
87 TextureAtlasBuilderError::InvalidData(id) => {
88 write!(f, "Invalid texture data for id: {}", id)
89 }
90 TextureAtlasBuilderError::TextureCreationError(err) => {
91 write!(f, "Texture creation error: {}", err)
92 }
93 }
94 }
95}
96
97impl TextureAtlasBuilder {
98 pub(crate) fn new(gpu: ArcRef<GPUInner>) -> Self {
99 Self {
100 items: HashMap::new(),
101 gpu,
102 }
103 }
104
105 pub fn add_texture_file(mut self, id: &str, file: &str) -> Self {
106 self.items
107 .insert(id.to_string(), ItemQueue::File(file.to_string()));
108 self
109 }
110
111 pub fn add_texture_file_buf(mut self, id: &str, data: &[u8]) -> Self {
112 self.items
113 .insert(id.to_string(), ItemQueue::Memory(data.to_vec()));
114 self
115 }
116
117 pub fn add_texture_raw(mut self, id: &str, data: Vec<u8>, width: u32, height: u32) -> Self {
118 self.items
119 .insert(id.to_string(), ItemQueue::Raw(data, width, height));
120 self
121 }
122
123 pub fn build(self) -> Result<TextureAtlas, TextureAtlasBuilderError> {
124 if self.items.is_empty() {
125 return Err(TextureAtlasBuilderError::EmptyAtlas);
126 }
127
128 let mut texture_items = HashMap::new();
129
130 for (id, item) in self.items {
131 use image::GenericImageView;
132
133 let (texture_data, size) = match item {
134 ItemQueue::File(file) => {
135 if !std::path::Path::new(&file).exists() {
136 return Err(TextureAtlasBuilderError::FileNotFound(file));
137 }
138
139 let canonical_path = std::fs::canonicalize(&file)
140 .map_err(|_| TextureAtlasBuilderError::FileNotFound(file.clone()))?;
141
142 let image = image::open(&canonical_path)
143 .map_err(|_| TextureAtlasBuilderError::InvalidData(file.clone()))?;
144
145 let (width, height) = image.dimensions();
146 let data = image.to_rgba8();
147
148 (data.to_vec(), Point2::new(width as i32, height as i32))
149 }
150 ItemQueue::Memory(data) => {
151 let image = image::load_from_memory(&data)
152 .map_err(|_| TextureAtlasBuilderError::InvalidData(id.clone()))?;
153
154 let (width, height) = image.dimensions();
155 let data = image.to_rgba8();
156
157 (data.to_vec(), Point2::new(width as i32, height as i32))
158 }
159 ItemQueue::Raw(data, width, height) => {
160 if data.len() != (width * height * 4) as usize {
161 return Err(TextureAtlasBuilderError::InvalidData(id.clone()));
162 }
163
164 let size = Point2::new(width as i32, height as i32);
165 (data, size)
166 }
167 };
168
169 texture_items.insert(id.to_string(), (texture_data, size));
170 }
171
172 let rect_config = rect_packer::Config {
173 width: MAX_WIDTH_SIZE as i32,
174 height: MAX_WIDTH_SIZE as i32,
175 border_padding: 1,
176 rectangle_padding: 1,
177 };
178
179 let mut packer = rect_packer::Packer::new(rect_config);
180 let mut placemenets = HashMap::new();
181 let mut atlas_size = Point2::new(0, 0);
182
183 for (id, (_, size)) in &texture_items {
184 if size.x > MAX_WIDTH_SIZE || size.y > MAX_WIDTH_SIZE {
185 return Err(TextureAtlasBuilderError::ExceedsMaxSize(
186 size.x,
187 size.y,
188 ));
189 }
190
191 let rect = packer.pack(size.x, size.y, false)
192 .ok_or_else(|| {
193 TextureAtlasBuilderError::InvalidData(format!(
194 "Failed to pack texture with id: {}",
195 id
196 ))
197 })?;
198
199 placemenets.insert(id.clone(), rect);
200 atlas_size.x = atlas_size.x.max(rect.x + rect.width);
201 atlas_size.y = atlas_size.y.max(rect.y + rect.height);
202 }
203
204 if atlas_size.x > MAX_WIDTH_SIZE || atlas_size.y > MAX_WIDTH_SIZE {
205 return Err(TextureAtlasBuilderError::ExceedsMaxSize(atlas_size.x, atlas_size.y));
206 }
207
208 let mut texture_data = vec![0; (atlas_size.x * atlas_size.y * 4) as usize];
209 let mut items = HashMap::new();
210 for (id, rect) in placemenets {
211 let (data, size) = texture_items.get(&id).ok_or_else(|| {
212 TextureAtlasBuilderError::InvalidData(format!("Missing data for id: {}", id))
213 })?;
214
215 let atlas_w = atlas_size.x as f32;
216 let atlas_h = atlas_size.y as f32;
217 let half_texel_x = 0.5 / atlas_w;
218 let half_texel_y = 0.5 / atlas_h;
219
220 let rect_uv = RectF::new(
221 (rect.x as f32 + half_texel_x) / atlas_w,
222 (rect.y as f32 + half_texel_y) / atlas_h,
223 (rect.x as f32 + rect.width as f32 - half_texel_x) / atlas_w,
224 (rect.y as f32 + rect.height as f32 - half_texel_y) / atlas_h,
225 );
226
227 let size = Point2::new(size.x, size.y);
228
229 for j in 0..size.y {
230 for i in 0..size.x {
231 let src_index = ((j * size.x + i) * 4) as usize;
232 let dst_index = (((rect.y + j) * atlas_size.x + (rect.x + i)) * 4) as usize;
233
234 texture_data[dst_index..dst_index + 4]
235 .copy_from_slice(&data[src_index..src_index + 4]);
236 }
237 }
238
239 items.insert(
240 id,
241 TextureAtlasCoord {
242 rect_uv,
243 size,
244 },
245 );
246 }
247
248 let format = if self.gpu.borrow().is_srgb() {
249 TextureFormat::Rgba8UnormSrgb
250 } else {
251 TextureFormat::Rgba8Unorm
252 };
253
254 let texture = TextureBuilder::new(self.gpu)
255 .set_raw_image(&texture_data, atlas_size, format)
256 .set_usage(TextureUsage::Sampler)
257 .build()
258 .map_err(TextureAtlasBuilderError::TextureCreationError)?;
259
260 Ok(TextureAtlas::new(texture, items))
261 }
262}