diff --git a/cache/src/main/java/net/runelite/cache/models/JagexColor.java b/cache/src/main/java/net/runelite/cache/models/JagexColor.java new file mode 100644 index 0000000000..993073bf90 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/models/JagexColor.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.models; + +public final class JagexColor +{ + public static final double BRIGHTNESS_MAX = .6; + public static final double BRIGHTNESS_HIGH = .7; + public static final double BRIGHTNESS_LOW = .8; + public static final double BRIGTHNESS_MIN = .9; + + private static final double HUE_OFFSET = (.5 / 64.D); + private static final double SATURATION_OFFSET = (.5 / 8.D); + + private JagexColor() + { + } + + public static short packHSL(int hue, int saturation, int luminance) + { + return (short) ((short) (hue & 63) << 10 + | (short) (saturation & 7) << 7 + | (short) (luminance & 127)); + } + + public static int unpackHue(short hsl) + { + return hsl >> 10 & 63; + } + + public static int unpackSaturation(short hsl) + { + return hsl >> 7 & 7; + } + + public static int unpackLuminance(short hsl) + { + return hsl & 127; + } + + public static String formatHSL(short hsl) + { + return String.format("%02Xh%Xs%02Xl", unpackHue(hsl), unpackSaturation(hsl), unpackLuminance(hsl)); + } + + public static int HSLtoRGB(short hsl, double brightness) + { + double hue = (double) unpackHue(hsl) / 64.D + HUE_OFFSET; + double saturation = (double) unpackSaturation(hsl) / 8.D + SATURATION_OFFSET; + double luminance = (double) unpackLuminance(hsl) / 128.D; + + // This is just a standard hsl to rgb transform + // the only difference is the offsets above and the brightness transform below + double chroma = (1.D - Math.abs((2.D * luminance) - 1.D)) * saturation; + double x = chroma * (1 - Math.abs(((hue * 6.D) % 2.D) - 1.D)); + double lightness = luminance - (chroma / 2); + + double r = lightness, g = lightness, b = lightness; + switch ((int) (hue * 6.D)) + { + case 0: + r += chroma; + g += x; + break; + case 1: + g += chroma; + r += x; + break; + case 2: + g += chroma; + b += x; + break; + case 3: + b += chroma; + g += x; + break; + case 4: + b += chroma; + r += x; + break; + default: + r += chroma; + b += x; + break; + } + + int rgb = ((int) (r * 256.0D) << 16) + | ((int) (g * 256.0D) << 8) + | (int) (b * 256.0D); + + rgb = adjustForBrightness(rgb, brightness); + + if (rgb == 0) + { + rgb = 1; + } + return rgb; + } + + public static int adjustForBrightness(int rgb, double brightness) + { + double r = (double) (rgb >> 16) / 256.0D; + double g = (double) (rgb >> 8 & 255) / 256.0D; + double b = (double) (rgb & 255) / 256.0D; + + r = Math.pow(r, brightness); + g = Math.pow(g, brightness); + b = Math.pow(b, brightness); + + return ((int) (r * 256.0D) << 16) + | ((int) (g * 256.0D) << 8) + | (int) (b * 256.0D); + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/models/ObjExporter.java b/cache/src/main/java/net/runelite/cache/models/ObjExporter.java index 8913610177..fb65f99972 100644 --- a/cache/src/main/java/net/runelite/cache/models/ObjExporter.java +++ b/cache/src/main/java/net/runelite/cache/models/ObjExporter.java @@ -24,7 +24,6 @@ */ package net.runelite.cache.models; -import java.awt.Color; import java.io.PrintWriter; import net.runelite.cache.TextureManager; import net.runelite.cache.definitions.ModelDefinition; @@ -32,6 +31,8 @@ import net.runelite.cache.definitions.TextureDefinition; public class ObjExporter { + private static final double BRIGHTNESS = JagexColor.BRIGTHNESS_MIN; + private final TextureManager textureManager; private final ModelDefinition model; @@ -111,11 +112,10 @@ public class ObjExporter if (textureId == -1) { - Color color = rs2hsbToColor(model.faceColors[i]); - - double r = color.getRed() / 255.0; - double g = color.getGreen() / 255.0; - double b = color.getBlue() / 255.0; + int rgb = JagexColor.HSLtoRGB( model.faceColors[i], BRIGHTNESS); + double r = ((rgb >> 16) & 0xff) / 255.0; + double g = ((rgb >> 8) & 0xff) / 255.0; + double b = (rgb & 0xff) / 255.0; mtlWriter.println("Kd " + r + " " + g + " " + b); } @@ -140,12 +140,4 @@ public class ObjExporter } } } - - private static Color rs2hsbToColor(int hsb) - { - int decode_hue = (hsb >> 10) & 0x3f; - int decode_saturation = (hsb >> 7) & 0x07; - int decode_brightness = (hsb & 0x7f); - return Color.getHSBColor((float) decode_hue / 63, (float) decode_saturation / 7, (float) decode_brightness / 127); - } } diff --git a/cache/src/test/java/net/runelite/cache/models/JagexColorTest.java b/cache/src/test/java/net/runelite/cache/models/JagexColorTest.java new file mode 100644 index 0000000000..25291d4528 --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/models/JagexColorTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2020 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.models; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class JagexColorTest +{ + private static final double[] BRIGHTNESS_LEVELS = { + JagexColor.BRIGTHNESS_MIN, + JagexColor.BRIGHTNESS_LOW, + JagexColor.BRIGHTNESS_HIGH, + JagexColor.BRIGHTNESS_MAX, + }; + + // copy/pasted from the client, the reference colors + private static int[] computeColorTable(double brightness, int min, int max) + { + int[] colorPalette = new int[65536]; + int var4 = min * 128; + + for (int var5 = min; var5 < max; ++var5) + { + double var6 = (double) (var5 >> 3) / 64.0D + 0.0078125D; + double var8 = (double) (var5 & 7) / 8.0D + 0.0625D; + + for (int var10 = 0; var10 < 128; ++var10) + { + double var11 = (double) var10 / 128.0D; + double var13 = var11; + double var15 = var11; + double var17 = var11; + if (var8 != 0.0D) + { + double var19; + if (var11 < 0.5D) + { + var19 = var11 * (1.0D + var8); + } + else + { + var19 = var11 + var8 - var11 * var8; + } + + double var21 = 2.0D * var11 - var19; + double var23 = var6 + 0.3333333333333333D; + if (var23 > 1.0D) + { + --var23; + } + + double var27 = var6 - 0.3333333333333333D; + if (var27 < 0.0D) + { + ++var27; + } + + if (6.0D * var23 < 1.0D) + { + var13 = var21 + (var19 - var21) * 6.0D * var23; + } + else if (2.0D * var23 < 1.0D) + { + var13 = var19; + } + else if (3.0D * var23 < 2.0D) + { + var13 = var21 + (var19 - var21) * (0.6666666666666666D - var23) * 6.0D; + } + else + { + var13 = var21; + } + + if (6.0D * var6 < 1.0D) + { + var15 = var21 + (var19 - var21) * 6.0D * var6; + } + else if (2.0D * var6 < 1.0D) + { + var15 = var19; + } + else if (3.0D * var6 < 2.0D) + { + var15 = var21 + (var19 - var21) * (0.6666666666666666D - var6) * 6.0D; + } + else + { + var15 = var21; + } + + if (6.0D * var27 < 1.0D) + { + var17 = var21 + (var19 - var21) * 6.0D * var27; + } + else if (2.0D * var27 < 1.0D) + { + var17 = var19; + } + else if (3.0D * var27 < 2.0D) + { + var17 = var21 + (var19 - var21) * (0.6666666666666666D - var27) * 6.0D; + } + else + { + var17 = var21; + } + } + + int var29 = (int) (var13 * 256.0D); + int var20 = (int) (var15 * 256.0D); + int var30 = (int) (var17 * 256.0D); + int var22 = var30 + (var20 << 8) + (var29 << 16); + var22 = adjustForBrightness(var22, brightness); + if (var22 == 0) + { + var22 = 1; + } + + colorPalette[var4++] = var22; + } + } + + return colorPalette; + } + + private static int adjustForBrightness(int rgb, double brightness) + { + double var3 = (double) (rgb >> 16) / 256.0D; + double var5 = (double) (rgb >> 8 & 255) / 256.0D; + double var7 = (double) (rgb & 255) / 256.0D; + var3 = Math.pow(var3, brightness); + var5 = Math.pow(var5, brightness); + var7 = Math.pow(var7, brightness); + int var9 = (int) (var3 * 256.0D); + int var10 = (int) (var5 * 256.0D); + int var11 = (int) (var7 * 256.0D); + return var11 + (var10 << 8) + (var9 << 16); + } + + @Test + public void testHslToRgb() + { + for (double brightness : BRIGHTNESS_LEVELS) + { + int[] colorPalette = computeColorTable(brightness, 0, 512); + for (int i = 0; i < 0xFFFF; i++) + { + int rgb = JagexColor.HSLtoRGB((short) i, brightness); + int crgb = colorPalette[i]; + assertEquals("idx " + i + " brightness " + brightness, crgb, rgb); + } + } + } +} \ No newline at end of file