devela/media/image/sixel/output/
pixel_format.rs

1// devela::media::image::sixel::output::pixel_format
2//
3// TOC
4// - PixelFormat
5//   - bits_per_pixel
6//   - bytes_per_pixel
7//   - required_bytes
8//   - normalize
9// - get_rgb
10// - sixel_helper_compute_depth
11// - expand_rgb
12// - expand_palette
13
14#![allow(clippy::identity_op, reason = "symmetry")]
15
16use crate::{ConstDefault, SixelError, SixelResult};
17
18/// Pixel format type of input image.
19///
20/// # Adaptation
21/// Derived from `pixelFormat` enum in the `libsixel` C library.
22#[repr(u8)]
23#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
24pub enum PixelFormat {
25    /// RGB color 15bpp.
26    RGB555 = 1,
27    /// RGB color 16bpp.
28    RGB565 = 2,
29    /// RGB color 24bpp. (Default)
30    #[default]
31    RGB888 = 3, // for compatibility, the value must be 3.
32    /// BGR color 15bpp.
33    BGR555 = 4,
34    /// BGR color 16bpp.
35    BGR565 = 5,
36    /// BGR color 24bpp.
37    BGR888 = 6,
38    /// ARGB color 32bpp.
39    ARGB8888 = 0x10,
40    /// RGBA color 32bpp.
41    RGBA8888 = 0x11,
42    /// ABGR color 32bpp.
43    ABGR8888 = 0x12,
44    /// BGRA color 32bpp.
45    BGRA8888 = 0x13,
46    /// Grayscale 1bpp.
47    G1 = (1 << 6),
48    /// Grayscale 2bpp.
49    G2 = (1 << 6) | 0x01,
50    /// Grayscale 4bpp.
51    G4 = (1 << 6) | 0x02,
52    /// Grayscale 8bpp.
53    G8 = (1 << 6) | 0x03,
54    /// AG grayscale 16bpp.
55    AG88 = (1 << 6) | 0x13,
56    /// GA grayscale 16bpp.
57    GA88 = (1 << 6) | 0x23,
58    /// Palette 1bpp.
59    PAL1 = (1 << 7),
60    /// Palette 2bpp.
61    PAL2 = (1 << 7) | 0x01,
62    /// Palette 4bpp.
63    PAL4 = (1 << 7) | 0x02,
64    /// Palette 8bpp.
65    PAL8 = (1 << 7) | 0x03,
66}
67#[rustfmt::skip]
68impl ConstDefault for PixelFormat { const DEFAULT: Self = Self::RGB888; }
69
70impl PixelFormat {
71    /// Returns the bits per pixel of the current format.
72    #[rustfmt::skip]
73    pub const fn bits_per_pixel(self) -> usize {
74        match self {
75            PixelFormat::RGB555
76            | PixelFormat::BGR555 => 15,
77            PixelFormat::RGB565
78            | PixelFormat::BGR565
79            | PixelFormat::AG88
80            | PixelFormat::GA88 => 16,
81            PixelFormat::RGB888
82            | PixelFormat::BGR888
83            | PixelFormat::G8
84            | PixelFormat::PAL8 => 24,
85            PixelFormat::ARGB8888
86            | PixelFormat::RGBA8888
87            | PixelFormat::ABGR8888
88            | PixelFormat::BGRA8888 => 32,
89            PixelFormat::G1 | PixelFormat::PAL1 => 1,
90            PixelFormat::G2 | PixelFormat::PAL2 => 2,
91            PixelFormat::G4 | PixelFormat::PAL4 => 4,
92        }
93    }
94
95    /// Returns the bytes per pixel of the current format.
96    pub const fn bytes_per_pixel(self) -> usize {
97        match self {
98            PixelFormat::ARGB8888
99            | PixelFormat::RGBA8888
100            | PixelFormat::ABGR8888
101            | PixelFormat::BGRA8888 => 4,
102            PixelFormat::RGB888 | PixelFormat::BGR888 => 3,
103            PixelFormat::RGB555
104            | PixelFormat::RGB565
105            | PixelFormat::BGR555
106            | PixelFormat::BGR565
107            | PixelFormat::AG88
108            | PixelFormat::GA88 => 2,
109            PixelFormat::G1
110            | PixelFormat::G2
111            | PixelFormat::G4
112            | PixelFormat::G8
113            | PixelFormat::PAL1
114            | PixelFormat::PAL2
115            | PixelFormat::PAL4
116            | PixelFormat::PAL8 => 1,
117        }
118    }
119
120    /// Returns the number of bytes required to store an image of the given dimensions,
121    /// using the current pixel format.
122    pub const fn required_bytes(self, width: i32, height: i32) -> usize {
123        let total_bits = width as usize * height as usize * self.bits_per_pixel();
124        crate::Mem::bytes_from_bits(total_bits)
125    }
126
127    /// returns dst_pixelformat: PixelFormat,
128    pub(crate) fn normalize(
129        self,
130        dst: &mut [u8],
131        src: &[u8],
132        width: i32,
133        height: i32,
134    ) -> SixelResult<PixelFormat> /* height of source image */ {
135        match self {
136            PixelFormat::G8 => {
137                expand_rgb(dst, src, width, height, self, 1);
138                Ok(PixelFormat::RGB888)
139            }
140
141            PixelFormat::RGB565
142            | PixelFormat::RGB555
143            | PixelFormat::BGR565
144            | PixelFormat::BGR555
145            | PixelFormat::GA88
146            | PixelFormat::AG88 => {
147                expand_rgb(dst, src, width, height, self, 2);
148                Ok(PixelFormat::RGB888)
149            }
150
151            PixelFormat::RGB888 | PixelFormat::BGR888 => {
152                expand_rgb(dst, src, width, height, self, 3);
153                Ok(PixelFormat::RGB888)
154            }
155
156            PixelFormat::RGBA8888
157            | PixelFormat::ARGB8888
158            | PixelFormat::BGRA8888
159            | PixelFormat::ABGR8888 => {
160                expand_rgb(dst, src, width, height, self, 4);
161                Ok(PixelFormat::RGB888)
162            }
163
164            PixelFormat::PAL1 | PixelFormat::PAL2 | PixelFormat::PAL4 => {
165                expand_palette(dst, src, width, height, self)?;
166                Ok(PixelFormat::PAL8)
167            }
168
169            PixelFormat::G1 | PixelFormat::G2 | PixelFormat::G4 => {
170                expand_palette(dst, src, width, height, self)?;
171                Ok(PixelFormat::G8)
172            }
173            PixelFormat::PAL8 => {
174                dst[..((width * height) as usize)]
175                    .copy_from_slice(&src[..((width * height) as usize)]);
176                Ok(self)
177            }
178        }
179    }
180}
181
182/// TODO
183#[allow(clippy::identity_op, reason = "symmetry")]
184fn get_rgb(data: &[u8], pixelformat: PixelFormat, depth: usize) -> (u8, u8, u8) {
185    let mut count = 0;
186    let mut pixels: u32 = 0;
187    while count < depth {
188        pixels = data[count] as u32 | (pixels << 8);
189        count += 1;
190    }
191    /*
192        /* TODO: we should swap bytes (only necessary on LSByte first hardware?) */
193    #if SWAP_BYTES
194        if (depth == 2) {
195            low    = pixels & 0xff;
196            high   = (pixels >> 8) & 0xff;
197            pixels = (low << 8) | high;
198        }
199    #endif*/
200    let (r, g, b) = match pixelformat {
201        PixelFormat::RGB555 => {
202            (((pixels >> 10) & 0x1f) << 3, ((pixels >> 5) & 0x1f) << 3, ((pixels >> 0) & 0x1f) << 3)
203        }
204        PixelFormat::RGB565 => {
205            (((pixels >> 11) & 0x1f) << 3, ((pixels >> 5) & 0x3f) << 2, ((pixels >> 0) & 0x1f) << 3)
206        }
207        PixelFormat::RGB888 => ((pixels >> 16) & 0xff, (pixels >> 8) & 0xff, (pixels >> 0) & 0xff),
208        PixelFormat::BGR555 => {
209            (((pixels >> 0) & 0x1f) << 3, ((pixels >> 5) & 0x1f) << 3, ((pixels >> 10) & 0x1f) << 3)
210        }
211        PixelFormat::BGR565 => {
212            (((pixels >> 0) & 0x1f) << 3, ((pixels >> 5) & 0x3f) << 2, ((pixels >> 11) & 0x1f) << 3)
213        }
214        PixelFormat::BGR888 => ((pixels >> 0) & 0xff, (pixels >> 8) & 0xff, (pixels >> 16) & 0xff),
215        PixelFormat::ARGB8888 => {
216            ((pixels >> 16) & 0xff, (pixels >> 8) & 0xff, (pixels >> 0) & 0xff)
217        }
218        PixelFormat::RGBA8888 => {
219            ((pixels >> 24) & 0xff, (pixels >> 16) & 0xff, (pixels >> 8) & 0xff)
220        }
221        PixelFormat::ABGR8888 => {
222            ((pixels >> 0) & 0xff, (pixels >> 8) & 0xff, (pixels >> 16) & 0xff)
223        }
224        PixelFormat::BGRA8888 => {
225            ((pixels >> 8) & 0xff, (pixels >> 16) & 0xff, (pixels >> 24) & 0xff)
226        }
227        PixelFormat::G8 | PixelFormat::AG88 => (pixels & 0xff, pixels & 0xff, pixels & 0xff),
228        PixelFormat::GA88 => ((pixels >> 8) & 0xff, (pixels >> 8) & 0xff, (pixels >> 8) & 0xff),
229        _ => (0, 0, 0),
230    };
231    (r as u8, g as u8, b as u8)
232}
233
234/// TODO
235fn expand_rgb(
236    dst: &mut [u8],
237    src: &[u8],
238    width: i32,
239    height: i32,
240    pixelformat: PixelFormat,
241    depth: usize,
242) {
243    for y in 0..height {
244        for x in 0..width {
245            let src_offset = depth * (y * width + x) as usize;
246            let dst_offset: usize = 3 * (y * width + x) as usize;
247            let (r, g, b) = get_rgb(&src[src_offset..], pixelformat, depth);
248
249            dst[dst_offset + 0] = r;
250            dst[dst_offset + 1] = g;
251            dst[dst_offset + 2] = b;
252        }
253    }
254}
255
256/// TODO
257fn expand_palette(
258    dst: &mut [u8],
259    src: &[u8],
260    width: i32,
261    height: i32,
262    pixelformat: PixelFormat,
263) -> SixelResult<()> {
264    let bpp = match pixelformat {
265        PixelFormat::PAL1 | PixelFormat::G1 => 1,
266
267        PixelFormat::PAL2 | PixelFormat::G2 => 2,
268
269        PixelFormat::PAL4 | PixelFormat::G4 => 4,
270
271        PixelFormat::PAL8 | PixelFormat::G8 => {
272            dst[..((width * height) as usize)].copy_from_slice(&src[..((width * height) as usize)]);
273            return Ok(());
274        }
275
276        //          sixel_helper_set_additional_message(    "expand_palette: invalid pixelformat.");
277        _ => return Err(SixelError::BadArgument),
278    };
279    let mut dst_offset = 0;
280    let mut src_offset = 0;
281
282    let max_x = width * bpp / 8;
283    for _y in 0..height {
284        for _x in 0..max_x {
285            for i in 0..8 / bpp {
286                let shift = ((8 / bpp) - 1 - i) * (bpp & (1 << (bpp - 1)));
287                dst[dst_offset] = ((src[src_offset] as i32) >> shift) as u8;
288                dst_offset += 1;
289            }
290            src_offset += 1;
291        }
292
293        let x = width - max_x * 8 / bpp;
294        if x > 0 {
295            for i in 0..x {
296                dst[dst_offset] = src[src_offset] >> ((8 - (i + 1) * bpp) & ((1 << bpp) - 1));
297                dst_offset += 1;
298            }
299            src_offset += 1;
300        }
301    }
302    Ok(())
303}