mirror of
https://github.com/netbirdio/IronRDP.git
synced 2026-05-22 18:43:12 -07:00
feat: add QOIZ image codec
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 <marcandre.lureau@redhat.com>
This commit is contained in:
committed by
Benoît Cortier
parent
613fd51f26
commit
87df67fdc7
Generated
+21
@@ -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",
|
||||
]
|
||||
|
||||
+2
-1
@@ -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"
|
||||
|
||||
@@ -28,7 +28,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
println!(" --width <WIDTH> Width of the display (default: 3840)");
|
||||
println!(" --height <HEIGHT> Height of the display (default: 2400)");
|
||||
println!(" --codec <CODEC> Codec to use (default: remotefx)");
|
||||
println!(" Valid values: qoi, remotefx, bitmap, none");
|
||||
println!(" Valid values: qoi, qoiz, remotefx, bitmap, none");
|
||||
println!(" --fps <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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<HashMap<&'a str, boo
|
||||
///
|
||||
/// * `remotefx` (on by default)
|
||||
/// * `qoi` (on by default, when feature "qoi")
|
||||
/// * `qoiz` (on by default, when feature "qoiz")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
@@ -711,6 +733,7 @@ pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
|
||||
List of codecs:
|
||||
- `remotefx` (on by default)
|
||||
- `qoi` (on by default, when feature "qoi")
|
||||
- `qoiz` (on by default, when feature "qoiz")
|
||||
"#
|
||||
.to_owned());
|
||||
}
|
||||
@@ -739,6 +762,14 @@ List of codecs:
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "qoiz")]
|
||||
if config.remove("qoiz").unwrap_or(true) {
|
||||
codecs.push(Codec {
|
||||
id: CODEC_ID_QOIZ.0,
|
||||
property: CodecProperty::QoiZ,
|
||||
});
|
||||
}
|
||||
|
||||
let codec_names = config.keys().copied().collect::<Vec<_>>().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<BitmapCodecs, Strin
|
||||
List of codecs:
|
||||
- `remotefx` (on by default)
|
||||
- `qoi` (on by default, when feature "qoi")
|
||||
- `qoiz` (on by default, when feature "qoiz")
|
||||
"#
|
||||
.to_owned());
|
||||
}
|
||||
@@ -797,6 +830,14 @@ List of codecs:
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "qoiz")]
|
||||
if config.remove("qoiz").unwrap_or(true) {
|
||||
codecs.push(Codec {
|
||||
id: 0,
|
||||
property: CodecProperty::QoiZ,
|
||||
});
|
||||
}
|
||||
|
||||
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
|
||||
if !codec_names.is_empty() {
|
||||
return Err(format!("Unknown codecs: {codec_names}"));
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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<u8>,
|
||||
#[cfg(feature = "qoiz")]
|
||||
qoiz: Option<u8>,
|
||||
}
|
||||
|
||||
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<u8>) {
|
||||
self.qoi = qoi
|
||||
}
|
||||
|
||||
#[cfg(feature = "qoiz")]
|
||||
#[cfg_attr(feature = "__bench", visibility::make(pub))]
|
||||
pub(crate) fn set_qoiz(&mut self, qoiz: Option<u8>) {
|
||||
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<UpdateFragmenter> {
|
||||
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<Mutex<zstd_safe::CCtx<'static>>>,
|
||||
}
|
||||
|
||||
#[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<UpdateFragmenter> {
|
||||
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<Vec<u8>> {
|
||||
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<UpdateFragmenter> {
|
||||
let destination = ExclusiveRectangle {
|
||||
left: bitmap.x,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<InclusiveRectangle>,
|
||||
) -> 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user