Skip to content

Instantly share code, notes, and snippets.

@Jire
Created October 9, 2021 03:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jire/a452aea530c679d325e3d676ae75e699 to your computer and use it in GitHub Desktop.
Save Jire/a452aea530c679d325e3d676ae75e699 to your computer and use it in GitHub Desktop.
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