With a list/array of colors in hexadecimal format like this:
[ "#fff000", "#238923", "#aaaaa0", "#ff2300", "#2ff014", "#010203" ]
The idea is to order/organize it so that the red ones are first, the green ones second, and the blue ones last. But that is not enough because there are tones and tones of colors, the idea would be that they were organized in a "chromatic" way:
- Bluish reds (red is the main color, then blue)
- Reds (red is the main color, blue and green have the same value)
- Greenish reds (red is the main color, then green)
- reddish greens
- Green
- teal greens
- greenish blues
- blues
- reddish blues
As you can see, it would be like a "color circle" where the ends meet: I put blue before green for red, and green before red for blue. So for example: #331122 (bluish red) would come before #330000 (red-red) which would come before #331100 (greenish red). The sort scheme would be something like this:
R G B
-------- -------- --------
RB RR RG GR GG GB BG BB BR
So the above list would look like this:
[ "#ff2300", "#fff000", "#aaaaa0", "#2ff014", "#238923", "#010203" ]
I can't use an existing sort method in the programming language because it's not really a normal sort and it's not going to go alphabetically. That is why the values for the three colors must be taken into account independently. He would have to do something of his own.
So I'm looking for an answer that:
- Propose a theoretical solution for a large number of colors.
- Also work with a limited number of colors (25-50 which could be used to sort a paginated table).
- Be efficient (algorithmic order) - should you consider hashing/buckets?
Note the code I present in this answer is not intended to be a solution, but just to explore some ideas.
initial idea
The first thing that came to mind was to convert each RGB color to HSV (Hue, Saturation, Value) representation. In this representation, the "Hue" component tells you what color it is (that value is a real number between 0 and 360 that represents an angle, which is the orientation of an imaginary vector in color space). The "S" component is the "Saturation" which tells you how much white that color has added to it. It is an integer between 0 and 1, where 0 is the minimum saturation (white) and 1 is the maximum (no white, pure color). Finally, the V is the "value" of luminosity that comes to say how much black is mixed with the color (0 is all black, 1 is no black at all, "pure" color).
I thought that by transforming each color in this way and simply ordering the resulting trios, they would already be grouped by "chromatic similarity", since we would be ordering first by Hue (color) and in the case of the same color, by saturation and then by brightness.
Unfortunately the results are not what I expected. The following python code is used to generate a bunch of random RGB hex values:
Some elements of the resulting list:
The graphic representation of this "palette" of colors, in the same random order in which they were generated, would be as follows:
I got this representation with this code, in case someone wants to replicate it:
The following function implements the conversion of one of those values to HSV form:
Now we can ask Python to sort the sequence
mil_colores
using the above function as the sort key:The first ten elements already ordered would be these:
Which graphically gives this slightly disappointing result:
We can see that, yes... more or less they have been grouped by color, being able to appreciate the "rainbow strip" with orange above, violet below... but within each strip the order seems a bit random and is due to that values with a very close HUE, but different values or saturations end up very close.
second thought
Then I realized that what we want is for nearby colors to end up together. This forces us to define the concept of distance between colors.
A color at the end is nothing more than a point in a space of three coordinates: R, G, B. Therefore we can use the Euclidean distance between them (root of the sum of squares of distances between each coordinate).
Although the distance is now well defined, achieving an arrangement in which close colors end up together is basically another form of the traveling salesman problem . That is, we must find a path of all points that minimizes the distance traveled. That tour would be the desired ordering of colors.
Since that problem is NP-hard, I've given up on implementing it
Update. Reflections and heuristics
After some more thought, I come to the conclusion that sorting "by distance" is really just one more way to sort them, and not necessarily the best one, since the concept of "best" is not well defined here. In the "proximity" sort, colors can appear separately that in another sort would be considered to go together, for example, a very dark red could appear "with the blacks" instead of "with the reds". Which is better?
I think the problem at the bottom cannot be solved because it is not well defined. The input color set can actually be understood mathematically as a set of points in 3-dimensional space (which would be its components, either RGB or HSV). By asking for an ordering, basically we want to pass that to a single dimension. Therefore it is a projection problem .
It is somewhat equivalent to the problem of mapping the surface of the earth's sphere on a plane. There is no single solution, and therefore there are different cartographic projections, depending on which aspect we give priority to. Some projections are useful because they make navigation easier by keeping the angles constant. Others keep the areas of the countries constant and are more useful to get an idea of their relative size, etc.
Something similar happens with color ordinations. If all the colors were "pure" (in HSV they would have S=1 and V=1) their ordering would be simple, based only on their "Hue", and it would be like this:
But if we also have "pastel" colors (in which the saturation is lowered and therefore they are closer to white), or "dark" colors (in which the brightness is lowered and therefore they are closer to black), there is nowhere to color them. in the impeccable hue arrangement just seen.
This paintbox shows how the manufacturer has solved the problem:
He has made two drawers. The top one for saturated colors (sorted by hue) and the bottom one for pastel tones (also sorted by hue). The "dark" colors have been interspersed according to their hue. For example a dark blue at the end of the blues. That "breaks" the beautiful rainbow because after that dark blue comes a lighter green-blue. Furthermore, he has only considered two levels of saturation (the two drawers). There could be many more...
Is this arrangement better or worse than another? Again it depends on what is intended. The artist may want to have the paints with the same hue together and care little whether or not the "rgb distance" between the colors matches the distance between paints in his box.
In short, there is no defined criterion for preferring one arrangement over another.
All that said, I present another heuristic to order the thousand random colors that is based on:
This is the algorithm (python):
And this is the result:
If instead of a thousand colors I use ten thousand, the result is more impressive, going from this:
to this:
To show that the algorithm also scales for a few colors, these are the ones proposed by Alvaro:
And this is the ordering that results:
Your problem is simpler to solve than it seems at first glance if you understand a couple of things first.
Los algoritmos de ordenamiento para esto son ampliamente conocidos y puedes escoger entre un gran número de ellos con ventajas y desventajas entre ellos. En todos los casos es necesario poder determinar cuando un elemento tiene más precedencia que otro. En esa parte es en la que debes concentrarte.
Para comparar debes saber una cosa primero y es que los colores no se pueden representar usando un "círculo de color" como pretendes pues dependiendo del formato a usar (en tu caso RGB) siempre son una figura geométrica tridimensional. En el caso de RGB se usa un cubo como este:
Es posible usar un cilindro para representarlo teniendo en cuenta un par de cosas:
Por ejemplo el rojo puro
FF0000
es exactamente igual a este otro rojo más oscuro#BE0000
. Esto es difícil de digerir a simple vista pero si lo representas con una imagen te darás cuenta que la única diferencia es que uno es más brillante que otro, o sea que el rojo puro se encuentra más arriba en el eje vertical del cilindro de colores.Rojo puro
Rojo oscuro
Si te fijas bien te darás cuenta que esta representación coincide más menos con el modelo HSL donde el color se expresa con grados formando un círculo. En realidad ni HSL ni HSV son cilindros tampoco sino figuras formadas por conos pero el hecho que puedan representarse de forma circular es suficiente para tus propósitos.
En ambos modelos se puede deducir un par de cosas:
El blanco
#FFFFFF
, el negro#000000
y los puros grises (igual valor deR
,G
yB
) son el mismo color variando por el brillo pero que no tienen afinidad por ningún componente en particular lo que cambia es su posición en el eje vertical de acuerdo al modelo escogido.Que existen colores que a simple vista parecen negros pero en realidad son rojos, verdes, etc, pero que son muy oscuros o claros para decirlo de una manera muy sobre-simplificada.
Que no puedes decidir por un sólo parámetro, el color, sino que debes tener en cuenta otros como luminosidad, chroma o valor de acuerdo al modelo escogido. El resultado final será diferente de acuerdo a tus preferencias.
Las prioridad de colores impuesta por tu problema requiere que el violeta puro (
#ff00ff
) se encuentre el primero en la lista y este se encuentra exactamente a los 300o grados de la circunferencia de color así que lo único que debes hacer para obtener los valores que deseas es rotar 60 grados la circunferencia en sentido contrario a las manecillas del reloj.Aquí te dejo el algoritmo:
Como ves la solución es bastante simple una vez que cambias el modelo mental y comparas usando los grados de una circunferencia en lugar de usar valores de color.
Si lo pruebas con los datos de @abulafia te das cuenta que hay violetas en ambos extremos pero que los menores son los violetas que tienen un mayor componente azul que es exactamente lo que pides.
Puedes mejorar los algoritmos, el punto que me ocupa es que entiendas cuales son las partes importantes. Yo use el sort de javascript que no tiene un algoritmo definido pero al menos en Chrome usará una variante de Merge Sort llamada TimSort. En otros navegadores puede que usen Insertion sort, el resultado es realmente variable.
Ideally, you would be able to convert all three HSL values to a single integer or string and use an integer algorithm like radix sort that can be faster than a comparison algorithm. Note that this requires an additional conversion step for each HSL value, so you need to assess whether there are actually benefits to using it. For very large collections the benefit will be obvious. The reality is that the data set (best, average, worst case, its size, etc) affects the performance of each comparison algorithm so you must decide which one you want to use. There is no infallible answer but advantages and disadvantages such as speed, complexity and memory consumption.