texture_packer 0.19.0

A texture packing library using the skyline heuristic
Documentation
use crate::{
    frame::Frame,
    packer::{Packer, SkylinePacker},
    rect::Rect,
    texture::{Pixel, SubTexture, Texture},
    texture_packer_config::TexturePackerConfig,
};
use std::collections::HashMap;

pub type PackResult<T> = Result<T, PackError>;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PackError {
    TextureTooLargeToFitIntoAtlas,
}

pub struct TexturePacker<'a, T: 'a + Clone, P> {
    textures: HashMap<String, SubTexture<'a, T>>,
    frames: HashMap<String, Frame>,
    packer: P,
    config: TexturePackerConfig,
}

impl<'a, Pix: Pixel, T: 'a + Clone + Texture<Pixel = Pix>>
    TexturePacker<'a, T, SkylinePacker<Pix>>
{
    pub fn new_skyline(config: TexturePackerConfig) -> TexturePacker<'a, T, SkylinePacker<Pix>> {
        TexturePacker {
            textures: HashMap::new(),
            frames: HashMap::new(),
            packer: SkylinePacker::<Pix>::new(config),
            config: config,
        }
    }
}

impl<'a, Pix: Pixel, P: Packer<Pixel = Pix>, T: Clone + Texture<Pixel = Pix>>
    TexturePacker<'a, T, P>
{
    pub fn can_pack(&self, texture: &'a T) -> bool {
        self.packer.can_pack(texture)
    }

    pub fn pack_ref(&mut self, key: String, texture: &'a T) -> PackResult<()> {
        if !self.packer.can_pack(texture) {
            return Err(PackError::TextureTooLargeToFitIntoAtlas);
        }

        let (w, h) = (texture.width(), texture.height());
        let source = if self.config.trim {
            trim_texture(texture)
        } else {
            Rect::new(0, 0, w, h)
        };

        let texture = SubTexture::from_ref(texture, source);
        if let Some(mut frame) = self.packer.pack(key.clone(), &texture) {
            frame.frame.x += self.config.border_padding;
            frame.frame.y += self.config.border_padding;
            frame.trimmed = self.config.trim;
            frame.source = source;
            frame.source.w = w;
            frame.source.h = h;
            self.frames.insert(key.clone(), frame);
        }

        self.textures.insert(key, texture);
        Ok(())
    }

    pub fn pack_own(&mut self, key: String, texture: T) -> PackResult<()> {
        if !self.packer.can_pack(&texture) {
            return Err(PackError::TextureTooLargeToFitIntoAtlas);
        }

        let (w, h) = (texture.width(), texture.height());
        let source = if self.config.trim {
            trim_texture(&texture)
        } else {
            Rect::new(0, 0, w, h)
        };

        let texture = SubTexture::new(texture, source);
        if let Some(mut frame) = self.packer.pack(key.clone(), &texture) {
            frame.frame.x += self.config.border_padding;
            frame.frame.y += self.config.border_padding;
            frame.trimmed = self.config.trim;
            frame.source = source;
            frame.source.w = w;
            frame.source.h = h;
            self.frames.insert(key.clone(), frame);
        }

        self.textures.insert(key, texture);
        Ok(())
    }

    pub fn get_frames(&self) -> &HashMap<String, Frame> {
        &self.frames
    }

    pub fn get_frame(&self, key: &str) -> Option<&Frame> {
        if let Some(frame) = self.frames.get(key) {
            Some(frame)
        } else {
            None
        }
    }

    fn get_frame_at(&self, x: u32, y: u32) -> Option<&Frame> {
        for (_, frame) in self.frames.iter() {
            if frame.frame.contains_point(x, y) {
                return Some(frame);
            }
        }
        None
    }
}

impl<'a, Pix, P, T: Clone> Texture for TexturePacker<'a, T, P>
where
    Pix: Pixel,
    P: Packer<Pixel = Pix>,
    T: Texture<Pixel = Pix>,
{
    type Pixel = Pix;

    fn width(&self) -> u32 {
        let mut right = None;

        for (_, frame) in self.frames.iter() {
            if let Some(r) = right {
                if frame.frame.right() > r {
                    right = Some(frame.frame.right());
                }
            } else {
                right = Some(frame.frame.right());
            }
        }

        if let Some(right) = right {
            right + 1 + self.config.border_padding
        } else {
            0
        }
    }

    fn height(&self) -> u32 {
        let mut bottom = None;

        for (_, frame) in self.frames.iter() {
            if let Some(b) = bottom {
                if frame.frame.bottom() > b {
                    bottom = Some(frame.frame.bottom());
                }
            } else {
                bottom = Some(frame.frame.bottom());
            }
        }

        if let Some(bottom) = bottom {
            bottom + 1 + self.config.border_padding
        } else {
            0
        }
    }

    fn get(&self, x: u32, y: u32) -> Option<Pix> {
        if let Some(frame) = self.get_frame_at(x, y) {
            if let Some(texture) = self.textures.get(&frame.key) {
                if self.config.texture_outlines && frame.frame.is_outline(x, y) {
                    return Some(<Pix as Pixel>::outline());
                }

                let x = x - frame.frame.x;
                let y = y - frame.frame.y;

                return if frame.rotated {
                    texture.get_rotated(x, y)
                } else {
                    texture.get(x, y)
                };
            }
        }

        None
    }

    fn set(&mut self, _x: u32, _y: u32, _val: Pix) {
        panic!("Can't set pixel directly");
    }
}

fn trim_texture<T: Texture>(texture: &T) -> Rect {
    let mut x1 = 0;
    for x in 0..texture.width() {
        if texture.is_column_transparent(x) {
            x1 = x + 1;
        } else {
            break;
        }
    }

    let mut x2 = texture.width() - 1;
    for x in 0..texture.width() {
        let x = texture.width() - x - 1;
        if texture.is_column_transparent(x) {
            x2 = x - 1;
        } else {
            break;
        }
    }

    let mut y1 = 0;
    for y in 0..texture.height() {
        if texture.is_row_transparent(y) {
            y1 = y + 1;
        } else {
            break;
        }
    }

    let mut y2 = texture.height() - 1;
    for y in 0..texture.height() {
        let y = texture.height() - y - 1;
        if texture.is_row_transparent(y) {
            y2 = y - 1;
        } else {
            break;
        }
    }
    Rect::new_with_points(x1, y1, x2, y2)
}