quaso 0.47.0

Toolset for making Micro Games quickly
Documentation
use crate::{
    animation::frame::{SpriteAnimationImage, SpriteFrameAnimation},
    assets::name_from_path,
    context::GameContext,
    game::GameSubsystem,
};
use anput::world::World;
use image::{
    AnimationDecoder, ImageFormat, RgbaImage,
    codecs::{gif::GifDecoder, png::PngDecoder},
};
use keket::{
    database::{handle::AssetHandle, path::AssetPathStatic},
    protocol::AssetProtocol,
};
use spitfire_draw::utils::TextureRef;
use spitfire_glow::renderer::GlowTextureFormat;
use std::{error::Error, io::Cursor};
use vek::Rect;

pub struct AnimTextureFrame {
    pub image: RgbaImage,
    pub duration: f32,
}

pub struct AnimTextureAsset {
    pub frames: Vec<AnimTextureFrame>,
}

impl AnimTextureAsset {
    pub fn total_duration(&self) -> f32 {
        self.frames.iter().map(|frame| frame.duration).sum()
    }

    pub fn build_animation(&self, texture: TextureRef) -> SpriteFrameAnimation {
        let mut result = SpriteFrameAnimation::default();
        for (index, frame) in self.frames.iter().enumerate() {
            result.images.insert(
                index,
                SpriteAnimationImage {
                    texture: texture.clone(),
                    region: Rect::new(0.0, 0.0, 1.0, 1.0),
                    page: index as f32,
                },
            );
            result.animation.add_frame(index, frame.duration);
        }
        result
    }
}

pub struct AnimTextureAssetSubsystem;

impl GameSubsystem for AnimTextureAssetSubsystem {
    fn run(&mut self, context: GameContext, _: f32) {
        for entity in context.assets.storage.added().iter_of::<AnimTextureAsset>() {
            if let Some((path, asset)) = context
                .assets
                .storage
                .lookup_one::<true, (&AssetPathStatic, &AnimTextureAsset)>(entity)
            {
                if asset.frames.is_empty() {
                    continue;
                }
                let buffer = asset
                    .frames
                    .iter()
                    .flat_map(|frame| frame.image.as_raw().to_owned())
                    .collect::<Vec<u8>>();
                context.draw.textures.insert(
                    name_from_path(&path).to_owned().into(),
                    context
                        .graphics
                        .texture(
                            asset.frames[0].image.width(),
                            asset.frames[0].image.height(),
                            asset.frames.len() as u32,
                            GlowTextureFormat::Rgba,
                            Some(buffer.as_slice()),
                        )
                        .unwrap(),
                );
            }
        }
        for entity in context
            .assets
            .storage
            .removed()
            .iter_of::<AnimTextureAsset>()
        {
            if let Some(path) = context
                .assets
                .storage
                .lookup_one::<true, &AssetPathStatic>(entity)
            {
                context.draw.textures.remove(name_from_path(&path));
            }
        }
    }
}

pub struct AnimTextureAssetProtocol;

impl AssetProtocol for AnimTextureAssetProtocol {
    fn name(&self) -> &str {
        "animtexture"
    }

    fn process_bytes(
        &mut self,
        handle: AssetHandle,
        storage: &mut World,
        bytes: Vec<u8>,
    ) -> Result<(), Box<dyn Error>> {
        let path = storage.component::<true, AssetPathStatic>(handle.entity())?;
        let format = image::guess_format(&bytes)
            .map_err(|_| format!("Failed to read texture format: {:?}", path.path()))?;
        drop(path);

        let frames = match format {
            ImageFormat::Gif => GifDecoder::new(Cursor::new(bytes))
                .map_err(|_| "Failed to decode GIF anim texture")?
                .into_frames()
                .collect_frames()
                .map_err(|_| "Failed to collect GIF frames")?,
            ImageFormat::Png => PngDecoder::new(Cursor::new(bytes))
                .map_err(|_| "Failed to decode APNG anim texture")?
                .apng()
                .map_err(|_| "Failed to decode APNG anim texture")?
                .into_frames()
                .collect_frames()
                .map_err(|_| "Failed to collect APNG frames")?,
            _ => return Err(format!("Unsupported anim texture format: {:?}", format).into()),
        }
        .into_iter()
        .map(|frame| {
            let (numer, denom) = frame.delay().numer_denom_ms();
            AnimTextureFrame {
                image: frame.into_buffer(),
                duration: (numer as f32 / denom as f32) * 0.001,
            }
        })
        .collect::<Vec<AnimTextureFrame>>();

        storage.insert(handle.entity(), (AnimTextureAsset { frames },))?;

        Ok(())
    }
}