1 个不稳定版本
0.1.0 | 2023年8月18日 |
---|
#685 in 图像
3.5MB
1K SLoC
图像效果
该项目实际上是一个应用于图像的库。它大量使用了image
crate,因为它是Rust的主要图像处理库,以及palette
库,用于辅助颜色转换等——尽管该项目最初使用了一个定制的颜色库。
目前有两个主要类别的效果,抖动,它适用于图像并支持< strong>有序 strong>和< strong>错误传播 strong>方法,以及< strong>过滤器 strong>,如亮度、对比度、饱和度等,这些过滤器适用于单个像素(因此也适用于图像)。
以下展示了哪些效果适用于哪些类型。
dither | filter | |
---|---|---|
pixel |
no | yes |
image |
yes | yes |
gif |
yes | yes |
pixel
=[u8; 3]
/[u8; 4]
image
=Vec<Vec<{pixel}>>
/ (image)DynamicImage
/ (image)ImageBuffer<Rgb(a)<u8>, Vec<u8>>
gif
= (image)Frame
(image): 来自 image
crate。
抖动
方法
由于每次都需要指定黑色和白色调色板,所以将< strong>1位 strong>抖动单独列出是为了方便。内部实际上只是调用了带有< strong>1位 strong>调色板的常规错误传播函数。
拜耳和< strong>基本 strong>由于工作方式不同,在实现上与其他分开。 拜耳 strong>,也称为< em>有序抖动 em>,不传播任何错误,只是使用矩阵在原地操纵每个像素。 基本 strong>是一个< em>天真 em>的实现,只是将错误传播到右边。
所有其他算法都是通过使用不同的参数来对 误差传播 进行变体。因此,实现是通过创建一个通用的误差传播函数来工作的,该函数接受
- 一个
list
的传播偏移量,即错误发送的位置和 增加的量。例如,如果一个矩阵提供 30 个分割,(1, 0, 5)
将指向 下一个 像素(x+1, y)
并发送 5/30 的错误。 - 分割的数量。大多数算法将错误完全分割,但一些,如 Atkison,只传播 一些。
在此之后,每个算法都通过宏有效地生成。
存在两种类型的抖动:误差传播 和 有序/Bayer。它们之间的唯一相似之处在于它们都会逐像素进行操作,将像素转换成调色板中的最接近匹配。测量哪个颜色是最接近的匹配是在不同的模块 colour
中完成的 - 更多详情 这里。
误差传播
对于这些算法,我要感谢 Tanner Helland 和 Efron Licht 提供的资源。Tanner 写了一篇关于误差传播的文章,包括多种帮助理解这个过程的方法,而 Efron 编写了 dither
crate,这为这个项目提供了灵感。
所以,对于 误差传播 算法,在它们找到最接近的匹配后,将计算 误差 - 这实际上是所选颜色和实际颜色之间的 RGB
差异。然后,根据所谓的 传播矩阵 和 部分量 将此错误传播到附近的像素。
传播矩阵 更像是一个坐标列表,除了要传播多少误差外,还以 (dx, dy, portion)
的形式。例如,(1, 0, 5)
将 $\frac{5}{N}$ 的错误发送到下一个右边的像素,其中 $N$ 是 部分量。请注意,错误 不需要 准确分布 - 例如 Atkinson 使用 8 个部分,但只传播 6 个。技术上你也可以 过度传播,但这只是给像素添加额外的错误。
有序 / Bayer
这个算法工作方式非常不同,开始更深入地探讨数学。例如,以下是整个算法的一部分
$$ c' = \textrm{nearest_palette_color}(c + r \times (M(x \textrm{ mod } y, y \textrm{ mod } n) - 1/2)) $$
在这里,$c'$ 是新的颜色,$M$ 是 阈值映射,而 $r$ 是颜色空间中的扩散量。我使用 $r = \frac{255}{3}$ - 但是在代码中它是 $\frac{1}{3}$,因为 rgb 值在 0.0
和 1.0
之间使用。如果诚实地说,我不完全记得我从哪里得到了这个 $r$ 值,但它似乎效果相当不错。
至于 阈值映射,可以预先计算 - 因为那里唯一的变量是矩阵大小,它通常是2的幂。更多关于这一点,请查看 有序抖动的维基百科页面。它们可以预先计算,但这个库支持 任何任意大小。
算法
目前支持以下抖动算法
名称 | 1位 | RGB(Web安全) | RGB(8位) |
---|---|---|---|
Floyd-Steinberg | ![]() |
![]() |
![]() |
Jarvis-Judice-Ninke | ![]() |
![]() |
![]() |
Stucki | ![]() |
![]() |
![]() |
Atkinson | ![]() |
![]() |
![]() |
Burkes | ![]() |
![]() |
![]() |
Sierra | ![]() |
![]() |
![]() |
SierraTwoRow | ![]() |
![]() |
![]() |
SierraLite | ![]() |
![]() |
![]() |
Bayer 2x2 | ![]() |
![]() |
![]() |
Bayer 4x4 | ![]() |
![]() |
![]() |
Bayer 8x8 | ![]() |
![]() |
![]() |
Bayer 16x16 | ![]() |
![]() |
![]() |
过滤器
方法
对于颜色,某些过滤器,如 亮度、饱和度、色调旋转,首先将每个 RGB 像素映射到 HSL 或 LCH。最初,由于计算简单,使用了 HSL。然而,LCH 在表示其各个组成部分方面更为准确。
然而,RGB -> LCH
的计算比 RGB -> HSL
更多。目前代码需要您更改它以使用正确的像素,但可能值得考虑允许用户使用 HSL 以获得最大速度。
算法
目前支持以下效果
名称 | 图像 |
---|---|
增加亮度 +0.2 | ![]() |
降低亮度 -0.2 | ![]() |
增加饱和度 +0.2 | ![]() |
降低饱和度 -0.2 | ![]() |
对比度 0.5 | ![]() |
对比度 1.5 | ![]() |
渐变映射 | ![]() |
旋转色调 180 | ![]() |
量化色调 | ![]() |
参数的尺度如下
- 增加亮度:取
-1.0
到1.0
。正数增强亮度,1.0 将其设置为最大亮度 - 反之亦然。 - 增加饱和度:与 增加亮度 相同 - 假设最大色度为 128 以便于缩放。(与
palette
的文档匹配) - 对比度:接受任何浮点数。
x > 1.0
增加对比度。x > 0.0 且 x < 1.0
降低对比度。- 负对比度 遵循相似的尺度,其中
-1.0
与1.0
相同 - 但每个颜色通道都被反转。 - 可能 很有趣尝试在其它空间中进行对比度计算... 这是我需要考虑的事情。
其他任何东西都有特定的类型(量化色调
、渐变映射
),或者按预期工作(旋转色调
)
颜色
曾经有一段时间,我开始手动实现所有颜色空间及其之间的转换。为此还专门编写了一个子库,但后来我发现有人比我先做了这件事,而且做得更好好多了,于是我就把所有的代码都扔掉了。
如果我不是一个收藏家,我就会这么说。
所有这些代码现在都在colour-exercise-rs
上——从RGB
到HSL
,再到LAB
和OKLCH,还有更多——从技术上讲,作为一个库并不是很有用,因为直接使用
palette
会更好,但如果你对颜色感兴趣,并且认为看到别人在学习过程中摸索可能会有所帮助,那么欢迎你来看看!
不过,那段代码的一个残余部分仍然存在。即colour/comparisons.rs
实现了多个距离函数。
colour/comparisons.rs
方法
目前使用的颜色距离函数是加权欧几里得
,其公式如下:
$$ f(R, G, B) = \begin{cases} \sqrt{2\Delta R^2 + 4\Delta G^2 + 3\Delta B^2} & \overline{R} < 128, \ \sqrt{3\Delta R^2 + 4\Delta G^2 + 2\Delta B^2} & \textrm{otherwise}, \end{cases} $$
这个库还有其他距离函数,例如cie76
、cie94
和ciede2000
。之所以使用加权欧几里得
,主要是因为效率更高,并且被认为足够好。不过,代码应该很容易修改以使用其他函数,所以使用的函数可能会改变或变成一个选项。
特别是ciede2000
非常复杂,可能慢一个数量级。它可能变得更高效,但就目前而言,它有点不切实际。
依赖项
~16MB
~132K SLoC