Add HSVColor, massively overhaul RGBAColor class

This commit is contained in:
DBotThePony 2023-07-15 19:39:25 +07:00
parent d855234808
commit 04f58c01db
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 388 additions and 127 deletions

View File

@ -0,0 +1,388 @@
package ru.dbotthepony.mc.otm.core.math
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.chars.CharAVLTreeSet
import net.minecraft.ChatFormatting
import kotlin.math.roundToInt
class HSVColor(hue: Float, saturation: Float, value: Float) : Comparable<HSVColor> {
val hue = (hue % 360f).let { if (it < 0f) it + 360f else it }
val saturation = saturation.coerceIn(0f, 1f)
val value = value.coerceIn(0f, 1f)
override fun equals(other: Any?): Boolean {
return other === this || other is HSVColor && other.hue == hue && other.saturation == saturation && other.value == value
}
override fun hashCode(): Int {
return hue.hashCode() + saturation.hashCode() * 31 + value.hashCode() * 31 * 31
}
fun copy(hue: Float = this.hue, saturation: Float = this.saturation, value: Float = this.value): HSVColor {
return HSVColor(hue, saturation, value)
}
operator fun component1() = hue
operator fun component2() = saturation
operator fun component3() = value
fun toRGBA(alpha: Float = 1f): RGBAColor {
val valueMin = (1f - saturation) * value
val delta = (value - valueMin) * (hue % 60f) / 60f
val valueInc = valueMin + delta
val valueDec = value - delta
return when ((hue / 60f).toInt()) {
0 -> RGBAColor(value, valueInc, valueMin, alpha)
1 -> RGBAColor(valueDec, value, valueMin, alpha)
2 -> RGBAColor(valueMin, value, valueInc, alpha)
3 -> RGBAColor(valueMin, valueDec, value, alpha)
4 -> RGBAColor(valueInc, valueMin, value, alpha)
5 -> RGBAColor(value, valueMin, valueDec, alpha)
else -> throw IllegalStateException("whut")
}
}
override fun compareTo(other: HSVColor): Int {
return comparator.compare(this, other)
}
companion object {
@JvmField val WHITE = HSVColor(0f, 1f, 1f)
private val comparator = Comparator
.comparing(HSVColor::hue)
.thenComparing(HSVColor::saturation)
.thenComparing(HSVColor::value)
}
}
private fun hex(value: Int): String {
require(value in 0 .. 255)
val v = value.toString(16)
if (v.length == 1)
return "0$v"
else
return v
}
class RGBAColor(red: Float, green: Float, blue: Float, alpha: Float = 1f) : Comparable<RGBAColor> {
constructor(r: Int, g: Int, b: Int) : this((r / 255f), (g / 255f), (b / 255f), 1f)
constructor(r: Int, g: Int, b: Int, a: Int) : this((r / 255f), (g / 255f), (b / 255f), (a / 255f))
constructor(r: Int, g: Int, b: Int, a: Float) : this((r / 255f), (g / 255f), (b / 255f), a)
val red = red.coerceIn(0f, 1f)
val green = green.coerceIn(0f, 1f)
val blue = blue.coerceIn(0f, 1f)
val alpha = alpha.coerceIn(0f, 1f)
val redInt get() = (red * 255f).roundToInt()
val greenInt get() = (green * 255f).roundToInt()
val blueInt get() = (blue * 255f).roundToInt()
val alphaInt get() = (alpha * 255f).roundToInt()
fun toRGBA(): Int {
return (redInt shl 24) or (greenInt shl 16) or (blueInt shl 8) or alphaInt
}
fun toARGB(): Int {
return (alphaInt shl 24) or (redInt shl 16) or (greenInt shl 8) or blueInt
}
fun toBGRA(): Int {
return (blueInt shl 24) or (greenInt shl 16) or (redInt shl 8) or alphaInt
}
val isFullyTransparent get() = alpha <= 0f
val isWhite: Boolean get() = red >= 1f && green >= 1f && blue >= 1f && alpha >= 1f
fun canRepresentHue(): Boolean {
val min = red.coerceAtMost(green).coerceAtMost(blue)
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
return min != max
}
fun hue(ifNoHue: Float = 0f): Float {
val min = red.coerceAtMost(green).coerceAtMost(blue)
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
if (min == max) {
return ifNoHue
}
val diff = max - min
return if (max == red && green >= blue) {
60f * (green - blue) / diff
} else if (max == red) {
60f * (green - blue) / diff + 360f
} else if (max == green) {
60f * (blue - red) / diff + 120f
} else if (max == blue) {
60f * (red - green) / diff + 240f
} else {
throw IllegalStateException("Whut $red $green $blue ($min / $max)")
}
}
fun toHSV(): HSVColor {
val min = red.coerceAtMost(green).coerceAtMost(blue)
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
if (min == max) {
return HSVColor(0f, if (max == 0f) 0f else 1f - min / max, max)
}
val diff = max - min
val hue = if (max == red && green >= blue) {
60f * (green - blue) / diff
} else if (max == red) {
60f * (green - blue) / diff + 360f
} else if (max == green) {
60f * (blue - red) / diff + 120f
} else if (max == blue) {
60f * (red - green) / diff + 240f
} else {
throw IllegalStateException("Whut $red $green $blue ($min / $max)")
}
return HSVColor(hue, 1f - min / max, max)
}
fun toHexStringRGB(): String {
return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}"
}
fun toHexStringRGBA(): String {
return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}${hex(alphaInt)}"
}
fun toHexStringARGB(): String {
return "#${hex(alphaInt)}${hex(redInt)}${hex(greenInt)}${hex(blueInt)}"
}
override fun toString(): String {
return "RGBAColor[$red $green $blue $alpha]"
}
operator fun component1() = red
operator fun component2() = green
operator fun component3() = blue
operator fun component4() = alpha
fun toIntInv(): Int {
return (blueInt shl 16) or (greenInt shl 8) or redInt
}
fun toRGB(): Int {
return (redInt shl 16) or (greenInt shl 8) or blueInt
}
fun copy(red: Float = this.red, green: Float = this.green, blue: Float = this.blue, alpha: Float = this.alpha): RGBAColor {
return RGBAColor(red, green, blue, alpha)
}
override fun compareTo(other: RGBAColor): Int {
if (canRepresentHue() && other.canRepresentHue())
return hue().compareTo(other.hue()).let {
if (it != 0)
it
else
toHSV().compareTo(other.toHSV())
}
return comparator.compare(this, other)
}
override fun equals(other: Any?): Boolean {
return other === this || other is RGBAColor && comparator.compare(this, other) == 0
}
override fun hashCode(): Int {
return red.hashCode() + green.hashCode() * 31 + blue.hashCode() * 31 * 31 + alpha.hashCode() * 31 * 31 * 31
}
fun linearInterpolation(t: Float, other: RGBAColor, interpolateAlpha: Boolean = true): RGBAColor {
return RGBAColor(
linearInterpolation(t, red, other.red),
linearInterpolation(t, green, other.green),
linearInterpolation(t, blue, other.blue),
if (interpolateAlpha) linearInterpolation(t, alpha, other.alpha) else alpha,
)
}
operator fun times(other: RGBAColor): RGBAColor {
if (isWhite)
return other
else if (other.isWhite)
return this
return RGBAColor(red * other.red, green * other.green, blue * other.blue, alpha * other.alpha)
}
@Suppress("unused")
companion object {
private val comparator = Comparator
.comparing(RGBAColor::red)
.thenComparing(RGBAColor::green)
.thenComparing(RGBAColor::blue)
.thenComparing(RGBAColor::alpha)
@JvmField val TRANSPARENT_BLACK = RGBAColor(0f, 0f, 0f, 0f)
@JvmField val TRANSPARENT_WHITE = RGBAColor(1f, 1f, 1f, 0f)
@JvmField val BLACK = RGBAColor(0f, 0f, 0f)
@JvmField val WHITE = RGBAColor(1f, 1f, 1f)
@JvmField val RED = RGBAColor(1f, 0f, 0f)
@JvmField val GREEN = RGBAColor(0f, 1f, 0f)
@JvmField val LIGHT_GREEN = RGBAColor(136, 255, 124)
@JvmField val SLATE_GRAY = RGBAColor(64, 64, 64)
@JvmField val GRAY = rgb(0x2C2C2CL)
@JvmField val DARK_BLUE = rgb(ChatFormatting.DARK_BLUE.color!!)
@JvmField val DARK_GREEN = rgb(ChatFormatting.DARK_GREEN.color!!)
@JvmField val DARK_AQUA = rgb(ChatFormatting.DARK_AQUA.color!!)
@JvmField val DARK_RED = rgb(ChatFormatting.DARK_RED.color!!)
@JvmField val DARK_PURPLE = rgb(ChatFormatting.DARK_PURPLE.color!!)
@JvmField val GOLD = rgb(ChatFormatting.GOLD.color!!)
@JvmField val DARK_GRAY = rgb(ChatFormatting.DARK_GRAY.color!!)
@JvmField val BLUE = rgb(ChatFormatting.BLUE.color!!)
@JvmField val AQUA = rgb(ChatFormatting.AQUA.color!!)
@JvmField val LIGHT_PURPLE = rgb(ChatFormatting.LIGHT_PURPLE.color!!)
@JvmField val YELLOW = rgb(ChatFormatting.YELLOW.color!!)
@JvmField val LOW_POWER = RGBAColor(173, 41, 41)
@JvmField val FULL_POWER = RGBAColor(255, 242, 40)
@JvmField val LOW_MATTER = RGBAColor(0, 24, 148)
@JvmField val FULL_MATTER = RGBAColor(72, 90, 255)
@JvmField val LOW_PATTERNS = RGBAColor(44, 104, 57)
@JvmField val FULL_PATTERNS = RGBAColor(65, 255, 87)
@JvmField val HALF_TRANSPARENT = RGBAColor(1f, 1f, 1f, 0.5f)
@JvmField val REDDISH = RGBAColor(1f, 0.4f, 0.4f)
@JvmField
val CODECRGBA: Codec<RGBAColor> = RecordCodecBuilder.create {
it.group(
Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red),
Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green),
Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue),
Codec.floatRange(0f, 1f).optionalFieldOf("alpha", 1f).forGetter(RGBAColor::alpha),
).apply(it, ::RGBAColor)
}
@JvmField
val CODECRGB: Codec<RGBAColor> = RecordCodecBuilder.create {
it.group(
Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red),
Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green),
Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue),
).apply(it, ::RGBAColor)
}
fun rgb(color: Int): RGBAColor {
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b)
}
fun rgb(color: Long): RGBAColor {
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b)
}
fun bgr(color: Int): RGBAColor {
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(b, g, r)
}
fun bgr(color: Long): RGBAColor {
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(b, g, r)
}
fun abgr(color: Int): RGBAColor {
val r = (color and -0x1000000 ushr 24) / 255f
val g = (color and 0xFF0000 ushr 16) / 255f
val b = (color and 0xFF00 ushr 8) / 255f
val a = (color and 0xFF) / 255f
return RGBAColor(a, b, g, r)
}
fun argb(color: Int): RGBAColor {
val a = (color and -0x1000000 ushr 24) / 255f
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b, a)
}
private val hexChars = CharAVLTreeSet()
init {
"#0123456789abcdefABCDEF".forEach { hexChars.add(it) }
}
fun isHexCharacter(char: Char): Boolean {
return char in hexChars
}
private val shorthandRGBHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE)
private val longhandRGBHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE)
private val shorthandRGBAHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE)
private val longhandRGBAHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE)
fun fromHexStringRGB(value: String): RGBAColor? {
if (value.length == 3 || value.length == 4) {
val match = shorthandRGBHex.find(value) ?: return null
val red = match.groupValues[1].toIntOrNull(16) ?: return null
val green = match.groupValues[2].toIntOrNull(16) ?: return null
val blue = match.groupValues[3].toIntOrNull(16) ?: return null
return RGBAColor(red * 16, green * 16, blue * 16)
} else if (value.length == 6 || value.length == 7) {
val match = longhandRGBHex.find(value) ?: return null
val red = match.groupValues[1].toIntOrNull(16) ?: return null
val green = match.groupValues[2].toIntOrNull(16) ?: return null
val blue = match.groupValues[3].toIntOrNull(16) ?: return null
return RGBAColor(red, green, blue)
} else {
return null
}
}
fun fromHexStringRGBA(value: String): RGBAColor? {
if (value.length == 4 || value.length == 5) {
val match = shorthandRGBAHex.find(value) ?: return null
val red = match.groupValues[1].toIntOrNull(16) ?: return null
val green = match.groupValues[2].toIntOrNull(16) ?: return null
val blue = match.groupValues[3].toIntOrNull(16) ?: return null
val alpha = match.groupValues[4].toIntOrNull(16) ?: return null
return RGBAColor(red * 16, green * 16, blue * 16, alpha * 16)
} else if (value.length == 8 || value.length == 9) {
val match = longhandRGBAHex.find(value) ?: return null
val red = match.groupValues[1].toIntOrNull(16) ?: return null
val green = match.groupValues[2].toIntOrNull(16) ?: return null
val blue = match.groupValues[3].toIntOrNull(16) ?: return null
val alpha = match.groupValues[4].toIntOrNull(16) ?: return null
return RGBAColor(red, green, blue, alpha)
} else {
return null
}
}
}
}
fun linearInterpolation(t: Float, a: RGBAColor, b: RGBAColor): RGBAColor {
return a.linearInterpolation(t, b)
}

