It is necessary to generate 3 colors, i.e. so that one would not look like the other, but you need the maximum number of colors, i.e.

new Color(UnityEngine.Random.Range(0,1f), UnityEngine.Random.Range(0,1f), UnityEngine.Random.Range(0,1f))

will create an arbitrary color, and then how to check whether it does not look like (the operator == definitely does not fit) to another color, it always turns out that it is a bit like and becomes difficult to distinguish at speed, that is, changing rapidly and you need to determine where what.

And yet, setting different values ​​for the three colors will not work, since red can be any of the three, i.e. what would be the chance that one color will be the first exactly the same as the second and third.

The bottom line : I need a method that returns a boolean variable after asking whether color1 is like color2.

  • What does your understanding look like? - Aqua
  • @SeeSharp, this, as it were, is the essence of the question :) - RiotBr3aker

1 answer 1

Generally speaking, color can be represented as ordinary coordinates in three-dimensional space. It is possible to calculate the distance between two colors of interest and with a certain delta to get an answer to the question “Does color1 look like color2?”.

And calculate the length of such a vector is quite simple:

enter image description here

But there is one big problem , RGB is a linear space, which evenly distributes all 3 colors evenly when the human eye does not react so evenly to colors ... well, this topic is deep and doesn’t really help solve the issue if you are interested - google CIELAB vs sRGB .


How to get a better metric to compare colors?

The answer is simple in its formulation - go to another, more “full-fledged” color space, for example CIELAB. Even for these purposes, as far as I know, LUVs are used, not very familiar with it, so I can not compare them.

But even here there is a small problem, from Linear RGB you cannot go to LAB, you need an intermediate conversion to XYZ, and let's start with it.


Linear RGB -> sRGB -> XYZ

It is worth starting with the fact that the color used in Unity is Linear RGB, not sRGB. To fix this, you need to open almost any link in the RGB space and find the conversion:

enter image description here

Interpret this in an easier language:

 private static float LinearToSRGB(float channel) { if (channel > 0.0031308f) { channel = 1.055f * Mathf.Pow(channel, 1 / 2.4f) - 0.055f; } else { channel *= 12.92f; } return channel; } public static Color LinearToSRGB(Color col) { return new Color( LinearToSRGB(col.r), LinearToSRGB(col.g), LinearToSRGB(col.b) ); } 

Also it is necessary to take into account in which color space the initial color was set. Maybe you originally asked sRGB or even gamma-corrected RGB. For the first case, no conversion is required at all, and for the conversion of gamma -> sRGB you can easily find a formula on the Internet.


Now, finally, you can start the conversion sRGB -> XYZ :

All that is required is to multiply the matrices:

enter image description here

Where [M] is a special matrix of values that has long been defined for us . We will need 2 matrices: for D50 and D65, respectively: enter image description here enter image description here

It is worth a little stop and find out what the D50 and D65. It is quite difficult to translate into Russian, but I will still try: it is a kind of white in its own way and generally the brightest color in the color space - white, of course. Well, around these points built whole standards, algorithms, or rather constants, which we need.

For further configuration, enter the enum:

 public enum Illuminant { D50, D65 } 

Why do we need both standards?

D50, though older than D65, is still used, as far as I know.


