fix(cursor): encode macOS cursor alpha correctly in Sciter

This commit is contained in:
fufesou
2026-09-12 20:10:57 +08:00
parent 8dfff101f8
commit bbf86259e9
2 changed files with 90 additions and 2 deletions

View File

@@ -1,7 +1,10 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{atomic::AtomicUsize, Arc, Mutex, RwLock},
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
};
use sciter::{
@@ -28,6 +31,8 @@ use crate::{
type Video = AssetPtr<video_destination>;
mod cursor;
lazy_static::lazy_static! {
static ref VIDEO: Arc<Mutex<Option<Video>>> = Default::default();
}
@@ -39,6 +44,8 @@ lazy_static::lazy_static! {
pub struct SciterHandler {
element: Arc<Mutex<Option<Element>>>,
close_state: HashMap<String, String>,
// The I/O session clones its UI handler, so share the peer's pixel format.
peer_is_macos: Arc<AtomicBool>,
}
impl SciterHandler {
@@ -122,7 +129,7 @@ impl SciterHandler {
impl InvokeUiSession for SciterHandler {
fn set_cursor_data(&self, cd: CursorData) {
let mut colors = hbb_common::compress::decompress(&cd.colors);
let mut colors = cursor::png_colors(&cd, self.peer_is_macos.load(Ordering::Relaxed));
if colors.iter().filter(|x| **x != 0).next().is_none() {
log::info!("Fix transparent");
// somehow all 0 images shows black rect, here is a workaround
@@ -301,6 +308,8 @@ impl InvokeUiSession for SciterHandler {
}
fn set_peer_info(&self, pi: &PeerInfo) {
self.peer_is_macos
.store(pi.platform == "Mac OS", Ordering::Relaxed);
let mut pi_sciter = Value::map();
pi_sciter.set_item("username", pi.username.clone());
pi_sciter.set_item("hostname", pi.hostname.clone());

79
src/ui/remote/cursor.rs Normal file
View File

@@ -0,0 +1,79 @@
use base::message_proto::CursorData;
const CHANNELS: usize = 4;
const ALPHA: usize = CHANNELS - 1;
const MAX_CHANNEL: u16 = u8::MAX as u16;
pub(super) fn png_colors(cursor: &CursorData, is_macos: bool) -> Vec<u8> {
let mut colors = hbb_common::compress::decompress(&cursor.colors);
// New macOS packets are premultiplied for Flutter, but PNG stores straight
// colors. Legacy macOS packets (scale zero) already contain straight RGBA.
if !(is_macos && cursor.scale > 0.0) {
return colors;
}
for pixel in colors.chunks_exact_mut(CHANNELS) {
let alpha = u16::from(pixel[ALPHA]);
if alpha == 0 {
continue;
}
for channel in &mut pixel[..ALPHA] {
// Capture can round RGB and alpha differently by one channel value.
*channel =
((u16::from(*channel) * MAX_CHANNEL + alpha / 2) / alpha).min(MAX_CHANNEL) as u8;
}
}
colors
}
#[cfg(test)]
mod tests {
use super::*;
const STRAIGHT: [u8; 16] = [
255, 255, 255, 128, 128, 64, 32, 128, 240, 100, 20, 255, 0, 0, 0, 0,
];
const PREMULTIPLIED: [u8; 16] = [
128, 128, 128, 128, 64, 32, 16, 128, 240, 100, 20, 255, 0, 0, 0, 0,
];
fn encode_packet(pixels: &[u8], scale: f64, is_macos: bool) -> Vec<u8> {
let cursor = CursorData {
width: 4,
height: 1,
colors: hbb_common::compress::compress(pixels).into(),
scale,
..Default::default()
};
let colors = png_colors(&cursor, is_macos);
let mut png = Vec::new();
repng::encode(&mut png, cursor.width as _, cursor.height as _, &colors).unwrap();
assert_eq!(hbb_common::compress::decompress(&cursor.colors), pixels);
image::load_from_memory_with_format(&png, image::ImageFormat::Png)
.unwrap()
.to_rgba8()
.into_raw()
}
#[test]
fn macos_density_packets_encode_straight_png_colors() {
for scale in [1.0, 2.0] {
let actual = encode_packet(&PREMULTIPLIED, scale, true);
for (got, expected) in actual
.chunks_exact(CHANNELS)
.zip(STRAIGHT.chunks_exact(CHANNELS))
{
assert_eq!(got[ALPHA], expected[ALPHA]);
for channel in 0..ALPHA {
assert!(got[channel].abs_diff(expected[channel]) <= 1, "{actual:?}");
}
}
}
}
#[test]
fn legacy_and_non_macos_packets_keep_their_colors() {
for (scale, is_macos) in [(0.0, true), (0.0, false), (2.0, false)] {
assert_eq!(encode_packet(&STRAIGHT, scale, is_macos), STRAIGHT);
}
}
}