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