q565/decode/
streaming_no_header.rs

1use crate::{
2    decode::ops::{direct_bigger_diff, direct_small_diff, indexed_diff},
3    utils::hash,
4};
5use byteorder::{ByteOrder, NativeEndian};
6use core::hint::unreachable_unchecked;
7
8#[repr(C)]
9#[derive(Debug, Clone, Copy)]
10pub struct Q565StreamingDecodeContext {
11    state: Q565StreamingDecodeState,
12    prev: u16,
13    arr: [u16; 64],
14}
15
16#[repr(u8)]
17#[derive(Debug, Clone, Copy)]
18enum Q565StreamingDecodeState {
19    Default = 0,
20    LumaOrDiffIndexedByte2(u8),
21    RawRgb565Byte1,
22    RawRgb565Byte2(u8),
23}
24
25impl Default for Q565StreamingDecodeContext {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl Q565StreamingDecodeContext {
32    pub const fn new() -> Self {
33        Self {
34            state: Q565StreamingDecodeState::Default,
35            prev: 0,
36            arr: [0; 64],
37        }
38    }
39
40    /// Decodes a Q565 image into a buffer in a streaming fashion, without the header.
41    ///
42    /// Returns the number of pixels written to the output buffer, if successful. Note that this
43    /// doesn't accumulate over multiple calls. You'll need to keep track of the number of pixels
44    /// written and pass the correct output slice to the next call.
45    ///
46    /// # Safety
47    ///
48    /// This function does not do *any* output bounds checks.
49    ///
50    /// The caller needs to ensure that the input is a valid Q565 image. Any failure to do so
51    /// results in undefined behavior.
52    pub unsafe fn streaming_decode_to_slice_unchecked<B: ByteOrder>(
53        &mut self,
54        input: &[u8],
55        output: &mut [u16],
56    ) -> usize {
57        let mut output_idx = 0;
58        let mut input_idx = 0;
59
60        macro_rules! next {
61            () => {
62                if let Some(&b) = input.get(input_idx) {
63                    input_idx += 1;
64                    b
65                } else {
66                    return output_idx;
67                }
68            };
69        }
70
71        unsafe fn set_pixel<B: ByteOrder>(
72            state: &mut Q565StreamingDecodeContext,
73            pixel: u16,
74            output: &mut [u16],
75            output_idx: &mut usize,
76        ) {
77            state.prev = pixel;
78
79            let mut buf = [0u8; 2];
80            NativeEndian::write_u16(&mut buf, pixel);
81
82            *output.get_unchecked_mut(*output_idx) = B::read_u16(&buf);
83            *output_idx += 1;
84        }
85
86        loop {
87            let byte = next!();
88            let pixel = match self.state {
89                Q565StreamingDecodeState::Default => {
90                    let op = byte >> 6;
91
92                    match op {
93                        0b00 => {
94                            let pixel = *self.arr.get_unchecked(usize::from(byte));
95                            set_pixel::<B>(self, pixel, output, &mut output_idx);
96                            continue;
97                        }
98                        0b01 => {
99                            let pixel = direct_small_diff(self.prev, byte);
100                            set_pixel::<B>(self, pixel, output, &mut output_idx);
101
102                            continue;
103                        }
104                        0b10 => {
105                            self.state = Q565StreamingDecodeState::LumaOrDiffIndexedByte2(byte);
106                            continue;
107                        }
108                        0b11 => {
109                            if byte == 0xFE {
110                                self.state = Q565StreamingDecodeState::RawRgb565Byte1;
111                                continue;
112                            } else if byte != 0xFF {
113                                let count = (byte & 0b0011_1111) + 1;
114                                let count = usize::from(count);
115
116                                let mut buf = [0u8; 2];
117                                NativeEndian::write_u16(&mut buf, self.prev);
118
119                                output
120                                    .get_unchecked_mut(output_idx..)
121                                    .get_unchecked_mut(..count)
122                                    .fill(B::read_u16(&buf));
123                                output_idx += count;
124
125                                continue;
126                            } else {
127                                return output_idx;
128                            }
129                        }
130                        _ => unsafe { unreachable_unchecked() },
131                    }
132                }
133                Q565StreamingDecodeState::LumaOrDiffIndexedByte2(byte1) => {
134                    let op = byte1 >> 5;
135                    match op {
136                        0b100 => direct_bigger_diff(self.prev, byte1, byte),
137                        0b101 => indexed_diff(&self.arr, byte1, byte),
138                        _ => unsafe { unreachable_unchecked() },
139                    }
140                }
141                Q565StreamingDecodeState::RawRgb565Byte1 => {
142                    self.state = Q565StreamingDecodeState::RawRgb565Byte2(byte);
143                    continue;
144                }
145                Q565StreamingDecodeState::RawRgb565Byte2(byte1) => {
146                    u16::from_le_bytes([byte1, byte])
147                }
148            };
149
150            let index = hash(pixel);
151            *self.arr.get_unchecked_mut(usize::from(index)) = pixel;
152            set_pixel::<B>(self, pixel, output, &mut output_idx);
153            self.state = Q565StreamingDecodeState::Default;
154        }
155    }
156}