What's the best way to "round" a Color object to the nearest Color Constant?
Asked Answered
A

3

12

I will be retrieving the exact color of a pixel and would like to relate that exact color to a constant like Color.blue. Is there an easy way to "round" to the nearest Color Constant? Additionally, is there a way to define your own Color Constants?

Ambulatory answered 13/6, 2011 at 18:6 Comment(0)
B
21

The basic approach is to find the closest standard color to your sample by simply comparing the sample to each of them. The problem, of course, is in defining "closest." The most obvious would be use the Euclidean distance in RGB space. The problem is that this distance does not correspond very well with our perceptual sense of "closest color". A discussion of this problem, along with a nice (easily computed) metric (including pseudocode!) can be found in this paper.

EDIT: Just in case the link to that paper goes dead (or if you're lazy and are willing to use code without understanding what it does), here's my Java version of the "color distance function" the paper suggests as a "low-cost approximation" to their recommended distance function (a weighted Euclidean distance in RGB space):

double colorDistance(Color c1, Color c2)
{
    int red1 = c1.getRed();
    int red2 = c2.getRed();
    int rmean = (red1 + red2) >> 1;
    int r = red1 - red2;
    int g = c1.getGreen() - c2.getGreen();
    int b = c1.getBlue() - c2.getBlue();
    return Math.sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}

Note that if you're just going to rank color distances, you can dispense with the call to Math.sqrt(), saving some computation costs.

Borgerhout answered 13/6, 2011 at 18:16 Comment(3)
That paper brings up the interesting point, which I must admit I've never previously considered, that there may not be an objective answer to a question like, "Which of these colors is most similar to this one?" We can invent a rule that can be objectively applied -- like sum of the squares of the differences -- but that doesn't mean that the rule is "true". (Like, I could define the "best candidate for president" as the one who is tallest, which is easily measurable. But being measurable doesn't make it a good rule.)Churn
If the function is called with two equal colors (actually the same color) - e.g colorDistance(0x123456, 0x123456), is the function guaranteed to return 0? And only in this case it will return 0?Eliaseliason
@Jamrelian - Yes, both the code I posted and the full formula in the linked paper will return 0 if the two colors are identical. Also, both formulas will return a non-zero result for two different colors.Borgerhout
P
3

Probably the best way would be to loop over every constant, and comparing their respective RGB channels (getRed, getGreen, getBlue). Keep track of the one that is closest.

Color color = new Color(...);
Color[] constantColors = new Color[] { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow };
Color nearestColor = null;
Integer nearestDistance = new Integer(Integer.MAX_VALUE);

for (Color constantColor : constantColors) {
    if (nearestDistance > Math.sqrt(
            Math.pow(color.getRed() - constantColor.getRed(), 2)
            - Math.pow(color.getGreen() - constantColor.getGreen(), 2)
            - Math.pow(color.getBlue() - constantColor.getBlue(), 2)
        )
    ) {
        nearestColor = color;
    }
}

No, you can't add color constants to the class, but you can create a class of your own to hold constants.

class MyColors {
    public static final Color heliotrope = new Color(...);
}

Edit: added difference algorithm, thanks to @Ted's link.

Panthea answered 13/6, 2011 at 18:14 Comment(6)
Also, you could have MyColor extend the Color class to preserve the old constants along with yours.Deloisedelong
You can't really compare an alpha channel in the same way you compre RGB components.Churn
@Jay: good point, none of the constants are transparent anyway.Panthea
Jonah, thank you very much for your help. Your original answer reminded me of a vague idea I had given a little thought to. Ted's paper has given me even more to think about. Thanks again for your help - I'll probably make my own class to define more specific constants.Ambulatory
Small point: For efficiency, I wouldn't bother taking the square root. That doesn't affect the ordering -- if x>y then sqrt(x)>sqrt(y) -- and it just takes more time to compute.Churn
Math.pwr doesn't exist, did you mean Math.pow?Rubicon
B
2

You can use Java's built-in color conversion with an IndexColorModel containing the palette of possible colors. Internally, the class uses Euclidean distance over the color components to determine the closest color.

import java.awt.Color;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;

public class ColorConverter {
    private final Color[] colors;
    private final IndexColorModel colorModel;

    public ColorConverter(Color[] colors) {
        this.colors = colors;
        this.colorModel = createColorModel(colors);
    }

    private static IndexColorModel createColorModel(Color[] colors) {
        final int[] cmap = new int[colors.length];
        for (int i = 0; i<colors.length; i++) {
            cmap[i] = colors[i].getRGB();
        }
        final int bits = (int) Math.ceil(Math.log(cmap.length)/Math.log(2));
        return new IndexColorModel(bits, cmap.length, cmap, 0, false, -1, DataBuffer.TYPE_BYTE);
    }

    public Color nearestColor(Color color) {
        final byte index = ((byte[])colorModel.getDataElements(color.getRGB(), null))[0];
        return colors[index];
    }
}
Bronchopneumonia answered 30/4, 2017 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.