rfb/
pixel_formats.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4//
5// Copyright 2022 Oxide Computer Company
6
7//! Pixel Formats
8//!
9//! The pixel format data structure is specified in section 7.4 of RFC 6143. The data structure
10//! describes how large a pixel is in bits, how many bits of the pixel are used for describing
11//! color, and how colors are encoded in the pixel: either as a color specification or a color map,
12//! with a color specification being the most common.
13//!
14//! The color specification format describes which bits in the pixel represent each color (red,
15//! green, and blue), the max value of each color, and the endianness of the pixel. The location of
16//! each color is described by a shift, in which the shift represents how many shifts are needed to
17//! get the color value to the least significant bit (that is, how many right shifts are needed).
18//!
19//! For example, consider the 32-bit pixel value 0x01020304 with a depth of 24. Let's say the pixel
20//! format has a red shift of 0, green shift of 8, blue shift of 16, all colors have a max value of
21//! 255, and the host is little-endian. This is the pixel format little-endian xBGR.
22//!
23//! So to get the value of each color, we would do:
24//! - red = (0x01020304 >> 0) & 255 = 0x04
25//! - blue = (0x01020304 >> 8) & 255 = 0x03
26//! - green = (0x01020304 >> 16) & 255 = 0x02
27//!
28//! This is relatively straightforward when considering a single pixel format. But an RFB server
29//! must be able to translate between pixel formats, including translating between hosts of
30//! different endianness. Further, it is convenient to represent pixels using a vector of bytes
31//! instead of a vector of n-bit values, and transformations on pixels are done for groups of bytes
32//! representing a pixel, rather than a single pixel value. But thinking about pixels in this
33//! representation can be tricky, as the pixel format describes shifts, which operate the same on a
34//! value regardless of endianness, but code operating on a byte vector must be endian-aware.
35//!
36//! If we think about the same pixel before as a byte vector, we would have the following
37//! representation: [ 0x04, 0x03, 0x02, 0x01 ]. Note that the bytes are in reverse order from the
38//! value above because the host is little-endian, so the least-significant byte (0x04) is first.
39//!
40//! So to get the value of each color, we would index into the pixel based on the shift. A shift of
41//! 0 indicates the color is at the least significant byte (the first byte, byte 0 for
42//!   little-endian pixels), a shift of 8 is the second least significant byte (1), and so on:
43//! - red = pixel\[0\] & 255 = 0x04
44//! - green = pixel\[1\] & 255 = 0x03
45//! - blue = pixel\[2\] & 255 = 0x02
46//!
47//! Since the RFB server is considering pixels that might be from little-endian or big-endian hosts
48//! though, consider if the same byte vector came from an RGBx big endian pixel. In that case, the
49//! least significant byte is byte 3 and the most significant byte is byte 0. So the color values
50//! for this vector would be:
51//! - red = pixel\[3\] & 255 = 0x01
52//! - green = pixel\[2\] & 255 = 0x02
53//! - blue = pixel\[1\] & 255 = 0x03
54//!
55
56#[derive(Debug, thiserror::Error)]
57pub enum PixelFormatError {
58    #[error("unsupported or unknown fourcc: 0x{0:x}")]
59    UnsupportedFourCc(u32),
60}
61
62///  Utility functions and constants related to fourcc codes.
63///
64/// Fourcc is a 4-byte ASCII code representing a pixel format. For example, the value
65/// 0x34325258 is '42RX' in ASCII (34='4', 32='2', 52='R', and 58='X'). This code maps to the pixel
66/// format 32-bit little-endian xRGB.
67///
68/// A good reference for mapping common fourcc codes to their corresponding pixel formats is the
69/// drm_fourcc.h header file in the linux source code.
70pub mod fourcc {
71    use super::{rgb_888, PixelFormatError};
72    use crate::rfb::{ColorFormat, ColorSpecification, PixelFormat};
73
74    // XXX: it might make sense to turn fourcc values into a type (such as an enum or collection of
75    // enums)
76    pub const FOURCC_XR24: u32 = 0x34325258; // little-endian xRGB, 8:8:8:8
77    pub const FOURCC_RX24: u32 = 0x34325852; // little-endian RGBx, 8:8:8:8
78    pub const FOURCC_BX24: u32 = 0x34325842; // little-endian BGRx, 8:8:8:8
79    pub const FOURCC_XB24: u32 = 0x34324258; // little-endian xBGR, 8:8:8:8
80
81    pub fn fourcc_to_pixel_format(fourcc: u32) -> Result<PixelFormat, PixelFormatError> {
82        match fourcc {
83            // little-endian xRGB
84            FOURCC_XR24 => Ok(PixelFormat {
85                bits_per_pixel: rgb_888::BITS_PER_PIXEL,
86                depth: rgb_888::DEPTH,
87                big_endian: false,
88                color_spec: ColorSpecification::ColorFormat(ColorFormat {
89                    red_max: rgb_888::MAX_VALUE,
90                    green_max: rgb_888::MAX_VALUE,
91                    blue_max: rgb_888::MAX_VALUE,
92                    red_shift: rgb_888::BITS_PER_COLOR * 2,
93                    green_shift: rgb_888::BITS_PER_COLOR * 1,
94                    blue_shift: rgb_888::BITS_PER_COLOR * 0,
95                }),
96            }),
97
98            // little-endian RGBx
99            FOURCC_RX24 => Ok(PixelFormat {
100                bits_per_pixel: rgb_888::BITS_PER_PIXEL,
101                depth: rgb_888::DEPTH,
102                big_endian: false,
103                color_spec: ColorSpecification::ColorFormat(ColorFormat {
104                    red_max: rgb_888::MAX_VALUE,
105                    green_max: rgb_888::MAX_VALUE,
106                    blue_max: rgb_888::MAX_VALUE,
107                    red_shift: rgb_888::BITS_PER_COLOR * 3,
108                    green_shift: rgb_888::BITS_PER_COLOR * 2,
109                    blue_shift: rgb_888::BITS_PER_COLOR * 1,
110                }),
111            }),
112
113            // little-endian BGRx
114            FOURCC_BX24 => Ok(PixelFormat {
115                bits_per_pixel: rgb_888::BITS_PER_PIXEL,
116                depth: rgb_888::DEPTH,
117                big_endian: false,
118                color_spec: ColorSpecification::ColorFormat(ColorFormat {
119                    red_max: rgb_888::MAX_VALUE,
120                    green_max: rgb_888::MAX_VALUE,
121                    blue_max: rgb_888::MAX_VALUE,
122                    red_shift: rgb_888::BITS_PER_COLOR * 1,
123                    green_shift: rgb_888::BITS_PER_COLOR * 2,
124                    blue_shift: rgb_888::BITS_PER_COLOR * 3,
125                }),
126            }),
127
128            // little-endian xBGR
129            FOURCC_XB24 => Ok(PixelFormat {
130                bits_per_pixel: rgb_888::BITS_PER_PIXEL,
131                depth: rgb_888::DEPTH,
132                big_endian: false,
133                color_spec: ColorSpecification::ColorFormat(ColorFormat {
134                    red_max: rgb_888::MAX_VALUE,
135                    green_max: rgb_888::MAX_VALUE,
136                    blue_max: rgb_888::MAX_VALUE,
137                    red_shift: rgb_888::BITS_PER_COLOR * 0,
138                    green_shift: rgb_888::BITS_PER_COLOR * 1,
139                    blue_shift: rgb_888::BITS_PER_COLOR * 2,
140                }),
141            }),
142
143            v => Err(PixelFormatError::UnsupportedFourCc(v)),
144        }
145    }
146}
147
148/// Utility functions for 32-bit RGB pixel formats, with 8-bits used per color.
149pub mod rgb_888 {
150    use crate::rfb::{ColorSpecification, PixelFormat};
151
152    pub const BYTES_PER_PIXEL: usize = 4;
153    pub const BITS_PER_PIXEL: u8 = 32;
154
155    /// Number of bits used for color in a pixel
156    pub const DEPTH: u8 = 24;
157
158    /// Number of bits used for a single color value
159    pub const BITS_PER_COLOR: u8 = 8;
160
161    /// Max value for a given color
162    pub const MAX_VALUE: u16 = 255;
163
164    /// Returns true if a shift as specified in a pixel format is valid for rgb888 formats.
165    pub fn valid_shift(shift: u8) -> bool {
166        shift == 0 || shift == 8 || shift == 16 || shift == 24
167    }
168
169    /// Returns the byte index into a 4-byte pixel vector for a given color shift, accounting for endianness.
170    pub fn color_shift_to_index(shift: u8, big_endian: bool) -> usize {
171        assert!(valid_shift(shift));
172
173        if big_endian {
174            ((DEPTH - shift) / BITS_PER_COLOR) as usize
175        } else {
176            (shift / BITS_PER_COLOR) as usize
177        }
178    }
179
180    /// Returns the index of the unused byte (the only byte not representing R, G, or B).
181    pub fn unused_index(r: usize, g: usize, b: usize) -> usize {
182        (3 + 2 + 1) - r - g - b
183    }
184
185    /// Given a set of red/green/blue shifts from a pixel format and its endianness, determine
186    /// which byte index in a 4-byte vector representing a pixel maps to which color.
187    ///
188    /// For example, for the shifts red=0, green=8, blue=16, and a little-endian format, the
189    /// indices would be red=0, green=1, blue=2, and x=3.
190    pub fn rgbx_index(
191        red_shift: u8,
192        green_shift: u8,
193        blue_shift: u8,
194        big_endian: bool,
195    ) -> (usize, usize, usize, usize) {
196        let r = color_shift_to_index(red_shift, big_endian);
197        let g = color_shift_to_index(green_shift, big_endian);
198        let b = color_shift_to_index(blue_shift, big_endian);
199        let x = unused_index(r, g, b);
200
201        (r, g, b, x)
202    }
203
204    /// Translate between RGB888 formats. The input and output format must both be RGB888.
205    pub fn transform(pixels: &Vec<u8>, input: &PixelFormat, output: &PixelFormat) -> Vec<u8> {
206        assert!(input.is_rgb_888());
207        assert!(output.is_rgb_888());
208
209        //let mut buf = Vec::with_capacity(pixels.len());
210        //buf.resize(pixels.len(), 0x0u8);
211        let mut buf = vec![0; pixels.len()];
212
213        let (ir, ig, ib, ix) = match &input.color_spec {
214            ColorSpecification::ColorFormat(cf) => rgbx_index(
215                cf.red_shift,
216                cf.green_shift,
217                cf.blue_shift,
218                input.big_endian,
219            ),
220            ColorSpecification::ColorMap(_) => {
221                unreachable!();
222            }
223        };
224
225        let (or, og, ob, ox) = match &output.color_spec {
226            ColorSpecification::ColorFormat(cf) => rgbx_index(
227                cf.red_shift,
228                cf.green_shift,
229                cf.blue_shift,
230                output.big_endian,
231            ),
232            ColorSpecification::ColorMap(_) => {
233                unreachable!();
234            }
235        };
236
237        let mut i = 0;
238        while i < pixels.len() {
239            // Get the value for each color from the input...
240            let r = pixels[i + ir];
241            let g = pixels[i + ig];
242            let b = pixels[i + ib];
243            let x = pixels[i + ix];
244
245            // and assign it to the right spot in the output pixel
246            buf[i + or] = r;
247            buf[i + og] = g;
248            buf[i + ob] = b;
249            buf[i + ox] = x;
250
251            i += 4;
252        }
253
254        buf
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use crate::pixel_formats::rgb_888::{color_shift_to_index, rgbx_index};
261
262    use super::{fourcc, rgb_888::transform};
263
264    #[test]
265    fn test_color_shift_to_index() {
266        assert_eq!(color_shift_to_index(0, false), 0);
267        assert_eq!(color_shift_to_index(8, false), 1);
268        assert_eq!(color_shift_to_index(16, false), 2);
269        assert_eq!(color_shift_to_index(24, false), 3);
270
271        assert_eq!(color_shift_to_index(0, true), 3);
272        assert_eq!(color_shift_to_index(8, true), 2);
273        assert_eq!(color_shift_to_index(16, true), 1);
274        assert_eq!(color_shift_to_index(24, true), 0);
275    }
276
277    #[test]
278    fn test_rgbx_index() {
279        assert_eq!(rgbx_index(0, 8, 16, false), (0, 1, 2, 3));
280        assert_eq!(rgbx_index(0, 8, 16, true), (3, 2, 1, 0));
281
282        assert_eq!(rgbx_index(8, 16, 24, false), (1, 2, 3, 0));
283        assert_eq!(rgbx_index(8, 16, 24, true), (2, 1, 0, 3));
284
285        assert_eq!(rgbx_index(0, 16, 24, false), (0, 2, 3, 1));
286        assert_eq!(rgbx_index(0, 16, 24, true), (3, 1, 0, 2));
287
288        assert_eq!(rgbx_index(8, 16, 24, false), (1, 2, 3, 0));
289        assert_eq!(rgbx_index(8, 16, 24, true), (2, 1, 0, 3));
290
291        assert_eq!(rgbx_index(0, 24, 8, false), (0, 3, 1, 2));
292        assert_eq!(rgbx_index(0, 24, 8, true), (3, 0, 2, 1));
293    }
294
295    #[test]
296    fn test_rgb888_transform() {
297        let pixels = vec![0u8, 1u8, 2u8, 3u8];
298
299        // little-endian xRGB
300        let xrgb_le = fourcc::fourcc_to_pixel_format(fourcc::FOURCC_XR24).unwrap();
301
302        // little-endian RGBx
303        let rgbx_le = fourcc::fourcc_to_pixel_format(fourcc::FOURCC_RX24).unwrap();
304
305        // little-endian BGRx
306        let bgrx_le = fourcc::fourcc_to_pixel_format(fourcc::FOURCC_BX24).unwrap();
307
308        // little-endian xBGR
309        let xbgr_le = fourcc::fourcc_to_pixel_format(fourcc::FOURCC_XB24).unwrap();
310
311        // same pixel format
312        assert_eq!(transform(&pixels, &xrgb_le, &xrgb_le), pixels);
313        assert_eq!(transform(&pixels, &rgbx_le, &rgbx_le), pixels);
314        assert_eq!(transform(&pixels, &bgrx_le, &bgrx_le), pixels);
315        assert_eq!(transform(&pixels, &xbgr_le, &xbgr_le), pixels);
316
317        // little-endian xRGB -> little-endian RGBx
318        //  B  G  R  x            x  B  G  R
319        // [0, 1, 2, 3]       -> [3, 0, 1, 2]
320        let p2 = vec![3u8, 0u8, 1u8, 2u8];
321        assert_eq!(transform(&pixels, &xrgb_le, &rgbx_le), p2);
322
323        // little-endian RGBx -> little-endian xRGB
324        //  x  B  G  R            B  G  R  x
325        // [0, 1, 2, 3]       -> [1, 2, 3, 0]
326        let p3 = vec![1u8, 2u8, 3u8, 0u8];
327        assert_eq!(transform(&pixels, &rgbx_le, &xrgb_le), p3);
328
329        // little-endian xRGB -> little-endian BGRx
330        //  B  G  R  x            x  R  G  B
331        // [0, 1, 2, 3]       -> [3, 2, 1, 0]
332        let p4 = vec![3u8, 2u8, 1u8, 0u8];
333        assert_eq!(transform(&pixels, &xrgb_le, &bgrx_le), p4);
334        // little-endian BGRx -> little-endian xRGB
335        //  x  R  G  B            B  G  R  x
336        // [0, 1, 2, 3]       -> [3, 2, 1, 0]
337        assert_eq!(transform(&pixels, &bgrx_le, &xrgb_le), p4);
338
339        // little-endian BGRx -> little-endian xBGR
340        //  x  R  G  B            R  G  B  x
341        // [0, 1, 2, 3]       -> [1, 2, 3, 0]
342        let p5 = vec![1u8, 2u8, 3u8, 0u8];
343        assert_eq!(transform(&pixels, &bgrx_le, &xbgr_le), p5);
344    }
345}