Skip to content

Commit c07211d

Browse files
committed
feat: add QOI image codec
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 9f4f633 commit c07211d

File tree

17 files changed

+205
-21
lines changed

17 files changed

+205
-21
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
[alias]
22
xtask = "run --package xtask --"
3+
4+
[patch.crates-io]
5+
qoi = { git = "https://github.com/elmarco/qoi-rust.git", branch = "raw" }

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ test = false
2727
default = ["rustls"]
2828
rustls = ["ironrdp-tls/rustls"]
2929
native-tls = ["ironrdp-tls/native-tls"]
30+
qoi = ["ironrdp/qoi"]
3031

3132
[dependencies]
3233
# Protocols

crates/ironrdp-connector/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ doctest = false
1616
test = false
1717

1818
[features]
19+
default = []
1920
arbitrary = ["dep:arbitrary"]
21+
qoi = ["ironrdp-pdu/qoi"]
2022

2123
[dependencies]
2224
ironrdp-svc = { path = "../ironrdp-svc", version = "0.3" } # public

crates/ironrdp-pdu/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ doctest = false
1919
default = []
2020
std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"]
2121
alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"]
22+
qoi = []
2223

2324
[dependencies]
2425
bitflags = "2.4"

crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const GUID_REMOTEFX: Guid = Guid(0x7677_2f12, 0xbd72, 0x4463, 0xaf, 0xb3, 0xb7,
3939
const GUID_IMAGE_REMOTEFX: Guid = Guid(0x2744_ccd4, 0x9d8a, 0x4e74, 0x80, 0x3c, 0x0e, 0xcb, 0xee, 0xa1, 0x9c, 0x54);
4040
#[rustfmt::skip]
4141
const GUID_IGNORE: Guid = Guid(0x9c43_51a6, 0x3535, 0x42ae, 0x91, 0x0c, 0xcd, 0xfc, 0xe5, 0x76, 0x0b, 0x58);
42+
#[rustfmt::skip]
43+
#[cfg(feature="qoi")]
44+
const GUID_QOI: Guid = Guid(0x4dae_9af8, 0xb399, 0x4df6, 0xb4, 0x3a, 0x66, 0x2f, 0xd9, 0xc0, 0xf5, 0xd6);
4245

4346
#[derive(Debug, PartialEq, Eq)]
4447
pub struct Guid(u32, u16, u16, u8, u8, u8, u8, u8, u8, u8, u8);
@@ -166,6 +169,8 @@ impl Encode for Codec {
166169
CodecProperty::RemoteFx(_) => GUID_REMOTEFX,
167170
CodecProperty::ImageRemoteFx(_) => GUID_IMAGE_REMOTEFX,
168171
CodecProperty::Ignore => GUID_IGNORE,
172+
#[cfg(feature = "qoi")]
173+
CodecProperty::Qoi => GUID_QOI,
169174
_ => return Err(other_err!("invalid codec")),
170175
};
171176
guid.encode(dst)?;
@@ -203,6 +208,8 @@ impl Encode for Codec {
203208
}
204209
};
205210
}
211+
#[cfg(feature = "qoi")]
212+
CodecProperty::Qoi => dst.write_u16(0),
206213
CodecProperty::Ignore => dst.write_u16(0),
207214
CodecProperty::None => dst.write_u16(0),
208215
};
@@ -226,6 +233,8 @@ impl Encode for Codec {
226233
RemoteFxContainer::ClientContainer(container) => container.size(),
227234
RemoteFxContainer::ServerContainer(size) => *size,
228235
},
236+
#[cfg(feature = "qoi")]
237+
CodecProperty::Qoi => 0,
229238
CodecProperty::Ignore => 0,
230239
CodecProperty::None => 0,
231240
}
@@ -260,6 +269,8 @@ impl<'de> Decode<'de> for Codec {
260269
_ => unreachable!(),
261270
}
262271
}
272+
#[cfg(feature = "qoi")]
273+
GUID_QOI => CodecProperty::Qoi,
263274
GUID_IGNORE => CodecProperty::Ignore,
264275
_ => CodecProperty::None,
265276
}
@@ -271,6 +282,8 @@ impl<'de> Decode<'de> for Codec {
271282
"invalid codec property length"
272283
));
273284
}
285+
#[cfg(feature = "qoi")]
286+
GUID_QOI => CodecProperty::Qoi,
274287
GUID_IGNORE => CodecProperty::Ignore,
275288
_ => CodecProperty::None,
276289
}
@@ -292,6 +305,8 @@ pub enum CodecProperty {
292305
RemoteFx(RemoteFxContainer),
293306
ImageRemoteFx(RemoteFxContainer),
294307
Ignore,
308+
#[cfg(feature = "qoi")]
309+
Qoi,
295310
None,
296311
}
297312

