tileset/utils/
mod.rs

1#![doc = include_str!("readme.md")]
2use crate::{grids::rpg_maker_xp::GridCornerRMXP, GridAtlas, GridCompleteAtlas, GridCornerRMVX, GridEdgeWang};
3use image::{
4    error::{ParameterError, ParameterErrorKind},
5    ColorType, GenericImageView, ImageError, ImageFormat, ImageResult, RgbaImage,
6};
7use std::{
8    collections::BTreeMap,
9    fmt::{Display, Formatter},
10    io::{Error, ErrorKind},
11    path::{Path, PathBuf},
12};
13
14/// Decompose image grids by cells count.
15pub fn decompose_image_grid_by_cells<P>(path: P, cols: u32, rows: u32) -> ImageResult<()>
16where
17    P: AsRef<Path>,
18{
19    let path = path.as_ref().canonicalize()?;
20    let dir = path.parent().expect("The path must have a parent directory");
21    let name = path.file_stem().expect("The path must have a file name");
22    let image = image::open(&path)?;
23    let (width, height) = image.dimensions();
24    let cell_width = width / cols;
25    let cell_height = height / rows;
26    for row in 0..rows {
27        for col in 0..cols {
28            let view = image.view(col * cell_width, row * cell_height, cell_width, cell_height);
29            let out = dir.join(format!("{}-{}-{}.png", name.to_str().unwrap(), col, row));
30            view.to_image().save(&out)?;
31        }
32    }
33    Ok(())
34}
35
36/// Create a new tile set from rpg maker xp atlas.
37///
38/// ## Example
39///
40/// ```no_run
41/// # use tileset::{GridAtlas, GridCompleteAtlas};
42/// let raw: GridCompleteAtlas = GridAtlas::load("assets/grass-xp.png").unwrap();
43/// let size = raw.get_cell_size();
44/// ```
45#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
46pub struct AnimationSlice {}
47
48/// Create a new animation slice from image.
49pub fn grid_corner_mask(lu: bool, ru: bool, ld: bool, rd: bool) -> u8 {
50    (lu as u8) << 0 | (ru as u8) << 1 | (ld as u8) << 2 | (rd as u8) << 3
51}
52
53/// Create a new tile set from rpg maker xp atlas.
54///
55/// ## Example
56///
57/// ```no_run
58/// # use tileset::{GridAtlas, GridCompleteAtlas};
59/// let raw: GridCompleteAtlas = GridAtlas::load("assets/grass-xp.png").unwrap();
60/// let size = raw.get_cell_size();
61/// ```
62#[derive(Debug)]
63pub struct MaskBuilder {
64    map: BTreeMap<u8, (u32, u32)>,
65    defaults: (u32, u32),
66}
67impl Display for MaskBuilder {
68    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69        writeln!(f, "match mask {{")?;
70        for (mask, (x, y)) in self.map.iter() {
71            writeln!(f, "    0b{:08b} => ({}, {}),", mask, x.saturating_sub(1), y.saturating_sub(1))?;
72        }
73        writeln!(f, "    _ => ({}, {}),", self.defaults.0.saturating_sub(1), self.defaults.1.saturating_sub(1))?;
74        writeln!(f, "}}")?;
75        Ok(())
76    }
77}
78
79impl MaskBuilder {
80    /// Create a new mask set
81    pub fn new(x: u32, y: u32) -> MaskBuilder {
82        Self { map: BTreeMap::default(), defaults: (x, y) }
83    }
84    /// Get all of the masks
85    pub fn masks(&self) -> Vec<u8> {
86        self.map.keys().copied().collect()
87    }
88    /// Set mask by bits
89    pub fn has_bits(&mut self, (x, y): (u32, u32), mask: &[u32]) {
90        let s: u32 = mask.iter().map(|i| 2u32.pow(*i)).sum();
91        self.has_mask((x, y), s as u8);
92    }
93    /// Set mask by byte
94    pub fn has_mask(&mut self, (x, y): (u32, u32), mask: u8) {
95        let pop = self.map.insert(mask, (x, y));
96        if let Some((i, j)) = pop {
97            panic!("duplicate mask {}: new {:?}, old {:?}", mask, (x, y), (i, j))
98        }
99    }
100    /// The masks of complete set
101    pub fn complete_set() -> Self {
102        let mut masks = MaskBuilder::new(1, 4);
103        // part1
104        masks.has_bits((1, 1), &[4]);
105        masks.has_bits((2, 1), &[2, 4]);
106        masks.has_bits((3, 1), &[2, 4, 6]);
107        masks.has_bits((4, 1), &[4, 6]);
108        masks.has_bits((1, 2), &[0, 4]);
109        masks.has_bits((2, 2), &[0, 2, 4]);
110        masks.has_bits((3, 2), &[0, 2, 4, 6]);
111        masks.has_bits((4, 2), &[0, 4, 6]);
112        masks.has_bits((1, 3), &[0]);
113        masks.has_bits((2, 3), &[0, 2]);
114        masks.has_bits((3, 3), &[0, 2, 6]);
115        masks.has_bits((4, 3), &[0, 6]);
116        // <default>
117        masks.has_bits((2, 4), &[2]);
118        masks.has_bits((3, 4), &[2, 6]);
119        masks.has_bits((4, 4), &[6]);
120        // part2, &[0, 1, 2, 3, 4, 5, 6, 7]
121        masks.has_bits((5, 1), &[0, 2, 4, 6, 7]);
122        masks.has_bits((6, 1), &[2, 3, 4, 6]);
123        masks.has_bits((7, 1), &[2, 4, 5, 6]);
124        masks.has_bits((8, 1), &[0, 1, 2, 4, 6]);
125        masks.has_bits((5, 2), &[0, 2, 3, 4]);
126        masks.has_bits((6, 2), &[0, 1, 2, 3, 4, 5, 6]);
127        masks.has_bits((7, 2), &[0, 2, 3, 4, 5, 6, 7]);
128        masks.has_bits((8, 2), &[0, 4, 5, 6]);
129        masks.has_bits((5, 3), &[0, 1, 2, 4]);
130        masks.has_bits((6, 3), &[0, 1, 2, 3, 4, 6, 7]);
131        masks.has_bits((7, 3), &[0, 1, 2, 4, 5, 6, 7]);
132        masks.has_bits((8, 3), &[0, 4, 6, 7]);
133        masks.has_bits((5, 4), &[0, 2, 4, 5, 6]);
134        masks.has_bits((6, 4), &[0, 1, 2, 6]);
135        masks.has_bits((7, 4), &[0, 2, 6, 7]);
136        masks.has_bits((8, 4), &[0, 2, 3, 4, 6]);
137        // part3
138        masks.has_bits((9, 1), &[2, 3, 4]);
139        masks.has_bits((10, 1), &[0, 2, 3, 4, 5, 6]);
140        masks.has_bits((11, 1), &[2, 3, 4, 5, 6]);
141        masks.has_bits((12, 1), &[4, 5, 6]);
142        masks.has_bits((9, 2), &[0, 1, 2, 3, 4]);
143        masks.has_bits((10, 2), &[0, 1, 2, 4, 5, 6]);
144        masks.has_bits((11, 2), &[]);
145        masks.has_bits((12, 2), &[0, 2, 4, 5, 6, 7]);
146        masks.has_bits((9, 3), &[0, 1, 2, 3, 4, 6]);
147        masks.has_bits((10, 3), &[0, 1, 2, 3, 4, 5, 6, 7]);
148        masks.has_bits((11, 3), &[0, 2, 3, 4, 6, 7]);
149        masks.has_bits((12, 3), &[0, 4, 5, 6, 7]);
150        masks.has_bits((9, 4), &[0, 1, 2]);
151        masks.has_bits((10, 4), &[0, 1, 2, 6, 7]);
152        masks.has_bits((11, 4), &[0, 1, 2, 4, 6, 7]);
153        masks.has_bits((12, 4), &[0, 6, 7]);
154
155        masks
156    }
157    /// The masks of blob set type A
158    pub fn blob7x7_set() -> Self {
159        let mut masks = MaskBuilder::new(1, 1);
160        // part1
161        // <default>
162        masks.has_mask((2, 1), 4);
163        masks.has_mask((3, 1), 92);
164        masks.has_mask((4, 1), 124);
165        masks.has_mask((5, 1), 116);
166        masks.has_mask((6, 1), 80);
167        // <excess>
168        masks.has_mask((1, 2), 16);
169        masks.has_mask((2, 2), 20);
170        masks.has_mask((3, 2), 87);
171        masks.has_mask((4, 2), 223);
172        masks.has_mask((5, 2), 241);
173        masks.has_mask((6, 2), 21);
174        masks.has_mask((7, 2), 64);
175
176        masks.has_mask((1, 3), 29);
177        masks.has_mask((2, 3), 117);
178        masks.has_mask((3, 3), 85);
179        masks.has_mask((4, 3), 71);
180        masks.has_mask((5, 3), 221);
181        masks.has_mask((6, 3), 125);
182        masks.has_mask((7, 3), 112);
183
184        masks.has_mask((1, 4), 31);
185        masks.has_mask((2, 4), 253);
186        masks.has_mask((3, 4), 113);
187        masks.has_mask((4, 4), 28);
188        masks.has_mask((5, 4), 127);
189        masks.has_mask((6, 4), 247);
190        masks.has_mask((7, 4), 209);
191
192        masks.has_mask((1, 5), 23);
193        masks.has_mask((2, 5), 199);
194        masks.has_mask((3, 5), 213);
195        masks.has_mask((4, 5), 95);
196        masks.has_mask((5, 5), 255);
197        masks.has_mask((6, 5), 245);
198        masks.has_mask((7, 5), 81);
199
200        masks.has_mask((1, 6), 5);
201        masks.has_mask((2, 6), 84);
202        masks.has_mask((3, 6), 93);
203        masks.has_mask((4, 6), 119);
204        masks.has_mask((5, 6), 215);
205        masks.has_mask((6, 6), 193);
206        masks.has_mask((7, 6), 17);
207
208        masks.has_mask((1, 7), 0);
209        masks.has_mask((2, 7), 1);
210        masks.has_mask((3, 7), 7);
211        masks.has_mask((4, 7), 197);
212        masks.has_mask((5, 7), 69);
213        masks.has_mask((6, 7), 68);
214        masks.has_mask((7, 7), 65);
215
216        masks
217    }
218}
219
220/// Convert a 7x7 blob type A tile set to complete set atlas
221pub fn convert_blob7x7a<P>(image: P) -> ImageResult<()>
222where
223    P: AsRef<Path>,
224{
225    let path = image.as_ref().canonicalize()?;
226    let new_name = path.file_stem().and_then(|s| s.to_str()).map(|s| format!("{}-std.png", s)).unwrap();
227    let raw = image::open(image.as_ref())?.to_rgba8();
228    let new = GridCompleteAtlas::from_blob7x7a(&raw, raw.width() / 7, raw.height() / 7);
229    new.save(path.with_file_name(new_name))
230}
231
232/// Convert a 4x4 corner tile set to complete set atlas
233pub fn convert_edge4x4<P>(image: P) -> ImageResult<()>
234where
235    P: AsRef<Path>,
236{
237    let (raw, output) = image_with_new_path(image)?;
238    let new = GridEdgeWang::create(&raw, (0, 0), (raw.width() / 4, raw.height() / 4))?;
239    new.as_complete().save(output)
240}
241
242/// Convert a 4x6 rpg tile set to complete set atlas
243pub fn convert_rpg4x6<P>(image: P) -> ImageResult<()>
244where
245    P: AsRef<Path>,
246{
247    let (raw, output) = image_with_new_path(image)?;
248    let rpg = GridCornerRMVX::create(&raw, (0, 0), (raw.width() / 4, raw.height() / 6))?;
249    rpg.as_complete().save(output)
250}
251
252/// Convert a 6x8 rpg tile set to complete set atlas
253pub fn convert_rpg6x8<P>(image: P) -> ImageResult<()>
254where
255    P: AsRef<Path>,
256{
257    let (raw, output) = image_with_new_path(image)?;
258    let rpg = GridCornerRMXP::create(&raw, (0, 0), (raw.width() / 6, raw.height() / 8))?;
259    rpg.as_complete().save(output)
260}
261
262fn image_with_new_path<P>(image: P) -> ImageResult<(RgbaImage, PathBuf)>
263where
264    P: AsRef<Path>,
265{
266    let path = image.as_ref().canonicalize()?;
267    let raw = image::open(image.as_ref())?.to_rgba8();
268    let new_name = path.file_stem().and_then(|s| s.to_str()).map(|s| format!("{}-std.png", s)).unwrap();
269    let new_path = path.with_file_name(new_name);
270    Ok((raw, new_path))
271}
272
273/// force save image as png
274pub(crate) fn save_as_png<P>(image: &RgbaImage, path: P) -> ImageResult<()>
275where
276    P: AsRef<Path>,
277{
278    image::save_buffer_with_format(path, &image, image.width(), image.height(), ColorType::Rgba8, ImageFormat::Png)
279}
280
281pub(crate) fn parameter_error<T, S: ToString>(message: S) -> ImageResult<T> {
282    Err(ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::Generic(message.to_string()))))
283}
284
285pub(crate) fn io_error<T, S>(message: S, kind: ErrorKind) -> ImageResult<T>
286where
287    S: ToString,
288{
289    Err(ImageError::IoError(Error::new(kind, message.to_string())))
290}
291
292pub(crate) fn check_image_multiple(image: &RgbaImage, width: u32, height: u32) -> ImageResult<()> {
293    let (w, h) = image.dimensions();
294    if w % width != 0 {
295        parameter_error(format!("Image width {} is not a multiple of {}", w, width))?
296    }
297    if h % height != 0 {
298        parameter_error(format!("Image height {} is not a multiple of {}", h, height))?
299    }
300    Ok(())
301}