Repository navigation
Display Scaling
这一页解决一个具体的用户可见问题:「截图选区里很清楚,贴出来是糊的。」
GTK3 不支持 wp_fractional_scale_v1。在分数缩放的桌面上:
-
Monitor::scale_factor()报告的是整数缓冲区缩放,不是真实缩放 - 实测:两块真实缩放为 1.3333 和 1.5 的屏,都报 2
-
devicePixelRatio顶替不了,它就是那个整数 2
后果:1.5 倍缩放的桌面上,WebKit 只拿到缓冲区缩放 2。按原物理尺寸显示就要先被平滑放大 4/3,再被合成器缩回 3/4。头一趟就把细节抹平了。
screenshot::desktop_scale_at 是本项目唯一对外的「真实缩放」查询。要把图片像素换算成 CSS 像素,必须问合成器,不能用 scale_factor()。
真实缩放与缓冲区缩放都由后端查好,随 payload 下发给前端。
pin/resample.rs 直接按缓冲区分辨率出图,并预先补偿合成器那一步缩小。
合成器的重采样核是脉冲实测出来的:缓冲区里一个孤立白点,在屏上只留下一个 177/255 的点。
177/255 ≈ 0.694 ≈ 0.8333²
即标准双线性。知道了核,就能反过来问:「这张缓冲区图被缩完,等于原图吗?」不等于就把残差加回去。
Lanczos3 预放大作初值
→ 前向双线性模拟合成器缩小
→ 计算残差
→ 回投影加回
→ 共 4 轮
| 方案 | PSNR |
|---|---|
| 默认平滑 | 30.28 dB |
image-rendering: pixelated |
33.95 dB |
| Lanczos 预放大 | 34.73 dB |
| 反投影预补偿 | 43.02 dB |
生产实现以 Q7 定点保留子像素精度。Lanczos 只缓存当前垂直核需要的行,双线性前向与回投影按行运行,不保留多份全图 f32 RGBA。
64 MiB 是每个 RGBA 平面的硬预算,不是进程总内存上限。 最坏工作集还包含 Q7 缓冲(两字节/通道)、i16 残差、解码原图、可选屏显目标和最终 PNG。
这个预算可容纳 4K 原生屏在 150% 真实缩放 / 200% 缓冲区缩放下的 5120×2880 补偿图。超限会明确记录失败并回退原图,不在几何阶段静默跳过。
| 尺寸 | 耗时(release) |
|---|---|
| 3413×1920 | 约 614 ms |
| 5120×2880 | 约 1.28 s |
直接测试进程的瞬时总峰值 RSS 354 MiB,空测试壳基线 6.6 MiB,补偿增量约 347 MiB。
补偿一直在后台运行并且全局串行:多张贴图不会叠加工作集或阻塞开窗。关闭窗口后还在排队的补偿会在解码前取消。
赶上首帧的补偿随第一份 payload 下发;没赶上的走事件换图。
复制与保存永远用原图,不用补偿图——补偿只为屏幕显示服务。
后端补偿是主路径,前端 react/pin/rendering.ts 只保留两条判据决定 image-rendering:
| 判据 | 条件 | 结果 |
|---|---|---|
isPixelExact |
显示尺寸 × 真实缩放 == 图片像素数 | 最近邻 |
isBufferExact |
图片已是缓冲区分辨率 | 1:1 搬入,任何滤镜都不该介入 |
全屏贴图会被内容尺寸缩小,而 scale 仍是 1。那种情况用最近邻反而差 4.5 dB。
判据必须基于「实际像素数是否整数对应」,不是基于某个缩放变量的值。
列表行的 48 CSS px 缩略图用 image crate 的 thumbnail() 快路径,不是 Lanczos,也不做补偿。后端缩到最长边 128 px 并按 id 缓存。
小图上补偿的收益不足以抵消开销——优化要按尺寸分级,不是一套算法用到底。