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