@@ -625,15 +640,19 @@ bitflags! {
625640
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
626641
#[repr(u8)]
627642
pub enum CodecId {
628-
None = 0x0,
629-
RemoteFx = 0x3,
643+
None = 0x00,
644+
RemoteFx = 0x03,
645+
#[cfg(feature = "qoi")]
646+
Qoi = 0x0A,
630647
}
631648

632649
impl CodecId {
633650
pub const fn from_u8(value: u8) -> Option<Self> {
634651
match value {
635-
0 => Some(Self::None),
636-
3 => Some(Self::RemoteFx),
652+
0x00 => Some(Self::None),
653+
0x03 => Some(Self::RemoteFx),
654+
#[cfg(feature = "qoi")]
655+
0x0A => Some(Self::Qoi),
637656
_ => None,
638657
}
639658
}
@@ -696,6 +715,14 @@ pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
696715
});
697716
}
698717

718+
#[cfg(feature = "qoi")]
719+
if config.remove("qoi").unwrap_or(true) {
720+
codecs.push(Codec {
721+
id: CodecId::Qoi as u8,
722+
property: CodecProperty::Qoi,
723+
});
724+
}
725+
699726
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
700727
if !codec_names.is_empty() {
701728
return Err(format!("Unknown codecs: {}", codec_names));
@@ -717,6 +744,7 @@ pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
717744
/// # List of codecs
718745
///
719746
/// * `remotefx` (on by default)
747+
/// * `qoi` (on by default, when feature "qoi")
720748
///
721749
/// # Returns
722750
///
@@ -736,6 +764,14 @@ pub fn server_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
736764
});
737765
}
738766

767+
#[cfg(feature = "qoi")]
768+
if config.remove("qoi").unwrap_or(true) {
769+
codecs.push(Codec {
770+
id: 0,
771+
property: CodecProperty::Qoi,
772+
});
773+
}
774+
739775
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
740776
if !codec_names.is_empty() {
741777
return Err(format!("Unknown codecs: {}", codec_names));

crates/ironrdp-server/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ name = "perfenc"
1919
required-features = ["__bench"]
2020

2121
[features]
22-
default = ["rayon"]
22+
default = ["rayon", "qoi"]
2323
helper = ["dep:x509-cert", "dep:rustls-pemfile"]
2424
rayon = ["dep:rayon"]
25+
qoi = ["dep:qoi", "ironrdp-pdu/qoi"]
2526

2627
# Internal (PRIVATE!) features used to aid testing.
2728
# Don't rely on these whatsoever. They may disappear at any time.
@@ -50,6 +51,7 @@ rustls-pemfile = { version = "2.2.0", optional = true }
5051
rayon = { version = "1.10.0", optional = true }
5152
bytes = "1"
5253
visibility = { version = "0.1", optional = true }
54+
qoi = { version = "0.4", optional = true }
5355

5456
[dev-dependencies]
5557
tokio = { version = "1", features = ["sync", "fs"] }

crates/ironrdp-server/examples/perfenc.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,16 @@ async fn main() -> Result<(), anyhow::Error> {
4848
(None, None)
4949
}
5050
OptCodec::None => (None, None),
51+
#[cfg(feature = "qoi")]
52+
OptCodec::Qoi => (None, Some(0)),
5153
};
52-
let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, remotefx);
54+
let mut encoder = UpdateEncoder::new(
55+
DesktopSize { width, height },
56+
flags,
57+
remotefx,
58+
#[cfg(feature = "qoi")]
59+
qoicodec,
60+
);
5361

5462
let mut total_raw = 0u64;
5563
let mut total_enc = 0u64;
@@ -148,6 +156,8 @@ enum OptCodec {
148156
RemoteFX,
149157
Bitmap,
150158
None,
159+
#[cfg(feature = "qoi")]
160+
Qoi,
151161
}
152162

153163
impl Default for OptCodec {
@@ -164,6 +174,8 @@ impl core::str::FromStr for OptCodec {
164174
"remotefx" => Ok(Self::RemoteFX),
165175
"bitmap" => Ok(Self::Bitmap),
166176
"none" => Ok(Self::None),
177+
#[cfg(feature = "qoi")]
178+
"qoi" => Ok(Self::Qoi),
167179
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
168180
}
169181
}

