1use shuyi_assets::bitmap::Bitmap;
9use shuyi_assets::bitmap::convert::png_pixels;
10use shuyi_assets::bundle::Bundle;
11
12use crate::codec::encode_png;
13
14pub const SCALE: u32 = 2;
16
17pub const GAP: u32 = 8;
19
20pub const GROUND: [u8; 4] = [128, 128, 128, 255];
23
24struct Row<'a> {
27 width: u32,
28 height: u32,
29 rgba: Vec<u8>,
30 bitmap: Bitmap<'a>,
31}
32
33impl Row<'_> {
34 fn height(&self) -> u32 {
36 self.height.max(u32::from(self.bitmap.height())) * SCALE
37 }
38
39 fn width(&self) -> u32 {
41 (self.width + u32::from(self.bitmap.width())) * SCALE + GAP
42 }
43}
44
45pub fn draw(source: &Bundle, pack: &Bundle, paths: &[String]) -> Result<Vec<u8>, String> {
50 let mut rows = Vec::with_capacity(paths.len());
51 for path in paths {
52 let colour = source.bytes(path).ok_or_else(|| format!("no {path}"))?;
53 let (width, height, rgba) =
54 png_pixels(colour).map_err(|e| format!("{}: {e}", source.display_path(path)))?;
55 let one_bit = pack.bytes(path).unwrap_or_default();
56 let bitmap = Bitmap::parse(one_bit).map_err(|e| format!("{path} in the pack: {e}"))?;
57 rows.push(Row {
58 width,
59 height,
60 rgba,
61 bitmap,
62 });
63 }
64 let width = rows.iter().map(Row::width).max().unwrap_or(0) + 2 * GAP;
65 let height = rows.iter().map(|row| row.height() + GAP).sum::<u32>() + GAP;
66 let mut sheet = Sheet {
67 width,
68 pixels: GROUND.repeat(usize::try_from(width * height).unwrap_or(0)),
69 };
70 let mut top = GAP;
71 for row in &rows {
72 for y in 0..row.height {
73 for x in 0..row.width {
74 let at = usize::try_from((y * row.width + x) * 4).unwrap_or(0);
75 let mut pixel = [0; 4];
76 pixel.copy_from_slice(&row.rgba[at..at + 4]);
77 sheet.dot(GAP + x * SCALE, top + y * SCALE, over_ground(pixel));
78 }
79 }
80 let left = GAP + row.width * SCALE + GAP;
81 for y in 0..row.bitmap.height() {
82 for x in 0..row.bitmap.width() {
83 let dot = match row.bitmap.pixel(0, x, y) {
84 Some(true) => [255, 255, 255, 255],
85 Some(false) => [0, 0, 0, 255],
86 None => GROUND,
87 };
88 let (x, y) = (u32::from(x), u32::from(y));
89 sheet.dot(left + x * SCALE, top + y * SCALE, dot);
90 }
91 }
92 top += row.height() + GAP;
93 }
94 encode_png(width, height, &sheet.pixels)
95}
96
97struct Sheet {
99 width: u32,
100 pixels: Vec<u8>,
101}
102
103impl Sheet {
104 fn dot(&mut self, x: u32, y: u32, rgba: [u8; 4]) {
106 for dy in 0..SCALE {
107 for dx in 0..SCALE {
108 let at = usize::try_from(((y + dy) * self.width + x + dx) * 4).unwrap_or(0);
109 if let Some(pixel) = self.pixels.get_mut(at..at + 4) {
110 pixel.copy_from_slice(&rgba);
111 }
112 }
113 }
114 }
115}
116
117fn over_ground(pixel: [u8; 4]) -> [u8; 4] {
119 let alpha = u32::from(pixel[3]);
120 let mix = |channel: usize| {
121 let (colour, ground) = (u32::from(pixel[channel]), u32::from(GROUND[channel]));
122 u8::try_from((colour * alpha + ground * (255 - alpha) + 127) / 255).unwrap_or(u8::MAX)
123 };
124 [mix(0), mix(1), mix(2), 255]
125}
126
127#[cfg(test)]
128mod tests {
129 use shuyi_assets::bitmap::encode;
130 use shuyi_assets::bundle::Layer;
131
132 use super::*;
133
134 fn bundle(name: &'static str, bytes: Vec<u8>) -> Bundle {
136 let file: &'static [u8] = bytes.leak();
137 let files: &'static [(&str, &[u8])] = vec![("a.png", file)].leak();
138 Bundle::new(vec![Layer::table(name, files)])
139 }
140
141 #[test]
142 fn each_picture_is_drawn_beside_its_source_twice_its_size() {
143 let png = encode_png(2, 1, &[255, 0, 0, 255, 255, 255, 255, 128]).unwrap();
145 let one_bit = encode((2, 1), (2, 1), &[&[0]], Some(&[0x80])).unwrap();
147 let source = bundle("assets", png);
148 let pack = bundle("pack", one_bit);
149 let sheet = draw(&source, &pack, &["a.png".to_owned()]).unwrap();
150 let (width, height, rgba) = png_pixels(&sheet).unwrap();
151 assert_eq!((width, height), (GAP + 4 + GAP + 4 + GAP, GAP + 2 + GAP));
153 let at = |x: u32, y: u32| {
154 let i = usize::try_from((y * width + x) * 4).unwrap();
155 [rgba[i], rgba[i + 1], rgba[i + 2], rgba[i + 3]]
156 };
157 assert_eq!(at(0, 0), GROUND);
158 assert_eq!(at(GAP, GAP), [255, 0, 0, 255]);
159 assert_eq!(at(GAP + 1, GAP + 1), [255, 0, 0, 255]);
160 assert_eq!(at(GAP + 2, GAP), [192, 192, 192, 255]);
161 let left = GAP + 4 + GAP;
162 assert_eq!(at(left, GAP), [0, 0, 0, 255]);
163 assert_eq!(at(left + 3, GAP + 1), GROUND);
164 let png = bundle("assets", b"not a png".to_vec());
166 let error = draw(&png, &pack, &["a.png".to_owned()]).unwrap_err();
167 assert!(error.starts_with("assets/a.png: "), "{error}");
168 let error = draw(&source, &source, &["a.png".to_owned()]).unwrap_err();
169 assert!(error.starts_with("a.png in the pack: "), "{error}");
170 let empty = png_pixels(&draw(&source, &pack, &[]).unwrap()).unwrap();
172 assert_eq!((empty.0, empty.1), (2 * GAP, GAP));
173 }
174}