use crate::gfx::{Graphics, SubTexture, Texture};
use crate::grid::{Grid, GridMut};
use crate::math::{Numeric, RectF, RectU, Vec2F, Vec2U};
use crate::prelude::TexturePixel;
use fey_color::{Grey8, GreyAlpha8, Rgb8, Rgba8};
use fey_img::Image;
use fey_packer::{Item, Packed, RectPacker};
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
use std::rc::Rc;
pub type TexturePackerRgba8<'a, K> = TexturePacker<'a, K, Rgba8>;
pub type TexturePackerRgb8<'a, K> = TexturePacker<'a, K, Rgb8>;
pub type TexturePackerGreyAlpha8<'a, K> = TexturePacker<'a, K, GreyAlpha8>;
pub type TexturePackerGrey8<'a, K> = TexturePacker<'a, K, Grey8>;
pub struct TexturePacker<'a, K, P: TexturePixel> {
to_pack: Vec<ToPack<'a, K, P>>,
}
struct ToPack<'a, K, P: TexturePixel> {
key: K,
img: PackImage<'a, P>,
src_rect: RectU,
trim_rect: RectU,
}
impl<'a, K: Clone + Eq + Hash, P: TexturePixel> TexturePacker<'a, K, P> {
pub fn new() -> Self {
Self {
to_pack: Vec::new(),
}
}
pub fn add_image(
&mut self,
key: K,
img: impl Into<PackImage<'a, P>>,
src_rect: impl Into<Option<RectU>>,
trim_threshold: impl Into<Option<P::Channel>>,
) {
let img = img.into();
let src_rect = src_rect.into().unwrap_or(RectU::sized(img.size()));
let src = img.view_at(src_rect);
let trim_rect = trim_threshold
.into()
.and_then(|a| src.get_bounds(|p| p.alpha() > a))
.unwrap_or_else(|| RectU::sized(src_rect.size()));
self.to_pack.push(ToPack {
key,
img,
src_rect,
trim_rect,
});
}
#[inline]
pub fn len(&self) -> usize {
self.to_pack.len()
}
pub fn pack(self, gfx: &Graphics) -> Option<(Texture, HashMap<K, SubTexture>)> {
self.pack_ext(gfx, gfx.max_texture_size(), 1, 2)
}
pub fn pack_ext(
self,
gfx: &Graphics,
max_size: u32,
spacing: u32,
padding: u32,
) -> Option<(Texture, HashMap<K, SubTexture>)> {
let padding = Vec2U::splat(padding);
let items: Vec<Item<usize>> = self
.to_pack
.iter()
.enumerate()
.map(|(i, item)| Item::new(item.trim_rect.size() + padding, i))
.collect();
let (size, mut packed) = RectPacker::new()
.with_max_size(max_size)
.with_spacing(spacing)
.pack(items)?;
packed.sort_by_key(|i| i.data);
let mut tex_img = Image::<P, _>::new_vec(size, P::default());
let padding = padding.to_f32();
let sub_info: Vec<(K, RectF, Vec2F, Vec2F)> = packed
.into_iter()
.map(|Packed { data: i, pos }| {
let ToPack {
key,
img,
src_rect,
trim_rect,
} = &self.to_pack[i];
let src = img.view_at(*trim_rect + src_rect.top_left());
let dst_rect = RectU::pos_size(pos, trim_rect.size());
let mut dst = tex_img.view_mut_at(dst_rect);
dst.draw_copied(&src);
(
key.clone(),
dst_rect.to_f32().inflate(padding),
trim_rect.top_left().to_f32() - padding,
src_rect.size().to_f32(),
)
})
.collect();
let tex_img = tex_img.to_rgba8();
let tex = gfx.create_texture_from_img(&tex_img);
let subs = sub_info
.into_iter()
.map(|(key, rect, offset, size)| {
(key, SubTexture::new_ext(tex.clone(), rect, offset, size))
})
.collect();
Some((tex, subs))
}
}
#[derive(Debug, Clone)]
pub enum PackImage<'a, P: TexturePixel> {
Ref(&'a Image<P>),
Owned(Image<P>),
Shared(Rc<Image<P>>),
}
impl<P: TexturePixel> Deref for PackImage<'_, P> {
type Target = Image<P>;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
Self::Ref(r) => *r,
Self::Owned(r) => r,
Self::Shared(r) => &r,
}
}
}
impl<P: TexturePixel> From<Image<P>> for PackImage<'_, P> {
#[inline]
fn from(value: Image<P>) -> Self {
Self::Owned(value)
}
}
impl<'a, P: TexturePixel> From<&'a Image<P>> for PackImage<'a, P> {
#[inline]
fn from(value: &'a Image<P>) -> Self {
Self::Ref(value)
}
}
impl<'a, P: TexturePixel> From<&'a mut Image<P>> for PackImage<'a, P> {
#[inline]
fn from(value: &'a mut Image<P>) -> Self {
Self::Ref(value)
}
}
impl<P: TexturePixel> From<Rc<Image<P>>> for PackImage<'_, P> {
#[inline]
fn from(value: Rc<Image<P>>) -> Self {
Self::Shared(value)
}
}
impl<P: TexturePixel> From<&Rc<Image<P>>> for PackImage<'_, P> {
#[inline]
fn from(value: &Rc<Image<P>>) -> Self {
Self::Shared(value.clone())
}
}