A much better and equally simple approach would be to find the "closest matching color" by looking up each value in the table.
by 'better' i really mean more accurate.
First lets look at what your method yields for some randomly chosen HSL values:
Left side displays the original HSL values, right side displays the converted values using ur method
It looks noticeably different and we can do much better, first lets generate the HSL -> RGB table:
Code:
public static void generatePalette(double brightness) {
int index = 0;
for (int y = 0; y < 512; y++) {
double hue = ((double) (y / 8) / 64.0) + 0.0078125;
double saturation = ((double) (y & 0x7) / 8.0) + 0.0625;
for (int x = 0; x < 128; x++) {
double lightness = (double) x / 128.0;
double red = lightness;
double green = lightness;
double blue = lightness;
if (saturation != 0.0) {
double a;
if (lightness < 0.5) {
a = lightness * (1.0 + saturation);
} else {
a = (lightness + saturation) - (lightness * saturation);
}
double b = (2.0 * lightness) - a;
double fRed = hue + (1.0 / 3.0);
double fBlue = hue - (1.0 / 3.0);
if (fRed > 1.0) fRed--;
if (fBlue < 0.0) fBlue++;
red = hueToRgb(fRed, a, b);
green = hueToRgb(hue, a, b);
blue = hueToRgb(fBlue, a, b);
}
table[index++] = applyBrightness(((int) (red * 256.0) << 16) | ((int) (green * 256.0) << 8) | (int) (blue * 256.0), brightness);
}
}
}
Now we need a function that checks how 'different' a color is from another, there are
multiple different techniques but the 2 really simple ones work quite well
1) computing the Euclidean distance of the 2 colors which is defined as
Code:
d = sqrt(sq(r2 - r1) + sq(g2 - g1) + sq(b2 - b1))
2) weighted approach which is defined as
Code:
d = sqrt(sq((r2 - r1) * 0.3) + sq((g2 - g1) * 0.59) + sq((b2 - b1) * 0.11))
NOTE: the sqrt can be omitted in both if ur not interested in the actual d value
Using the 2nd method, the method for finding the closest color would look like this:
Code:
private int findClosestHSL(int rgb, int[] colors) {
double r1 = ((rgb >> 16) & 0xFF) / 255.0;
double g1 = ((rgb >> 8) & 0xFF) / 255.0;
double b1 = (rgb & 0xFF) / 255.0;
double closestDist = Double.MAX_VALUE;
int closestHSL = colors[0];
for (int hsl = 0; hsl < colors.length; hsl++) {
int color = colors[hsl];
double r2 = ((color >> 16) & 0xFF) / 255.0;
double g2 = ((color >> 8) & 0xFF) / 255.0;
double b2 = (color & 0xFF) / 255.0;
double d = sq((r2 - r1) * 0.3) + sq((g2 - g1) * 0.59) + sq((b2 - b1) * 0.11);
if (d < closestDist) {
closestDist = d;
closestHSL = hsl;
}
}
return closestHSL;
}
Using this method yields a much better result compared to the above:
In case the input to the findClosest() method isn't the actual table and just an array of arbitrary RS HSL values you could also compute the RGB value directly from the HSL value instead of building the whole table and then doing a look-up
What we need to do is compute x, y from h, s, l, assuming the input is a bitpacked hsl value, we can extract h, s, l easily:
Code:
int h = hsl >> 10 & 0x3f;
int s = hsl >> 7 & 0x7;
int l = hsl & 0x7f;
in the code that generates the table, y goes from 0 to 512(exclusive) and x goes from 0 to 128(exclusive)
Lets say from computing x as it's the simplest
l is computed as
Code:
double lightness = (double) x / 128.0;
Therefore x is just
computing y is also quite simple, we can see that h, s is computed as:
Code:
double hue = ((double) (y / 8) / 64.0) + 0.0078125;
double saturation = ((double) (y & 0x7) / 8.0) + 0.0625;
therefore y can be computed as
Code:
int y = (h << 3) | s;
So our compute method(s) for hsl -> rgb will look like this:
Code:
public static int compute(int hsl, double brightness) {
int h = hsl >> 10 & 0x3f;
int s = hsl >> 7 & 0x7;
int l = hsl & 0x7f;
int x = l & 0x7F;
int y = (h << 3) | s;
return compute(x, y, brightness);
}
private static int compute(int x, int y, double brightness) {
double hue = ((double) (y / 8) / 64.0) + 0.0078125; // 0.0078125 is 1 / 128
double saturation = ((double) (y & 0x7) / 8.0) + 0.0625; // 0.0625 is 1 / 16
double lightness = (double) x / 128.0;
double red = saturation;
double green = saturation;
double blue = saturation;
if (lightness != 0.0) {
double a;
if (saturation < 0.5) {
a = saturation * (1.0 + lightness);
} else {
a = (saturation + lightness) - (saturation * lightness);
}
double b = (2.0 * saturation) - a;
double fRed = hue + (1.0 / 3.0);
double fBlue = hue - (1.0 / 3.0);
if (fRed > 1.0) fRed--;
if (fBlue < 0.0) fBlue++;
red = hueToRgb(fRed, a, b);
green = hueToRgb(hue, a, b);
blue = hueToRgb(fBlue, a, b);
}
return applyBrightness(((int) (red * 256.0) << 16) | ((int) (green * 256.0) << 8) | (int) (blue * 256.0), brightness);
}