提问人:Alex Lopez 提问时间:11/8/2023 最后编辑:Alex Lopez 更新时间:11/10/2023 访问量:96
如何找到 4 种互补的 24 位 RGB 颜色,没有重叠位?
How do I find 4 complementary 24bit RGB colors with no overlapping bits?
问:
我正在寻找四种颜色,它们具有可区分性,在某种程度上互补/视觉上吸引人,并且没有共同点。我的意思是如下:
- 仅考虑 u32 的前 24 位,并使用它们来表示 RGB 颜色。
- 以这种方式选择四种颜色。
- 对于以下按位属性,颜色不应具有共同的位。
例如,如果我们将这些颜色视为唯一标识符,那么我们应该能够以有趣的方式将这些颜色混合在一个无符号的 32 位整数中。 表示与 不同的颜色,依此类推。四种颜色中的每一种都是独一无二的,每一种可能的组合都是独一无二的。COLOR_1
COLOR_1 | COLOR_2
但是,我有一个额外的约束,即这四种颜色中不能有两个位重叠。这意味着如果我们有以下 u32 ,那么以下操作应该为 true: .这相当于从整数中删除,而不会影响属于 和 的任何位。let mix: u32 = COLOR_1 | COLOR_2 | COLOR_3;
(mix & !COLOR_1) == COLOR_2 | COLOR_3
COLOR_1
COLOR_2
COLOR_3
我认为探索所有可能的四分体将是一个好的开始(这是四分体的图片,也由这个圆圈上的四个点表示)。但是,我不确定如何适当地探索 RGB 色彩空间以解决我的问题。
有没有办法用我正在寻找的特性来获得接近四分体的东西?或者也许还有另一种色彩理论方法?总体要求如下:
- 4 RGB 颜色以 u32 的低阶 24 位表示,因此没有两种颜色共享任何共同位。
- 接近彼此成 90 度角的四分点的颜色。这不是一个严格的要求,因为我只寻找四种颜色,这些颜色很容易与点 1 的按位属性区分开来。
- 亮度不是一个大问题,但颜色在黑色背景下应该是可见的。
更新:我删除了我的代码,因为它错误地寻求解决方案并分散了下面提供的出色答案的注意力。
答:
从数学上讲,这似乎出奇的棘手。我是否理解您是对的,您正在寻找 4 种颜色:
- 在基于字节的 RGB 中,对两种颜色进行位处理始终为 0
- 在 HSL 色彩空间中彼此成 90°(或 180°)角
- 颜色应该是彩色的/具有高色度(即 HSL 中的亮度为 0.5 和饱和度 1)
第一个是硬性要求,第二个和第三个可能没有那么多。
我不知道如何想出一套这样的颜色。你可以暴力破解它,因为“只有”~10¹⁶·⁸ 的可能性,但让我们尝试一些更有趣的东西:
您只需取 24 个 1 位并将它们随机分配给 4 种颜色中的一种,就可以随机生成一组 4 种颜色:
fn generate_candidate(&mut self) -> [u32; 4] {
let mut rng = thread_rng();
let mut colorbits = [0u32; 4];
for i in 0..24 {
*colorbits.choose_mut(&mut rng).unwrap() |= 1 << i;
}
colorbits
}
// Yeah, this can't generate all possible combinations, it'll never have less than 24 1-bits. I don't think having less is useful.
您还可以从现有候选者中生成新的相似候选者,方法是将一位移动到不同的颜色
fn tweak_candidate(&mut self, candidate: &[u32; 4]) -> [u32; 4] {
let mut rng = thread_rng();
let mut new = candidate.clone();
let bit = rng.gen_range(0..24);
// unset bit everywhere
for c in &mut new {
*c = *c & !(1 << bit);
}
// set bit in randomly chosen color
*new.choose_mut(&mut rng).unwrap() |= 1 << bit;
new
}
这足以让可能的颜色四倍的空间中行走。要想在那次行走中找到一个好的四元组,你需要定义什么是好的四元组,但你也必须对蛮力做同样的事情:
fn rank_candidate(&mut self, candidate: &[u32; 4]) -> f64 {
let rgbs =
candidate.map(|bits| Rgb::from(<[u8; 3]>::try_from(&bits.to_be_bytes()[1..]).unwrap()));
let hsls = rgbs.clone().map(|rgb| Hsl::from(rgb));
let mut hues = hsls.clone().map(|c| c.hue());
hues.sort_by(f64::total_cmp);
let angles: [f64; 3] = array::from_fn(|i| (hues[i + 1] - hues[i]));
- angles.map(|a| (a - 90.).powf(2.)).into_iter().sum::<f64>()
- hsls
.map(|c| (100. - c.saturation()).powf(2.) + (100. - c.lightness() * 2.).powf(2.))
.iter()
.sum::<f64>() * 0.1;
}
有了它,你可以应用像模拟退火这样的元启发式方法,为你找到一组漂亮的颜色。
有趣的是,似乎有关位和角度的条件将光度限制在~0.25。
评论