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",
|
"tracing",
|
||||||
"visibility",
|
"visibility",
|
||||||
"x509-cert",
|
"x509-cert",
|
||||||
|
"zstd-safe",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2739,6 +2740,7 @@ dependencies = [
|
|||||||
"ironrdp-svc",
|
"ironrdp-svc",
|
||||||
"qoicoubeh",
|
"qoicoubeh",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"zstd-safe",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6971,3 +6973,22 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"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"
|
path = "src/perfenc.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["qoi"]
|
default = ["qoi", "qoiz"]
|
||||||
qoi = ["ironrdp/qoi"]
|
qoi = ["ironrdp/qoi"]
|
||||||
|
qoiz = ["ironrdp/qoiz"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
println!(" --width <WIDTH> Width of the display (default: 3840)");
|
println!(" --width <WIDTH> Width of the display (default: 3840)");
|
||||||
println!(" --height <HEIGHT> Height of the display (default: 2400)");
|
println!(" --height <HEIGHT> Height of the display (default: 2400)");
|
||||||
println!(" --codec <CODEC> Codec to use (default: remotefx)");
|
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)");
|
println!(" --fps <FPS> Frames per second (default: none)");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,8 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
OptCodec::None => {}
|
OptCodec::None => {}
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
OptCodec::Qoi => update_codecs.set_qoi(Some(0)),
|
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);
|
let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, update_codecs);
|
||||||
@@ -176,6 +178,8 @@ enum OptCodec {
|
|||||||
None,
|
None,
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
Qoi,
|
Qoi,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
QoiZ,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OptCodec {
|
impl Default for OptCodec {
|
||||||
@@ -194,6 +198,8 @@ impl core::str::FromStr for OptCodec {
|
|||||||
"none" => Ok(Self::None),
|
"none" => Ok(Self::None),
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
"qoi" => Ok(Self::Qoi),
|
"qoi" => Ok(Self::Qoi),
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
"qoiz" => Ok(Self::QoiZ),
|
||||||
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
|
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ default = ["rustls"]
|
|||||||
rustls = ["ironrdp-tls/rustls", "tokio-tungstenite/rustls-tls-native-roots"]
|
rustls = ["ironrdp-tls/rustls", "tokio-tungstenite/rustls-tls-native-roots"]
|
||||||
native-tls = ["ironrdp-tls/native-tls", "tokio-tungstenite/native-tls"]
|
native-tls = ["ironrdp-tls/native-tls", "tokio-tungstenite/native-tls"]
|
||||||
qoi = ["ironrdp/qoi"]
|
qoi = ["ironrdp/qoi"]
|
||||||
|
qoiz = ["ironrdp/qoiz"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Protocols
|
# Protocols
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ test = false
|
|||||||
default = []
|
default = []
|
||||||
arbitrary = ["dep:arbitrary"]
|
arbitrary = ["dep:arbitrary"]
|
||||||
qoi = ["ironrdp-pdu/qoi"]
|
qoi = ["ironrdp-pdu/qoi"]
|
||||||
|
qoiz = ["ironrdp-pdu/qoiz"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ironrdp-svc = { path = "../ironrdp-svc", version = "0.4" } # public
|
ironrdp-svc = { path = "../ironrdp-svc", version = "0.4" } # public
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ default = []
|
|||||||
std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"]
|
std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"]
|
||||||
alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"]
|
alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"]
|
||||||
qoi = []
|
qoi = []
|
||||||
|
qoiz = ["qoi"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.9"
|
bitflags = "2.9"
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub use self::bitmap_cache::{
|
|||||||
pub use self::bitmap_codecs::{
|
pub use self::bitmap_codecs::{
|
||||||
client_codecs_capabilities, server_codecs_capabilities, BitmapCodecs, CaptureFlags, Codec, CodecId, CodecProperty,
|
client_codecs_capabilities, server_codecs_capabilities, BitmapCodecs, CaptureFlags, Codec, CodecId, CodecProperty,
|
||||||
EntropyBits, Guid, NsCodec, RemoteFxContainer, RfxCaps, RfxCapset, RfxClientCapsContainer, RfxICap, RfxICapFlags,
|
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::brush::{Brush, SupportLevel};
|
||||||
pub use self::frame_acknowledge::FrameAcknowledge;
|
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]
|
#[rustfmt::skip]
|
||||||
#[cfg(feature="qoi")]
|
#[cfg(feature="qoi")]
|
||||||
const GUID_QOI: Guid = Guid(0x4dae_9af8, 0xb399, 0x4df6, 0xb4, 0x3a, 0x66, 0x2f, 0xd9, 0xc0, 0xf5, 0xd6);
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Guid(u32, u16, u16, u8, u8, u8, u8, u8, u8, u8, u8);
|
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,
|
CodecProperty::Ignore => GUID_IGNORE,
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
CodecProperty::Qoi => GUID_QOI,
|
CodecProperty::Qoi => GUID_QOI,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
CodecProperty::QoiZ => GUID_QOIZ,
|
||||||
_ => return Err(other_err!("invalid codec")),
|
_ => return Err(other_err!("invalid codec")),
|
||||||
};
|
};
|
||||||
guid.encode(dst)?;
|
guid.encode(dst)?;
|
||||||
@@ -211,6 +216,8 @@ impl Encode for Codec {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
CodecProperty::Qoi => dst.write_u16(0),
|
CodecProperty::Qoi => dst.write_u16(0),
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
CodecProperty::QoiZ => dst.write_u16(0),
|
||||||
CodecProperty::Ignore => dst.write_u16(0),
|
CodecProperty::Ignore => dst.write_u16(0),
|
||||||
CodecProperty::None => dst.write_u16(0),
|
CodecProperty::None => dst.write_u16(0),
|
||||||
};
|
};
|
||||||
@@ -236,6 +243,8 @@ impl Encode for Codec {
|
|||||||
},
|
},
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
CodecProperty::Qoi => 0,
|
CodecProperty::Qoi => 0,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
CodecProperty::QoiZ => 0,
|
||||||
CodecProperty::Ignore => 0,
|
CodecProperty::Ignore => 0,
|
||||||
CodecProperty::None => 0,
|
CodecProperty::None => 0,
|
||||||
}
|
}
|
||||||
@@ -280,6 +289,13 @@ impl<'de> Decode<'de> for Codec {
|
|||||||
}
|
}
|
||||||
CodecProperty::Qoi
|
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,
|
_ => CodecProperty::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -301,6 +317,8 @@ pub enum CodecProperty {
|
|||||||
Ignore,
|
Ignore,
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
Qoi,
|
Qoi,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
QoiZ,
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,6 +657,7 @@ pub struct CodecId(u8);
|
|||||||
pub const CODEC_ID_NONE: CodecId = CodecId(0);
|
pub const CODEC_ID_NONE: CodecId = CodecId(0);
|
||||||
pub const CODEC_ID_REMOTEFX: CodecId = CodecId(3);
|
pub const CODEC_ID_REMOTEFX: CodecId = CodecId(3);
|
||||||
pub const CODEC_ID_QOI: CodecId = CodecId(0x0A);
|
pub const CODEC_ID_QOI: CodecId = CodecId(0x0A);
|
||||||
|
pub const CODEC_ID_QOIZ: CodecId = CodecId(0x0B);
|
||||||
|
|
||||||
impl Debug for CodecId {
|
impl Debug for CodecId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@@ -646,6 +665,7 @@ impl Debug for CodecId {
|
|||||||
0 => "None",
|
0 => "None",
|
||||||
3 => "RemoteFx",
|
3 => "RemoteFx",
|
||||||
0x0A => "QOI",
|
0x0A => "QOI",
|
||||||
|
0x0B => "QOIZ",
|
||||||
_ => "unknown",
|
_ => "unknown",
|
||||||
};
|
};
|
||||||
write!(f, "CodecId({name})")
|
write!(f, "CodecId({name})")
|
||||||
@@ -658,6 +678,7 @@ impl CodecId {
|
|||||||
0 => Some(CODEC_ID_NONE),
|
0 => Some(CODEC_ID_NONE),
|
||||||
3 => Some(CODEC_ID_REMOTEFX),
|
3 => Some(CODEC_ID_REMOTEFX),
|
||||||
0x0A => Some(CODEC_ID_QOI),
|
0x0A => Some(CODEC_ID_QOI),
|
||||||
|
0x0B => Some(CODEC_ID_QOIZ),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,6 +721,7 @@ fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result<HashMap<&'a str, boo
|
|||||||
///
|
///
|
||||||
/// * `remotefx` (on by default)
|
/// * `remotefx` (on by default)
|
||||||
/// * `qoi` (on by default, when feature "qoi")
|
/// * `qoi` (on by default, when feature "qoi")
|
||||||
|
/// * `qoiz` (on by default, when feature "qoiz")
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
@@ -711,6 +733,7 @@ pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
|
|||||||
List of codecs:
|
List of codecs:
|
||||||
- `remotefx` (on by default)
|
- `remotefx` (on by default)
|
||||||
- `qoi` (on by default, when feature "qoi")
|
- `qoi` (on by default, when feature "qoi")
|
||||||
|
- `qoiz` (on by default, when feature "qoiz")
|
||||||
"#
|
"#
|
||||||
.to_owned());
|
.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(", ");
|
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
|
||||||
if !codec_names.is_empty() {
|
if !codec_names.is_empty() {
|
||||||
return Err(format!("Unknown codecs: {codec_names}"));
|
return Err(format!("Unknown codecs: {codec_names}"));
|
||||||
@@ -760,6 +791,7 @@ List of codecs:
|
|||||||
///
|
///
|
||||||
/// * `remotefx` (on by default)
|
/// * `remotefx` (on by default)
|
||||||
/// * `qoi` (on by default, when feature "qoi")
|
/// * `qoi` (on by default, when feature "qoi")
|
||||||
|
/// * `qoiz` (on by default, when feature "qoiz")
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
@@ -771,6 +803,7 @@ pub fn server_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
|
|||||||
List of codecs:
|
List of codecs:
|
||||||
- `remotefx` (on by default)
|
- `remotefx` (on by default)
|
||||||
- `qoi` (on by default, when feature "qoi")
|
- `qoi` (on by default, when feature "qoi")
|
||||||
|
- `qoiz` (on by default, when feature "qoiz")
|
||||||
"#
|
"#
|
||||||
.to_owned());
|
.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(", ");
|
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
|
||||||
if !codec_names.is_empty() {
|
if !codec_names.is_empty() {
|
||||||
return Err(format!("Unknown codecs: {codec_names}"));
|
return Err(format!("Unknown codecs: {codec_names}"));
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ doctest = true
|
|||||||
test = false
|
test = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["rayon", "qoi"]
|
default = ["rayon", "qoi", "qoiz"]
|
||||||
helper = ["dep:x509-cert", "dep:rustls-pemfile"]
|
helper = ["dep:x509-cert", "dep:rustls-pemfile"]
|
||||||
rayon = ["dep:rayon"]
|
rayon = ["dep:rayon"]
|
||||||
qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"]
|
qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"]
|
||||||
|
qoiz = ["dep:zstd-safe", "qoi", "ironrdp-pdu/qoiz"]
|
||||||
|
|
||||||
# Internal (PRIVATE!) features used to aid testing.
|
# Internal (PRIVATE!) features used to aid testing.
|
||||||
# Don't rely on these whatsoever. They may disappear at any time.
|
# 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"
|
bytes = "1"
|
||||||
visibility = { version = "0.1", optional = true }
|
visibility = { version = "0.1", optional = true }
|
||||||
qoicoubeh = { version = "0.5", optional = true }
|
qoicoubeh = { version = "0.5", optional = true }
|
||||||
|
zstd-safe = { version = "7.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::num::NonZeroU16;
|
use core::num::NonZeroU16;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use ironrdp_acceptor::DesktopSize;
|
use ironrdp_acceptor::DesktopSize;
|
||||||
use ironrdp_graphics::diff::{find_different_rects_sub, Rect};
|
use ironrdp_graphics::diff::{find_different_rects_sub, Rect};
|
||||||
use ironrdp_pdu::encode_vec;
|
use ironrdp_pdu::encode_vec;
|
||||||
@@ -35,6 +36,8 @@ pub(crate) struct UpdateEncoderCodecs {
|
|||||||
remotefx: Option<(EntropyBits, u8)>,
|
remotefx: Option<(EntropyBits, u8)>,
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
qoi: Option<u8>,
|
qoi: Option<u8>,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
qoiz: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateEncoderCodecs {
|
impl UpdateEncoderCodecs {
|
||||||
@@ -44,6 +47,8 @@ impl UpdateEncoderCodecs {
|
|||||||
remotefx: None,
|
remotefx: None,
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
qoi: None,
|
qoi: None,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
qoiz: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +62,12 @@ impl UpdateEncoderCodecs {
|
|||||||
pub(crate) fn set_qoi(&mut self, qoi: Option<u8>) {
|
pub(crate) fn set_qoi(&mut self, qoi: Option<u8>) {
|
||||||
self.qoi = qoi
|
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 {
|
impl Default for UpdateEncoderCodecs {
|
||||||
@@ -94,6 +105,10 @@ impl UpdateEncoder {
|
|||||||
if let Some(id) = codecs.qoi {
|
if let Some(id) = codecs.qoi {
|
||||||
bitmap = BitmapUpdater::Qoi(QoiHandler::new(id));
|
bitmap = BitmapUpdater::Qoi(QoiHandler::new(id));
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
if let Some(id) = codecs.qoiz {
|
||||||
|
bitmap = BitmapUpdater::Qoiz(QoizHandler::new(id));
|
||||||
|
}
|
||||||
|
|
||||||
bitmap
|
bitmap
|
||||||
} else {
|
} else {
|
||||||
@@ -306,6 +321,8 @@ enum BitmapUpdater {
|
|||||||
RemoteFx(RemoteFxHandler),
|
RemoteFx(RemoteFxHandler),
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
Qoi(QoiHandler),
|
Qoi(QoiHandler),
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
Qoiz(QoizHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitmapUpdater {
|
impl BitmapUpdater {
|
||||||
@@ -316,6 +333,8 @@ impl BitmapUpdater {
|
|||||||
Self::RemoteFx(up) => up.handle(bitmap),
|
Self::RemoteFx(up) => up.handle(bitmap),
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
Self::Qoi(up) => up.handle(bitmap),
|
Self::Qoi(up) => up.handle(bitmap),
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
Self::Qoiz(up) => up.handle(bitmap),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,28 +464,85 @@ impl QoiHandler {
|
|||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
impl BitmapUpdateHandler for QoiHandler {
|
impl BitmapUpdateHandler for QoiHandler {
|
||||||
fn handle(&mut self, bitmap: &BitmapUpdate) -> Result<UpdateFragmenter> {
|
fn handle(&mut self, bitmap: &BitmapUpdate) -> Result<UpdateFragmenter> {
|
||||||
use ironrdp_graphics::image_processing::PixelFormat::*;
|
let data = qoi_encode(bitmap)?;
|
||||||
|
|
||||||
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()?;
|
|
||||||
set_surface(bitmap, self.codec_id, &data)
|
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> {
|
fn set_surface(bitmap: &BitmapUpdate, codec_id: u8, data: &[u8]) -> Result<UpdateFragmenter> {
|
||||||
let destination = ExclusiveRectangle {
|
let destination = ExclusiveRectangle {
|
||||||
left: bitmap.x,
|
left: bitmap.x,
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ impl RdpServerOptions {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|codec| matches!(codec.property, CodecProperty::Qoi))
|
.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)]
|
#[derive(Clone)]
|
||||||
@@ -754,6 +762,10 @@ impl RdpServer {
|
|||||||
CodecProperty::Qoi if self.opts.has_qoi() => {
|
CodecProperty::Qoi if self.opts.has_qoi() => {
|
||||||
update_codecs.set_qoi(Some(codec.id));
|
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]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"]
|
qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"]
|
||||||
|
qoiz = ["dep:zstd-safe", "qoi"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public
|
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" }
|
ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.3" }
|
||||||
tracing = { version = "0.1", features = ["log"] }
|
tracing = { version = "0.1", features = ["log"] }
|
||||||
qoicoubeh = { version = "0.5", optional = true }
|
qoicoubeh = { version = "0.5", optional = true }
|
||||||
|
zstd-safe = { version = "7.2", optional = true, features = ["std"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ pub struct Processor {
|
|||||||
mouse_pos_update: Option<(u16, u16)>,
|
mouse_pos_update: Option<(u16, u16)>,
|
||||||
no_server_pointer: bool,
|
no_server_pointer: bool,
|
||||||
pointer_software_rendering: bool,
|
pointer_software_rendering: bool,
|
||||||
|
#[cfg(feature = "qoiz")]
|
||||||
|
zdctx: zstd_safe::DCtx<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
@@ -363,20 +365,34 @@ impl Processor {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "qoi")]
|
#[cfg(feature = "qoi")]
|
||||||
ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOI => {
|
ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOI => {
|
||||||
let (header, decoded) = qoi::decode_to_vec(bits.extended_bitmap_data.data)
|
qoi_apply(
|
||||||
.map_err(|e| reason_err!("QOI decode", "{}", e))?;
|
image,
|
||||||
match header.channels {
|
destination,
|
||||||
qoi::Channels::Rgb => {
|
bits.extended_bitmap_data.data,
|
||||||
let rectangle = image.apply_rgb24(&decoded, &destination, false)?;
|
&mut update_rectangle,
|
||||||
|
)?;
|
||||||
update_rectangle = update_rectangle
|
}
|
||||||
.map(|rect: InclusiveRectangle| rect.union(&rectangle))
|
#[cfg(feature = "qoiz")]
|
||||||
.or(Some(rectangle));
|
ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOIZ => {
|
||||||
}
|
let compressed = &bits.extended_bitmap_data.data;
|
||||||
qoi::Channels::Rgba => {
|
let mut input = zstd_safe::InBuffer::around(compressed);
|
||||||
warn!("Unsupported RGBA QOI data");
|
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);
|
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 struct ProcessorBuilder {
|
||||||
pub io_channel_id: u16,
|
pub io_channel_id: u16,
|
||||||
pub user_channel_id: u16,
|
pub user_channel_id: u16,
|
||||||
@@ -421,6 +461,8 @@ impl ProcessorBuilder {
|
|||||||
mouse_pos_update: None,
|
mouse_pos_update: None,
|
||||||
no_server_pointer: self.no_server_pointer,
|
no_server_pointer: self.no_server_pointer,
|
||||||
pointer_software_rendering: self.pointer_software_rendering,
|
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"]
|
default = ["panic_hook"]
|
||||||
panic_hook = ["iron-remote-desktop/panic_hook"]
|
panic_hook = ["iron-remote-desktop/panic_hook"]
|
||||||
qoi = ["ironrdp/qoi"]
|
qoi = ["ironrdp/qoi"]
|
||||||
|
qoiz = ["ironrdp/qoiz"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Protocols
|
# Protocols
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ rdpdr = ["dep:ironrdp-rdpdr"]
|
|||||||
rdpsnd = ["dep:ironrdp-rdpsnd"]
|
rdpsnd = ["dep:ironrdp-rdpsnd"]
|
||||||
displaycontrol = ["dep:ironrdp-displaycontrol"]
|
displaycontrol = ["dep:ironrdp-displaycontrol"]
|
||||||
qoi = ["ironrdp-server?/qoi", "ironrdp-pdu?/qoi", "ironrdp-connector?/qoi", "ironrdp-session?/qoi"]
|
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.
|
# Internal (PRIVATE!) features used to aid testing.
|
||||||
# Don't rely on these whatsoever. They may disappear at any time.
|
# Don't rely on these whatsoever. They may disappear at any time.
|
||||||
__bench = ["ironrdp-server/__bench"]
|
__bench = ["ironrdp-server/__bench"]
|
||||||
|
|||||||
Reference in New Issue
Block a user