sheep/
lib.rs

1#[cfg(feature = "amethyst")]
2extern crate serde;
3
4#[cfg(feature = "amethyst")]
5#[macro_use]
6extern crate serde_derive;
7
8extern crate smallvec;
9extern crate twox_hash;
10
11mod format;
12mod pack;
13mod sprite;
14
15pub use {
16    format::Format,
17    pack::{
18        maxrects::{MaxrectsOptions, MaxrectsPacker},
19        simple::SimplePacker,
20        Packer, PackerResult,
21    },
22    sprite::{InputSprite, Sprite, SpriteAnchor, SpriteData},
23};
24
25#[cfg(feature = "amethyst")]
26pub use format::amethyst::{AmethystFormat, SerializedSpriteSheet, SpritePosition};
27#[cfg(feature = "amethyst")]
28pub use format::named::AmethystNamedFormat;
29
30use sprite::{create_pixel_buffer, write_sprite};
31
32use smallvec::SmallVec;
33use std::collections::hash_map::HashMap;
34use std::hash::BuildHasherDefault;
35use twox_hash::XxHash64;
36
37#[derive(Debug, Clone)]
38pub struct SpriteSheet {
39    pub bytes: Vec<u8>,
40    pub stride: usize,
41    pub dimensions: (u32, u32),
42    anchors: Vec<SpriteAnchor>,
43}
44
45pub fn pack<P: Packer>(
46    input: Vec<InputSprite>,
47    stride: usize,
48    options: P::Options,
49) -> Vec<SpriteSheet> {
50    let mut hashes: HashMap<&[u8], usize, BuildHasherDefault<XxHash64>> = Default::default();
51    let mut aliases: HashMap<usize, SmallVec<[usize; 1]>> = HashMap::with_capacity(input.len());
52    for (id, sprite) in input.iter().enumerate() {
53        let alias_id = hashes.entry(sprite.bytes.as_slice()).or_insert(id);
54        aliases.entry(*alias_id).or_default().push(id);
55    }
56
57    let sprites = input
58        .into_iter()
59        .enumerate()
60        .map(|(id, sprite)| Sprite::from_input(id, sprite))
61        .collect::<Vec<Sprite>>();
62    let sprite_data = sprites
63        .iter()
64        .enumerate()
65        .filter(|(id, _)| aliases.contains_key(id))
66        .map(|(_, it)| it.data)
67        .collect::<Vec<SpriteData>>();
68
69    let packer_result = P::pack(&sprite_data, options);
70
71    packer_result
72        .into_iter()
73        .map(|mut sheet| {
74            let mut buffer = create_pixel_buffer(sheet.dimensions, stride);
75            let mut aliased_anchors = Vec::<SpriteAnchor>::new();
76            for anchor in &sheet.anchors {
77                write_sprite(
78                    &mut buffer,
79                    sheet.dimensions,
80                    stride,
81                    &sprites[anchor.id],
82                    &anchor,
83                );
84                aliased_anchors.extend(
85                    aliases[&anchor.id]
86                        .iter()
87                        .skip(1)
88                        .map(|id| SpriteAnchor { id: *id, ..*anchor }),
89                );
90            }
91            sheet.anchors.extend(aliased_anchors);
92
93            SpriteSheet {
94                bytes: buffer,
95                stride: stride,
96                dimensions: sheet.dimensions,
97                anchors: sheet.anchors,
98            }
99        })
100        .collect()
101}
102
103pub fn encode<F>(sprite_sheet: &SpriteSheet, options: F::Options) -> F::Data
104where
105    F: Format,
106{
107    F::encode(sprite_sheet.dimensions, &sprite_sheet.anchors, options)
108}
109
110pub fn trim(input: &[InputSprite], stride: usize, alpha_channel_index: usize) -> Vec<InputSprite> {
111    input
112        .iter()
113        .map(|sprite| sprite.trimmed(stride, alpha_channel_index))
114        .collect()
115}
116
117#[cfg(test)]
118mod tests {
119    // Note this useful idiom: importing names from outer (for mod tests) scope.
120    use super::*;
121    #[test]
122    fn alias_test() {
123        let bytes1 = vec![0, 0, 0, 0];
124        let bytes2 = vec![1, 1, 1, 1];
125        let dimensions = (1, 1);
126        let sprite1 = InputSprite {
127            bytes: bytes1,
128            dimensions,
129        };
130        let sprite2 = InputSprite {
131            bytes: bytes2,
132            dimensions,
133        };
134
135        let input = vec![sprite1.clone(), sprite1, sprite2];
136        let sheets = pack::<SimplePacker>(input, 4, ());
137
138        assert_eq!(sheets[0].anchors.len(), 3);
139        assert_eq!(sheets[0].bytes.len(), 8);
140    }
141
142    #[test]
143    fn alias_with_trimming_test() {
144        let bytes1 = vec![1, 1, 1, 1];
145        let bytes2 = vec![1, 1, 1, 1, 1, 1, 1, 0];
146        let sprite1 = InputSprite {
147            bytes: bytes1,
148            dimensions: (1, 1),
149        };
150        let sprite2 = InputSprite {
151            bytes: bytes2,
152            dimensions: (2, 1),
153        };
154
155        let input = vec![sprite2.clone(), sprite1.clone(), sprite1, sprite2];
156        let input = trim(input.as_slice(), 4, 3);
157        let sheets = pack::<SimplePacker>(input, 4, ());
158
159        assert_eq!(sheets[0].anchors.len(), 4);
160        assert_eq!(sheets[0].bytes.len(), 4);
161    }
162}