Needed this for my editor but figured I'd implement it in game too for widgets/interfaces, surprisingly enough it only took about 5mins to do so(obviously cause i already had an rgb color picker so i just needed to change a few things)
Anyway the Runescape client uses 16 bit HSL(Hue, Saturation, Lightness) where 6 bits are used for hue, 3 for saturation and 7 for lightness. NOTE: HSL is not the same as HSB(for some reason quite a few people think they're the same thing)
For anyone that still doesn't understand what this picker does: it's a color picker for rs colors (with an 24 bit RGB color picker for example u would often lose some info/detail when converting to runescapes 16 bit HSL)
The code to generate the HSL -> RGB table is already in the client but i'll add it just in case:
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 lightness = ((double) (y & 0x7) / 8.0) + 0.0625;
for (int x = 0; x < 128; x++) {
double intensity = (double) x / 128.0;
double red = intensity;
double green = intensity;
double blue = intensity;
if (lightness != 0.0) {
double a;
if (intensity < 0.5) {
a = intensity * (1.0 + lightness);
} else {
a = (intensity + lightness) - (intensity * lightness);
}
double b = (2.0 * intensity) - 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 = getValue(fRed, a, b);
green = getValue(hue, a, b);
blue = getValue(fBlue, a, b);
}
table[index++] = generatePalette(((int) (red * 256.0) << 16) | ((int) (green * 256.0) << 8) | (int) (blue * 256.0), brightness);
}
}
}
Alright, now how do we get the hsl value given the 3 components(h, s, l)?
This is very simple, remember that 6 bits are used for hue, 3 for saturation and 7 for lightness
we simply 'extract' those values and add them together:
Code:
public static int getHsl(int hue, int saturation, int lightness) {
return hue << 10 | saturation << 7 | lightness;
}
if ur confused by the bitwise OR(|) operator look it up
it can also be rewritten as:
Code:
return (hue << 10) + (saturation << 7) + lightness;
Code for the picker:
Code:
public class WidgetHSLColorPicker {
private final int width;
private final int height;
private final int hueWidth;
private final int pCircleDiameter;
private final int hCircleDiameter;
private int currentHue;
private int currentSaturation;
private int currentLightness;
private int pickedColor;
private int lastColorX;
private int lastColorY;
private int lastHueY;
private final int pCircleRadius;
private final int hCircleRadius;
public WidgetHSLColorPicker(int width, int height, int hueWidth, int pickedCircleDiameter, int hueCircleDiameter) {
this.width = width;
this.height = height;
this.hueWidth = hueWidth;
this.pCircleDiameter = pickedCircleDiameter;
this.hCircleDiameter = hueCircleDiameter;
this.pickedColor = -1;
this.pCircleRadius = pCircleDiameter / 2;
this.hCircleRadius = hCircleDiameter / 2;
}
public void render(int x, int y) {
Raster.drawHSLColorPicker(x, y, width, height, hueWidth, currentHue);
handleBounds();
Raster.drawCircle(x + lastColorX - (pCircleDiameter / 2f), y + lastColorY - (pCircleDiameter / 2f), pCircleDiameter, pCircleDiameter, 0xFFFFFF, 255, false);
Raster.drawCircle(x + width + (hueWidth / 2f) - hCircleRadius, y + lastHueY - (hCircleDiameter / 2f), hCircleDiameter, hCircleDiameter, 0xFFFFFF, 255, false);
}
public void handlePick(int x, int y) {
if (x <= width) {
currentSaturation = (int) MathUtils.map(x, 0, width, 0, 7);
currentLightness = (int) (127 - MathUtils.map(y, 0, height - 1, 0, 127));
lastColorX = x;
lastColorY = y;
} else {
currentHue = (int) (63 - MathUtils.map(y, 0, height - 1, 0, 63));
lastHueY = y;
}
pickedColor = Palette.getHsl(currentHue, currentSaturation, currentLightness);
}
private void handleBounds() {
// color picker(saturation, lightness)
if (lastColorX >= width - pCircleRadius) {
lastColorX = width - pCircleRadius - 1;
}
if (lastColorX <= pCircleRadius) {
lastColorX = pCircleRadius + 1;
}
if (lastColorY >= height - pCircleRadius) {
lastColorY = height - pCircleRadius - 1;
}
if (lastColorY <= pCircleRadius) {
lastColorY = pCircleRadius + 1;
}
// hue picker
if (lastHueY >= height - hCircleRadius) {
lastHueY = height - hCircleRadius - 1;
}
if (lastHueY <= hCircleRadius) {
lastHueY = hCircleRadius + 1;
}
}
public int getPickedColor() {
return pickedColor;
}
public void setPickedColor(int pickedColor) {
this.pickedColor = pickedColor;
}
}
Code:
public static void drawHSLColorPicker(int x, int y, int w, int h, int hueWidth, int hue) {
if (x < clipLeft) {
w -= clipLeft - x;
x = clipLeft;
}
if (y < clipTop) {
h -= clipTop - y;
y = clipTop;
}
if (x + w > clipRight)
w = clipRight - x;
if (y + h > clipBottom)
h = clipBottom - y;
createSL(x, y, w, h, hue);
createHue(x + w, y, hueWidth, h);
drawStroke(x, y, w + hueWidth, h, 0, 1);
}
Code:
private static void createHue(int x, int y, int hueWidth, int hueHeight) {
for (int pX = x; pX < x + hueWidth; pX++) {
for (int pY = y; pY < y + hueHeight; pY++) {
int hue = (int) (63 - MathUtils.map(pY, y, y + hueHeight, 0, 63));
raster[pX + pY * Raster.width] = Palette.getRgbForHsl(hue, 7, 80); // feel free to add a variable for lightness
}
}
}
private static void createSL(int x, int y, int pickerWidth, int pickerHeight, int hue) {
for (int pX = x; pX < x + pickerWidth; pX++) {
for (int pY = y; pY < y + pickerHeight; pY++) {
int saturation = (int) MathUtils.map(pX, x, x + pickerWidth, 0, 7);
int lightness = (int) (127 - MathUtils.map(pY, y, y + pickerHeight, 0, 127));
raster[pX + pY * Raster.width] = Palette.getRgbForHsl(hue, saturation, lightness);
}
}
}
Code:
public static void addHSLPicker(int id, int width, int height, int hueWidth, int pickedCircleDiameter, int hueCircleDiameter, String tooltip) {
RSInterface widget = addInterface(id);
widget.hslPicker = new WidgetHSLColorPicker(width, height, hueWidth, pickedCircleDiameter, hueCircleDiameter);
widget.width = width + hueWidth;
widget.height = height;
widget.type = TYPE_HSL_PICKER;
widget.atActionType = 7;
widget.tooltip = tooltip;
}
public static final int TYPE_HSL_PICKER = 118;
//smh should use getters but just for the sake of consistency...
public WidgetHSLColorPicker hslPicker;
Code:
public static int getRgbForHsl(int hue, int saturation, int lightness) {
int hsl = getHsl(hue, saturation, lightness);
return table[hsl];
}
Code:
public static float map(float value, float iStart, float iStop, float oStart, float oStop) {
return oStart + (oStop - oStart) * ((value - iStart) / (iStop - iStart));
}
Usage example:
Code:
addHSLPicker(id, 150, 150, 30, 10, 10, "Some Picker");
Example: