1use std::sync::Arc;
2
3use ironrdp_core::assert_impl;
4use ironrdp_graphics::color_conversion::rdp_16bit_to_rgb;
5use ironrdp_graphics::image_processing::{ImageRegion, ImageRegionMut, PixelFormat};
6use ironrdp_graphics::pointer::DecodedPointer;
7use ironrdp_graphics::rectangle_processing::Region;
8use ironrdp_pdu::geometry::{InclusiveRectangle, Rectangle as _};
9use tracing::trace;
10
11use crate::{custom_err, SessionResult};
12
13const TILE_SIZE: u16 = 64;
14
15pub struct DecodedImage {
16 pixel_format: PixelFormat,
17 data: Vec<u8>,
18
19 pointer_src_rect: InclusiveRectangle,
21 pointer_draw_x: u16,
23 pointer_draw_y: u16,
25
26 pointer_x: u16,
27 pointer_y: u16,
28
29 pointer: Option<Arc<DecodedPointer>>,
30 pointer_backbuffer: Vec<u8>,
32 show_pointer: bool,
34 pointer_visible_on_screen: bool,
36
37 width: u16,
38 height: u16,
39}
40
41assert_impl!(DecodedImage: Send);
42
43impl core::fmt::Debug for DecodedImage {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 f.debug_struct("DecodedImage")
46 .field("pixel_format", &self.pixel_format)
47 .field("data_len", &self.data.len())
48 .field("pointer_src_rect", &self.pointer_src_rect)
49 .field("pointer_draw_x", &self.pointer_draw_x)
50 .field("pointer_draw_y", &self.pointer_draw_y)
51 .field("pointer_x", &self.pointer_x)
52 .field("pointer_y", &self.pointer_y)
53 .field("pointer", &self.pointer)
54 .field("pointer_backbuffer", &self.pointer_backbuffer)
55 .field("show_pointer", &self.show_pointer)
56 .field("pointer_visible_on_screen", &self.pointer_visible_on_screen)
57 .field("width", &self.width)
58 .field("height", &self.height)
59 .finish()
60 }
61}
62
63#[derive(PartialEq, Eq)]
64enum PointerLayer {
65 Background,
66 Pointer,
67}
68
69struct PointerRenderingState {
70 redraw: bool,
71 update_rectangle: InclusiveRectangle,
72}
73
74#[expect(clippy::too_many_arguments)]
75fn copy_cursor_data(
76 from: &[u8],
77 from_pos: (usize, usize),
78 from_stride: usize,
79 to: &mut [u8],
80 to_stride: usize,
81 to_pos: (usize, usize),
82 size: (usize, usize),
83 dst_size: (usize, usize),
84 composite: bool,
85) {
86 const PIXEL_SIZE: usize = 4;
87
88 if to_pos.0 + size.0 > dst_size.0 || to_pos.1 + size.1 > dst_size.1 {
89 return;
91 }
92
93 let (from_x, from_y) = from_pos;
94 let (to_x, to_y) = to_pos;
95 let (width, height) = size;
96
97 for y in 0..height {
98 let from_start = (from_y + y) * from_stride + from_x * PIXEL_SIZE;
99 let to_start = (to_y + y) * to_stride + to_x * PIXEL_SIZE;
100
101 if composite {
102 for pixel in 0..width {
103 let dest_r = to[to_start + pixel * PIXEL_SIZE];
104 let dest_g = to[to_start + pixel * PIXEL_SIZE + 1];
105 let dest_b = to[to_start + pixel * PIXEL_SIZE + 2];
106
107 let src_r = from[from_start + pixel * PIXEL_SIZE];
108 let src_g = from[from_start + pixel * PIXEL_SIZE + 1];
109 let src_b = from[from_start + pixel * PIXEL_SIZE + 2];
110 let src_a = from[from_start + pixel * PIXEL_SIZE + 3];
111
112 if src_a == 0 && src_r == 255 && src_g == 255 && src_b == 255 {
114 to[to_start + pixel * PIXEL_SIZE] = 255 - dest_r;
115 to[to_start + pixel * PIXEL_SIZE + 1] = 255 - dest_g;
116 to[to_start + pixel * PIXEL_SIZE + 2] = 255 - dest_b;
117 to[to_start + pixel * PIXEL_SIZE + 3] = 255;
118 continue;
119 }
120
121 if src_a == 0 {
123 continue;
124 }
125
126 #[expect(clippy::as_conversions, reason = "(u16 >> 8) fits into u8 + hot loop")]
127 {
128 to[to_start + pixel * PIXEL_SIZE] =
130 src_r + ((u16::from(dest_r) * u16::from(255 - src_a)) >> 8) as u8;
131 to[to_start + pixel * PIXEL_SIZE + 1] =
132 src_g + ((u16::from(dest_g) * u16::from(255 - src_a)) >> 8) as u8;
133 to[to_start + pixel * PIXEL_SIZE + 2] =
134 src_b + ((u16::from(dest_b) * u16::from(255 - src_a)) >> 8) as u8;
135 }
137 }
138 } else {
139 to[to_start..to_start + width * PIXEL_SIZE]
140 .copy_from_slice(&from[from_start..from_start + width * PIXEL_SIZE]);
141 }
142 }
143}
144
145impl DecodedImage {
146 pub fn new(pixel_format: PixelFormat, width: u16, height: u16) -> Self {
147 let len = usize::from(width) * usize::from(height) * usize::from(pixel_format.bytes_per_pixel());
148
149 Self {
150 pixel_format,
151 data: vec![0; len],
152 width,
153 height,
154
155 pointer_src_rect: InclusiveRectangle {
156 left: 0,
157 top: 0,
158 right: 0,
159 bottom: 0,
160 },
161 pointer_x: 0,
162 pointer_y: 0,
163 pointer_draw_x: 0,
164 pointer_draw_y: 0,
165 pointer_backbuffer: Vec::new(),
166 pointer: None,
167 show_pointer: false,
168 pointer_visible_on_screen: true,
169 }
170 }
171
172 pub fn pixel_format(&self) -> PixelFormat {
173 self.pixel_format
174 }
175
176 pub fn data(&self) -> &[u8] {
177 &self.data
178 }
179
180 pub fn width(&self) -> u16 {
181 self.width
182 }
183
184 pub fn bytes_per_pixel(&self) -> usize {
185 usize::from(self.pixel_format.bytes_per_pixel())
186 }
187
188 pub fn stride(&self) -> usize {
189 usize::from(self.width) * self.bytes_per_pixel()
190 }
191
192 pub fn data_for_rect(&self, rect: &InclusiveRectangle) -> &[u8] {
193 let start = usize::from(rect.left) * self.bytes_per_pixel() + usize::from(rect.top) * self.stride();
194 let end =
195 start + usize::from(rect.height() - 1) * self.stride() + usize::from(rect.width()) * self.bytes_per_pixel();
196 &self.data[start..end]
197 }
198
199 pub fn height(&self) -> u16 {
200 self.height
201 }
202
203 fn apply_pointer_layer(&mut self, layer: PointerLayer) -> SessionResult<Option<InclusiveRectangle>> {
204 if layer == PointerLayer::Pointer && !self.pointer_visible_on_screen {
207 return Ok(None);
208 }
209
210 if self.data.is_empty() {
211 return Ok(None);
212 }
213
214 let pointer = if let Some(pointer) = &self.pointer {
215 pointer
216 } else {
217 return Ok(None);
218 };
219
220 if self.pointer_src_rect.width() == 0 || self.pointer_src_rect.height() == 0 {
221 return Ok(None);
222 }
223
224 let dest_rect = InclusiveRectangle {
225 left: self.pointer_draw_x,
226 top: self.pointer_draw_y,
227 right: self.pointer_draw_x + self.pointer_src_rect.width() - 1,
228 bottom: self.pointer_draw_y + self.pointer_src_rect.height() - 1,
229 };
230
231 if dest_rect.width() == 0 || dest_rect.height() == 0 {
232 return Ok(None);
233 }
234
235 let pointer_src_rect_width = usize::from(self.pointer_src_rect.width());
236 let pointer_src_rect_height = usize::from(self.pointer_src_rect.height());
237 let pointer_draw_x = usize::from(self.pointer_draw_x);
238 let pointer_draw_y = usize::from(self.pointer_draw_y);
239 let width = usize::from(self.width);
240 let height = usize::from(self.height);
241
242 match &layer {
243 PointerLayer::Background => {
244 if self.pointer_backbuffer.is_empty() {
245 return Ok(None);
247 }
248
249 copy_cursor_data(
250 &self.pointer_backbuffer,
251 (0, 0),
252 pointer_src_rect_width * 4,
253 &mut self.data,
254 width * 4,
255 (pointer_draw_x, pointer_draw_y),
256 (pointer_src_rect_width, pointer_src_rect_height),
257 (width, height),
258 false,
259 );
260 }
261 PointerLayer::Pointer => {
262 let buffer_size = self
264 .pointer_backbuffer
265 .len()
266 .max(pointer_src_rect_width * pointer_src_rect_height * 4);
267 self.pointer_backbuffer.resize(buffer_size, 0);
268
269 copy_cursor_data(
270 &self.data,
271 (pointer_draw_x, pointer_draw_y),
272 width * 4,
273 &mut self.pointer_backbuffer,
274 pointer_src_rect_width * 4,
275 (0, 0),
276 (pointer_src_rect_width, pointer_src_rect_height),
277 (width, height),
278 false,
279 );
280
281 copy_cursor_data(
283 pointer.bitmap_data.as_slice(),
284 (
285 usize::from(self.pointer_src_rect.left),
286 usize::from(self.pointer_src_rect.top),
287 ),
288 usize::from(pointer.width) * 4,
289 &mut self.data,
290 width * 4,
291 (pointer_draw_x, pointer_draw_y),
292 (pointer_src_rect_width, pointer_src_rect_height),
293 (width, height),
294 true,
295 );
296 }
297 }
298
299 Ok(Some(dest_rect))
301 }
302
303 pub(crate) fn show_pointer(&mut self) -> SessionResult<Option<InclusiveRectangle>> {
304 if !self.show_pointer {
305 self.show_pointer = true;
306 self.apply_pointer_layer(PointerLayer::Pointer)
307 } else {
308 Ok(None)
309 }
310 }
311
312 pub(crate) fn hide_pointer(&mut self) -> SessionResult<Option<InclusiveRectangle>> {
313 if self.show_pointer {
314 self.show_pointer = false;
315 self.apply_pointer_layer(PointerLayer::Background)
316 } else {
317 Ok(None)
318 }
319 }
320
321 fn recalculate_pointer_geometry(&mut self) {
322 let x = self.pointer_x;
323 let y = self.pointer_y;
324
325 let pointer = match &self.pointer {
326 Some(pointer) if self.show_pointer => pointer,
327 _ => return,
328 };
329
330 let left_virtual = i32::from(x) - i32::from(pointer.hotspot_x);
331 let top_virtual = i32::from(y) - i32::from(pointer.hotspot_y);
332 let right_virtual = left_virtual + i32::from(pointer.width) - 1;
333 let bottom_virtual = top_virtual + i32::from(pointer.height) - 1;
334
335 let (left, draw_x) = if left_virtual < 0 {
336 (pointer.hotspot_x - x, 0)
338 } else {
339 (0, x - pointer.hotspot_x)
340 };
341
342 let (top, draw_y) = if top_virtual < 0 {
343 (pointer.hotspot_y - y, 0)
345 } else {
346 (0, y - pointer.hotspot_y)
347 };
348
349 let right = if right_virtual >= i32::from(self.width - 1) {
351 if draw_x + 1 >= self.width {
352 self.pointer_visible_on_screen = false;
354 return;
355 } else {
356 self.width - (draw_x + 1)
357 }
358 } else {
359 pointer.width - 1
360 };
361
362 let bottom = if bottom_virtual >= i32::from(self.height - 1) {
364 if (draw_y + 1) >= self.height {
365 self.pointer_visible_on_screen = false;
367 return;
368 } else {
369 self.height - (draw_y + 1)
370 }
371 } else {
372 pointer.height - 1
373 };
374
375 self.pointer_visible_on_screen = true;
376
377 let pointer_src_rect = InclusiveRectangle {
378 left,
379 top,
380 right,
381 bottom,
382 };
383
384 self.pointer_src_rect = pointer_src_rect;
385 self.pointer_draw_x = draw_x;
386 self.pointer_draw_y = draw_y;
387 }
388
389 pub(crate) fn move_pointer(&mut self, x: u16, y: u16) -> SessionResult<Option<InclusiveRectangle>> {
390 self.pointer_x = x;
391 self.pointer_y = y;
392
393 if self.pointer.is_some() && self.show_pointer {
394 let old_rect = self.apply_pointer_layer(PointerLayer::Background)?;
395 self.recalculate_pointer_geometry();
396 let new_rect = self.apply_pointer_layer(PointerLayer::Pointer)?;
397
398 match (old_rect, new_rect) {
399 (None, None) => Ok(None),
400 (None, Some(rect)) => Ok(Some(rect)),
401 (Some(rect), None) => Ok(Some(rect)),
402 (Some(a), Some(b)) => Ok(Some(a.union(&b))),
403 }
404 } else {
405 Ok(None)
406 }
407 }
408
409 pub(crate) fn update_pointer(&mut self, pointer: Arc<DecodedPointer>) -> SessionResult<Option<InclusiveRectangle>> {
410 self.show_pointer = true;
411
412 let old_rect = if self.pointer.is_some() {
414 self.apply_pointer_layer(PointerLayer::Background)?
415 } else {
416 None
417 };
418
419 self.pointer = Some(pointer);
420 self.recalculate_pointer_geometry();
421
422 let new_rect = self.apply_pointer_layer(PointerLayer::Pointer)?;
424
425 match (old_rect, new_rect) {
426 (None, None) => Ok(None),
427 (None, Some(rect)) => Ok(Some(rect)),
428 (Some(rect), None) => Ok(Some(rect)),
429 (Some(a), Some(b)) => Ok(Some(a.union(&b))),
430 }
431 }
432
433 fn is_pointer_redraw_required(&self, update_rectangle: &InclusiveRectangle) -> bool {
434 let pointer_dest_rect = InclusiveRectangle {
435 left: self.pointer_draw_x,
436 top: self.pointer_draw_y,
437 right: self.pointer_draw_x + self.pointer_src_rect.width() - 1,
438 bottom: self.pointer_draw_y + self.pointer_src_rect.height() - 1,
439 };
440
441 update_rectangle.intersect(&pointer_dest_rect).is_some() && self.show_pointer
442 }
443
444 fn pointer_rendering_begin(
447 &mut self,
448 update_rectangle: &InclusiveRectangle,
449 ) -> SessionResult<PointerRenderingState> {
450 if !self.is_pointer_redraw_required(update_rectangle) || self.pointer.is_none() {
451 return Ok(PointerRenderingState {
452 redraw: false,
453 update_rectangle: update_rectangle.clone(),
454 });
455 }
456
457 let state = self
458 .apply_pointer_layer(PointerLayer::Background)?
459 .map(|cursor_erase_rect| PointerRenderingState {
460 redraw: true,
461 update_rectangle: cursor_erase_rect.union(update_rectangle),
462 })
463 .unwrap_or_else(|| PointerRenderingState {
464 redraw: false,
465 update_rectangle: update_rectangle.clone(),
466 });
467
468 Ok(state)
469 }
470
471 fn pointer_rendering_end(
472 &mut self,
473 pointer_rendering_state: PointerRenderingState,
474 ) -> SessionResult<InclusiveRectangle> {
475 if !pointer_rendering_state.redraw {
476 return Ok(pointer_rendering_state.update_rectangle);
477 }
478
479 let update_rectangle = self
480 .apply_pointer_layer(PointerLayer::Pointer)?
481 .map(|pointer_draw_rectangle| pointer_draw_rectangle.union(&pointer_rendering_state.update_rectangle))
482 .unwrap_or_else(|| pointer_rendering_state.update_rectangle);
483
484 Ok(update_rectangle)
485 }
486
487 pub(crate) fn apply_tile(
491 &mut self,
492 tile_output: &[u8],
493 pixel_format: PixelFormat,
494 clipping_rectangles: &Region,
495 update_rectangle: &InclusiveRectangle,
496 ) -> SessionResult<InclusiveRectangle> {
497 trace!("Tile: {:?}", update_rectangle);
498
499 let pointer_rendering_state = self.pointer_rendering_begin(&clipping_rectangles.extents)?;
500
501 let update_region = clipping_rectangles.intersect_rectangle(update_rectangle);
502 for region_rectangle in &update_region.rectangles {
503 let source_x = region_rectangle.left - update_rectangle.left;
504 let source_y = region_rectangle.top - update_rectangle.top;
505 let stride = u16::from(pixel_format.bytes_per_pixel()) * TILE_SIZE;
506 let source_image_region = ImageRegion {
507 region: InclusiveRectangle {
508 left: source_x,
509 top: source_y,
510 right: source_x + region_rectangle.width() - 1,
511 bottom: source_y + region_rectangle.height() - 1,
512 },
513 data: tile_output,
514 step: stride,
515 pixel_format,
516 };
517
518 let mut destination_image_region = ImageRegionMut {
519 region: region_rectangle.clone(),
520 step: self.width() * u16::from(self.pixel_format.bytes_per_pixel()),
521 pixel_format: self.pixel_format,
522 data: &mut self.data,
523 };
524
525 trace!("Source image region: {:?}", source_image_region.region);
526 trace!("Destination image region: {:?}", destination_image_region.region);
527
528 source_image_region
529 .copy_to(&mut destination_image_region)
530 .map_err(|e| custom_err!("copy_to", e))?;
531 }
532
533 let update_rectangle = self.pointer_rendering_end(pointer_rendering_state)?;
534
535 Ok(update_rectangle)
536 }
537
538 pub(crate) fn apply_rgb16_bitmap(
540 &mut self,
541 rgb16: &[u8],
542 update_rectangle: &InclusiveRectangle,
543 ) -> SessionResult<InclusiveRectangle> {
544 const SRC_COLOR_DEPTH: usize = 2;
545 const DST_COLOR_DEPTH: usize = 4;
546
547 let image_width = usize::from(self.width);
548 let rectangle_width = usize::from(update_rectangle.width());
549 let top = usize::from(update_rectangle.top);
550 let left = usize::from(update_rectangle.left);
551
552 let pointer_rendering_state = self.pointer_rendering_begin(update_rectangle)?;
553
554 rgb16
555 .chunks_exact(rectangle_width * SRC_COLOR_DEPTH)
556 .rev()
557 .enumerate()
558 .for_each(|(row_idx, row)| {
559 row.chunks_exact(SRC_COLOR_DEPTH)
560 .enumerate()
561 .for_each(|(col_idx, src_pixel)| {
562 let rgb16_value = u16::from_le_bytes(
563 src_pixel
564 .try_into()
565 .expect("src_pixel contains exactly two u8 elements"),
566 );
567 let dst_idx = ((top + row_idx) * image_width + left + col_idx) * DST_COLOR_DEPTH;
568
569 let [r, g, b] = rdp_16bit_to_rgb(rgb16_value);
570 self.data[dst_idx] = r;
571 self.data[dst_idx + 1] = g;
572 self.data[dst_idx + 2] = b;
573 self.data[dst_idx + 3] = 0xff;
574 })
575 });
576
577 let update_rectangle = self.pointer_rendering_end(pointer_rendering_state)?;
578
579 Ok(update_rectangle)
580 }
581
582 fn apply_rgb24_iter<'a, I>(
584 &mut self,
585 rgb24: I,
586 update_rectangle: &InclusiveRectangle,
587 ) -> SessionResult<InclusiveRectangle>
588 where
589 I: Iterator<Item = &'a [u8]>,
590 {
591 const SRC_COLOR_DEPTH: usize = 3;
592 const DST_COLOR_DEPTH: usize = 4;
593
594 let image_width = usize::from(self.width);
595 let top = usize::from(update_rectangle.top);
596 let left = usize::from(update_rectangle.left);
597
598 let pointer_rendering_state = self.pointer_rendering_begin(update_rectangle)?;
599
600 rgb24.enumerate().for_each(|(row_idx, row)| {
601 row.chunks_exact(SRC_COLOR_DEPTH)
602 .enumerate()
603 .for_each(|(col_idx, src_pixel)| {
604 let dst_idx = ((top + row_idx) * image_width + left + col_idx) * DST_COLOR_DEPTH;
605
606 self.data[dst_idx..dst_idx + SRC_COLOR_DEPTH].copy_from_slice(src_pixel);
608 self.data[dst_idx + 3] = 0xFF;
610 })
611 });
612
613 let update_rectangle = self.pointer_rendering_end(pointer_rendering_state)?;
614
615 Ok(update_rectangle)
616 }
617
618 pub(crate) fn apply_rgb24(
619 &mut self,
620 rgb24: &[u8],
621 update_rectangle: &InclusiveRectangle,
622 flip: bool,
623 ) -> SessionResult<InclusiveRectangle> {
624 const SRC_COLOR_DEPTH: usize = 3;
625 let rectangle_width = usize::from(update_rectangle.width());
626 let lines = rgb24.chunks_exact(rectangle_width * SRC_COLOR_DEPTH);
627 if flip {
628 self.apply_rgb24_iter(lines.rev(), update_rectangle)
629 } else {
630 self.apply_rgb24_iter(lines, update_rectangle)
631 }
632 }
633
634 pub(crate) fn apply_rgb32_bitmap(
636 &mut self,
637 rgb32: &[u8],
638 format: PixelFormat,
639 update_rectangle: &InclusiveRectangle,
640 ) -> SessionResult<InclusiveRectangle> {
641 const SRC_COLOR_DEPTH: usize = 4;
642 const DST_COLOR_DEPTH: usize = 4;
643
644 let image_width = usize::from(self.width);
645 let rectangle_width = usize::from(update_rectangle.width());
646 let top = usize::from(update_rectangle.top);
647 let left = usize::from(update_rectangle.left);
648
649 let pointer_rendering_state = self.pointer_rendering_begin(update_rectangle)?;
650
651 if format == self.pixel_format {
652 rgb32
653 .chunks_exact(rectangle_width * SRC_COLOR_DEPTH)
654 .rev()
655 .enumerate()
656 .for_each(|(row_idx, row)| {
657 row.chunks_exact(SRC_COLOR_DEPTH)
658 .enumerate()
659 .for_each(|(col_idx, src_pixel)| {
660 let dst_idx = ((top + row_idx) * image_width + left + col_idx) * DST_COLOR_DEPTH;
661
662 self.data[dst_idx..dst_idx + SRC_COLOR_DEPTH].copy_from_slice(src_pixel);
663 })
664 });
665 } else {
666 rgb32
667 .chunks_exact(rectangle_width * SRC_COLOR_DEPTH)
668 .rev()
669 .enumerate()
670 .try_for_each(|(row_idx, row)| {
671 row.chunks_exact(SRC_COLOR_DEPTH)
672 .enumerate()
673 .try_for_each(|(col_idx, src_pixel)| {
674 let dst_idx = ((top + row_idx) * image_width + left + col_idx) * DST_COLOR_DEPTH;
675
676 let c = format
677 .read_color(src_pixel)
678 .map_err(|err| custom_err!("read color", err))?;
679 self.data[dst_idx..dst_idx + SRC_COLOR_DEPTH].copy_from_slice(&[c.r, c.g, c.b, c.a]);
680
681 Ok(())
682 })?;
683
684 Ok(())
685 })?;
686 }
687
688 let update_rectangle = self.pointer_rendering_end(pointer_rendering_state)?;
689
690 Ok(update_rectangle)
691 }
692}