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