2

I've created a huge list of colours with names and RGB values (Took a very long time) now I've created an algorithm that gets the corresponding colour to the closest values.

It seems to work very well BUT sometimes when there's an odd value that's completely out it gets the wrong colour.

Example output

Log: InputRGB: R:7.1009636 | G:83.84344 | B:2.5013387
Log: ColorToCompare: Ball Blue (R13.0,G67.0,B80.0) CLOSE:0.4588677 | CurrentColor: Acid Green CLOSE: 0.41585693
Log: ColorToCompare: Bitter Lemon (R79.0,G88.0,B5.0) CLOSE:0.5143066 | CurrentColor: Ball Blue CLOSE: 0.4588677
Log: ColorToCompare: Citrine (R89.0,G82.0,B4.0) CLOSE:0.5610447 | CurrentColor: Bitter Lemon CLOSE: 0.5143066
Log: ColorToCompare: Smoky Black (R6.0,G5.0,B3.0) CLOSE:0.57945675 | CurrentColor: Citrine CLOSE: 0.5610447
Log: ColorName:Smoky Black
Log: End Color: R:6.0 G:5.0 B:3.0
Log: InputRGB:    R:7.1009636 | G:83.84344 | B:2.5013387

The code I've created to calculate this:

   public String getClosetColor(float red, float green, float blue){

        Functions.log("InputRGB: R:" + red + " | G:" + green + " | B:" + blue);

        Color lastColor = null;
        for(Color eachColor : this.colors)
        {
            if(lastColor == null){
                lastColor = eachColor;
            }

            float lastColorCloseness = (getClose(red, lastColor.red) + getClose(green, lastColor.green) + getClose(blue, lastColor.blue)) / 3f;
            float thisColorCloseness = (getClose(red, eachColor.red) + getClose(green, eachColor.green) + getClose(blue, eachColor.blue)) / 3f;

            if(Float.isFinite(thisColorCloseness) && Float.isFinite(lastColorCloseness))
            {
                //If they are the same, choose a random one.
                if(lastColorCloseness == thisColorCloseness){
                    if(MathUtils.random() > 0.5f){
                        lastColor = eachColor;
                    }
                }
                //If this one is greater then set it.
                else if(thisColorCloseness > lastColorCloseness){
                    Functions.log(
                            "ColorToCompare: " + eachColor.nameOfColor + " (R" + eachColor.red + ",G" + eachColor.green + ",B" + eachColor.blue + ") CLOSE:" + thisColorCloseness +
                                    " | CurrentColor: " + lastColor.nameOfColor + " CLOSE: " + lastColorCloseness
                    );

                    lastColor = eachColor;
                }
            }
        }

        Functions.log("ColorName:" + lastColor.nameOfColor);
        Functions.log("End Color: R:" + lastColor.red + " G:" + lastColor.green + " B:" + lastColor.blue);
        Functions.log("InputRGB:    R:" + red + " | G:" + green + " | B:" + blue);

        return "";
    }

    //Basically if one is higher than the other then devide by it.
    private float getClose(float firstNumber, float secondNumber){
        if(firstNumber < secondNumber){
            return firstNumber / secondNumber;
        }
        else{
            return secondNumber / firstNumber;
        }
    }
Oliver Dixon
  • 7,012
  • 5
  • 61
  • 95
  • possible duplicate of [What's the best way to "round" a Color object to the nearest Color Constant?](http://stackoverflow.com/questions/6334311/whats-the-best-way-to-round-a-color-object-to-the-nearest-color-constant) – Joe Sep 02 '14 at 10:28
  • http://stackoverflow.com/questions/12069494/rgb-similar-color-approximation-algorithm for *visual* closeness. – Joop Eggen Sep 02 '14 at 10:28
  • Yes I looked and didn't see these, sorry! – Oliver Dixon Sep 02 '14 at 10:37

3 Answers3

2

I don't know how you came up with your distance function but it's a bit awkward. Let me explain:

You use the ratio of colors instead of the difference like:

float lastColorCloseness = (getClose(red, lastColor.red) + getClose(green, lastColor.green) + getClose(blue, lastColor.blue)) / 3f;

This has the strange effect of not applying equally to equally distanced colors. For example compare col1(100, 50, 200) with col2(50, 100, 150) and col3(150, 100, 250).

Well, assuming that col2 and col3 have distance from col1 equals:

abs(100-50)+abs(50-100)+abs(200-150)=150
abs(100-150)+abs(50-100)+abs(200-250)=150

your distance function is giving different results:

(50/100+50/100+150/250)/3=0.53
(50/100+50/100+200/250)/3=0.6

And as @David Wallace mentioned it's not the most exaggerated results. Use a distance function like Euclidean instead.

Eypros
  • 5,370
  • 6
  • 42
  • 75
1

This is happening because your getClose method isn't doing a good job. If two numbers are both very small, the gap between them is greatly exaggerated.

You'd be much better off doing something like

1 / ( 1 + ( firstNumber - secondNumber ) * ( firstNumber - secondNumber ))

in getClose.

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
0

For defining a measure of "closeness", you would need to adjust for human eyes color perception plus possibly the display device.

First, a distance of X in one channel is worse than a distance of X/2 in two channels (a change in tone is more apparent than a comparable change in brightness). The larger a single channel distance the less "similar" the color is. Simply adding differences doesn't accout for that.

A simple to implement measure of distance is the difference between channels squared:

 int deltaR = red1 - red2;
 int deltaG = green1 - green2;
 int deltaB = blue1 - blue2;
 int distance = (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB);

The human eye is not equally sensitive to each color channel. So you might want to adjust weighting color channels for that. A simple weighting can be derived from RGB to grayscale weigthing (Converting RGB to grayscale/intensity)

Modifying distance function for the weights gives:

 float distance = (deltaR * deltaR) * 0.2989F
                + (deltaG * deltaG) * 0.5870F
                + (deltaB * deltaB) * 0.1140F;

That should provide a reasonable closeness function. Simply chose the color with smallest color distance.

Community
  • 1
  • 1
Durandal
  • 19,919
  • 4
  • 36
  • 70