View File

@ -1,127 +0,0 @@
package ru.dbotthepony.mc.otm.core.math
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.ChatFormatting
import kotlin.math.roundToInt
data class RGBAColor(val red: Float, val green: Float, val blue: Float, val alpha: Float = 1f) {
constructor(r: Int, g: Int, b: Int) : this((r / 255f), (g / 255f), (b / 255f), 1f)
constructor(r: Int, g: Int, b: Int, a: Int) : this((r / 255f), (g / 255f), (b / 255f), (a / 255f))
constructor(r: Int, g: Int, b: Int, a: Float) : this((r / 255f), (g / 255f), (b / 255f), a)
constructor(color: Long) : this(
(((color and -0x1000000) ushr 24) / 255f),
(((color and 0xFF0000) ushr 16) / 255f),
(((color and 0xFF00) ushr 8) / 255f),
(((color and 0xFF)) / 255f)
)
val isWhite = red >= 1f && green >= 1f && blue >= 1f && alpha >= 1f
val redInt = (red.coerceIn(0f, 1f) * 255f).roundToInt()
val greenInt = (green.coerceIn(0f, 1f) * 255f).roundToInt()
val blueInt = (blue.coerceIn(0f, 1f) * 255f).roundToInt()
val alphaInt = (alpha.coerceIn(0f, 1f) * 255f).roundToInt()
fun toRGB(): Int {
return (redInt shl 16) or (greenInt shl 8) or blueInt
}
fun toRGBA(): Int {
return (redInt shl 24) or (greenInt shl 16) or (blueInt shl 8) or alphaInt
}
fun toARGB(): Int {
return (alphaInt shl 24) or (redInt shl 16) or (greenInt shl 8) or blueInt
}
fun toBGRA(): Int {
return (blueInt shl 24) or (greenInt shl 16) or (redInt shl 8) or alphaInt
}
fun toIntInv(): Int {
return (blueInt shl 16) or (greenInt shl 8) or redInt
}
fun linearInterpolation(t: Float, other: RGBAColor, interpolateAlpha: Boolean = true): RGBAColor {
return RGBAColor(
linearInterpolation(t, red, other.red),
linearInterpolation(t, green, other.green),
linearInterpolation(t, blue, other.blue),
if (interpolateAlpha) linearInterpolation(t, alpha, other.alpha) else alpha,
)
}
operator fun times(other: RGBAColor): RGBAColor {
if (isWhite)
return other
else if (other.isWhite)
return this
return RGBAColor(red * other.red, green * other.green, blue * other.blue, alpha * other.alpha)
}
val isFullyTransparent get() = alpha <= 0f
companion object {
@JvmField val TRANSPARENT_BLACK = RGBAColor(0f, 0f, 0f, 0f)
@JvmField val TRANSPARENT_WHITE = RGBAColor(1f, 1f, 1f, 0f)
@JvmField val BLACK = RGBAColor(0f, 0f, 0f, 1f)
@JvmField val WHITE = RGBAColor(1f, 1f, 1f, 1f)
@JvmField val RED = RGBAColor(1f, 0f, 0f)
@JvmField val GREEN = RGBAColor(0f, 1f, 0f, 1f)
@JvmField val LIGHT_GREEN = RGBAColor(136, 255, 124)
@JvmField val SLATE_GRAY = RGBAColor(64, 64, 64)
@JvmField val GRAY = RGBAColor(0x2C2C2CFFL)
@JvmField val DARK_BLUE = rgb(ChatFormatting.DARK_BLUE.color!!)
@JvmField val DARK_GREEN = rgb(ChatFormatting.DARK_GREEN.color!!)
@JvmField val DARK_AQUA = rgb(ChatFormatting.DARK_AQUA.color!!)
@JvmField val DARK_RED = rgb(ChatFormatting.DARK_RED.color!!)
@JvmField val DARK_PURPLE = rgb(ChatFormatting.DARK_PURPLE.color!!)
@JvmField val GOLD = rgb(ChatFormatting.GOLD.color!!)
@JvmField val DARK_GRAY = rgb(ChatFormatting.DARK_GRAY.color!!)
@JvmField val BLUE = rgb(ChatFormatting.BLUE.color!!)
@JvmField val AQUA = rgb(ChatFormatting.AQUA.color!!)
@JvmField val LIGHT_PURPLE = rgb(ChatFormatting.LIGHT_PURPLE.color!!)
@JvmField val YELLOW = rgb(ChatFormatting.YELLOW.color!!)
@JvmField val LOW_POWER = RGBAColor(173, 41, 41)
@JvmField val FULL_POWER = RGBAColor(255, 242, 40)
@JvmField val LOW_MATTER = RGBAColor(0, 24, 148)
@JvmField val FULL_MATTER = RGBAColor(72, 90, 255)
@JvmField val LOW_PATTERNS = RGBAColor(44, 104, 57)
@JvmField val FULL_PATTERNS = RGBAColor(65, 255, 87)
@JvmField val HALF_TRANSPARENT = RGBAColor(1f, 1f, 1f, 0.5f)
@JvmField val REDDISH = RGBAColor(1f, 0.4f, 0.4f)
fun inv(color: Int): RGBAColor {
val r = (color and -0x1000000 ushr 24) / 255f
val g = (color and 0xFF0000 ushr 16) / 255f
val b = (color and 0xFF00 ushr 8) / 255f
val a = (color and 0xFF) / 255f
return RGBAColor(a, b, g, r)
}
fun rgb(color: Int): RGBAColor {
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b)
}
fun argb(color: Int): RGBAColor {
val a = (color and -0x1000000 ushr 24) / 255f
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b, a)
}
}
}
fun linearInterpolation(t: Float, a: RGBAColor, b: RGBAColor): RGBAColor {
return a.linearInterpolation(t, b)
}