ironrdp_graphics/rdp6/bitmap_stream/
decoder.rs1use ironrdp_core::{decode, DecodeError};
2use ironrdp_pdu::bitmap::rdp6::{BitmapStream as BitmapStreamPdu, ColorPlaneDefinition};
3
4use crate::color_conversion::Rgb;
5use crate::rdp6::rle::{decompress_8bpp_plane, RleDecodeError};
6
7#[derive(Debug)]
8pub enum BitmapDecodeError {
9 Decode(DecodeError),
10 Rle(RleDecodeError),
11 InvalidUncompressedDataSize,
12}
13
14impl core::fmt::Display for BitmapDecodeError {
15 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16 match self {
17 BitmapDecodeError::Decode(_error) => write!(f, "failed to decode RDP6 bitmap stream PDU"),
18 BitmapDecodeError::Rle(_error) => {
19 write!(f, "failed to perform RLE decompression of RDP6 bitmap stream")
20 }
21 BitmapDecodeError::InvalidUncompressedDataSize => write!(
22 f,
23 "color plane data size provided in PDU is not sufficient to reconstruct the bitmap"
24 ),
25 }
26 }
27}
28
29impl core::error::Error for BitmapDecodeError {
30 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
31 match self {
32 BitmapDecodeError::Decode(err) => Some(err),
33 BitmapDecodeError::Rle(err) => Some(err),
34 BitmapDecodeError::InvalidUncompressedDataSize => None,
35 }
36 }
37}
38
39impl From<DecodeError> for BitmapDecodeError {
40 fn from(err: DecodeError) -> Self {
41 BitmapDecodeError::Decode(err)
42 }
43}
44
45impl From<RleDecodeError> for BitmapDecodeError {
46 fn from(err: RleDecodeError) -> Self {
47 BitmapDecodeError::Rle(err)
48 }
49}
50
51#[derive(Debug, Default)]
53pub struct BitmapStreamDecoder {
54 planes_buffer: Vec<u8>,
56}
57
58struct BitmapStreamDecoderImpl<'a> {
60 bitmap: BitmapStreamPdu<'a>,
61 image_width: usize,
62 image_height: usize,
63 chroma_width: usize,
64 chroma_height: usize,
65 full_plane_size: usize,
66 chroma_plane_size: usize,
67 uncompressed_planes_size: usize,
68 color_plane_offsets: [usize; 3],
69}
70
71struct AYCoCgParams {
72 color_loss_level: u8,
73 chroma_subsampling: bool,
74 alpha: bool,
75}
76
77impl<'a> BitmapStreamDecoderImpl<'a> {
78 fn init(bitmap: BitmapStreamPdu<'a>, image_width: usize, image_height: usize) -> Self {
79 let (chroma_width, chroma_height) = if bitmap.has_subsampled_chroma() {
80 (image_width.div_ceil(2), image_height.div_ceil(2))
85 } else {
86 (image_width, image_height)
87 };
88
89 let full_plane_size = image_width * image_height;
90 let chroma_plane_size = chroma_width * chroma_height;
91
92 let uncompressed_planes_size = if bitmap.has_subsampled_chroma() {
93 full_plane_size + chroma_plane_size * 2
94 } else {
95 full_plane_size * 3
96 };
97
98 let color_plane_offsets = [0, full_plane_size, full_plane_size + chroma_plane_size];
99
100 Self {
101 bitmap,
102 image_width,
103 image_height,
104 chroma_width,
105 chroma_height,
106 full_plane_size,
107 chroma_plane_size,
108 uncompressed_planes_size,
109 color_plane_offsets,
110 }
111 }
112
113 fn decompress_planes(&'a self, aux_buffer: &'a mut Vec<u8>) -> Result<&'a [u8], BitmapDecodeError> {
114 let planes = if self.bitmap.header.enable_rle_compression {
115 aux_buffer.resize(self.uncompressed_planes_size, 0);
117 let uncompressed_planes_buffer = &mut aux_buffer[..self.uncompressed_planes_size];
118
119 let compressed = self.bitmap.color_panes_data();
120 let mut src_offset = 0;
121
122 if self.bitmap.header.use_alpha {
124 src_offset += decompress_8bpp_plane(
126 &compressed[src_offset..],
127 uncompressed_planes_buffer,
128 self.image_width,
129 self.image_height,
130 )?;
131 }
132
133 src_offset += decompress_8bpp_plane(
135 &compressed[src_offset..],
136 &mut uncompressed_planes_buffer[self.color_plane_offsets[0]..],
137 self.image_width,
138 self.image_height,
139 )?;
140
141 src_offset += decompress_8bpp_plane(
143 &compressed[src_offset..],
144 &mut uncompressed_planes_buffer[self.color_plane_offsets[1]..],
145 self.chroma_width,
146 self.chroma_height,
147 )?;
148
149 decompress_8bpp_plane(
151 &compressed[src_offset..],
152 &mut uncompressed_planes_buffer[self.color_plane_offsets[2]..],
153 self.chroma_width,
154 self.chroma_height,
155 )?;
156
157 &uncompressed_planes_buffer[..self.uncompressed_planes_size]
158 } else {
159 let color_planes_offset = if self.bitmap.header.use_alpha {
161 self.full_plane_size
162 } else {
163 0
164 };
165
166 let expected_data_size = color_planes_offset + self.uncompressed_planes_size;
167
168 if self.bitmap.color_panes_data().len() < expected_data_size {
169 return Err(BitmapDecodeError::InvalidUncompressedDataSize);
170 }
171
172 &self.bitmap.color_panes_data()[color_planes_offset..]
173 };
174
175 Ok(planes)
176 }
177
178 fn write_argb_planes_to_rgb24(&self, planes: &[u8], dst: &mut Vec<u8>) {
179 let (r_offset, g_offset, b_offset) = (
181 self.color_plane_offsets[0],
182 self.color_plane_offsets[1],
183 self.color_plane_offsets[2],
184 );
185
186 let r_plane = &planes[r_offset..r_offset + self.full_plane_size];
187 let g_plane = &planes[g_offset..g_offset + self.full_plane_size];
188 let b_plane = &planes[b_offset..b_offset + self.full_plane_size];
189
190 for i in 0..self.full_plane_size {
191 let (r, g, b) = (r_plane[i], g_plane[i], b_plane[i]);
192
193 dst.extend_from_slice(&[r, g, b]);
194 }
195 }
196
197 fn write_aycocg_planes_to_rgb24(&self, params: AYCoCgParams, planes: &[u8], dst: &mut Vec<u8>) {
198 #![allow(clippy::similar_names, reason = "it’s hard to find better names for co, cg, etc")]
199
200 let sample_shift = usize::from(params.chroma_subsampling);
201
202 let (y_offset, co_offset, cg_offset) = (
203 self.color_plane_offsets[0],
204 self.color_plane_offsets[1],
205 self.color_plane_offsets[2],
206 );
207
208 let y_plane = &planes[y_offset..y_offset + self.full_plane_size];
209 let co_plane = &planes[co_offset..co_offset + self.chroma_plane_size];
210 let cg_plane = &planes[cg_offset..cg_offset + self.chroma_plane_size];
211
212 for (idx, y) in y_plane.iter().copied().enumerate() {
213 let chroma_row = (idx / self.image_width) >> sample_shift;
214 let chroma_col = (idx % self.image_width) >> sample_shift;
215 let chroma_idx = chroma_row * self.chroma_width + chroma_col;
216
217 let co = co_plane[chroma_idx];
218 let cg = cg_plane[chroma_idx];
219
220 let Rgb { r, g, b } = ycocg_with_cll_to_rgb(params.color_loss_level, y, co, cg);
221
222 if params.alpha {
225 dst.extend_from_slice(&[r, g, b]);
226 } else {
227 dst.extend_from_slice(&[b, g, r]);
228 }
229 }
230 }
231
232 fn decode(self, dst: &mut Vec<u8>, aux_buffer: &'a mut Vec<u8>) -> Result<(), BitmapDecodeError> {
233 dst.reserve(self.image_height * self.image_width * 3);
235
236 match self.bitmap.header.color_plane_definition {
237 ColorPlaneDefinition::Argb => {
238 let color_planes = self.decompress_planes(aux_buffer)?;
239 self.write_argb_planes_to_rgb24(color_planes, dst);
240 }
241 ColorPlaneDefinition::AYCoCg {
242 color_loss_level,
243 use_chroma_subsampling,
244 ..
245 } => {
246 let params: AYCoCgParams = AYCoCgParams {
247 color_loss_level,
248 chroma_subsampling: use_chroma_subsampling,
249 alpha: self.bitmap.header.use_alpha,
250 };
251 let color_planes = self.decompress_planes(aux_buffer)?;
252 self.write_aycocg_planes_to_rgb24(params, color_planes, dst);
253 }
254 }
255
256 Ok(())
257 }
258}
259
260fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb {
262 #![allow(clippy::similar_names)] let chroma_shift = cll - 1;
270
271 let clip_i16 =
272 |v: i16| u8::try_from(v.clamp(0, 255)).expect("fits into u8 because the value is clamped to [0..256]");
273
274 let co_signed = (co << chroma_shift).cast_signed();
275 let cg_signed = (cg << chroma_shift).cast_signed();
276
277 let y = i16::from(y);
278 let co = i16::from(co_signed);
279 let cg = i16::from(cg_signed);
280
281 let t = y - cg;
282 let r = clip_i16(t + co);
283 let g = clip_i16(y + cg);
284 let b = clip_i16(t - co);
285
286 Rgb { r, g, b }
287}
288
289impl BitmapStreamDecoder {
290 pub fn decode_bitmap_stream_to_rgb24(
293 &mut self,
294 bitmap_data: &[u8],
295 dst: &mut Vec<u8>,
296 image_width: usize,
297 image_height: usize,
298 ) -> Result<(), BitmapDecodeError> {
299 let bitmap = decode::<BitmapStreamPdu<'_>>(bitmap_data)?;
300
301 let decoder = BitmapStreamDecoderImpl::init(bitmap, image_width, image_height);
302
303 decoder.decode(dst, &mut self.planes_buffer)
304 }
305}