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

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.


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.


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];
    }
}

Tags:

Java

Colors

Pixel