rustcolorsbit-manipulationrgbmaze# How do I find 4 complementary 24bit RGB colors with no overlapping bits?

I am seeking four colors that are distinguishable, somewhat complementary/visually appealing, and share no bits in common. What I mean by this is as follows:

- Consider only the first 24 bits of a u32 and use them to represent an RGB color.
- Select four colors in this manner.
- The colors should have no bits in common for the following bitwise properties.

For example, if we treat these colors as unique identifiers then we should be able to mix these colors in interesting ways in a unsigned 32 bit integer. `COLOR_1`

would represent a different color than `COLOR_1 | COLOR_2`

, and so on. Each of the four colors is unique and every possible combination is unique.

However, I have the additional constraint that no two bits among these four colors should overlap. That means if we have the following u32 `let mix: u32 = COLOR_1 | COLOR_2 | COLOR_3;`

, then the following operation should be true: `(mix & !COLOR_1) == COLOR_2 | COLOR_3`

. This is equivalent to removing `COLOR_1`

from the integer without affecting any bits belonging to `COLOR_2`

and `COLOR_3`

.

I thought exploring all possible tetrads would be a good start (here is picture of a tetrad as well represented by the four points on this circle). However, I am not sure how to appropriately explore the RGB color space for a solution to my problem.

Is there a way to get something close to a tetrad with the properties I am looking for? Or maybe there is another color theory approach? The requirements overall are as follows:

- 4 RGB colors represented in the low order 24 bits of a u32 such that no two colors share any bits in common.
- Colors that are close to tetradic points that have a 90 degree angle towards each other. This is not a strict requirement as I am only seeking four colors that are easily distinguishable from one another with the bitwise properties from point 1.
- Luminance is not a great concern but the colors should be visible against a black background.

Update: I removed my code as it incorrectly sought a solution and distracts from the great answer provided below.

Solution

This seems surprisingly tricky, mathematically. Am I understanding you right that you're looking for 4 colors which:

- Bit-anding two of the colors in byte-based RGB is always 0
- Have a 90° (or 180°) angle towards each other in HSL color space
- The colors should be colorful / have high chroma (i.e. luminosity 0.5 and saturation 1 in HSL)

The first is a hard requirement, the second and third probably not so much.

I don't know how to come up with a set of such colors. You could brute-force this, as there's "only" ~10¹⁶·⁸ possibilities, but let's try something more interesting:

You can randomly generate a set of 4 colors by just taking 24 1-bits and randomly assigning them to one of the 4 colors:

```
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.
```

You can also generate a new similar candidate from an existing one by taking one bit and moving it to a different color

```
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
}
```

This is enough to allow walking through the space of possible colors quadruples. To find a good quadruples on that walk, you need to define what a good quadruples is, but you would have also had to do the same for brute-forcing:

```
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;
}
```

And with that, you can apply a metaheuristic like simulated annealing to find you a nice set of colors.

Interestingly, it seems that the conditions about bits and angle limits the luminosity to ~0.25.

- How do I return an associated type from a higher-ranked trait bound trait?
- Rust tuple assignment
- How to create a cyclic reference with Arc and Weak?
- How can I make a cloned Arc live long enough for a tokio task in Rust?
- System tray tooltips in Tauri
- Why does taking a static reference to a const return a reference to a temporary variable?
- How do you disable dead code warnings at the crate level in Rust?
- How to initialise a 16-byte array with a random value?
- Simpler way to convert array into HashMap
- Rust accessing Option from mutex
- How much overhead does RUST_BACKTRACE=1 have?
- Cargo lambda build does not find OpenSSL development headers
- Infinite "loop" or "while let" are the same in terms of performances?
- How to clone a struct storing a boxed trait object?
- How can I access a struct field within the "new" associated function?
- SIMD instruction emulation in WebAssembly?
- Assignment from function to variable defined as mutable reference?
- Calling Rust from C# with IntPtr fails on Enum (EntryPointNotFound)
- How to use an internal library Enum for Clap Args
- How do you idiomatically implement a nontrivial typestate pattern in Rust?
- How do I convert from an integer to a string?
- How to use serde serialize_with inside a custom Serializer for a struct
- Cargo `OUT_DIR` env var not found when run a specific test
- What's rust's idiomatic way of writing vector into a file (or as a string) with tabs between elements and newline at the end?
- What is the idiomatic way to write the sine of the negative of an angle in Rust?
- How to move an opaque value like a hot potato from one Enum constructor to the next to the next?
- Identity '[]' not found when calling get_credentials_for_identity AWS identity pool endpoint
- Is there any way to create a const &'static CStr?
- Change elements in vector using multithreading in Rust
- How to reduce flicker in terminal re-drawing?