1use crate::palette::Rgb;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Image {
16 pub width: usize,
17 pub height: usize,
18 pub pixels: Vec<u32>,
19}
20
21pub const fn solid(rgb: Rgb) -> u32 {
23 0xFF00_0000 | (rgb & 0x00FF_FFFF)
24}
25
26const MAX_SIZE: usize = 2048;
28
29const DEFAULT: [(u32, u32, u32); 16] = [
31 (0, 0, 0),
32 (20, 20, 80),
33 (80, 13, 13),
34 (20, 80, 20),
35 (80, 20, 80),
36 (20, 80, 80),
37 (80, 80, 20),
38 (53, 53, 53),
39 (26, 26, 26),
40 (33, 33, 60),
41 (60, 26, 26),
42 (33, 60, 33),
43 (60, 33, 60),
44 (33, 60, 60),
45 (60, 60, 33),
46 (80, 80, 80),
47];
48
49fn percent(r: u32, g: u32, b: u32) -> Rgb {
50 let c = |v: u32| v.min(100) * 255 / 100;
51 c(r) << 16 | c(g) << 8 | c(b)
52}
53
54fn hls(h: u32, l: u32, s: u32) -> Rgb {
57 let h = ((h + 240) % 360) as f64;
58 let (l, s) = (l.min(100) as f64 / 100.0, s.min(100) as f64 / 100.0);
59 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
60 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
61 let (r, g, b) = match h as u32 / 60 {
62 0 => (c, x, 0.0),
63 1 => (x, c, 0.0),
64 2 => (0.0, c, x),
65 3 => (0.0, x, c),
66 4 => (x, 0.0, c),
67 _ => (c, 0.0, x),
68 };
69 let m = l - c / 2.0;
70 let v = |f: f64| (((f + m) * 255.0).round().clamp(0.0, 255.0)) as u32;
71 v(r) << 16 | v(g) << 8 | v(b)
72}
73
74fn numbers(data: &[u8]) -> (Vec<u32>, usize) {
77 let mut out = Vec::new();
78 let mut cur: Option<u32> = None;
79 let mut i = 0;
80 while i < data.len() {
81 match data[i] {
82 d @ b'0'..=b'9' => cur = Some(cur.unwrap_or(0).saturating_mul(10).saturating_add((d - b'0') as u32)),
83 b';' => out.push(cur.take().unwrap_or(0)),
84 _ => break,
85 }
86 i += 1;
87 }
88 if let Some(v) = cur {
89 out.push(v);
90 }
91 (out, i)
92}
93
94pub fn decode(params: &[u32], data: &[u8], background: Rgb) -> Image {
98 let p1 = params.first().copied().unwrap_or(0);
99 let transparent = params.get(1).copied() == Some(1);
100 let mut aspect = match p1 {
102 2 => 5,
103 3 | 4 => 3,
104 7..=9 => 1,
105 _ => 2,
106 };
107 let mut palette: [Rgb; 256] = std::array::from_fn(|i| {
108 let (r, g, b) = DEFAULT[i % 16];
109 percent(r, g, b)
110 });
111 let mut color = palette[0];
112 let mut rows: Vec<Vec<u32>> = Vec::new();
113 let (mut x, mut band) = (0usize, 0usize);
114 let (mut width, mut height) = (0usize, 0usize);
115 let mut raster = (0usize, 0usize);
116 let mut drawn = false;
117
118 let mut i = 0;
119 while i < data.len() {
120 let c = data[i];
121 i += 1;
122 match c {
123 b'?'..=b'~' | b'!' => {
124 let (count, bits) = if c == b'!' {
125 let (n, used) = numbers(&data[i..]);
126 i += used;
127 let Some(&ch) = data.get(i).filter(|ch| (b'?'..=b'~').contains(ch)) else { continue };
128 i += 1;
129 (n.first().copied().unwrap_or(1).max(1) as usize, ch - b'?')
130 } else {
131 (1, c - b'?')
132 };
133 drawn = true;
134 let count = count.min(MAX_SIZE.saturating_sub(x));
135 if bits != 0 {
136 for bit in 0..6 {
137 if bits & (1 << bit) == 0 {
138 continue;
139 }
140 for dy in 0..aspect {
141 let y = (band * 6 + bit) * aspect + dy;
142 if y >= MAX_SIZE {
143 continue;
144 }
145 if rows.len() <= y {
146 rows.resize_with(y + 1, Vec::new);
147 }
148 let row = &mut rows[y];
149 if row.len() < x + count {
150 row.resize(x + count, 0);
151 }
152 row[x..x + count].fill(solid(color));
153 }
154 }
155 }
156 x += count;
157 width = width.max(x);
158 height = height.max(((band + 1) * 6 * aspect).min(MAX_SIZE));
159 }
160 b'#' => {
161 let (n, used) = numbers(&data[i..]);
162 i += used;
163 let Some(®) = n.first() else { continue };
164 let reg = (reg as usize).min(255);
165 if n.len() >= 5 {
166 palette[reg] = match n[1] {
167 1 => hls(n[2], n[3], n[4]),
168 _ => percent(n[2], n[3], n[4]),
169 };
170 }
171 color = palette[reg];
172 }
173 b'"' => {
174 let (n, used) = numbers(&data[i..]);
175 i += used;
176 if !drawn {
177 let (pan, pad) = (n.first().copied().unwrap_or(1), n.get(1).copied().unwrap_or(1).max(1));
178 aspect = ((pan as f64 / pad as f64).round() as usize).clamp(1, 10);
179 raster = (
180 n.get(2).copied().unwrap_or(0).min(MAX_SIZE as u32) as usize,
181 n.get(3).copied().unwrap_or(0).min(MAX_SIZE as u32) as usize,
182 );
183 }
184 }
185 b'$' => x = 0,
186 b'-' => {
187 x = 0;
188 band += 1;
189 }
190 _ => {}
191 }
192 }
193
194 if !transparent {
196 width = width.max(raster.0);
197 height = height.max(raster.1);
198 }
199 let fill = if transparent { 0 } else { solid(background) };
200 let mut pixels = vec![fill; width * height];
201 for (y, row) in rows.iter().enumerate().take(height) {
202 for (x, &p) in row.iter().enumerate().take(width) {
203 if p != 0 {
204 pixels[y * width + x] = p;
205 }
206 }
207 }
208 Image { width, height, pixels }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn bands_colors_and_repeats() {
217 let img = decode(&[0, 1], b"#1;2;100;0;0#2;2;0;100;0\"1;1#1~#2!2~-#1~#2!2~", 0);
219 assert_eq!((img.width, img.height), (3, 12));
220 assert_eq!(img.pixels[0], solid(0xFF0000));
221 assert_eq!(img.pixels[1], solid(0x00FF00));
222 assert_eq!(img.pixels[11 * 3 + 2], solid(0x00FF00));
223 }
224
225 #[test]
226 fn passes_and_transparency() {
227 let img = decode(&[0, 1], b"\"1;1#1;2;100;0;0@$#2;2;0;0;100A", 0);
229 assert_eq!(img.pixels[..6], [solid(0xFF0000), solid(0x0000FF), 0, 0, 0, 0]);
230 let img = decode(&[], b"\"1;1;2;6@", 0x123456);
232 assert_eq!((img.width, img.height), (2, 6));
233 assert_eq!(img.pixels[1], solid(0x123456));
234 assert_eq!(img.pixels[0], solid(0));
235 }
236
237 #[test]
238 fn pixel_shape() {
239 let img = decode(&[], b"~", 0);
241 assert_eq!((img.width, img.height), (1, 12));
242 let img = decode(&[9], b"~", 0);
243 assert_eq!(img.height, 6);
244 }
245
246 #[test]
247 fn random_input_is_harmless() {
248 let mut seed = 0x1234_5678u32;
249 let alphabet = b"?@AB~!#$-\";0123456789;;;";
250 for _ in 0..200 {
251 let data: Vec<u8> = (0..2000)
252 .map(|_| {
253 seed ^= seed << 13;
254 seed ^= seed >> 17;
255 seed ^= seed << 5;
256 alphabet[seed as usize % alphabet.len()]
257 })
258 .collect();
259 let img = decode(&[0, 0], &data, 0);
260 assert!(img.width <= MAX_SIZE && img.height <= MAX_SIZE);
261 assert_eq!(img.pixels.len(), img.width * img.height);
262 }
263 let img = decode(&[9], b"\"1;1;99999;99999!999999~", 0);
265 assert_eq!((img.width, img.height), (MAX_SIZE, MAX_SIZE));
266 }
267
268 #[test]
269 fn hls_colors() {
270 assert_eq!(hls(0, 50, 100), 0x0000FF);
271 assert_eq!(hls(120, 50, 100), 0xFF0000);
272 assert_eq!(hls(240, 50, 100), 0x00FF00);
273 assert_eq!(hls(0, 100, 0), 0xFFFFFF);
274 }
275}