Created
October 9, 2021 03:30
-
-
Save Jire/a452aea530c679d325e3d676ae75e699 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.lunariaps.cache | |
import net.runelite.cache.IndexType | |
import net.runelite.cache.definitions.loaders.ModelLoader | |
import net.runelite.cache.fs.Store | |
import java.awt.Color | |
import java.io.File | |
import kotlin.math.* | |
data class RS2HSB(val value: Short) { | |
val rgb = toRGB(value) | |
val hex = rgb.toString(16) | |
companion object { | |
val BLACK = RS2HSB(0) | |
val WHITE = RS2HSB(127) | |
val RED = RS2HSB(947) | |
val COIN_GOLD = RS2HSB(8128) | |
val RUNE = RS2HSB(-29403) | |
val Int.red get() = (this shr 16) and 0xFF | |
val Int.green get() = (this shr 8) and 0xFF | |
val Int.blue get() = this and 0xFF | |
fun toRGB(RS2HSB: Int): Int { | |
val decodeHue = RS2HSB shr 10 and 0x3F | |
val decodeSaturation = RS2HSB shr 7 and 0x7 | |
val decodeBrightness = RS2HSB and 0x7F | |
return Color.HSBtoRGB( | |
decodeHue.toFloat() / 63, | |
decodeSaturation.toFloat() / 7, | |
decodeBrightness.toFloat() / 127 | |
) and 0xFFFFFF | |
} | |
fun toRGB(RS2HSB: Short) = toRGB(RS2HSB.toInt()) | |
fun fromRGB(red: Int, green: Int, blue: Int): Short { | |
val hsb = Color.RGBtoHSB(red, green, blue, null) | |
val hue = hsb[0] | |
val saturation = hsb[1] | |
val brightness = hsb[2] | |
val encodedHue = (hue * 63F).toInt() // to 6-bits | |
val encodedSaturation = (saturation * 7F).toInt() // to 3-bits | |
val encodedBrightness = (brightness * 127F).toInt() // to 7-bits | |
return ((encodedHue shl 10) + (encodedSaturation shl 7) + encodedBrightness).toShort() | |
} | |
fun fromRGB(rgb: Int) = fromRGB(rgb.red, rgb.green, rgb.blue) | |
fun Color.toRS2HSB() = fromRGB(red, green, blue) | |
operator fun ShortArray.set(index: Int, rs2HSB: RS2HSB) = set(index, rs2HSB.value) | |
fun search(rgb: Int, vararg palette: Short = runescapePalette) = | |
search(rgb.red, rgb.green, rgb.blue, *palette) | |
fun search(red: Int, green: Int, blue: Int, vararg palette: Short = runescapePalette) = | |
palette.map { it to toRGB(it) }.minByOrNull { | |
val rgb = it.second | |
deltaCIEDE2000(red, green, blue, rgb.red, rgb.green, rgb.blue) | |
}?.first ?: -1 | |
fun deltaEuclidean( | |
red1: Int, green1: Int, blue1: Int, | |
red2: Int, green2: Int, blue2: Int | |
) = sqrt( | |
(red2 - red1).toDouble().pow(2) | |
+ (green2 - green1).toDouble().pow(2) | |
+ (blue2 - blue1).toDouble().pow(2) | |
) | |
fun deltaCIEDE2000( | |
red1: Int, | |
green1: Int, | |
blue1: Int, | |
red2: Int, | |
green2: Int, | |
blue2: Int | |
): Double { | |
val blue1 = blue1.toDouble() | |
val blue2 = blue2.toDouble() | |
val Lmean = (red1 + red2) / 2.0 | |
val C1 = sqrt(green1.toDouble().pow(2) + blue1.pow(2)) | |
val C2 = sqrt(green2.toDouble().pow(2) + blue2.pow(2)) | |
val Cmean = (C1 + C2) / 2.0 | |
val G = (1 - sqrt(Cmean.pow(7.0) / (Cmean.pow(7.0) + 25.0.pow(7.0)))) / 2 | |
val a1prime = green1 * (1 + G) | |
val a2prime = green2 * (1 + G) | |
val C1prime = sqrt(a1prime * a1prime + blue1 * blue1) | |
val C2prime = sqrt(a2prime * a2prime + blue2 * blue2) | |
val Cmeanprime = (C1prime + C2prime) / 2 | |
val h1prime = atan2(blue1, a1prime) + 2 * Math.PI * if (atan2(blue1, a1prime) < 0) 1 else 0 | |
val h2prime = atan2(blue2, a2prime) + 2 * Math.PI * if (atan2(blue2, a2prime) < 0) 1 else 0 | |
val Hmeanprime = | |
if (abs(h1prime - h2prime) > Math.PI) (h1prime + h2prime + 2 * Math.PI) / 2 | |
else (h1prime + h2prime) / 2 | |
val T = | |
1.0 - 0.17 * | |
cos(Hmeanprime - Math.PI / 6.0) + 0.24 * | |
cos(2 * Hmeanprime) + 0.32 * | |
cos(3 * Hmeanprime + Math.PI / 30) - 0.2 * | |
cos(4 * Hmeanprime - 21 * Math.PI / 60) | |
val deltahprime = | |
if (abs(h1prime - h2prime) <= Math.PI) h2prime - h1prime | |
else if (h2prime <= h1prime) h2prime - h1prime + 2 * Math.PI | |
else h2prime - h1prime - 2 * Math.PI | |
val deltaLprime = red2 - red1 | |
val deltaCprime = C2prime - C1prime | |
val deltaHprime = 2.0 * sqrt(C1prime * C2prime) * sin(deltahprime / 2.0) | |
val SL = 1.0 + 0.015 * (Lmean - 50) * (Lmean - 50) / sqrt(20 + (Lmean - 50) * (Lmean - 50)) | |
val SC = 1.0 + 0.045 * Cmeanprime | |
val SH = 1.0 + 0.015 * Cmeanprime * T | |
val deltaTheta = 30 * Math.PI / 180 * exp( | |
-((180 / Math.PI * Hmeanprime - 275) / 25) * | |
((180 / Math.PI * Hmeanprime - 275) / 25) | |
) | |
val RC = 2 * sqrt(Cmeanprime.pow(7.0) / (Cmeanprime.pow(7.0) + 25.0.pow(7.0))) | |
val RT = -RC * sin(2 * deltaTheta) | |
val KL = 1.0 | |
val KC = 1.0 | |
val KH = 1.0 | |
return sqrt( | |
deltaLprime / (KL * SL) * (deltaLprime / (KL * SL)) + | |
deltaCprime / (KC * SC) * (deltaCprime / (KC * SC)) + | |
deltaHprime / (KH * SH) * (deltaHprime / (KH * SH)) + | |
RT * (deltaCprime / (KC * SC)) * (deltaHprime / (KH * SH)) | |
) | |
} | |
val runescapePalette by lazy { | |
val paletteSet: MutableSet<Short> = HashSet() | |
val store = Store(File("cache/input")).apply { load() } | |
val loader = ModelLoader() | |
for (archive in store.getIndex(IndexType.MODELS).archives) { | |
val data = archive.decompress(store.storage.loadArchive(archive)) | |
val colors = loader.load(archive.archiveId, data).faceColors ?: continue | |
for (color in colors) paletteSet.add(color) | |
} | |
paletteSet.toShortArray() | |
} | |
fun searchDebug(rgb: Int, vararg palette: Short = runescapePalette) { | |
val best = search(rgb, *palette) | |
println("for 0x${(rgb).toString(16)} best match is ${best}(0x${toRGB(best).toString(16)})") | |
} | |
@JvmStatic | |
fun main(args: Array<String>) { | |
val colors = shortArrayOf( | |
33, 24, 20, 16, 7706, 7830, 7582, 0, 4550, 6798, 4550, | |
5648, 10378, 25238, 21776, 13122, 5541, | |
8741, 8078, 21776, 25238, 5231, 6798 | |
) | |
searchDebug(0x8d603f, *colors) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment