bevy_image 0.17.0

Provides image types for Bevy Engine
Documentation
use bevy_app::prelude::*;
use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle};
use bevy_math::{Rect, URect, UVec2};
use bevy_platform::collections::HashMap;
#[cfg(not(feature = "bevy_reflect"))]
use bevy_reflect::TypePath;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};

use crate::Image;

/// Adds support for texture atlases.
pub struct TextureAtlasPlugin;

impl Plugin for TextureAtlasPlugin {
    fn build(&self, app: &mut App) {
        app.init_asset::<TextureAtlasLayout>();

        #[cfg(feature = "bevy_reflect")]
        app.register_asset_reflect::<TextureAtlasLayout>();
    }
}

/// Stores a mapping from sub texture handles to the related area index.
///
/// Generated by [`TextureAtlasBuilder`].
///
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
#[derive(Debug)]
pub struct TextureAtlasSources {
    /// Maps from a specific image handle to the index in `textures` where they can be found.
    pub texture_ids: HashMap<AssetId<Image>, usize>,
}

impl TextureAtlasSources {
    /// Retrieves the texture *section* index of the given `texture` handle.
    pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
        let id = texture.into();
        self.texture_ids.get(&id).cloned()
    }

    /// Creates a [`TextureAtlas`] handle for the given `texture` handle.
    pub fn handle(
        &self,
        layout: Handle<TextureAtlasLayout>,
        texture: impl Into<AssetId<Image>>,
    ) -> Option<TextureAtlas> {
        Some(TextureAtlas {
            layout,
            index: self.texture_index(texture)?,
        })
    }

    /// Retrieves the texture *section* rectangle of the given `texture` handle in pixels.
    pub fn texture_rect(
        &self,
        layout: &TextureAtlasLayout,
        texture: impl Into<AssetId<Image>>,
    ) -> Option<URect> {
        layout.textures.get(self.texture_index(texture)?).cloned()
    }

    /// Retrieves the texture *section* rectangle of the given `texture` handle in UV coordinates.
    /// These are within the range [0..1], as a fraction of the entire texture atlas' size.
    pub fn uv_rect(
        &self,
        layout: &TextureAtlasLayout,
        texture: impl Into<AssetId<Image>>,
    ) -> Option<Rect> {
        self.texture_rect(layout, texture).map(|rect| {
            let rect = rect.as_rect();
            let size = layout.size.as_vec2();
            Rect::from_corners(rect.min / size, rect.max / size)
        })
    }
}

/// Stores a map used to lookup the position of a texture in a [`TextureAtlas`].
/// This can be used to either use and look up a specific section of a texture, or animate frame-by-frame as a sprite sheet.
///
/// Optionally it can store a mapping from sub texture handles to the related area index (see
/// [`TextureAtlasBuilder`]).
///
/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// [Example usage animating sprite in response to an event.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
///
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
#[derive(Asset, PartialEq, Eq, Debug, Clone)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(Reflect),
    reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
    feature = "serialize",
    derive(serde::Serialize, serde::Deserialize),
    reflect(Serialize, Deserialize)
)]
#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
pub struct TextureAtlasLayout {
    /// Total size of texture atlas.
    pub size: UVec2,
    /// The specific areas of the atlas where each texture can be found
    pub textures: Vec<URect>,
}

impl TextureAtlasLayout {
    /// Create a new empty layout with custom `dimensions`
    pub fn new_empty(dimensions: UVec2) -> Self {
        Self {
            size: dimensions,
            textures: Vec::new(),
        }
    }

    /// Generate a [`TextureAtlasLayout`] as a grid where each
    /// `tile_size` by `tile_size` grid-cell is one of the *section* in the
    /// atlas. Grid cells are separated by some `padding`, and the grid starts
    /// at `offset` pixels from the top left corner. Resulting layout is
    /// indexed left to right, top to bottom.
    ///
    /// # Arguments
    ///
    /// * `tile_size` - Each layout grid cell size
    /// * `columns` - Grid column count
    /// * `rows` - Grid row count
    /// * `padding` - Optional padding between cells
    /// * `offset` - Optional global grid offset
    pub fn from_grid(
        tile_size: UVec2,
        columns: u32,
        rows: u32,
        padding: Option<UVec2>,
        offset: Option<UVec2>,
    ) -> Self {
        let padding = padding.unwrap_or_default();
        let offset = offset.unwrap_or_default();
        let mut sprites = Vec::new();
        let mut current_padding = UVec2::ZERO;

        for y in 0..rows {
            if y > 0 {
                current_padding.y = padding.y;
            }
            for x in 0..columns {
                if x > 0 {
                    current_padding.x = padding.x;
                }

                let cell = UVec2::new(x, y);
                let rect_min = (tile_size + current_padding) * cell + offset;

                sprites.push(URect {
                    min: rect_min,
                    max: rect_min + tile_size,
                });
            }
        }

        let grid_size = UVec2::new(columns, rows);

        Self {
            size: ((tile_size + current_padding) * grid_size) - current_padding,
            textures: sprites,
        }
    }

    /// Add a *section* to the list in the layout and returns its index
    /// which can be used with [`TextureAtlas`]
    ///
    /// # Arguments
    ///
    /// * `rect` - The section of the texture to be added
    ///
    /// [`TextureAtlas`]: crate::TextureAtlas
    pub fn add_texture(&mut self, rect: URect) -> usize {
        self.textures.push(rect);
        self.textures.len() - 1
    }

    /// The number of textures in the [`TextureAtlasLayout`]
    pub fn len(&self) -> usize {
        self.textures.len()
    }

    pub fn is_empty(&self) -> bool {
        self.textures.is_empty()
    }
}

/// An index into a [`TextureAtlasLayout`], which corresponds to a specific section of a texture.
///
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
/// image file for either sprite animation or global mapping.
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture
/// for efficient rendering of related game objects.
///
/// Check the following examples for usage:
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(Reflect),
    reflect(Default, Debug, PartialEq, Hash, Clone)
)]
pub struct TextureAtlas {
    /// Texture atlas layout handle
    pub layout: Handle<TextureAtlasLayout>,
    /// Texture atlas section index
    pub index: usize,
}

impl TextureAtlas {
    /// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`
    pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {
        let atlas = texture_atlases.get(&self.layout)?;
        atlas.textures.get(self.index).copied()
    }

    /// Returns this [`TextureAtlas`] with the specified index.
    pub fn with_index(mut self, index: usize) -> Self {
        self.index = index;
        self
    }

    /// Returns this [`TextureAtlas`] with the specified [`TextureAtlasLayout`] handle.
    pub fn with_layout(mut self, layout: Handle<TextureAtlasLayout>) -> Self {
        self.layout = layout;
        self
    }
}

impl From<Handle<TextureAtlasLayout>> for TextureAtlas {
    fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {
        Self {
            layout: texture_atlas,
            index: 0,
        }
    }
}