crates/ironrdp-server/src/encoder/mod.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,27 @@ impl fmt::Debug for UpdateEncoder {
4646

4747
impl UpdateEncoder {
4848
#[cfg_attr(feature = "__bench", visibility::make(pub))]
49-
pub(crate) fn new(desktop_size: DesktopSize, surface_flags: CmdFlags, remotefx: Option<(EntropyBits, u8)>) -> Self {
50-
let bitmap_updater = if !surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
51-
BitmapUpdater::Bitmap(BitmapHandler::new())
52-
} else if remotefx.is_some() {
53-
let (algo, id) = remotefx.unwrap();
54-
BitmapUpdater::RemoteFx(RemoteFxHandler::new(algo, id, desktop_size))
49+
pub(crate) fn new(
50+
desktop_size: DesktopSize,
51+
surface_flags: CmdFlags,
52+
remotefx: Option<(EntropyBits, u8)>,
53+
#[cfg(feature = "qoi")] qoi_codec_id: Option<u8>,
54+
) -> Self {
55+
let bitmap_updater = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
56+
let mut up = BitmapUpdater::None(NoneHandler);
57+
58+
if let Some((algo, id)) = remotefx {
59+
up = BitmapUpdater::RemoteFx(RemoteFxHandler::new(algo, id, desktop_size));
60+
}
61+
62+
#[cfg(feature = "qoi")]
63+
if let Some(id) = qoi_codec_id {
64+
up = BitmapUpdater::Qoi(QoiHandler::new(id));
65+
}
66+
67+
up
5568
} else {
56-
BitmapUpdater::None(NoneHandler)
69+
BitmapUpdater::Bitmap(BitmapHandler::new())
5770
};
5871

5972
Self {
@@ -264,6 +277,8 @@ enum BitmapUpdater {
264277
None(NoneHandler),
265278
Bitmap(BitmapHandler),
266279
RemoteFx(RemoteFxHandler),
280+
#[cfg(feature = "qoi")]
281+
Qoi(QoiHandler),
267282
}
268283

269284
impl BitmapUpdater {
@@ -272,6 +287,8 @@ impl BitmapUpdater {
272287
Self::None(up) => up.handle(bitmap),
273288
Self::Bitmap(up) => up.handle(bitmap),
274289
Self::RemoteFx(up) => up.handle(bitmap),
290+
#[cfg(feature = "qoi")]
291+
Self::Qoi(up) => up.handle(bitmap),
275292
}
276293
}
277294

@@ -385,6 +402,47 @@ impl BitmapUpdateHandler for RemoteFxHandler {
385402
}
386403
}
387404

405+
#[cfg(feature = "qoi")]
406+
#[derive(Clone, Debug)]
407+
struct QoiHandler {
408+
codec_id: u8,
409+
}
410+
411+
#[cfg(feature = "qoi")]
412+
impl QoiHandler {
413+
fn new(codec_id: u8) -> Self {
414+
Self { codec_id }
415+
}
416+
}
417+
418+
#[cfg(feature = "qoi")]
419+
impl BitmapUpdateHandler for QoiHandler {
420+
fn handle(&mut self, bitmap: &BitmapUpdate) -> Result<UpdateFragmenter> {
421+
use ironrdp_graphics::image_processing::PixelFormat::*;
422+
423+
let channels = match bitmap.format {
424+
ARgb32 => qoi::RawChannels::Argb,
425+
XRgb32 => qoi::RawChannels::Xrgb,
426+
ABgr32 => qoi::RawChannels::Abgr,
427+
XBgr32 => qoi::RawChannels::Xbgr,
428+
BgrA32 => qoi::RawChannels::Bgra,
429+
BgrX32 => qoi::RawChannels::Bgrx,
430+
RgbA32 => qoi::RawChannels::Rgba,
431+
RgbX32 => qoi::RawChannels::Rgbx,
432+
};
433+
434+
let enc = qoi::Encoder::new_raw(
435+
&bitmap.data,
436+
bitmap.width.get().into(),
437+
bitmap.height.get().into(),
438+
bitmap.stride,
439+
channels,
440+
)?;
441+
let data = enc.encode_to_vec()?;
442+
set_surface(bitmap, self.codec_id, &data)
443+
}
444+
}
445+
388446
fn set_surface(bitmap: &BitmapUpdate, codec_id: u8, data: &[u8]) -> Result<UpdateFragmenter> {
389447
let destination = ExclusiveRectangle {
390448
left: bitmap.x,

crates/ironrdp-server/src/server.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ impl RdpServerOptions {
5454
.iter()
5555
.any(|codec| matches!(codec.property, CodecProperty::RemoteFx(_)))
5656
}
57+
58+
#[cfg(feature = "qoi")]
59+
fn has_qoi(&self) -> bool {
60+
self.codecs
61+
.0
62+
.iter()
63+
.any(|codec| matches!(codec.property, CodecProperty::Qoi))
64+
}
5765
}
5866

5967
#[derive(Clone)]
@@ -678,6 +686,8 @@ impl RdpServer {
678686
}
679687

680688
let mut rfxcodec = None;
689+
#[cfg(feature = "qoi")]
690+
let mut qoicodec = None;
681691
let mut surface_flags = CmdFlags::empty();
682692
for c in result.capabilities {
683693
match c {
@@ -739,6 +749,10 @@ impl RdpServer {
739749
}
740750
}
741751
CodecProperty::NsCodec(_) => (),
752+
#[cfg(feature = "qoi")]
753+
CodecProperty::Qoi if self.opts.has_qoi() => {
754+
qoicodec = Some(codec.id);
755+
}
742756
_ => (),
743757
}
744758
}
@@ -748,7 +762,13 @@ impl RdpServer {
748762
}
749763

750764
let desktop_size = self.display.lock().await.size().await;
751-
let encoder = UpdateEncoder::new(desktop_size, surface_flags, rfxcodec);
765+
let encoder = UpdateEncoder::new(
766+
desktop_size,
767+
surface_flags,
768+
rfxcodec,
769+
#[cfg(feature = "qoi")]
770+
qoicodec,
771+
);
752772

753773
let state = self
754774
.client_loop(reader, writer, result.io_channel_id, result.user_channel_id, encoder)

0 commit comments

Comments
 (0)