1use std::{fmt::Display, io, path::Path};
2
3use image::{GenericImageView, GrayImage, ImageError, ImageReader, Luma};
4use snafu::{Backtrace, Snafu};
5
6use crate::compress::huffman::{NibbleHuffman, NibbleHuffmanCode};
7
8const HUFFMAN: NibbleHuffman = NibbleHuffman {
10 codes: [
11 NibbleHuffmanCode { length: 1, bits: 0b1 },
12 NibbleHuffmanCode { length: 4, bits: 0b0110 },
13 NibbleHuffmanCode { length: 5, bits: 0b01010 },
14 NibbleHuffmanCode { length: 4, bits: 0b0100 },
15 NibbleHuffmanCode { length: 5, bits: 0b00010 },
16 NibbleHuffmanCode { length: 6, bits: 0b011110 },
17 NibbleHuffmanCode { length: 6, bits: 0b010110 },
18 NibbleHuffmanCode { length: 6, bits: 0b000110 },
19 NibbleHuffmanCode { length: 5, bits: 0b00110 },
20 NibbleHuffmanCode { length: 6, bits: 0b011111 },
21 NibbleHuffmanCode { length: 6, bits: 0b010111 },
22 NibbleHuffmanCode { length: 6, bits: 0b000111 },
23 NibbleHuffmanCode { length: 4, bits: 0b0010 },
24 NibbleHuffmanCode { length: 5, bits: 0b01110 },
25 NibbleHuffmanCode { length: 5, bits: 0b00111 },
26 NibbleHuffmanCode { length: 4, bits: 0b0000 },
27 ],
28};
29
30const WIDTH: usize = 104;
31const HEIGHT: usize = 16;
32const SIZE: usize = WIDTH * HEIGHT / 8;
33
34const LOGO_HEADER: u32 = 0x0000d082;
35const LOGO_FOOTER: u32 = 0xfff4c307;
36
37pub struct Logo {
39 pixels: [u8; SIZE],
40}
41
42impl Default for Logo {
43 fn default() -> Self {
44 Self { pixels: [0u8; SIZE] }
45 }
46}
47
48#[derive(Snafu, Debug)]
50pub enum LogoError {
51 #[snafu(display("invalid logo header, expected {expected:08x} but got {actual:08x}:\n{backtrace}"))]
53 InvalidHeader {
54 expected: u32,
56 actual: u32,
58 backtrace: Backtrace,
60 },
61 #[snafu(display("invalid logo footer, expected {expected:08x} but got {actual:08x}:\n{backtrace}"))]
63 InvalidFooter {
64 expected: u32,
66 actual: u32,
68 backtrace: Backtrace,
70 },
71 #[snafu(display("wrong logo size, expected {expected} bytes but got {actual} bytes:\n{backtrace}"))]
73 WrongSize {
74 expected: usize,
76 actual: usize,
78 backtrace: Backtrace,
80 },
81}
82
83#[derive(Snafu, Debug)]
85pub enum LogoLoadError {
86 #[snafu(transparent)]
88 Io {
89 source: io::Error,
91 },
92 #[snafu(transparent)]
94 Image {
95 source: ImageError,
97 },
98 #[snafu(display("logo image contains a pixel at {x},{y} which isn't white or black:\n{backtrace}"))]
100 InvalidColor {
101 x: u32,
103 y: u32,
105 backtrace: Backtrace,
107 },
108 #[snafu(display("logo image must be {expected} pixels but got {actual} pixels:\n{backtrace}"))]
110 ImageSize {
111 expected: ImageSize,
113 actual: ImageSize,
115 backtrace: Backtrace,
117 },
118}
119
120#[derive(Snafu, Debug)]
122pub enum LogoSaveError {
123 #[snafu(transparent)]
125 Image {
126 source: ImageError,
128 },
129}
130
131#[derive(Debug)]
133pub struct ImageSize {
134 pub width: u32,
136 pub height: u32,
138}
139
140impl Display for ImageSize {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 write!(f, "{}x{}", self.width, self.height)
143 }
144}
145
146fn reverse32(data: &mut [u8]) {
147 for i in (0..data.len() & !3).step_by(4) {
148 let value = u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
149 let swapped = value.swap_bytes().to_le_bytes();
150 data[i..i + 4].copy_from_slice(&swapped);
151 }
152}
153
154impl Logo {
155 pub fn save_png<P: AsRef<Path>>(&self, path: P) -> Result<(), LogoSaveError> {
161 let mut image = GrayImage::new(WIDTH as u32, HEIGHT as u32);
162 for y in 0..HEIGHT {
163 for x in 0..WIDTH {
164 let luma = if self.get_pixel(x, y) { 0x00 } else { 0xff };
165 image.put_pixel(x as u32, y as u32, Luma([luma]));
166 }
167 }
168 image.save(path)?;
169 Ok(())
170 }
171
172 pub fn from_png<P: AsRef<Path>>(path: P) -> Result<Self, LogoLoadError> {
178 let image = ImageReader::open(path)?.decode()?;
179 if image.width() != WIDTH as u32 || image.height() != HEIGHT as u32 {
180 ImageSizeSnafu {
181 expected: ImageSize { width: WIDTH as u32, height: HEIGHT as u32 },
182 actual: ImageSize { width: image.width(), height: image.height() },
183 }
184 .fail()?;
185 }
186
187 let mut logo = Logo { pixels: [0; SIZE] };
188 for (x, y, color) in image.pixels() {
189 let [r, g, b, _] = color.0;
190 if (r != 0xff && r != 0x00) || g != r || b != r {
191 return InvalidColorSnafu { x, y }.fail();
192 }
193 logo.set_pixel(x as usize, y as usize, r == 0x00);
194 }
195 Ok(logo)
196 }
197
198 pub fn decompress(data: &[u8]) -> Result<Self, LogoError> {
204 let data = {
205 let mut swapped_data = data.to_owned();
206 reverse32(&mut swapped_data);
207 swapped_data.into_boxed_slice()
208 };
209
210 let mut bytes = [0u8; SIZE + 8];
211 HUFFMAN.decompress_to_slice(&data, &mut bytes);
212
213 let header = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
214 if header != LOGO_HEADER {
215 InvalidHeaderSnafu { expected: LOGO_HEADER, actual: header }.fail()?;
216 }
217
218 let footer = u32::from_le_bytes([
219 bytes[bytes.len() - 4],
220 bytes[bytes.len() - 3],
221 bytes[bytes.len() - 2],
222 bytes[bytes.len() - 1],
223 ]);
224 if footer != LOGO_FOOTER {
225 InvalidFooterSnafu { expected: LOGO_FOOTER, actual: footer }.fail()?;
226 }
227
228 let len = bytes.len();
229 let diff = &mut bytes[4..len - 4];
230 if diff.len() != SIZE {
231 WrongSizeSnafu { expected: SIZE, actual: diff.len() }.fail()?;
232 }
233 HUFFMAN.diff16_to_data(diff);
234
235 let mut logo = Logo::default();
236 logo.load_tiles(diff);
237 Ok(logo)
238 }
239
240 pub fn compress(&self) -> [u8; 0x9c] {
242 let mut diff = [0u8; SIZE + 8];
243 self.store_tiles(&mut diff[4..SIZE + 4]);
244 HUFFMAN.data_to_diff16(&mut diff[4..SIZE + 4]);
245
246 diff[0..4].copy_from_slice(&LOGO_HEADER.to_le_bytes());
247 diff[SIZE + 4..SIZE + 8].copy_from_slice(&LOGO_FOOTER.to_le_bytes());
248
249 let mut bytes = [0u8; 0x9c];
250 HUFFMAN.compress_to_slice(&diff, &mut bytes);
251 reverse32(&mut bytes);
252 bytes
253 }
254
255 fn load_tiles(&mut self, data: &[u8]) {
256 for y in 0..HEIGHT {
257 for x in 0..WIDTH {
258 let index = (y / 8) * WIDTH + (x / 8) * 8 + y % 8;
259 let value = if index >= data.len() {
260 false
261 } else {
262 let offset = x & 7;
263 (data[index] & (1 << offset)) != 0
264 };
265 self.set_pixel(x, y, value);
266 }
267 }
268 }
269
270 fn store_tiles(&self, data: &mut [u8]) {
271 for y in 0..HEIGHT {
272 for x in 0..WIDTH {
273 let bit = 1 << (x & 7);
274 let value = self.get_pixel_value(x, y, bit);
275 let index = (y / 8) * WIDTH + (x / 8) * 8 + y % 8;
276 if index < data.len() {
277 data[index] = (data[index] & !bit) | value
278 };
279 }
280 }
281 }
282
283 pub fn get_pixel(&self, x: usize, y: usize) -> bool {
285 let index = (y * WIDTH + x) / 8;
286 if index >= self.pixels.len() {
287 false
288 } else {
289 let offset = !x & 7;
290 (self.pixels[index] & (1 << offset)) != 0
291 }
292 }
293
294 pub fn set_pixel(&mut self, x: usize, y: usize, value: bool) {
296 let index = (y * WIDTH + x) / 8;
297 if index < self.pixels.len() {
298 let bit = 1 << (!x & 7);
299 let value = if value { bit } else { 0 };
300 self.pixels[index] = (self.pixels[index] & !bit) | value;
301 }
302 }
303
304 fn get_pixel_value(&self, x: usize, y: usize, value: u8) -> u8 {
305 if self.get_pixel(x, y) {
306 value
307 } else {
308 0
309 }
310 }
311
312 fn get_braille_index(&self, x: usize, y: usize) -> u8 {
313 let value = self.get_pixel_value(x, y, 0x80)
314 | self.get_pixel_value(x + 1, y, 0x40)
315 | self.get_pixel_value(x, y + 1, 0x20)
316 | self.get_pixel_value(x + 1, y + 1, 0x10)
317 | self.get_pixel_value(x, y + 2, 0x8)
318 | self.get_pixel_value(x + 1, y + 2, 0x4)
319 | self.get_pixel_value(x, y + 3, 0x2)
320 | self.get_pixel_value(x + 1, y + 3, 0x1);
321 !value
322 }
323}
324
325const BRAILLE: &[char; 256] = &[
331 '⠀', '⢀', '⡀', '⣀', '⠠', '⢠', '⡠', '⣠', '⠄', '⢄', '⡄', '⣄', '⠤', '⢤', '⡤', '⣤', '⠐', '⢐', '⡐', '⣐', '⠰', '⢰', '⡰', '⣰',
332 '⠔', '⢔', '⡔', '⣔', '⠴', '⢴', '⡴', '⣴', '⠂', '⢂', '⡂', '⣂', '⠢', '⢢', '⡢', '⣢', '⠆', '⢆', '⡆', '⣆', '⠦', '⢦', '⡦', '⣦',
333 '⠒', '⢒', '⡒', '⣒', '⠲', '⢲', '⡲', '⣲', '⠖', '⢖', '⡖', '⣖', '⠶', '⢶', '⡶', '⣶', '⠈', '⢈', '⡈', '⣈', '⠨', '⢨', '⡨', '⣨',
334 '⠌', '⢌', '⡌', '⣌', '⠬', '⢬', '⡬', '⣬', '⠘', '⢘', '⡘', '⣘', '⠸', '⢸', '⡸', '⣸', '⠜', '⢜', '⡜', '⣜', '⠼', '⢼', '⡼', '⣼',
335 '⠊', '⢊', '⡊', '⣊', '⠪', '⢪', '⡪', '⣪', '⠎', '⢎', '⡎', '⣎', '⠮', '⢮', '⡮', '⣮', '⠚', '⢚', '⡚', '⣚', '⠺', '⢺', '⡺', '⣺',
336 '⠞', '⢞', '⡞', '⣞', '⠾', '⢾', '⡾', '⣾', '⠁', '⢁', '⡁', '⣁', '⠡', '⢡', '⡡', '⣡', '⠅', '⢅', '⡅', '⣅', '⠥', '⢥', '⡥', '⣥',
337 '⠑', '⢑', '⡑', '⣑', '⠱', '⢱', '⡱', '⣱', '⠕', '⢕', '⡕', '⣕', '⠵', '⢵', '⡵', '⣵', '⠃', '⢃', '⡃', '⣃', '⠣', '⢣', '⡣', '⣣',
338 '⠇', '⢇', '⡇', '⣇', '⠧', '⢧', '⡧', '⣧', '⠓', '⢓', '⡓', '⣓', '⠳', '⢳', '⡳', '⣳', '⠗', '⢗', '⡗', '⣗', '⠷', '⢷', '⡷', '⣷',
339 '⠉', '⢉', '⡉', '⣉', '⠩', '⢩', '⡩', '⣩', '⠍', '⢍', '⡍', '⣍', '⠭', '⢭', '⡭', '⣭', '⠙', '⢙', '⡙', '⣙', '⠹', '⢹', '⡹', '⣹',
340 '⠝', '⢝', '⡝', '⣝', '⠽', '⢽', '⡽', '⣽', '⠋', '⢋', '⡋', '⣋', '⠫', '⢫', '⡫', '⣫', '⠏', '⢏', '⡏', '⣏', '⠯', '⢯', '⡯', '⣯',
341 '⠛', '⢛', '⡛', '⣛', '⠻', '⢻', '⡻', '⣻', '⠟', '⢟', '⡟', '⣟', '⠿', '⢿', '⡿', '⣿',
342];
343
344impl Display for Logo {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 for y in (0..HEIGHT).step_by(4) {
347 if y > 0 {
348 writeln!(f)?;
349 }
350 for x in (0..WIDTH).step_by(2) {
351 let index = self.get_braille_index(x, y) as usize;
352 let ch = BRAILLE.get(index).unwrap_or(&' ');
353 write!(f, "{ch}")?;
354 }
355 }
356
357 Ok(())
358 }
359}