ironrdp_graphics/
pointer.rs

1//! This module implements logic to decode pointer PDUs into RGBA bitmaps ready for rendering.
2//!
3//! # References:
4//! - Drawing pointers: <https://learn.microsoft.com/en-us/windows-hardware/drivers/display/pointer-drawing>
5//! - Drawing color pointers: <https://learn.microsoft.com/en-us/windows-hardware/drivers/display/drawing-color-pointers>
6//! - Drawing monochrome pointers <https://learn.microsoft.com/en-us/windows-hardware/drivers/display/drawing-monochrome-pointers>
7//!
8//!
9//! # Notes on xor/and masks encoding:
10//! RDP's pointer representation is a bit weird. It uses two masks to represent a pointer -
11//! andMask and xorMask. Xor mask is used as a base color for a pointer pixel, and andMask
12//! mask is used co control pixel's full transparency (`src_color.a = 0`), full opacity
13//! (`src_color.a = 255`) or pixel inversion (`dst_color.rgb = vec3(255) - dst_color.rgb`).
14//!
15//! Xor basks could be 1, 8, 16, 24 or 32 bits per pixel, and andMask is always 1 bit per pixel.
16//!
17//! Rules for decoding masks:
18//! - `andMask == 0` -> dst_color Copy pixel from xorMask
19//! - andMask == 1, xorMask == 0(black color) -> Transparent pixel
20//! - andMask == 1, xorMask == 1(white color) -> Pixel is inverted
21
22use ironrdp_core::ReadCursor;
23use ironrdp_pdu::pointer::{ColorPointerAttribute, LargePointerAttribute, PointerAttribute};
24
25use crate::color_conversion::rdp_16bit_to_rgb;
26
27const SUPPORTED_COLOR_BPP: [u16; 4] = [1, 16, 24, 32];
28
29#[derive(Debug)]
30pub enum PointerError {
31    InvalidXorMaskSize { expected: usize, actual: usize },
32    InvalidAndMaskSize { expected: usize, actual: usize },
33    NotSupportedBpp { bpp: u16 },
34    Pdu(ironrdp_pdu::PduError),
35}
36
37impl core::fmt::Display for PointerError {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        match self {
40            PointerError::InvalidXorMaskSize { expected, actual } => {
41                write!(
42                    f,
43                    "invalid pointer xorMask size. Expected: {expected}, actual: {actual}"
44                )
45            }
46            PointerError::InvalidAndMaskSize { expected, actual } => {
47                write!(
48                    f,
49                    "invalid pointer andMask size. Expected: {expected}, actual: {actual}"
50                )
51            }
52            PointerError::NotSupportedBpp { bpp } => {
53                write!(f, "not supported pointer bpp: {bpp}")
54            }
55            PointerError::Pdu(err) => err.fmt(f),
56        }
57    }
58}
59
60impl core::error::Error for PointerError {
61    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
62        match self {
63            PointerError::InvalidXorMaskSize { .. } => None,
64            PointerError::InvalidAndMaskSize { .. } => None,
65            PointerError::NotSupportedBpp { .. } => None,
66            PointerError::Pdu(error) => error.source(),
67        }
68    }
69}
70
71impl From<ironrdp_pdu::PduError> for PointerError {
72    fn from(error: ironrdp_pdu::PduError) -> Self {
73        PointerError::Pdu(error)
74    }
75}
76
77/// Represents RDP pointer in decoded form (color channels stored as RGBA pre-multiplied values)
78#[derive(Debug)]
79pub struct DecodedPointer {
80    pub width: u16,
81    pub height: u16,
82    pub hotspot_x: u16,
83    pub hotspot_y: u16,
84    pub bitmap_data: Vec<u8>,
85}
86
87/// Pointer bitmap rendering target. Defines properties and format of the decoded bitmap.
88#[derive(Clone, Copy, Debug)]
89pub enum PointerBitmapTarget {
90    /// Software rendering target will produce RGBA bitmaps with premultiplied alpha.
91    ///
92    /// Colors with alpha channel set to 0x00 are always invisible no matter their color
93    /// component. We could take advantage of that, and use a special color to represent
94    /// inverted pixels. [0xFF, 0xFF, 0xFF, 0x00] is used for such purpose in software
95    /// rendering mode.
96    Software,
97    /// Accelerated rendering target will produce RGBA bitmaps with non-premultiplied alpha.
98    /// Inverted pixels will be rendered following the check pattern.
99    Accelerated,
100}
101
102impl PointerBitmapTarget {
103    fn should_premultiply_alpha(self) -> bool {
104        match self {
105            Self::Software => true,
106            Self::Accelerated => false,
107        }
108    }
109
110    fn should_invert_pixels_using_check_pattern(self) -> bool {
111        match self {
112            Self::Software => false,
113            Self::Accelerated => true,
114        }
115    }
116}
117
118impl DecodedPointer {
119    pub fn new_invisible() -> Self {
120        Self {
121            width: 0,
122            height: 0,
123            bitmap_data: Vec::new(),
124            hotspot_x: 0,
125            hotspot_y: 0,
126        }
127    }
128
129    pub fn decode_pointer_attribute(
130        src: &PointerAttribute<'_>,
131        target: PointerBitmapTarget,
132    ) -> Result<Self, PointerError> {
133        Self::decode_pointer(
134            PointerData {
135                width: src.color_pointer.width,
136                height: src.color_pointer.height,
137                xor_bpp: src.xor_bpp,
138                xor_mask: src.color_pointer.xor_mask,
139                and_mask: src.color_pointer.and_mask,
140                hot_spot_x: src.color_pointer.hot_spot.x,
141                hot_spot_y: src.color_pointer.hot_spot.y,
142            },
143            target,
144        )
145    }
146
147    pub fn decode_color_pointer_attribute(
148        src: &ColorPointerAttribute<'_>,
149        target: PointerBitmapTarget,
150    ) -> Result<Self, PointerError> {
151        Self::decode_pointer(
152            PointerData {
153                width: src.width,
154                height: src.height,
155                xor_bpp: 24,
156                xor_mask: src.xor_mask,
157                and_mask: src.and_mask,
158                hot_spot_x: src.hot_spot.x,
159                hot_spot_y: src.hot_spot.y,
160            },
161            target,
162        )
163    }
164
165    pub fn decode_large_pointer_attribute(
166        src: &LargePointerAttribute<'_>,
167        target: PointerBitmapTarget,
168    ) -> Result<Self, PointerError> {
169        Self::decode_pointer(
170            PointerData {
171                width: src.width,
172                height: src.height,
173                xor_bpp: src.xor_bpp,
174                xor_mask: src.xor_mask,
175                and_mask: src.and_mask,
176                hot_spot_x: src.hot_spot.x,
177                hot_spot_y: src.hot_spot.y,
178            },
179            target,
180        )
181    }
182
183    fn decode_pointer(data: PointerData<'_>, target: PointerBitmapTarget) -> Result<Self, PointerError> {
184        if data.width == 0 || data.height == 0 {
185            return Ok(Self::new_invisible());
186        }
187
188        if !SUPPORTED_COLOR_BPP.contains(&data.xor_bpp) {
189            // 8bpp indexed colors are not supported yet (palette messages are not implemented)
190            // Other unknown bpps are not supported either
191            return Err(PointerError::NotSupportedBpp { bpp: data.xor_bpp });
192        }
193
194        let flip_vertical = data.xor_bpp != 1;
195
196        let and_stride = Stride::from_bits(data.width.into());
197        let xor_stride = Stride::from_bits(usize::from(data.width) * usize::from(data.xor_bpp));
198
199        if data.xor_mask.len() != xor_stride.length * usize::from(data.height) {
200            return Err(PointerError::InvalidXorMaskSize {
201                expected: xor_stride.length * usize::from(data.height),
202                actual: data.xor_mask.len(),
203            });
204        }
205
206        let default_and_mask = vec![0x00; and_stride.length * usize::from(data.height)];
207        let mut and_mask = data.and_mask;
208        if and_mask.is_empty() {
209            and_mask = &default_and_mask;
210        } else if and_mask.len() != and_stride.length * usize::from(data.height) {
211            return Err(PointerError::InvalidAndMaskSize {
212                expected: and_stride.length * usize::from(data.height),
213                actual: data.and_mask.len(),
214            });
215        }
216
217        let mut bitmap_data = Vec::new();
218
219        for row_idx in 0..data.height {
220            // For non-monochrome cursors we read strides from bottom to top
221            let (mut xor_stride_cursor, mut and_stride_cursor) = if flip_vertical {
222                let xor_stride_cursor =
223                    ReadCursor::new(&data.xor_mask[usize::from(data.height - row_idx - 1) * xor_stride.length..]);
224                let and_stride_cursor =
225                    ReadCursor::new(&and_mask[usize::from(data.height - row_idx - 1) * and_stride.length..]);
226                (xor_stride_cursor, and_stride_cursor)
227            } else {
228                let xor_stride_cursor = ReadCursor::new(&data.xor_mask[usize::from(row_idx) * xor_stride.length..]);
229                let and_stride_cursor = ReadCursor::new(&and_mask[usize::from(row_idx) * and_stride.length..]);
230                (xor_stride_cursor, and_stride_cursor)
231            };
232
233            let mut color_reader = ColorStrideReader::new(data.xor_bpp, xor_stride)?;
234            let mut bitmask_reader = BitmaskStrideReader::new(and_stride);
235
236            let compute_inverted_pixel = if target.should_invert_pixels_using_check_pattern() {
237                |row_idx: u16, col_idx: u16| -> [u8; 4] {
238                    // Checkered pattern is used to represent inverted pixels.
239                    if (row_idx + col_idx) % 2 == 0 {
240                        [0xff, 0xff, 0xff, 0xff]
241                    } else {
242                        [0x00, 0x00, 0x00, 0xff]
243                    }
244                }
245            } else {
246                |_, _| [0xFF, 0xFF, 0xFF, 0x00]
247            };
248
249            for col_idx in 0..data.width {
250                let and_bit = bitmask_reader.next_bit(&mut and_stride_cursor);
251                let color = color_reader.next_pixel(&mut xor_stride_cursor);
252
253                if and_bit == 1 && color == [0, 0, 0, 0xff] {
254                    // Force transparent pixel (The only way to get a transparent pixel with
255                    // non-32-bit cursors)
256                    bitmap_data.extend_from_slice(&[0, 0, 0, 0]);
257                } else if and_bit == 1 && color == [0xff, 0xff, 0xff, 0xff] {
258                    // Inverted pixel.
259                    bitmap_data.extend_from_slice(&compute_inverted_pixel(row_idx, col_idx));
260                } else if target.should_premultiply_alpha() {
261                    // Calculate premultiplied alpha via integer arithmetic
262                    let with_premultiplied_alpha = [
263                        u8::try_from((u16::from(color[0]) * u16::from(color[0])) >> 8)
264                            .expect("(u16 >> 8) fits into u8"),
265                        u8::try_from((u16::from(color[1]) * u16::from(color[1])) >> 8)
266                            .expect("(u16 >> 8) fits into u8"),
267                        u8::try_from((u16::from(color[2]) * u16::from(color[2])) >> 8)
268                            .expect("(u16 >> 8) fits into u8"),
269                        color[3],
270                    ];
271                    bitmap_data.extend_from_slice(&with_premultiplied_alpha);
272                } else {
273                    bitmap_data.extend_from_slice(&color);
274                }
275            }
276        }
277
278        Ok(Self {
279            width: data.width,
280            height: data.height,
281            bitmap_data,
282            hotspot_x: data.hot_spot_x,
283            hotspot_y: data.hot_spot_y,
284        })
285    }
286}
287
288#[derive(Clone, Copy)]
289struct Stride {
290    length: usize,
291    data_bytes: usize,
292    padding: usize,
293}
294
295impl Stride {
296    fn from_bits(bits: usize) -> Stride {
297        let length = bit_stride_size_align_u16(bits);
298        let data_bytes = bit_stride_size_align_u8(bits);
299        Stride {
300            length,
301            data_bytes,
302            padding: length - data_bytes,
303        }
304    }
305}
306
307struct BitmaskStrideReader {
308    current_byte: u8,
309    read_bits: usize,
310    read_stide_bytes: usize,
311    stride_data_bytes: usize,
312    stride_padding: usize,
313}
314
315impl BitmaskStrideReader {
316    fn new(stride: Stride) -> Self {
317        Self {
318            current_byte: 0,
319            read_bits: 8,
320            read_stide_bytes: 0,
321            stride_data_bytes: stride.data_bytes,
322            stride_padding: stride.padding,
323        }
324    }
325
326    fn next_bit(&mut self, cursor: &mut ReadCursor<'_>) -> u8 {
327        if self.read_bits == 8 {
328            self.read_bits = 0;
329
330            if self.read_stide_bytes == self.stride_data_bytes {
331                self.read_stide_bytes = 0;
332                cursor.read_slice(self.stride_padding);
333            }
334
335            self.current_byte = cursor.read_u8();
336        }
337
338        let bit = (self.current_byte >> (7 - self.read_bits)) & 1;
339        self.read_bits += 1;
340        bit
341    }
342}
343
344enum ColorStrideReader {
345    Color {
346        /// INVARIANT: `bpp == 16 || bpp == 24 || bpp == 32`
347        bpp: u16,
348        read_stide_bytes: usize,
349        stride_data_bytes: usize,
350        stride_padding: usize,
351    },
352    Bitmask(BitmaskStrideReader),
353}
354
355impl ColorStrideReader {
356    fn new(bpp: u16, stride: Stride) -> Result<Self, PointerError> {
357        Ok(match bpp {
358            1 => Self::Bitmask(BitmaskStrideReader::new(stride)),
359            bpp => Self::Color {
360                bpp: {
361                    // Enforce the bpp == 16 || bpp == 24 || bpp == 32 invariant.
362                    if !SUPPORTED_COLOR_BPP[1..].contains(&bpp) {
363                        return Err(PointerError::NotSupportedBpp { bpp });
364                    }
365
366                    bpp
367                },
368                read_stide_bytes: 0,
369                stride_data_bytes: stride.data_bytes,
370                stride_padding: stride.padding,
371            },
372        })
373    }
374
375    fn next_pixel(&mut self, cursor: &mut ReadCursor<'_>) -> [u8; 4] {
376        match self {
377            ColorStrideReader::Color {
378                bpp,
379                read_stide_bytes,
380                stride_data_bytes,
381                stride_padding,
382            } => {
383                if read_stide_bytes == stride_data_bytes {
384                    *read_stide_bytes = 0;
385                    cursor.read_slice(*stride_padding);
386                }
387
388                match bpp {
389                    16 => {
390                        *read_stide_bytes += 2;
391                        let color_16bit = cursor.read_u16();
392                        let [r, g, b] = rdp_16bit_to_rgb(color_16bit);
393                        [r, g, b, 0xff]
394                    }
395                    24 => {
396                        *read_stide_bytes += 3;
397
398                        let color_24bit = cursor.read_array::<3>();
399                        [color_24bit[2], color_24bit[1], color_24bit[0], 0xff]
400                    }
401                    32 => {
402                        *read_stide_bytes += 4;
403                        let color_32bit = cursor.read_array::<4>();
404                        [color_32bit[2], color_32bit[1], color_32bit[0], color_32bit[3]]
405                    }
406                    _ => unreachable!("per the invariant on self.bpp, this path is unreachable"),
407                }
408            }
409            ColorStrideReader::Bitmask(bitask) => {
410                if bitask.next_bit(cursor) == 1 {
411                    [0xff, 0xff, 0xff, 0xff]
412                } else {
413                    [0, 0, 0, 0xff]
414                }
415            }
416        }
417    }
418}
419
420fn bit_stride_size_align_u8(size_bits: usize) -> usize {
421    size_bits.div_ceil(8)
422}
423
424fn bit_stride_size_align_u16(size_bits: usize) -> usize {
425    size_bits.div_ceil(16) * 2
426}
427
428/// Message-agnostic pointer data.
429struct PointerData<'a> {
430    width: u16,
431    height: u16,
432    xor_bpp: u16,
433    xor_mask: &'a [u8],
434    and_mask: &'a [u8],
435    hot_spot_x: u16,
436    hot_spot_y: u16,
437}