use std::borrow::Cow;
use assets_manager::{
loader::{Loader, TomlLoader},
AnyCache, Asset, BoxedError, Compound, SharedString,
};
use blit::{slice::Slice, Blit, BlitBuffer, BlitOptions, ToBlitBuffer};
use image::ImageFormat;
use serde::Deserialize;
use vek::{Extent2, Vec2};
use crate::canvas::Canvas;
#[derive(Debug)]
pub struct Sprite {
sprite: Image,
metadata: SpriteMetadata,
}
impl Sprite {
pub fn from_buffer(buffer: &[u32], size: Extent2<usize>, offset: SpriteOffset) -> Self {
let sprite = Image(BlitBuffer::from_buffer(buffer, size.w, 127));
let metadata = SpriteMetadata {
offset,
..Default::default()
};
Self { sprite, metadata }
}
pub fn render(&self, offset: Vec2<f64>, canvas: &mut Canvas) {
let mut options = BlitOptions::new_position(offset.x, offset.y);
options.vertical_slice = self.metadata.vertical_slice;
options.horizontal_slice = self.metadata.horizontal_slice;
self.sprite
.0
.blit(canvas.buffer, canvas.size.into_tuple().into(), &options);
}
pub fn render_area(&self, offset: Vec2<f64>, area: Extent2<usize>, canvas: &mut Canvas) {
let mut options =
BlitOptions::new_position(offset.x, offset.y).with_area(area.into_tuple());
options.vertical_slice = self.metadata.vertical_slice;
options.horizontal_slice = self.metadata.horizontal_slice;
self.sprite
.0
.blit(canvas.buffer, canvas.size.into_tuple().into(), &options);
}
pub fn render_options(&self, blit_options: &BlitOptions, canvas: &mut Canvas) {
self.sprite
.0
.blit(canvas.buffer, canvas.size.into_tuple().into(), blit_options);
}
pub fn is_pixel_transparent(&self, pixel: Vec2<u32>) -> bool {
let offset: Vec2<i32> = pixel.as_() + self.metadata.offset.offset(self.size());
let index: i32 = offset.x + offset.y * self.sprite.0.width() as i32;
let pixel = self.sprite.0.pixels()[index as usize];
pixel == 0
}
pub fn width(&self) -> u32 {
self.sprite.0.width()
}
pub fn height(&self) -> u32 {
self.sprite.0.height()
}
pub fn size(&self) -> Extent2<u32> {
Extent2::new(self.width(), self.height())
}
pub fn into_blit_buffer(self) -> BlitBuffer {
self.sprite.0
}
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.sprite.0.pixels_mut()
}
}
impl Default for Sprite {
fn default() -> Self {
let sprite = Image(BlitBuffer::from_buffer(&[0], 1, 0));
let metadata = SpriteMetadata::default();
Self { sprite, metadata }
}
}
impl Compound for Sprite {
fn load(cache: AnyCache, id: &SharedString) -> Result<Self, BoxedError> {
let sprite = cache.load_owned::<Image>(id)?;
let metadata = cache.load::<SpriteMetadata>(id)?.read().clone();
Ok(Self { sprite, metadata })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Deserialize)]
pub enum SpriteOffset {
#[default]
Middle,
MiddleTop,
LeftTop,
Custom(Vec2<i32>),
}
impl SpriteOffset {
pub fn offset(&self, sprite_size: Extent2<u32>) -> Vec2<i32> {
match self {
SpriteOffset::Middle => {
Vec2::new(-(sprite_size.w as i32) / 2, -(sprite_size.h as i32) / 2)
}
SpriteOffset::MiddleTop => Vec2::new(-(sprite_size.w as i32) / 2, 0),
SpriteOffset::LeftTop => Vec2::zero(),
SpriteOffset::Custom(offset) => *offset,
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub(crate) struct SpriteMetadata {
pub(crate) vertical_slice: Option<Slice>,
pub(crate) horizontal_slice: Option<Slice>,
#[serde(default)]
pub(crate) offset: SpriteOffset,
}
impl Asset for SpriteMetadata {
const EXTENSION: &'static str = "toml";
type Loader = TomlLoader;
}
#[derive(Debug)]
struct Image(BlitBuffer);
impl Asset for Image {
const EXTENSION: &'static str = "png";
type Loader = ImageLoader;
}
pub struct ImageLoader;
impl Loader<Image> for ImageLoader {
fn load(content: Cow<[u8]>, ext: &str) -> Result<Image, BoxedError> {
assert_eq!(ext.to_lowercase(), "png");
let sprite = image::load_from_memory_with_format(&content, ImageFormat::Png)?
.into_rgba8()
.to_blit_buffer_with_alpha(127);
Ok(Image(sprite))
}
}