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 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}