mizu_core/
printer.rs

1use crate::serial::SerialDevice;
2use bitflags::bitflags;
3
4bitflags! {
5    struct PrinterStatus : u8 {
6        const LOW_BATTERY     = 1 << 7;
7        const OTHER_ERR       = 1 << 6;
8        const PAPER_JAM       = 1 << 5;
9        const PACKET_ERR      = 1 << 4;
10        const READY_TO_PRINT  = 1 << 3;
11        const IMAGE_DATA_FULL = 1 << 2;
12        const PRINTING        = 1 << 1;
13        const CHECKSUM_ERR    = 1;
14    }
15}
16
17enum PacketState {
18    Magic1,
19    Magic2,
20    Command,
21    CompressionFlag,
22    DataLengthLow,
23    DataLengthHigh,
24    CommandData,
25    ChecksumLow,
26    ChecksumHigh,
27    AliveIndicator,
28    Status,
29}
30
31#[derive(Default, Debug)]
32struct Packet {
33    command: u8,
34    compression_flag: u8,
35    data_length: u16,
36    data: Vec<u8>,
37    checksum: u16,
38}
39
40impl Packet {
41    fn clear(&mut self) {
42        let _ = std::mem::take(self);
43    }
44
45    fn compute_checksum(&self) -> u16 {
46        let mut sum = 0;
47        sum += self.command as u16;
48        sum += self.compression_flag as u16;
49        sum += self.data_length & 0xFF;
50        sum += self.data_length >> 8;
51        sum += self
52            .data
53            .iter()
54            .map(|&x| x as u16)
55            .fold(0u16, |a, b| a.wrapping_add(b));
56
57        sum
58    }
59}
60
61/// A custom GameBoy [`SerialDevice`] that emulates
62/// how the [GameBoy printer](https://en.wikipedia.org/wiki/Game_Boy_Printer) operated.
63///
64/// This device can be used to display printed images by games, check
65/// a sample implementation in `mizu` frontend crate.
66pub struct Printer {
67    ram: [u8; 0x2000],
68    ram_next_write_pointer: usize,
69    current_packet: Packet,
70    packet_input_state: PacketState,
71    remaining_data_length: u16,
72    byte_to_send: u8,
73    received_byte: u8,
74    status: PrinterStatus,
75    /// In order to print, after sending data packets, the GB must send an empty
76    /// data packet, otherwise the print command will be ignored
77    ready_to_print_next: bool,
78    printing_delay: u8,
79    received_bit_counter: u8,
80
81    /// dynamically sized image buffer that will increase on each row print,
82    /// trying to simulate the paper that the gameboy printer used.
83    image_buffer: Vec<u8>,
84    image_size: (u32, u32),
85}
86
87impl Default for Printer {
88    fn default() -> Self {
89        Self {
90            ram: [0; 0x2000],
91            ram_next_write_pointer: 0,
92            current_packet: Packet::default(),
93            packet_input_state: PacketState::Magic1,
94            remaining_data_length: 0,
95            byte_to_send: 0,
96            received_byte: 0,
97            status: PrinterStatus::empty(),
98            ready_to_print_next: false,
99            printing_delay: 0,
100            received_bit_counter: 0,
101            image_buffer: Vec::new(),
102            image_size: (0, 0),
103        }
104    }
105}
106
107impl Printer {
108    /// Returns the current printer image buffer
109    ///
110    /// The format is in RGB. i.e. 3 bytes per pixel.
111    pub fn get_image_buffer(&self) -> &[u8] {
112        &self.image_buffer
113    }
114
115    /// Returns the current printer image size (width, height)
116    pub fn get_image_size(&self) -> (u32, u32) {
117        self.image_size
118    }
119
120    /// Clear the current image buffer of the printer.
121    pub fn clear_image_buffer(&mut self) {
122        self.image_buffer.clear();
123        self.image_size = (0, 0);
124    }
125}
126
127impl Printer {
128    fn handle_next_byte(&mut self, byte: u8) {
129        match self.packet_input_state {
130            PacketState::Magic1 => {
131                if byte == 0x88 {
132                    self.packet_input_state = PacketState::Magic2;
133                    self.current_packet.clear();
134                }
135            }
136            PacketState::Magic2 => {
137                if byte == 0x33 {
138                    self.packet_input_state = PacketState::Command;
139                } else {
140                    self.packet_input_state = PacketState::Magic1;
141                }
142            }
143            PacketState::Command => {
144                self.current_packet.command = byte;
145                self.packet_input_state = PacketState::CompressionFlag;
146            }
147            PacketState::CompressionFlag => {
148                self.current_packet.compression_flag = byte;
149                self.packet_input_state = PacketState::DataLengthLow;
150            }
151            PacketState::DataLengthLow => {
152                self.current_packet.data_length &= 0xFF00;
153                self.current_packet.data_length |= byte as u16;
154                self.packet_input_state = PacketState::DataLengthHigh;
155            }
156            PacketState::DataLengthHigh => {
157                self.current_packet.data_length &= 0x00FF;
158                self.current_packet.data_length |= (byte as u16) << 8;
159
160                self.remaining_data_length = self.current_packet.data_length;
161                self.packet_input_state = if self.remaining_data_length != 0 {
162                    PacketState::CommandData
163                } else {
164                    PacketState::ChecksumLow
165                };
166            }
167            PacketState::CommandData => {
168                self.current_packet.data.push(byte);
169
170                self.remaining_data_length -= 1;
171
172                if self.remaining_data_length == 0 {
173                    self.packet_input_state = PacketState::ChecksumLow;
174                }
175            }
176            PacketState::ChecksumLow => {
177                self.current_packet.checksum &= 0xFF00;
178                self.current_packet.checksum |= byte as u16;
179                self.packet_input_state = PacketState::ChecksumHigh;
180            }
181
182            PacketState::ChecksumHigh => {
183                self.current_packet.checksum &= 0x00FF;
184                self.current_packet.checksum |= (byte as u16) << 8;
185                self.packet_input_state = PacketState::AliveIndicator;
186
187                // alive
188                self.byte_to_send = 0x81;
189            }
190            PacketState::AliveIndicator => {
191                self.packet_input_state = PacketState::Status;
192
193                self.byte_to_send = self.status.bits();
194                self.process_packet();
195            }
196            PacketState::Status => {
197                // go back to the beginning
198                self.packet_input_state = PacketState::Magic1;
199            }
200        }
201    }
202
203    fn process_packet(&mut self) {
204        if self.current_packet.checksum != self.current_packet.compute_checksum() {
205            self.status |= PrinterStatus::CHECKSUM_ERR;
206            panic!("checksum failed");
207        }
208
209        match self.current_packet.command {
210            1 => {
211                self.ram = [0; 0x2000];
212                self.ram_next_write_pointer = 0;
213                self.status = PrinterStatus::empty();
214                self.ready_to_print_next = false;
215            }
216            2 => {
217                if self.ready_to_print_next {
218                    if self.current_packet.data_length != 4 {
219                        self.status |= PrinterStatus::PACKET_ERR;
220                    } else {
221                        // print done
222                        self.status |= PrinterStatus::PRINTING;
223                        self.status.remove(PrinterStatus::READY_TO_PRINT);
224                        self.printing_delay = 20;
225
226                        let number_of_sheets = self.current_packet.data[0];
227                        let margins = self.current_packet.data[1];
228                        let palette = self.current_packet.data[2];
229                        let exposure = self.current_packet.data[3];
230
231                        self.print(
232                            number_of_sheets,
233                            margins,
234                            palette,
235                            exposure,
236                            self.ram_next_write_pointer,
237                        );
238                    }
239                    self.ready_to_print_next = false;
240                }
241            }
242            4 => {
243                if self.current_packet.data_length == 0 {
244                    self.ready_to_print_next = true;
245                } else {
246                    let start = self.ram_next_write_pointer;
247                    let end = start + self.current_packet.data_length as usize;
248                    self.ram_next_write_pointer = end;
249                    if end > self.ram.len() {
250                        // Should a flag be specified here?
251                        panic!("end is bigger than ram size");
252                    }
253
254                    assert_eq!(
255                        self.current_packet.data.len(),
256                        self.current_packet.data_length as usize
257                    );
258                    self.ram[start..end].copy_from_slice(&self.current_packet.data);
259                }
260
261                self.status |= PrinterStatus::READY_TO_PRINT
262            }
263            0xF => {
264                if self.status.contains(PrinterStatus::PRINTING) {
265                    self.printing_delay = self.printing_delay.saturating_sub(1);
266                    if self.printing_delay == 0 {
267                        self.status.remove(PrinterStatus::PRINTING);
268                    }
269                }
270            }
271            _ => {
272                self.status |= PrinterStatus::PACKET_ERR;
273            }
274        };
275    }
276
277    /// The data in ram are stored as normal tiles, every 16 bytes form one tile
278    /// (8x8). Every 16 * 20 bytes form one tile row (160x8).
279    fn print(
280        &mut self,
281        number_of_sheets: u8,
282        margins: u8,
283        palette: u8,
284        exposure: u8,
285        max_data_len: usize,
286    ) {
287        // line feed only
288        if number_of_sheets == 0 {
289            self.print_line_feed();
290            return;
291        }
292
293        // high nibble
294        let margin_before = margins >> 4;
295        // low nibble
296        let margin_after = margins & 0xF;
297
298        let exposure_multiply = compute_exposure_multiply(exposure);
299
300        // TODO: check if margin count is in pixel units or not, because its
301        //  a bit small, maximum of 15 pixels.
302        for _ in 0..margin_before {
303            self.print_line_feed();
304        }
305
306        let rows_to_print = max_data_len / 40;
307
308        let (_, old_height) = self.image_size;
309        let new_width = 160;
310        let new_height = old_height + rows_to_print as u32;
311
312        self.image_size = (new_width, new_height);
313
314        // reserve space for the rows
315        let old_size = self.image_buffer.len();
316        let extra_space = rows_to_print * 160 * 3;
317        self.image_buffer.reserve(extra_space);
318
319        for y in 0..rows_to_print {
320            for x in 0..20 {
321                let scroll_y = y / 8;
322                let fine_y_scroll = y % 8;
323
324                let tile = scroll_y * 20 + x;
325                let tile_index = tile * 16;
326                let ram_index = tile_index + (fine_y_scroll * 2);
327
328                let low = self.ram[ram_index];
329                let high = self.ram[ram_index + 1];
330
331                let mut result = [0; 8];
332                for (i, result_item) in result.iter_mut().enumerate() {
333                    let bin_i = 7 - i;
334                    *result_item = ((high >> bin_i) & 1) << 1 | ((low >> bin_i) & 1);
335                }
336
337                for pixel in &result {
338                    let color = (palette >> (pixel * 2)) & 0b11;
339
340                    // we use inverted gray shade, because white should not be
341                    // changed due to exposure, only black does
342                    let inverted_gray_shade = 85 * color;
343                    // apply exposure, the multiply value ranges are (0.75 ~ 1.25)
344                    let exposured_invertd_gray_shade =
345                        inverted_gray_shade as f64 * exposure_multiply;
346                    // lastly, just make sure the values do not exceed 255 and
347                    // not negative
348                    let exposured_invertd_gray_shade = exposured_invertd_gray_shade.clamp(0., 255.);
349
350                    // flip to convert to normal gray shade (255 white, 0 black)
351                    let gray_shade = 255 - (exposured_invertd_gray_shade as u8);
352
353                    // RGB
354                    for _ in 0..3 {
355                        self.image_buffer.push(gray_shade);
356                    }
357                }
358            }
359        }
360
361        // we should not exceed the space we have
362        assert_eq!(old_size + extra_space, self.image_buffer.len());
363
364        for _ in 0..margin_after {
365            self.print_line_feed();
366        }
367
368        if number_of_sheets > 1 {
369            // recursively print the next sheet if there is more than one
370            self.print(
371                number_of_sheets - 1,
372                margins,
373                palette,
374                exposure,
375                max_data_len,
376            );
377        }
378    }
379
380    /// prints one row of pixels
381    fn print_line_feed(&mut self) {
382        let (_, old_height) = self.image_size;
383        // add one line
384        self.image_size = (160, old_height + 1);
385
386        // add one row of white
387        self.image_buffer.reserve(160 * 3);
388
389        for _ in 0..(160 * 3) {
390            self.image_buffer.push(255);
391        }
392    }
393}
394
395// util function to map between two number ranges
396fn map_num(inp: i32, inp_start: i32, inp_end: i32, out_start: i32, out_end: i32) -> i32 {
397    ((inp - inp_start) as f64 / (inp_end - inp_start) as f64 * (out_end - out_start) as f64
398        + out_start as f64) as i32
399}
400
401/// maps the 7 bits from exposure to (75% ~ 125%) which is equivalent to
402/// an increase by the range (-25% ~ 25%)
403fn compute_exposure_multiply(exposure: u8) -> f64 {
404    (100 + map_num((exposure & 0x7F) as i32, 0, 0x7F, -25, 25)) as f64 / 100.
405}
406
407#[test]
408fn map_num_test() {
409    let a = map_num(5, 0, 100, 1, 11);
410    assert_eq!(a, 1);
411}
412
413#[test]
414fn compute_exposure_multiply_test() {
415    let mut min = 200f64;
416    let mut max = -200f64;
417
418    for exposure in 0..=0x7F {
419        let a = compute_exposure_multiply(exposure);
420
421        // a should always increase, the first time we are just setting the numbers
422        // so we cannot compare in the first time
423        if exposure != 0 {
424            assert!(a >= min);
425            assert!(a >= max);
426        }
427
428        min = min.min(a);
429        max = max.max(a);
430    }
431
432    // these are the possible ranges of exposure
433    assert_eq!(min, 0.75);
434    assert_eq!(max, 1.25);
435}
436
437impl SerialDevice for Printer {
438    fn exchange_bit_external_clock(&mut self, bit: bool) -> bool {
439        self.received_bit_counter += 1;
440
441        if self.received_bit_counter == 9 {
442            self.handle_next_byte(self.received_byte);
443            self.received_byte = 0;
444            self.received_bit_counter = 1;
445        }
446
447        self.received_byte = self.received_byte.wrapping_shl(1);
448        self.received_byte |= bit as u8;
449
450        let out = self.byte_to_send & 0x80 != 0;
451        self.byte_to_send = self.byte_to_send.wrapping_shl(1);
452
453        out
454    }
455}