From 87df67fdc76ff4f39d4b83521e34bf3b5e2e73bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 5 Mar 2025 15:16:31 +0400 Subject: [PATCH] feat: add QOIZ image codec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new QOIZ codec (UUID 229cc6dc-a860-4b52-b4d8-053a22b3892b) for SetSurface command. The PDU data contains the same data as the QOI codec, with zstd compression. Some benchmarks showing interesting results (using ironrdp/perfenc) QOI: 10s user CPU, 96.20% compression QOIZ: 11s user CPU, 99.76% compression Signed-off-by: Marc-André Lureau --- Cargo.lock | 21 ++++ benches/Cargo.toml | 3 +- benches/src/perfenc.rs | 8 +- crates/ironrdp-client/Cargo.toml | 1 + crates/ironrdp-connector/Cargo.toml | 1 + crates/ironrdp-pdu/Cargo.toml | 1 + crates/ironrdp-pdu/src/rdp/capability_sets.rs | 2 +- .../src/rdp/capability_sets/bitmap_codecs.rs | 41 +++++++ crates/ironrdp-server/Cargo.toml | 4 +- crates/ironrdp-server/src/encoder/mod.rs | 114 +++++++++++++++--- crates/ironrdp-server/src/server.rs | 12 ++ crates/ironrdp-session/Cargo.toml | 2 + crates/ironrdp-session/src/fast_path.rs | 66 ++++++++-- crates/ironrdp-web/Cargo.toml | 1 + crates/ironrdp/Cargo.toml | 1 + 15 files changed, 243 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12028e37..3b432e69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2723,6 +2723,7 @@ dependencies = [ "tracing", "visibility", "x509-cert", + "zstd-safe", ] [[package]] @@ -2739,6 +2740,7 @@ dependencies = [ "ironrdp-svc", "qoicoubeh", "tracing", + "zstd-safe", ] [[package]] @@ -6971,3 +6973,22 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/benches/Cargo.toml b/benches/Cargo.toml index c2cbf966..17bcd94b 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -10,8 +10,9 @@ name = "perfenc" path = "src/perfenc.rs" [features] -default = ["qoi"] +default = ["qoi", "qoiz"] qoi = ["ironrdp/qoi"] +qoiz = ["ironrdp/qoiz"] [dependencies] anyhow = "1.0.98" diff --git a/benches/src/perfenc.rs b/benches/src/perfenc.rs index 6b57560c..b14dc5bc 100644 --- a/benches/src/perfenc.rs +++ b/benches/src/perfenc.rs @@ -28,7 +28,7 @@ async fn main() -> Result<(), anyhow::Error> { println!(" --width Width of the display (default: 3840)"); println!(" --height Height of the display (default: 2400)"); println!(" --codec Codec to use (default: remotefx)"); - println!(" Valid values: qoi, remotefx, bitmap, none"); + println!(" Valid values: qoi, qoiz, remotefx, bitmap, none"); println!(" --fps Frames per second (default: none)"); std::process::exit(0); } @@ -54,6 +54,8 @@ async fn main() -> Result<(), anyhow::Error> { OptCodec::None => {} #[cfg(feature = "qoi")] OptCodec::Qoi => update_codecs.set_qoi(Some(0)), + #[cfg(feature = "qoiz")] + OptCodec::QoiZ => update_codecs.set_qoiz(Some(0)), }; let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, update_codecs); @@ -176,6 +178,8 @@ enum OptCodec { None, #[cfg(feature = "qoi")] Qoi, + #[cfg(feature = "qoiz")] + QoiZ, } impl Default for OptCodec { @@ -194,6 +198,8 @@ impl core::str::FromStr for OptCodec { "none" => Ok(Self::None), #[cfg(feature = "qoi")] "qoi" => Ok(Self::Qoi), + #[cfg(feature = "qoiz")] + "qoiz" => Ok(Self::QoiZ), _ => Err(anyhow::anyhow!("unknown codec: {}", s)), } } diff --git a/crates/ironrdp-client/Cargo.toml b/crates/ironrdp-client/Cargo.toml index a09adfe3..2dca9052 100644 --- a/crates/ironrdp-client/Cargo.toml +++ b/crates/ironrdp-client/Cargo.toml @@ -28,6 +28,7 @@ default = ["rustls"] rustls = ["ironrdp-tls/rustls", "tokio-tungstenite/rustls-tls-native-roots"] native-tls = ["ironrdp-tls/native-tls", "tokio-tungstenite/native-tls"] qoi = ["ironrdp/qoi"] +qoiz = ["ironrdp/qoiz"] [dependencies] # Protocols diff --git a/crates/ironrdp-connector/Cargo.toml b/crates/ironrdp-connector/Cargo.toml index 3edb6990..caf5d817 100644 --- a/crates/ironrdp-connector/Cargo.toml +++ b/crates/ironrdp-connector/Cargo.toml @@ -19,6 +19,7 @@ test = false default = [] arbitrary = ["dep:arbitrary"] qoi = ["ironrdp-pdu/qoi"] +qoiz = ["ironrdp-pdu/qoiz"] [dependencies] ironrdp-svc = { path = "../ironrdp-svc", version = "0.4" } # public diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index a72336c7..241a2a16 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -20,6 +20,7 @@ default = [] std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"] alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"] qoi = [] +qoiz = ["qoi"] [dependencies] bitflags = "2.9" diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets.rs b/crates/ironrdp-pdu/src/rdp/capability_sets.rs index cad2a83f..8419f697 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets.rs @@ -34,7 +34,7 @@ pub use self::bitmap_cache::{ pub use self::bitmap_codecs::{ client_codecs_capabilities, server_codecs_capabilities, BitmapCodecs, CaptureFlags, Codec, CodecId, CodecProperty, EntropyBits, Guid, NsCodec, RemoteFxContainer, RfxCaps, RfxCapset, RfxClientCapsContainer, RfxICap, RfxICapFlags, - CODEC_ID_NONE, CODEC_ID_QOI, CODEC_ID_REMOTEFX, + CODEC_ID_NONE, CODEC_ID_QOI, CODEC_ID_QOIZ, CODEC_ID_REMOTEFX, }; pub use self::brush::{Brush, SupportLevel}; pub use self::frame_acknowledge::FrameAcknowledge; diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs index 807970ad..720d00bf 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs @@ -43,6 +43,9 @@ const GUID_IGNORE: Guid = Guid(0x9c43_51a6, 0x3535, 0x42ae, 0x91, 0x0c, 0xcd, 0x #[rustfmt::skip] #[cfg(feature="qoi")] const GUID_QOI: Guid = Guid(0x4dae_9af8, 0xb399, 0x4df6, 0xb4, 0x3a, 0x66, 0x2f, 0xd9, 0xc0, 0xf5, 0xd6); +#[rustfmt::skip] +#[cfg(feature="qoiz")] +const GUID_QOIZ: Guid = Guid(0x229c_c6dc, 0xa860, 0x4b52, 0xb4, 0xd8, 0x05, 0x3a, 0x22, 0xb3, 0x89, 0x2b); #[derive(Debug, PartialEq, Eq)] pub struct Guid(u32, u16, u16, u8, u8, u8, u8, u8, u8, u8, u8); @@ -172,6 +175,8 @@ impl Encode for Codec { CodecProperty::Ignore => GUID_IGNORE, #[cfg(feature = "qoi")] CodecProperty::Qoi => GUID_QOI, + #[cfg(feature = "qoiz")] + CodecProperty::QoiZ => GUID_QOIZ, _ => return Err(other_err!("invalid codec")), }; guid.encode(dst)?; @@ -211,6 +216,8 @@ impl Encode for Codec { } #[cfg(feature = "qoi")] CodecProperty::Qoi => dst.write_u16(0), + #[cfg(feature = "qoiz")] + CodecProperty::QoiZ => dst.write_u16(0), CodecProperty::Ignore => dst.write_u16(0), CodecProperty::None => dst.write_u16(0), }; @@ -236,6 +243,8 @@ impl Encode for Codec { }, #[cfg(feature = "qoi")] CodecProperty::Qoi => 0, + #[cfg(feature = "qoiz")] + CodecProperty::QoiZ => 0, CodecProperty::Ignore => 0, CodecProperty::None => 0, } @@ -280,6 +289,13 @@ impl<'de> Decode<'de> for Codec { } CodecProperty::Qoi } + #[cfg(feature = "qoiz")] + GUID_QOIZ => { + if !property_buffer.is_empty() { + return Err(invalid_field_err!("qoi property", "must be empty")); + } + CodecProperty::QoiZ + } _ => CodecProperty::None, }; @@ -301,6 +317,8 @@ pub enum CodecProperty { Ignore, #[cfg(feature = "qoi")] Qoi, + #[cfg(feature = "qoiz")] + QoiZ, None, } @@ -639,6 +657,7 @@ pub struct CodecId(u8); pub const CODEC_ID_NONE: CodecId = CodecId(0); pub const CODEC_ID_REMOTEFX: CodecId = CodecId(3); pub const CODEC_ID_QOI: CodecId = CodecId(0x0A); +pub const CODEC_ID_QOIZ: CodecId = CodecId(0x0B); impl Debug for CodecId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -646,6 +665,7 @@ impl Debug for CodecId { 0 => "None", 3 => "RemoteFx", 0x0A => "QOI", + 0x0B => "QOIZ", _ => "unknown", }; write!(f, "CodecId({name})") @@ -658,6 +678,7 @@ impl CodecId { 0 => Some(CODEC_ID_NONE), 3 => Some(CODEC_ID_REMOTEFX), 0x0A => Some(CODEC_ID_QOI), + 0x0B => Some(CODEC_ID_QOIZ), _ => None, } } @@ -700,6 +721,7 @@ fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result Result>().join(", "); if !codec_names.is_empty() { return Err(format!("Unknown codecs: {codec_names}")); @@ -760,6 +791,7 @@ List of codecs: /// /// * `remotefx` (on by default) /// * `qoi` (on by default, when feature "qoi") +/// * `qoiz` (on by default, when feature "qoiz") /// /// # Returns /// @@ -771,6 +803,7 @@ pub fn server_codecs_capabilities(config: &[&str]) -> Result>().join(", "); if !codec_names.is_empty() { return Err(format!("Unknown codecs: {codec_names}")); diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml index ec58b869..2c1ca031 100644 --- a/crates/ironrdp-server/Cargo.toml +++ b/crates/ironrdp-server/Cargo.toml @@ -16,10 +16,11 @@ doctest = true test = false [features] -default = ["rayon", "qoi"] +default = ["rayon", "qoi", "qoiz"] helper = ["dep:x509-cert", "dep:rustls-pemfile"] rayon = ["dep:rayon"] qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"] +qoiz = ["dep:zstd-safe", "qoi", "ironrdp-pdu/qoiz"] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at any time. @@ -49,6 +50,7 @@ rayon = { version = "1.10.0", optional = true } bytes = "1" visibility = { version = "0.1", optional = true } qoicoubeh = { version = "0.5", optional = true } +zstd-safe = { version = "7.2", optional = true } [dev-dependencies] tokio = { version = "1", features = ["sync"] } diff --git a/crates/ironrdp-server/src/encoder/mod.rs b/crates/ironrdp-server/src/encoder/mod.rs index 8388f5b4..66e871b3 100644 --- a/crates/ironrdp-server/src/encoder/mod.rs +++ b/crates/ironrdp-server/src/encoder/mod.rs @@ -1,7 +1,8 @@ use core::fmt; use core::num::NonZeroU16; +use std::sync::{Arc, Mutex}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use ironrdp_acceptor::DesktopSize; use ironrdp_graphics::diff::{find_different_rects_sub, Rect}; use ironrdp_pdu::encode_vec; @@ -35,6 +36,8 @@ pub(crate) struct UpdateEncoderCodecs { remotefx: Option<(EntropyBits, u8)>, #[cfg(feature = "qoi")] qoi: Option, + #[cfg(feature = "qoiz")] + qoiz: Option, } impl UpdateEncoderCodecs { @@ -44,6 +47,8 @@ impl UpdateEncoderCodecs { remotefx: None, #[cfg(feature = "qoi")] qoi: None, + #[cfg(feature = "qoiz")] + qoiz: None, } } @@ -57,6 +62,12 @@ impl UpdateEncoderCodecs { pub(crate) fn set_qoi(&mut self, qoi: Option) { self.qoi = qoi } + + #[cfg(feature = "qoiz")] + #[cfg_attr(feature = "__bench", visibility::make(pub))] + pub(crate) fn set_qoiz(&mut self, qoiz: Option) { + self.qoiz = qoiz + } } impl Default for UpdateEncoderCodecs { @@ -94,6 +105,10 @@ impl UpdateEncoder { if let Some(id) = codecs.qoi { bitmap = BitmapUpdater::Qoi(QoiHandler::new(id)); } + #[cfg(feature = "qoiz")] + if let Some(id) = codecs.qoiz { + bitmap = BitmapUpdater::Qoiz(QoizHandler::new(id)); + } bitmap } else { @@ -306,6 +321,8 @@ enum BitmapUpdater { RemoteFx(RemoteFxHandler), #[cfg(feature = "qoi")] Qoi(QoiHandler), + #[cfg(feature = "qoiz")] + Qoiz(QoizHandler), } impl BitmapUpdater { @@ -316,6 +333,8 @@ impl BitmapUpdater { Self::RemoteFx(up) => up.handle(bitmap), #[cfg(feature = "qoi")] Self::Qoi(up) => up.handle(bitmap), + #[cfg(feature = "qoiz")] + Self::Qoiz(up) => up.handle(bitmap), } } @@ -445,28 +464,85 @@ impl QoiHandler { #[cfg(feature = "qoi")] impl BitmapUpdateHandler for QoiHandler { fn handle(&mut self, bitmap: &BitmapUpdate) -> Result { - use ironrdp_graphics::image_processing::PixelFormat::*; - - let raw_channels = match bitmap.format { - ARgb32 => qoi::RawChannels::Argb, - XRgb32 => qoi::RawChannels::Xrgb, - ABgr32 => qoi::RawChannels::Abgr, - XBgr32 => qoi::RawChannels::Xbgr, - BgrA32 => qoi::RawChannels::Bgra, - BgrX32 => qoi::RawChannels::Bgrx, - RgbA32 => qoi::RawChannels::Rgba, - RgbX32 => qoi::RawChannels::Rgbx, - }; - - let enc = qoi::EncoderBuilder::new(&bitmap.data, bitmap.width.get().into(), bitmap.height.get().into()) - .stride(bitmap.stride) - .raw_channels(raw_channels) - .build()?; - let data = enc.encode_to_vec()?; + let data = qoi_encode(bitmap)?; set_surface(bitmap, self.codec_id, &data) } } +#[cfg(feature = "qoiz")] +#[derive(Clone)] +struct QoizHandler { + codec_id: u8, + zctxt: Arc>>, +} + +#[cfg(feature = "qoiz")] +impl fmt::Debug for QoizHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("QoizHandler").field("codec_id", &self.codec_id).finish() + } +} + +#[cfg(feature = "qoiz")] +impl QoizHandler { + fn new(codec_id: u8) -> Self { + let mut zctxt = zstd_safe::CCtx::default(); + + zctxt.set_parameter(zstd_safe::CParameter::CompressionLevel(3)).unwrap(); + zctxt + .set_parameter(zstd_safe::CParameter::EnableLongDistanceMatching(true)) + .unwrap(); + let zctxt = Arc::new(Mutex::new(zctxt)); + + Self { codec_id, zctxt } + } +} + +#[cfg(feature = "qoiz")] +impl BitmapUpdateHandler for QoizHandler { + fn handle(&mut self, bitmap: &BitmapUpdate) -> Result { + let qoi = qoi_encode(bitmap)?; + let mut inb = zstd_safe::InBuffer::around(&qoi); + let mut data = vec![0; qoi.len()]; + let mut outb = zstd_safe::OutBuffer::around(data.as_mut_slice()); + + let mut zctxt = self.zctxt.lock().unwrap(); + let res = zctxt + .compress_stream2( + &mut outb, + &mut inb, + zstd_safe::zstd_sys::ZSTD_EndDirective::ZSTD_e_flush, + ) + .map_err(zstd_safe::get_error_name) + .unwrap(); + if res != 0 { + return Err(anyhow!("Failed to zstd compress")); + } + + set_surface(bitmap, self.codec_id, outb.as_slice()) + } +} + +#[cfg(feature = "qoi")] +fn qoi_encode(bitmap: &BitmapUpdate) -> Result> { + use ironrdp_graphics::image_processing::PixelFormat::*; + let raw_channels = match bitmap.format { + ARgb32 => qoi::RawChannels::Argb, + XRgb32 => qoi::RawChannels::Xrgb, + ABgr32 => qoi::RawChannels::Abgr, + XBgr32 => qoi::RawChannels::Xbgr, + BgrA32 => qoi::RawChannels::Bgra, + BgrX32 => qoi::RawChannels::Bgrx, + RgbA32 => qoi::RawChannels::Rgba, + RgbX32 => qoi::RawChannels::Rgbx, + }; + let enc = qoi::EncoderBuilder::new(&bitmap.data, bitmap.width.get().into(), bitmap.height.get().into()) + .stride(bitmap.stride) + .raw_channels(raw_channels) + .build()?; + Ok(enc.encode_to_vec()?) +} + fn set_surface(bitmap: &BitmapUpdate, codec_id: u8, data: &[u8]) -> Result { let destination = ExclusiveRectangle { left: bitmap.x, diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 9e43c537..54a8e79d 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -63,6 +63,14 @@ impl RdpServerOptions { .iter() .any(|codec| matches!(codec.property, CodecProperty::Qoi)) } + + #[cfg(feature = "qoiz")] + fn has_qoiz(&self) -> bool { + self.codecs + .0 + .iter() + .any(|codec| matches!(codec.property, CodecProperty::QoiZ)) + } } #[derive(Clone)] @@ -754,6 +762,10 @@ impl RdpServer { CodecProperty::Qoi if self.opts.has_qoi() => { update_codecs.set_qoi(Some(codec.id)); } + #[cfg(feature = "qoiz")] + CodecProperty::QoiZ if self.opts.has_qoiz() => { + update_codecs.set_qoiz(Some(codec.id)); + } _ => (), } } diff --git a/crates/ironrdp-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index 93eee93d..e498c3ea 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -18,6 +18,7 @@ test = false [features] default = [] qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"] +qoiz = ["dep:zstd-safe", "qoi"] [dependencies] ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public @@ -30,6 +31,7 @@ ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = ["std"] } # ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.3" } tracing = { version = "0.1", features = ["log"] } qoicoubeh = { version = "0.5", optional = true } +zstd-safe = { version = "7.2", optional = true, features = ["std"] } [lints] workspace = true diff --git a/crates/ironrdp-session/src/fast_path.rs b/crates/ironrdp-session/src/fast_path.rs index db34614e..229108fd 100644 --- a/crates/ironrdp-session/src/fast_path.rs +++ b/crates/ironrdp-session/src/fast_path.rs @@ -37,6 +37,8 @@ pub struct Processor { mouse_pos_update: Option<(u16, u16)>, no_server_pointer: bool, pointer_software_rendering: bool, + #[cfg(feature = "qoiz")] + zdctx: zstd_safe::DCtx<'static>, } impl Processor { @@ -363,20 +365,34 @@ impl Processor { } #[cfg(feature = "qoi")] ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOI => { - let (header, decoded) = qoi::decode_to_vec(bits.extended_bitmap_data.data) - .map_err(|e| reason_err!("QOI decode", "{}", e))?; - match header.channels { - qoi::Channels::Rgb => { - let rectangle = image.apply_rgb24(&decoded, &destination, false)?; - - update_rectangle = update_rectangle - .map(|rect: InclusiveRectangle| rect.union(&rectangle)) - .or(Some(rectangle)); - } - qoi::Channels::Rgba => { - warn!("Unsupported RGBA QOI data"); + qoi_apply( + image, + destination, + bits.extended_bitmap_data.data, + &mut update_rectangle, + )?; + } + #[cfg(feature = "qoiz")] + ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOIZ => { + let compressed = &bits.extended_bitmap_data.data; + let mut input = zstd_safe::InBuffer::around(compressed); + let mut data = vec![0; compressed.len() * 4]; + let mut pos = 0; + loop { + let mut output = zstd_safe::OutBuffer::around_pos(data.as_mut_slice(), pos); + self.zdctx + .decompress_stream(&mut output, &mut input) + .map_err(zstd_safe::get_error_name) + .map_err(|e| reason_err!("zstd", "{}", e))?; + pos = output.pos(); + if pos == output.capacity() { + data.resize(data.capacity() * 2, 0); + } else { + break; } } + + qoi_apply(image, destination, &data, &mut update_rectangle)?; } _ => { warn!("Unsupported codec ID: {}", bits.extended_bitmap_data.codec_id); @@ -398,6 +414,30 @@ impl Processor { } } +#[cfg(feature = "qoi")] +fn qoi_apply( + image: &mut DecodedImage, + destination: InclusiveRectangle, + data: &[u8], + update_rectangle: &mut Option, +) -> SessionResult<()> { + let (header, decoded) = qoi::decode_to_vec(data).map_err(|e| reason_err!("QOI decode", "{}", e))?; + match header.channels { + qoi::Channels::Rgb => { + let rectangle = image.apply_rgb24(&decoded, &destination, false)?; + + *update_rectangle = update_rectangle + .as_ref() + .map(|rect: &InclusiveRectangle| rect.union(&rectangle)) + .or(Some(rectangle)); + } + qoi::Channels::Rgba => { + warn!("Unsupported RGBA QOI data"); + } + } + Ok(()) +} + pub struct ProcessorBuilder { pub io_channel_id: u16, pub user_channel_id: u16, @@ -421,6 +461,8 @@ impl ProcessorBuilder { mouse_pos_update: None, no_server_pointer: self.no_server_pointer, pointer_software_rendering: self.pointer_software_rendering, + #[cfg(feature = "qoiz")] + zdctx: zstd_safe::DCtx::default(), } } } diff --git a/crates/ironrdp-web/Cargo.toml b/crates/ironrdp-web/Cargo.toml index f9002f77..04a832e1 100644 --- a/crates/ironrdp-web/Cargo.toml +++ b/crates/ironrdp-web/Cargo.toml @@ -21,6 +21,7 @@ crate-type = ["cdylib", "rlib"] default = ["panic_hook"] panic_hook = ["iron-remote-desktop/panic_hook"] qoi = ["ironrdp/qoi"] +qoiz = ["ironrdp/qoiz"] [dependencies] # Protocols diff --git a/crates/ironrdp/Cargo.toml b/crates/ironrdp/Cargo.toml index 4ab878d6..8598b464 100644 --- a/crates/ironrdp/Cargo.toml +++ b/crates/ironrdp/Cargo.toml @@ -32,6 +32,7 @@ rdpdr = ["dep:ironrdp-rdpdr"] rdpsnd = ["dep:ironrdp-rdpsnd"] displaycontrol = ["dep:ironrdp-displaycontrol"] qoi = ["ironrdp-server?/qoi", "ironrdp-pdu?/qoi", "ironrdp-connector?/qoi", "ironrdp-session?/qoi"] +qoiz = ["ironrdp-server?/qoiz", "ironrdp-pdu?/qoiz", "ironrdp-connector?/qoiz", "ironrdp-session?/qoiz"] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at any time. __bench = ["ironrdp-server/__bench"]