228

I want to design a program that can help me assess between 5 pre-defined colors which one is more similar to a variable color, and with what percentage. The thing is that I don't know how to do that manually step by step. So it is even more difficult to think of a program.

More details: The colors are from photographs of tubes with gel that as different colors. I have 5 tubes with different colors were each is representative of 1 of 5 levels. I want to take photographs of other samples and on the computer assess to which level that sample belongs by comparing colors, and I want to know that with a percentage of approximation too. I would like a program that does something like this: http://www.colortools.net/color_matcher.html

If you can tell me what steps to take, even if they are things for me to think and do manually. It would be very helpful.

smci
  • 32,567
  • 20
  • 113
  • 146
Ana Fernandes
  • 2,281
  • 2
  • 14
  • 4
  • 2
    I made a minor change to the text, changing a Portuguese word to what I think is the correct English equivalent...change it back if I erred. – Beska Jan 26 '12 at 12:30
  • 14
    There is a wikipedia article about color difference: http://en.wikipedia.org/wiki/Color_difference – Ocaso Protal Jan 26 '12 at 12:32
  • 5
    This should be interesting: http://stevehanov.ca/blog/index.php?id=116 It explores computing the difference in three different color models. – Vlad Jan 26 '12 at 12:37
  • Try to minimize any potential photographic variablity as well...more detail in answer below. – Beska Jan 26 '12 at 16:37
  • possible duplicate of [Color Logic Algorithm](http://stackoverflow.com/questions/2103368/color-logic-algorithm) – BlueRaja - Danny Pflughoeft Jan 26 '12 at 17:53
  • This is not really a programming logic solution, so I won't mark it as an answer, but this functionality is already somewhat available through the Adobe Kuler API. http://learn.adobe.com/wiki/display/kulerdev/C.+Samples - here is one of those samples that selects the blog background color based on an image - http://gondaba.com/ - I suspect choosing a corresponding color for a given color isn't that difficult. Of course, this would couple your program to a third party API, which you may not want. – Aaron Newton Jan 31 '12 at 22:07
  • Some nice algorithms for color contrast detectioN: http://www.splitbrain.org/blog/2008-09/18-calculating_color_contrast_with_php – Kevin Mar 22 '14 at 20:59
  • Maybe [Lea verou's game](http://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/) might be of help. – Laurent S. Jun 30 '15 at 12:34
  • This question and the number of near-duplicates strongly prove the case for a tag [tag:color-distance] aliased to [tag:color-difference] – smci Apr 14 '17 at 23:33
  • There's probably a large psychological aspect to perceived similarities. Background differences, and patterns can also affect colour perception. My partner bought a car with colour chosen from an approximately A5 painted metal panel. She found the actual cars colour to be "off" despite closely matching the sample placed beside it. – Paddy3118 Nov 24 '20 at 06:59

20 Answers20

171

See Wikipedia's article on Color Difference for the right leads. Basically, you want to compute a distance metric in some multidimensional colorspace.

But RGB is not "perceptually uniform", so your Euclidean RGB distance metric suggested by Vadim will not match the human-perceived distance between colors. For a start, L*a*b* is intended to be a perceptually uniform colorspace, and the deltaE metric is commonly used. But there are more refined colorspaces and more refined deltaE formulas that get closer to matching human perception.

You'll have to learn more about colorspaces and illuminants to do the conversions. But for a quick formula that is better than the Euclidean RGB metric, just do this:

  • Assume that your RGB values are in the sRGB colorspace
  • Find the sRGB to L*a*b* conversion formulas
  • Convert your sRGB colors to L*a*b*
  • Compute deltaE between your two L*a*b* values

It's not computationally expensive, it's just some nonlinear formulas and some multiplications and additions.

Ashu
  • 2,970
  • 1
  • 19
  • 31
Liudvikas Bukys
  • 5,790
  • 3
  • 25
  • 36
  • 5
    Or, if you're working in Ruby, check out the [`color` gem](https://rubygems.org/gems/color) which implements [deltaE](https://github.com/halostatue/color/blob/259f58c1a54c7d2c86f3a227b7dced6235c69cdd/lib/color/rgb.rb#L382) among other color operations. – Mike Jarema Mar 13 '15 at 15:48
  • 3
    Here is a gist for the above implementation in Javascript https://gist.github.com/ryancat/9972419b2a78f329ce3aebb7f1a09152 – Xin Chen May 09 '18 at 06:12
  • For those looking for Flutter / Dart implementation of deltaE, there's a package: https://pub.dev/packages/delta_e – maganap Jan 27 '21 at 08:45
  • I think the original deltaE distance [is just Euclidean distance](https://en.wikipedia.org/wiki/Color_difference#CIE76) in L\*a\*b\* space. – kennysong May 24 '21 at 15:07
  • A neat JavaScript library implementing the various deltaE algorithms can be found here: http://zschuessler.github.io/DeltaE/. The website also contains [an article](http://zschuessler.github.io/DeltaE/learn/) explaining the difference between the different algorithms. – Marten S Jul 17 '21 at 15:41
  • Here is a [kotlin solution](https://stackoverflow.com/a/57893721/599535) – Lifes May 20 '22 at 07:49
60

Just an idea that first came to my mind (sorry if stupid). Three components of colors can be assumed 3D coordinates of points and then you could calculate distance between points.

F.E.

Point1 has R1 G1 B1
Point2 has R2 G2 B2

Distance between colors is

d=sqrt((r2-r1)^2+(g2-g1)^2+(b2-b1)^2)

Percentage is

p=d/sqrt((255)^2+(255)^2+(255)^2)
Vadim Gulyakin
  • 1,415
  • 9
  • 7
  • 39
    If we're using the RGB colorspace the difference between 2 colors isn't the same as how humans *perceive* the difference though. But yes the basic idea is the same everywhere - we'd just have to map it into another color space (lab I'd think) – Voo Jan 26 '12 at 12:49
  • 7
    @Voo: I agree, HSV/HSL/LAB would be significantly better colour spaces than (s)RGB for distance-based similarity matching. – Jon Purdy Jan 26 '12 at 13:11
  • 6
    This is a good way of telling you how different two colors ARE, but does a poor job of telling you how different they will be PERCEIVED. Human eyes are far from perfect: we're more sensitive to green than red or blue, our brightness perception is logrithmic, etc. OP never specified which s/he wants; but [see here](http://stackoverflow.com/questions/2103368) for an algorithm specially-tailored for human sight. – BlueRaja - Danny Pflughoeft Jan 26 '12 at 17:57
  • 15
    Another problem here is 255, 0, 0 is the same distance from 0, 255, 0 as it is 0, 0, 255. –  Sep 11 '15 at 20:19
  • i think (1 - p) * 100 for similarity percentage – giraysam Jul 14 '17 at 13:32
  • 1
    When using RGB values as a vector, one could calculate the angle between those two vectors to get similarity. Percentage is then angle/360. – thormeier Sep 02 '20 at 15:02
  • This is good as a partial answer, but really should convert to other representations also. So then one measure distances across LaB, HSL, XYZ, etc. Cf. eg CIELAB ΔE* – Konchog Nov 24 '20 at 12:35
45

Actually I walked the same path a couple of months ago. There is no perfect answer to the question (that was asked here a couple of times) but there is one, more sophisticated than the sqrt(r-r) etc. answer and more easy to implement directly with RGB without moving to all kinds of alternate color spaces. I found this formula here which is a low cost approximation of the quite complicated real formula (by the CIE which is the W3C of colors, since this is a not finished quest, you can find older and simpler color difference equations there). Good Luck.

Edit: For posterity, here's the relevant C code:

typedef struct {
     unsigned char r, g, b;
} RGB;

double ColourDistance(RGB e1, RGB e2)
{
    long rmean = ( (long)e1.r + (long)e2.r ) / 2;
    long r = (long)e1.r - (long)e2.r;
    long g = (long)e1.g - (long)e2.g;
    long b = (long)e1.b - (long)e2.b;
    return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}
N3R4ZZuRR0
  • 2,400
  • 4
  • 18
  • 32
alonisser
  • 11,542
  • 21
  • 85
  • 139
26

If you have two Color objects c1 and c2, you can just compare each RGB value from c1 with that of c2.

int diffRed   = Math.abs(c1.getRed()   - c2.getRed());
int diffGreen = Math.abs(c1.getGreen() - c2.getGreen());
int diffBlue  = Math.abs(c1.getBlue()  - c2.getBlue());

Those values you can just divide by the amount of difference saturations (255), and you will get the difference between the two.

float pctDiffRed   = (float)diffRed   / 255;
float pctDiffGreen = (float)diffGreen / 255;
float pctDiffBlue   = (float)diffBlue  / 255;

After which you can just find the average color difference in percentage.

(pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100

Which would give you a difference in percentage between c1 and c2.

rustyshelf
  • 44,963
  • 37
  • 98
  • 104
kba
  • 19,333
  • 5
  • 62
  • 89
  • 1
    2 more minor things: 1 `pctDiffRed = diffRed / 255;` is going to give you 0 unless you cast to a float somewhere. 2 You'll need to multiply by 100 somewhere to get a percentage. – vaughandroid Jan 26 '12 at 12:34
  • 20
    This may not give the best "visible" difference, since the human eye perceives color changes differently. That being said, I'm guessing this is exactly what she's looking for, because she's probably looking for a equally quantifiable difference rather than a percieved difference. Just thought I'd this out here as something to consider in case it's relevant. – Beska Jan 26 '12 at 12:34
24

A color value has more than one dimension, so there is no intrinsic way to compare two colors. You have to determine for your use case the meaning of the colors and thereby how to best compare them.

Most likely you want to compare the hue, saturation and/or lightness properties of the colors as oppposed to the red/green/blue components. If you are having trouble figuring out how you want to compare them, take some pairs of sample colors and compare them mentally, then try to justify/explain to yourself why they are similar/different.

Once you know which properties/components of the colors you want to compare, then you need to figure out how to extract that information from a color.

Most likely you will just need to convert the color from the common RedGreenBlue representation to HueSaturationLightness, and then calculate something like

avghue = (color1.hue + color2.hue)/2
distance = abs(color1.hue-avghue)

This example would give you a simple scalar value indicating how far the gradient/hue of the colors are from each other.

See HSL and HSV at Wikipedia.

Supr
  • 18,572
  • 3
  • 31
  • 36
  • 2
    From the stuff I remember from my lectures about these things I would convert the image into the Lab color space and not HSV/HSL though. Any reasoning for picking that one? – Voo Jan 26 '12 at 12:55
  • Nope. RGB and HSL are the ones I'm most familiar with, so I picked HSL just to underscore the idea that the "default" RGB is not the only option -- it really depends on the application. Thanks for letting me know about the Lab color space. – Supr Jan 26 '12 at 13:11
  • 1
    I gave you +1 anyhow because the basic principle here is the "right" answer (convert in color space that handles perceived difference uniformly then do comparison). I'm not that sure which space would be the best - all these different color spaces are confusing as hell imo ;) – Voo Jan 26 '12 at 13:27
17

One of the best methods to compare two colors by human perception is CIE76. The difference is called Delta-E. When it is less than 1, the human eye can not recognize the difference.

There is wonderful color utilities class ColorUtils (code below), which includes CIE76 comparison methods. It is written by Daniel Strebel,University of Zurich.

From ColorUtils.class I use the method:

static double colorDifference(int r1, int g1, int b1, int r2, int g2, int b2)

r1,g1,b1 - RGB values of the first color

r2,g2,b2 - RGB values ot the second color that you would like to compare

If you work with Android, you can get these values like this:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


ColorUtils.class by Daniel Strebel,University of Zurich:

import android.graphics.Color;

public class ColorUtil {
public static int argb(int R, int G, int B) {
    return argb(Byte.MAX_VALUE, R, G, B);
}

public static int argb(int A, int R, int G, int B) {
    byte[] colorByteArr = {(byte) A, (byte) R, (byte) G, (byte) B};
    return byteArrToInt(colorByteArr);
}

public static int[] rgb(int argb) {
    return new int[]{(argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF};
}

public static int byteArrToInt(byte[] colorByteArr) {
    return (colorByteArr[0] << 24) + ((colorByteArr[1] & 0xFF) << 16)
            + ((colorByteArr[2] & 0xFF) << 8) + (colorByteArr[3] & 0xFF);
}

public static int[] rgb2lab(int R, int G, int B) {
    //http://www.brucelindbloom.com

    float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
    float Ls, as, bs;
    float eps = 216.f / 24389.f;
    float k = 24389.f / 27.f;

    float Xr = 0.964221f;  // reference white D50
    float Yr = 1.0f;
    float Zr = 0.825211f;

    // RGB to XYZ
    r = R / 255.f; //R 0..1
    g = G / 255.f; //G 0..1
    b = B / 255.f; //B 0..1

    // assuming sRGB (D65)
    if (r <= 0.04045)
        r = r / 12;
    else
        r = (float) Math.pow((r + 0.055) / 1.055, 2.4);

    if (g <= 0.04045)
        g = g / 12;
    else
        g = (float) Math.pow((g + 0.055) / 1.055, 2.4);

    if (b <= 0.04045)
        b = b / 12;
    else
        b = (float) Math.pow((b + 0.055) / 1.055, 2.4);


    X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
    Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
    Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;

    // XYZ to Lab
    xr = X / Xr;
    yr = Y / Yr;
    zr = Z / Zr;

    if (xr > eps)
        fx = (float) Math.pow(xr, 1 / 3.);
    else
        fx = (float) ((k * xr + 16.) / 116.);

    if (yr > eps)
        fy = (float) Math.pow(yr, 1 / 3.);
    else
        fy = (float) ((k * yr + 16.) / 116.);

    if (zr > eps)
        fz = (float) Math.pow(zr, 1 / 3.);
    else
        fz = (float) ((k * zr + 16.) / 116);

    Ls = (116 * fy) - 16;
    as = 500 * (fx - fy);
    bs = 200 * (fy - fz);

    int[] lab = new int[3];
    lab[0] = (int) (2.55 * Ls + .5);
    lab[1] = (int) (as + .5);
    lab[2] = (int) (bs + .5);
    return lab;
}

/**
 * Computes the difference between two RGB colors by converting them to the L*a*b scale and
 * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
 */
public static double getColorDifference(int a, int b) {
    int r1, g1, b1, r2, g2, b2;
    r1 = Color.red(a);
    g1 = Color.green(a);
    b1 = Color.blue(a);
    r2 = Color.red(b);
    g2 = Color.green(b);
    b2 = Color.blue(b);
    int[] lab1 = rgb2lab(r1, g1, b1);
    int[] lab2 = rgb2lab(r2, g2, b2);
    return Math.sqrt(Math.pow(lab2[0] - lab1[0], 2) + Math.pow(lab2[1] - lab1[1], 2) + Math.pow(lab2[2] - lab1[2], 2));
}
}
Ivo Stoyanov
  • 16,256
  • 8
  • 62
  • 65
  • the code above has an error in rgb2lab: division by 12 should be replaced by division by 12.92 in r, g and b conversion. otherwise the function is not continuous at r = 0.04045 – John Smith Apr 20 '16 at 10:32
  • This solution is almost perfect, but it seems to provide wrong answers with yellows and greens. For example: ` getColorDifference( new Color(178, 157, 87), // Yellow new Color(117, 178, 87) // Green ) ` Answer: 33.25.. getColorDifference( new Color(178, 157, 87), // Lighter yellow new Color(140, 115, 31) // Slightly darker yellow ); Answer: 40.9.. Somehow it thinks the version with yellow and green is closer (even though it's visually clearly more different) Image as well: https://imgur.com/yEatRuJ – Joonas Vali Aug 24 '23 at 18:02
10

Just another answer, although it's similar to Supr's one - just a different color space.

The thing is: Humans perceive the difference in color not uniformly and the RGB color space is ignoring this. As a result if you use the RGB color space and just compute the euclidean distance between 2 colors you may get a difference which is mathematically absolutely correct, but wouldn't coincide with what humans would tell you.

This may not be a problem - the difference is not that large I think, but if you want to solve this "better" you should convert your RGB colors into a color space that was specifically designed to avoid the above problem. There are several ones, improvements from earlier models (since this is based on human perception we need to measure the "correct" values based on experimental data). There's the Lab colorspace which I think would be the best although a bit complicated to convert it to. Simpler would be the CIE XYZ one.

Here's a site that lists the formula's to convert between different color spaces so you can experiment a bit.

Voo
  • 29,040
  • 11
  • 82
  • 156
5

Kotlin version with how much percent do you want to match.

Method call with percent optional argument

isMatchingColor(intColor1, intColor2, 95) // should match color if 95% similar

Method body

private fun isMatchingColor(intColor1: Int, intColor2: Int, percent: Int = 90): Boolean {
    val threadSold = 255 - (255 / 100f * percent)

    val diffAlpha = abs(Color.alpha(intColor1) - Color.alpha(intColor2))
    val diffRed = abs(Color.red(intColor1) - Color.red(intColor2))
    val diffGreen = abs(Color.green(intColor1) - Color.green(intColor2))
    val diffBlue = abs(Color.blue(intColor1) - Color.blue(intColor2))

    if (diffAlpha > threadSold) {
        return false
    }

    if (diffRed > threadSold) {
        return false
    }

    if (diffGreen > threadSold) {
        return false
    }

    if (diffBlue > threadSold) {
        return false
    }

    return true
}
Pankaj Kant Patel
  • 2,050
  • 21
  • 27
4

All methods below result in a scale from 0-100.

internal static class ColorDifference
{
    internal enum Method
    {
        Binary, // true or false, 0 is false
        Square,
        Dimensional,
        CIE76
    }

    public static double Calculate(Method method, int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate(method, c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double Calculate(Method method, int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
    {
        switch (method)
        {
            case Method.Binary:
                return (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) ? 0 : 100;
            case Method.CIE76:
                return CalculateCIE76(r1, r2, g1, g2, b1, b2);
            case Method.Dimensional:
                if (a1 == -1 || a2 == -1) return Calculate3D(r1, r2, g1, g2, b1, b2);
                else return Calculate4D(r1, r2, g1, g2, b1, b2, a1, a2);
            case Method.Square:
                return CalculateSquare(r1, r2, g1, g2, b1, b2, a1, a2);
            default:
                throw new InvalidOperationException();
        }
    }

    public static double Calculate(Method method, Color c1, Color c2, bool alpha)
    {
        switch (method)
        {
            case Method.Binary:
                return (c1.R == c2.R && c1.G == c2.G && c1.B == c2.B && (!alpha || c1.A == c2.A)) ? 0 : 100;
            case Method.CIE76:
                if (alpha) throw new InvalidOperationException();
                return CalculateCIE76(c1, c2);
            case Method.Dimensional:
                if (alpha) return Calculate4D(c1, c2);
                else return Calculate3D(c1, c2);
            case Method.Square:
                if (alpha) return CalculateSquareAlpha(c1, c2);
                else return CalculateSquare(c1, c2);
            default:
                throw new InvalidOperationException();
        }
    }

    // A simple idea, based on on a Square
    public static double CalculateSquare(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
    }

    public static double CalculateSquare(Color c1, Color c2)
    {
        return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double CalculateSquareAlpha(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double CalculateSquareAlpha(Color c1, Color c2)
    {
        return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
    }

    public static double CalculateSquare(int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
    {
        if (a1 == -1 || a2 == -1) return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2)) / 7.65;
        else return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2) + Math.Abs(a1 - a2)) / 10.2;
    }

    // from:http://stackoverflow.com/questions/9018016/how-to-compare-two-colors
    public static double Calculate3D(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate3D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
    }

    public static double Calculate3D(Color c1, Color c2)
    {
        return Calculate3D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double Calculate3D(int r1, int r2, int g1, int g2, int b1, int b2)
    {
        return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2)) / 4.41672955930063709849498817084;
    }

    // Same as above, but made 4D to include alpha channel
    public static double Calculate4D(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate4D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double Calculate4D(Color c1, Color c2)
    {
        return Calculate4D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
    }

    public static double Calculate4D(int r1, int r2, int g1, int g2, int b1, int b2, int a1, int a2)
    {
        return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2) + Math.Pow(Math.Abs(a1 - a2), 2)) / 5.1;
    }

    /**
    * Computes the difference between two RGB colors by converting them to the L*a*b scale and
    * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
    */
    public static double CalculateCIE76(int argb1, int argb2)
    {
        return CalculateCIE76(Color.FromArgb(argb1), Color.FromArgb(argb2));
    }

    public static double CalculateCIE76(Color c1, Color c2)
    {
        return CalculateCIE76(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double CalculateCIE76(int r1, int r2, int g1, int g2, int b1, int b2)
    {
        int[] lab1 = ColorConversion.ColorToLab(r1, g1, b1);
        int[] lab2 = ColorConversion.ColorToLab(r2, g2, b2);
        return Math.Sqrt(Math.Pow(lab2[0] - lab1[0], 2) + Math.Pow(lab2[1] - lab1[1], 2) + Math.Pow(lab2[2] - lab1[2], 2)) / 2.55;
    }
}


internal static class ColorConversion
{

    public static int[] ArgbToArray(int argb)
    {
        return new int[] { (argb >> 24), (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF };
    }

    public static int[] ColorToLab(int R, int G, int B)
    {
        // http://www.brucelindbloom.com

        double r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
        double Ls, fas, fbs;
        double eps = 216.0f / 24389.0f;
        double k = 24389.0f / 27.0f;

        double Xr = 0.964221f;  // reference white D50
        double Yr = 1.0f;
        double Zr = 0.825211f;

        // RGB to XYZ
        r = R / 255.0f; //R 0..1
        g = G / 255.0f; //G 0..1
        b = B / 255.0f; //B 0..1

        // assuming sRGB (D65)
        if (r <= 0.04045) r = r / 12;
        else r = (float)Math.Pow((r + 0.055) / 1.055, 2.4);

        if (g <= 0.04045) g = g / 12;
        else g = (float)Math.Pow((g + 0.055) / 1.055, 2.4);

        if (b <= 0.04045) b = b / 12;
        else b = (float)Math.Pow((b + 0.055) / 1.055, 2.4);

        X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
        Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
        Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;

        // XYZ to Lab
        xr = X / Xr;
        yr = Y / Yr;
        zr = Z / Zr;

        if (xr > eps) fx = (float)Math.Pow(xr, 1 / 3.0);
        else fx = (float)((k * xr + 16.0) / 116.0);

        if (yr > eps) fy = (float)Math.Pow(yr, 1 / 3.0);
        else fy = (float)((k * yr + 16.0) / 116.0);

        if (zr > eps) fz = (float)Math.Pow(zr, 1 / 3.0);
        else fz = (float)((k * zr + 16.0) / 116);

        Ls = (116 * fy) - 16;
        fas = 500 * (fx - fy);
        fbs = 200 * (fy - fz);

        int[] lab = new int[3];
        lab[0] = (int)(2.55 * Ls + 0.5);
        lab[1] = (int)(fas + 0.5);
        lab[2] = (int)(fbs + 0.5);
        return lab;
    }
}
Vozzie
  • 345
  • 2
  • 9
4

A simple method that only uses RGB is

cR=R1-R2 
cG=G1-G2 
cB=B1-B2 
uR=R1+R2 
distance=cR*cR*(2+uR/256) + cG*cG*4 + cB*cB*(2+(255-uR)/256)

I've used this one for a while now, and it works well enough for most purposes.

Bob Pickle
  • 99
  • 4
  • Using above formula, what's the range of values for distance – Aman Aggarwal Dec 14 '16 at 11:35
  • this is pretty close to the Euclidean color difference approximation. I'm guessing that it is skipping the root component to speed up calculation, so it's a range from 0 to 100^3. If you want to normalize to 100, do distance to power of `1/3` – Daniel Mar 11 '17 at 04:53
  • @Aman: the three RGB input ranges are 0 to 256 and the distance range is 0 to 65536*9 (found empirically). This method works, so +1. – Jeff Apr 19 '23 at 07:01
  • @Daniel: I think you mean the distance range goes from 0 to 589824 (65536*9). To normalize to 1, do power of 1/2 and then distance/768 (or something like that, but it's power 1/2 not 1/3). – Jeff Apr 19 '23 at 07:01
  • @Jeff yes, thanks for correcting that, `distance/256^2*9` will give `0..1`. In addition I would replace `/256` with `/255` (because the range is `0..255`, so result would be `0..1` otherwise it tops out at cca. 0.992 – Daniel Apr 19 '23 at 17:11
3

I've tried various methods like LAB color space, HSV comparisons and I've found that luminosity works pretty well for this purpose.

Here is Python version

def lum(c):
    def factor(component):
        component = component / 255;
        if (component <= 0.03928):
            component = component / 12.92;
        else:
            component = math.pow(((component + 0.055) / 1.055), 2.4);

        return component
    components = [factor(ci) for ci in c]

    return (components[0] * 0.2126 + components[1] * 0.7152 + components[2] * 0.0722) + 0.05;

def color_distance(c1, c2):

    l1 = lum(c1)
    l2 = lum(c2)
    higher = max(l1, l2)
    lower = min(l1, l2)

    return (higher - lower) / higher


c1 = ImageColor.getrgb('white')
c2 = ImageColor.getrgb('yellow')
print(color_distance(c1, c2))

Will give you

0.0687619047619048
Tadas Šubonis
  • 1,570
  • 2
  • 16
  • 21
  • What is the origin of `ImageColor`? **edit** I found, it is `from PIL import ImageColor` – ademar111190 Oct 18 '19 at 01:11
  • Isn't luminosity the brightness of a color? So in this case a green, blue and red color would not be reported to be different as long as the brightness is the same? – Peter B. Dec 05 '19 at 22:08
3

Android for ColorUtils API RGBToHSL: I had two int argb colors (color1, color2) and I wanted to get distance/difference among the two colors. Here is what I did;

private float getHue(int color) {
    int R = (color >> 16) & 0xff;
    int G = (color >>  8) & 0xff;
    int B = (color      ) & 0xff;
    float[] colorHue = new float[3];
    ColorUtils.RGBToHSL(R, G, B, colorHue);
    return colorHue[0];
}

Then I used below code to find the distance between the two colors.

private float getDistance(getHue(color1), getHue(color2)) {
    float avgHue = (hue1 + hue2)/2;
    return Math.abs(hue1 - avgHue);
}
Kaps
  • 2,345
  • 2
  • 26
  • 37
2

The best way is deltaE. DeltaE is a number that shows the difference of the colors. If deltae < 1 then the difference can't recognize by human eyes. I wrote a code in canvas and js for converting rgb to lab and then calculating delta e. On this example the code is recognising pixels which have different color with a base color that I saved as LAB1. and then if it is different makes those pixels red. You can increase or reduce the sensitivity of the color difference with increae or decrease the acceptable range of delta e. In this example I assigned 10 for deltaE in the line that I wrote (deltae <= 10):

<script>   
  var constants = {
    canvasWidth: 700, // In pixels.
    canvasHeight: 600, // In pixels.
    colorMap: new Array() 
          };



  // -----------------------------------------------------------------------------------------------------

  function fillcolormap(imageObj1) {


    function rgbtoxyz(red1,green1,blue1){ // a converter for converting rgb model to xyz model
 var red2 = red1/255;
 var green2 = green1/255;
 var blue2 = blue1/255;
 if(red2>0.04045){
      red2 = (red2+0.055)/1.055;
      red2 = Math.pow(red2,2.4);
 }
 else{
      red2 = red2/12.92;
 }
 if(green2>0.04045){
      green2 = (green2+0.055)/1.055;
      green2 = Math.pow(green2,2.4);    
 }
 else{
      green2 = green2/12.92;
 }
 if(blue2>0.04045){
      blue2 = (blue2+0.055)/1.055;
      blue2 = Math.pow(blue2,2.4);    
 }
 else{
      blue2 = blue2/12.92;
 }
 red2 = (red2*100);
 green2 = (green2*100);
 blue2 = (blue2*100);
 var x = (red2 * 0.4124) + (green2 * 0.3576) + (blue2 * 0.1805);
 var y = (red2 * 0.2126) + (green2 * 0.7152) + (blue2 * 0.0722);
 var z = (red2 * 0.0193) + (green2 * 0.1192) + (blue2 * 0.9505);
 var xyzresult = new Array();
 xyzresult[0] = x;
 xyzresult[1] = y;
 xyzresult[2] = z;
 return(xyzresult);
} //end of rgb_to_xyz function
function xyztolab(xyz){ //a convertor from xyz to lab model
 var x = xyz[0];
 var y = xyz[1];
 var z = xyz[2];
 var x2 = x/95.047;
 var y2 = y/100;
 var z2 = z/108.883;
 if(x2>0.008856){
      x2 = Math.pow(x2,1/3);
 }
 else{
      x2 = (7.787*x2) + (16/116);
 }
 if(y2>0.008856){
      y2 = Math.pow(y2,1/3);
 }
 else{
      y2 = (7.787*y2) + (16/116);
 }
 if(z2>0.008856){
      z2 = Math.pow(z2,1/3);
 }
 else{
      z2 = (7.787*z2) + (16/116);
 }
 var l= 116*y2 - 16;
 var a= 500*(x2-y2);
 var b= 200*(y2-z2);
 var labresult = new Array();
 labresult[0] = l;
 labresult[1] = a;
 labresult[2] = b;
 return(labresult);

}

    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var imageX = 0;
    var imageY = 0;

    context.drawImage(imageObj1, imageX, imageY, 240, 140);
    var imageData = context.getImageData(0, 0, 240, 140);
    var data = imageData.data;
    var n = data.length;
   // iterate over all pixels

    var m = 0;
    for (var i = 0; i < n; i += 4) {
      var red = data[i];
      var green = data[i + 1];
      var blue = data[i + 2];
    var xyzcolor = new Array();
    xyzcolor = rgbtoxyz(red,green,blue);
    var lab = new Array();
    lab = xyztolab(xyzcolor);
    constants.colorMap.push(lab); //fill up the colormap array with lab colors.         
      } 

  }

// -----------------------------------------------------------------------------------------------------

    function colorize(pixqty) {

         function deltae94(lab1,lab2){    //calculating Delta E 1994

         var c1 = Math.sqrt((lab1[1]*lab1[1])+(lab1[2]*lab1[2]));
         var c2 =  Math.sqrt((lab2[1]*lab2[1])+(lab2[2]*lab2[2]));
         var dc = c1-c2;
         var dl = lab1[0]-lab2[0];
         var da = lab1[1]-lab2[1];
         var db = lab1[2]-lab2[2];
         var dh = Math.sqrt((da*da)+(db*db)-(dc*dc));
         var first = dl;
         var second = dc/(1+(0.045*c1));
         var third = dh/(1+(0.015*c1));
         var deresult = Math.sqrt((first*first)+(second*second)+(third*third));
         return(deresult);
          } // end of deltae94 function
    var lab11 =  new Array("80","-4","21");
    var lab12 = new Array();
    var k2=0;
    var canvas = document.getElementById('myCanvas');
                                        var context = canvas.getContext('2d');
                                        var imageData = context.getImageData(0, 0, 240, 140);
                                        var data = imageData.data;

    for (var i=0; i<pixqty; i++) {

    lab12 = constants.colorMap[i];

    var deltae = deltae94(lab11,lab12);     
                                        if (deltae <= 10) {

                                        data[i*4] = 255;
                                        data[(i*4)+1] = 0;
                                        data[(i*4)+2] = 0;  
                                        k2++;
                                        } // end of if 
                                } //end of for loop
    context.clearRect(0,0,240,140);
    alert(k2);
    context.putImageData(imageData,0,0);
} 
// -----------------------------------------------------------------------------------------------------

$(window).load(function () {    
  var imageObj = new Image();
  imageObj.onload = function() {
  fillcolormap(imageObj);    
  }
  imageObj.src = './mixcolor.png';
});

// ---------------------------------------------------------------------------------------------------
 var pixno2 = 240*140; 
 </script>
Iman Sedighi
  • 7,624
  • 4
  • 48
  • 55
  • 1
    I'm a bit worried by some of your integer divisions. `1/3` and `16/116` both evaluate to `0`, which is almost certainly not what you want. Probably your algorithm is correct, but your code certainly isn't. – Dawood ibn Kareem Jan 02 '15 at 03:26
  • You are describing CIE-LAB dE94. Delta E means the change in Euclidean. Which is to say in standard Lab color space, the Euclidean distance given by your very standard euclidean distance formula. Whereas modifications of the Delta E, namely 76, 94, 2000 (there's also Delta E, CMC which is used for textiles and the like) are different distance formulae between positions within the Lab colorspace. The code for the Lab is the same in each, the code for the color difference is not. . In short, Delta E, is not what that's called. – Tatarize May 27 '15 at 02:11
2

I used this in my android up and it seems satisfactory although RGB space is not recommended:

    public double colourDistance(int red1,int green1, int blue1, int red2, int green2, int blue2)
{
      double rmean = ( red1 + red2 )/2;
    int r = red1 - red2;
    int g = green1 - green2;
    int b = blue1 - blue2;
    double weightR = 2 + rmean/256;
    double weightG = 4.0;
    double weightB = 2 + (255-rmean)/256;
    return Math.sqrt(weightR*r*r + weightG*g*g + weightB*b*b);
}

Then I used the following to get percent of similarity:

double maxColDist = 764.8339663572415;
double d1 = colourDistance(red1,green1,blue1,red2,green2,blue2);
String s1 = (int) Math.round(((maxColDist-d1)/maxColDist)*100) + "% match";

It works well enough.

1

I expect you want to analyze a whole image at the end, don't you? So you could check for the smallest/highest difference to the identity color matrix.

Most math operations for processing graphics use matrices, because the possible algorithms using them are often faster than classical point by point distance and comparism calculations. (e.g. for operations using DirectX, OpenGL, ...)

So I think you should start here:

http://en.wikipedia.org/wiki/Identity_matrix

http://en.wikipedia.org/wiki/Matrix_difference_equation

... and as Beska already commented above:

This may not give the best "visible" difference...

Which means also that your algorithm depends onto your definiton of "similar to" if you are processing images.

Beachwalker
  • 7,685
  • 6
  • 52
  • 94
0

You'll need to convert any RGB colors into the Lab color space to be able to compare them in the way that humans see them. Otherwise you'll be getting RGB colors that 'match' in some very strange ways.

The wikipedia link on Color Differences gives you an intro into the various Lab color space difference algorithms that have been defined over the years. The simplest that just checks the Euclidian distance of two lab colours, works but has a few flaws.

Conveniently there's a Java implementation of the more sophisticated CIEDE2000 algorithm in the OpenIMAJ project. Provide it your two sets of Lab colours and it'll give you back single distance value.

AiTuDou
  • 66
  • 6
0

The only "right" way to compare colors is to do it with deltaE in CIELab or CIELuv.

But for a lot of applications I think this is a good enough approximation:

distance = 3 * |dR| + 4 * |dG| + 3 * |dB|

I think a weighted manhattan distance makes a lot more sense when comparing colors. Remember that color primaries are only in our head. They don't have any physical significance. CIELab and CIELuv is modelled statistically from our perception of color.

onemasse
  • 6,514
  • 8
  • 32
  • 37
0

For quick and dirty, you can do

import java.awt.Color;
private Color dropPrecision(Color c,int threshold){
    return new Color((c.getRed()/threshold),
                     (c.getGreen()/threshold),
                     (c.getBlue()/threshold));
}
public boolean inThreshold(Color _1,Color _2,int threshold){
    return dropPrecision(_1,threshold)==dropPrecision(_2,threshold);
}

making use of integer division to quantize the colors.

Pang
  • 9,564
  • 146
  • 81
  • 122
Austin_Anderson
  • 900
  • 6
  • 16
0

Swift 5 Answer

I found this thread because I needed a Swift version of this question. As nobody has answered with the solution, here's mine:

extension UIColor {

    var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        getRed(&red, green: &green, blue: &blue, alpha: &alpha)

        return (red, green, blue, alpha)
    }

    func isSimilar(to colorB: UIColor) -> Bool {
        let rgbA = self.rgba
        let rgbB = colorB.rgba

        let diffRed = abs(CGFloat(rgbA.red) - CGFloat(rgbB.red))
        let diffGreen = abs(rgbA.green - rgbB.green)
        let diffBlue = abs(rgbA.blue - rgbB.blue)

        let pctRed = diffRed
        let pctGreen = diffGreen
        let pctBlue = diffBlue

        let pct = (pctRed + pctGreen + pctBlue) / 3 * 100

        return pct < 10 ? true : false
    }
}

Usage:

let black: UIColor = UIColor.black
let white: UIColor = UIColor.white

let similar: Bool = black.isSimilar(to: white)

I set less than 10% difference to return similar colours, but you can customise this yourself.

VSN
  • 1
0

While there are already some really good answers, here's an app that puts a lot together - it gives a name to any RGB hex string.

Needless to say the __main is just a demonstrator.

import sys
import matplotlib.colors as mc
import numpy as np
from scipy.spatial import KDTree
import cv2

class ColourNamer:
    def __init__(self):
        self.clut = {}
        self.clut_list = []
        self.clut_tree = None

        for name in mc.XKCD_COLORS:
            rgb = mc.to_rgb(mc.XKCD_COLORS[name])
            lab = cv2.cvtColor(np.single([[rgb]]), cv2.COLOR_RGB2Lab)[0][0]
            self.clut[tuple(lab)] = name[5:]
        self.clut_list = list(self.clut.keys())
        self.clut_tree = KDTree(self.clut_list)

    def name(self, rgb):
        lab = tuple(cv2.cvtColor(np.single([[rgb]]), cv2.COLOR_RGB2Lab)[0][0])
        dist, point = self.clut_tree.query(lab, 1)
        idx = int(point)
        key = self.clut_list[idx]
        return self.clut[key]

    def hex_to_rgb(self, value):  # return normative rgb tuple.
        value = value.lstrip('#')
        lv = len(value)  #6 = 8 bit, 12 = 16 bit.
        ints = tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
        return tuple(a / 65536 if lv == 12 else a/256 for a in ints)

if __name__ == '__main__':
    cn = ColourNamer()
    if len(sys.argv) > 0:
        hex_c = sys.argv[1]
        print(cn.name(cn.hex_to_rgb(hex_c)))

For example

$python colour.py #fe3288
strong pink

$python colour.py #134323
evergreen
Konchog
  • 1,920
  • 19
  • 23