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}