Wikipedia article on color difference 值得一读,其中链接的 CompuPhase 的article on a “low-cost approximation” 也是如此。我将基于后者的尝试。
您没有指定语言,所以我将用 未 优化的 Python 编写它(参考文章中已经存在的整数优化除外),以便它易于翻译翻译成其他语言。
n_colors = 25
n_global_moves = 32
class Color:
max_weighted_square_distance = (((512 + 127) * 65025) >> 8) + 4 * 65025 + (((767 - 127) * 65025) >> 8)
def __init__(self, r, g, b):
self.r, self.g, self.b = r, g, b
def weighted_square_distance(self, other):
rm = (self.r + other.r) // 2 # integer division
dr = self.r - other.r
dg = self.g - other.g
db = self.b - other.b
return (((512 + rm) * dr*dr) >> 8) + 4 * dg*dg + (((767 - rm) * db*db) >> 8)
def min_weighted_square_distance(self, index, others):
min_wsd = self.max_weighted_square_distance
for i in range(0, len(others)):
if i != index:
wsd = self.weighted_square_distance(others[i])
if min_wsd > wsd:
min_wsd = wsd
return min_wsd
def is_valid(self):
return 0 <= self.r <= 255 and 0 <= self.g <= 255 and 0 <= self.b <= 255
def add(self, other):
return Color(self.r + other.r, self.g + other.g, self.b + other.b)
def __repr__(self):
return f"({self.r}, {self.g}, {self.b})"
colors = [Color(127, 127, 127) for i in range(0, n_colors)]
steps = [Color(dr, dg, db) for dr in [-1, 0, 1]
for dg in [-1, 0, 1]
for db in [-1, 0, 1] if dr or dg or db] # i.e., except 0,0,0
moved = True
global_move_phase = False
global_move_count = 0
while moved or global_move_phase:
moved = False
for index in range(0, len(colors)):
color = colors[index]
if global_move_phase:
best_min_wsd = -1
else:
best_min_wsd = color.min_weighted_square_distance(index, colors)
for step in steps:
new_color = color.add(step)
if new_color.is_valid():
new_min_wsd = new_color.min_weighted_square_distance(index, colors)
if best_min_wsd < new_min_wsd:
best_min_wsd = new_min_wsd
colors[index] = new_color
moved = True
if not moved:
if global_move_count < n_global_moves:
global_move_count += 1
global_move_phase = True
else:
global_move_phase = False
print(f"n_colors: {n_colors}")
print(f"n_global_moves: {n_global_moves}")
print(colors)
首先将颜色全部设置为灰色,即放置在 RGB 颜色立方体的中心,然后在颜色立方体中移动,以便希望最大化颜色之间的最小距离。
为了节省 CPU 时间,使用距离的平方而不是距离本身,这需要计算平方根。
颜色一次移动一种,在 3 个方向的每个方向上最多移动 1 个颜色,移动到与其他颜色的最小距离最大的相邻颜色之一。通过这样做,全局最小距离(大约)最大化。
需要“全局移动”阶段来克服没有颜色移动的情况,但是强制所有颜色移动到一个不比当前位置差多少的位置会导致整体找到一个更好的配置随后的常规动作。这可以用3种颜色和没有全局移动最好看,将加权平方距离修改为简单的rd*rd+gd*gd+bd*bd:配置变为
[(2, 0, 0), (0, 253, 255), (255, 255, 2)]
同时,通过添加 2 个全局移动,配置成为预期的配置
[(0, 0, 0), (0, 255, 255), (255, 255, 0)]
该算法仅产生几种可能的解决方案之一。不幸的是,由于使用的度量不是欧几里得,不可能简单地翻转 8 种可能组合中的 3 维(即,替换 r→255-r 和/或相同的 g 和/或 b ) 以获得等效的解决方案。最好按照尝试颜色移动步骤的顺序引入随机性,并改变随机种子。
我没有对显示器的gamma进行校正,因为它的目的正是改变亮度的间距,以补偿眼睛在高亮度和低亮度下的不同灵敏度。当然,屏幕 gamma 曲线会偏离理想值,对 gamma 进行(取决于系统!)修改会产生更好的结果,但标准 gamma 是一个很好的起点。
这是 25 种颜色算法的输出:
请注意,前 8 种颜色(底行和上行的前 3 种颜色)靠近 RGB 立方体的角(它们不是 在角,因为非-欧几里得度量)。