还是上回魔改的 Gnome 截图软件,这回我加了个改进。原版保存截图文件时默认保存为透明背景 png,而透明 png 往往会被使用黑色背景来显示,因而完全无法显示出无色阴影,alpha 通道也失去了存在的意义。最好的解决办法就是把图像转成 rgb 的白背景 jpg。其实 imagemagick 这类软件也能完成转换,但本应该一步到位的功能还依赖外部软件,徒增操作复杂度。因此我决定把转换过程内嵌到截图软件中。

一些简单概念

  • RGBA,半透明 png 图片的像素格式即为 R(red) G(green) B(blue) A(alpha),RGB 为三基色,alpha 代表不透明度。css 中也有类似概念,似乎使用一个百分比来表示 alpha;

  • 色深,计算机用数值表达颜色,数值的位数越高,表达颜色越精确。RGB 色彩模型一般使用 24 位存储图片,R、G、B 各占 8 位。32 位是在 24 位的基础上再加上 8 位的 alpha 透明通道;

  • 多数 CPU 上的除法运算远慢于加法或乘法运算,一定要尽量避免除法运算;

RGBA 转 RGB 的算法

假设一个 32 位 rgba 像素 p,数据为 (red, green, blue, alpha),现在需要把图片转换成背景色为 (red_1, green_1, blue_1) 的 24 位 rgb 图片,那么原来的像素 p 需要和背景色进行一下运算转换成 rgb 像素,假设新的像素 p2 :(red_2, green_2, blue_2),那么有

red_2   = (1 - alpha/255) * red_1 + alpha/255 * red;
green_2 = (1 - alpha/255) * green_1 + alpha/255 * green;
blue_2  = (1 - alpha/255) * blue_1 + alpha/255 * blue;

然而实际代码并不能这么写。首先是除法,255 并不是 2 的 n 次幂,整数除以 255 编译器并不能将其处理成移位运算,会显著拖慢大量像素的转换运算;其次是精度问题,浮点数的二进制和十进制之间转换存在误差,上式中整数反复乘除,整型双精度型反复转换,能造成相当大的误差。

除法可用乘法和移位替代,例如 x / 255 可用 (x >> 8) * (265/255) 替代;尽量提取公因式,合并成整型的运算能减小误差,如下

double rate = 1.003921569;    // 256 除以 255
red_2   = (((255 - alpha) * red_1 + alpha * red) >> 8) * rate + 0.5;
green_2 = (((255 - alpha) * green_1 + alpha * green) >> 8) * rate + 0.5;
blue_2  = (((255 - alpha) * blue_1 + alpha * blue) >> 8) * rate + 0.5;

其中 rate 作为常数,实际代码中在循环外定义,最后加上 0.5 是为了在双精度型转整型时四舍五入。至此,单个像素的 RGBA 转 RGB 就完成了。

如何使用

GitHub 仓库在此,已合并上游最新版。Arch Linux 可用 AUR 安装,包名为 gnome-screenshot-heavy-shadow,其他发行版参照 GitHub 仓库内说明。

我给它增加了可选 -t 选项,使用窗口截图时不带 -t 选项,截图保存为白背景 jpg,带上 -t 选项则仍然保存为透明背景 png。另外 gnome-screenshot -i 交互模式中也添加了相应的选项。Wayland 下目前无法保存到剪贴板,这是桌面环境的锅,并不是这里的 bug,但交互模式下的保存到剪贴板仍可用。