In order not to get confused once again - let's create a separate class for the color in the XYZ space:

 public class XYZ { // точки самого яркого цвета - белого в обоих стандартах private static readonly Vector3 D50 = new Vector3(0.966797f, 1.0f, 0.825188f); private static readonly Vector3 D65 = new Vector3(0.95047f, 1.0f, 1.0883f); // цветовые компоненты public float x { get; set; } public float y { get; set; } public float z { get; set; } // конструктор от обычного цвета из Unity public XYZ(Color col, ColorUtility.Illuminant illuminant = ColorUtility.Illuminant.D65) { // первым делом переводим цвет из linear RGB в sRGB с помощью // метода, описанного выше col = LinearToSRGB(col); float r = col.r; float g = col.g; float b = col.b; // в зависимости от стандарта выбираем матрицу и самую яркую точку // перемножаем матрицы "вручную", как было описано выше // после чего нормализуем значения всех компонент цвета switch (illuminant) { case ColorUtility.Illuminant.D50: // sRGB -> XYZ x = 0.4360747f * r + 0.3850649f * g + 0.1430804f * b; y = 0.2225045f * r + 0.7168786f * g + 0.0606169f * b; z = 0.0139322f * r + 0.0971045f * g + 0.7141733f * b; float D50x = D50.x; float D50y = D50.y; float D50z = D50.z; // Clamping to D50 white point & normalizing them afterwards x = Mathf.Clamp(x, 0f, D50x) / D50x; y = Mathf.Clamp(y, 0f, D50y) / D50y; z = Mathf.Clamp(z, 0f, D50z) / D50z; break; case ColorUtility.Illuminant.D65: // sRGB -> XYZ x = 0.4124564f * r + 0.3575761f * g + 0.1804375f * b; y = 0.2126729f * r + 0.7151522f * g + 0.0721750f * b; z = 0.0193339f * r + 0.1191920f * g + 0.9503041f * b; float D65x = D65.x; float D65y = D65.y; float D65z = D65.z; // Clamping to D65 white point & normalizing them afterwards x = Mathf.Clamp(x, 0f, D65x) / D65x; y = Mathf.Clamp(y, 0f, D65y) / D65y; z = Mathf.Clamp(z, 0f, D65z) / D65z; break; } } } 

XYZ -> LAB

Formula for obtaining LAB color:

enter image description here

The functions themselves:

enter image description here

And constants:

enter image description here

Again, we translate 1in1 into the code and get the following:

 public class LAB { private const float e = 0.008856f; private const float k = 903.3f; public float l { get; set; } public float a { get; set; } public float b { get; set; } public LAB(Color col, ColorUtility.Illuminant illuminant = ColorUtility.Illuminant.D65) { Vector3 lab = XYZtoLAB(new XYZ(col, illuminant)); l = lab.x; a = lab.y; b = lab.z; } public LAB(XYZ col) { Vector3 lab = XYZtoLAB(col); l = lab.x; a = lab.y; b = lab.z; } // эта функция и есть нужная нам метрика, но об этом позже public float DeltaE(LAB color) { return Mathf.Sqrt(Mathf.Pow((this.l - color.l), 2f) + Mathf.Pow((this.a - color.a), 2f) + Mathf.Pow((this.b - color.b), 2f)); } private static float ApplyLABconversion(float value) { if (value > e) { value = Mathf.Pow(value, 1.0f / 3.0f); } else { value = (k * value + 16) / 116; } return value; } private static Vector3 XYZtoLAB(XYZ col) { float x = col.x; float y = col.y; float z = col.z; float fx = ApplyLABconversion(x); float fy = ApplyLABconversion(y); float fz = ApplyLABconversion(z); return new Vector3( 116.0f * fy - 16.0f, 500.0f * (fx - fy), 200.0f * (fy - fz) ); } } 

So how do you compare 2 colors for similarity?

We recall the very first formula:

enter image description here

And we apply it to LAB color, but not to the usual sRGB:

 public float DeltaE(LAB color) { return Mathf.Sqrt(Mathf.Pow((this.l - color.l), 2f) + Mathf.Pow((this.a - color.a), 2f) + Mathf.Pow((this.b - color.b), 2f)); } 

All that remains to be done now is to check if the delta is in the specified frames:

 public static bool AreSimilar(Color color1, Color color2, float delta, Illuminant illuminant = Illuminant.D65) { return (new LAB(color1, illuminant)).DeltaE(new LAB(color2, illuminant)) <= delta; } 

The larger the delta, the easier it will be to "pick up" similar colors, the lower the delta, respectively, the more difficult it will be to "pick up" similar colors.

For black and white, for example, this delta will be equal to 100. This is due to the fact that they are located on opposite edges of this color space. If, again, it is interesting to dig deeper - it is worthwhile to read clever articles, I obviously will not explain them better :)