bevy_image/
dynamic_texture_atlas_builder.rs1use crate::{Image, TextureAtlasLayout, TextureFormatPixelInfo as _};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{URect, UVec2};
4use guillotiere::{size2, Allocation, AtlasAllocator};
5use thiserror::Error;
6use tracing::error;
7
8#[derive(Debug, Error)]
9pub enum DynamicTextureAtlasBuilderError {
10 #[error("Couldn't allocate space to add the image requested")]
11 FailedToAllocateSpace,
12 #[error("cannot add texture to uninitialized atlas texture")]
14 UninitializedAtlas,
15 #[error("cannot add uninitialized texture to atlas")]
17 UninitializedSourceTexture,
18}
19
20pub struct DynamicTextureAtlasBuilder {
25 atlas_allocator: AtlasAllocator,
26 padding: u32,
27}
28
29impl DynamicTextureAtlasBuilder {
30 pub fn new(size: UVec2, padding: u32) -> Self {
37 Self {
38 atlas_allocator: AtlasAllocator::new(to_size2(size)),
39 padding,
40 }
41 }
42
43 pub fn add_texture(
55 &mut self,
56 atlas_layout: &mut TextureAtlasLayout,
57 texture: &Image,
58 atlas_texture: &mut Image,
59 ) -> Result<usize, DynamicTextureAtlasBuilderError> {
60 let allocation = self.atlas_allocator.allocate(size2(
61 (texture.width() + self.padding).try_into().unwrap(),
62 (texture.height() + self.padding).try_into().unwrap(),
63 ));
64 if let Some(allocation) = allocation {
65 assert!(
66 atlas_texture.asset_usage.contains(RenderAssetUsages::MAIN_WORLD),
67 "The atlas_texture image must have the RenderAssetUsages::MAIN_WORLD usage flag set"
68 );
69
70 self.place_texture(atlas_texture, allocation, texture)?;
71 let mut rect: URect = to_rect(allocation.rectangle);
72 rect.max = rect.max.saturating_sub(UVec2::splat(self.padding));
73 Ok(atlas_layout.add_texture(rect))
74 } else {
75 Err(DynamicTextureAtlasBuilderError::FailedToAllocateSpace)
76 }
77 }
78
79 fn place_texture(
80 &mut self,
81 atlas_texture: &mut Image,
82 allocation: Allocation,
83 texture: &Image,
84 ) -> Result<(), DynamicTextureAtlasBuilderError> {
85 let mut rect = allocation.rectangle;
86 rect.max.x -= self.padding as i32;
87 rect.max.y -= self.padding as i32;
88 let atlas_width = atlas_texture.width() as usize;
89 let rect_width = rect.width() as usize;
90 let format_size = atlas_texture.texture_descriptor.format.pixel_size();
91
92 let Some(ref mut atlas_data) = atlas_texture.data else {
93 return Err(DynamicTextureAtlasBuilderError::UninitializedAtlas);
94 };
95 let Some(ref data) = texture.data else {
96 return Err(DynamicTextureAtlasBuilderError::UninitializedSourceTexture);
97 };
98 for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() {
99 let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size;
100 let end = begin + rect_width * format_size;
101 let texture_begin = texture_y * rect_width * format_size;
102 let texture_end = texture_begin + rect_width * format_size;
103 atlas_data[begin..end].copy_from_slice(&data[texture_begin..texture_end]);
104 }
105 Ok(())
106 }
107}
108
109fn to_rect(rectangle: guillotiere::Rectangle) -> URect {
110 URect {
111 min: UVec2::new(
112 rectangle.min.x.try_into().unwrap(),
113 rectangle.min.y.try_into().unwrap(),
114 ),
115 max: UVec2::new(
116 rectangle.max.x.try_into().unwrap(),
117 rectangle.max.y.try_into().unwrap(),
118 ),
119 }
120}
121
122fn to_size2(vec2: UVec2) -> guillotiere::Size {
123 guillotiere::Size::new(vec2.x as i32, vec2.y as i32)
124}