color tests, some fixes

This commit is contained in:
Moritz Zwerger 2025-03-31 13:06:33 +02:00
parent 59ddd7fe5e
commit 6fd062d19d
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
10 changed files with 204 additions and 32 deletions

View File

@ -100,7 +100,7 @@ open class TextComponent(
override val legacy: String
get() {
val builder = StringBuilder()
ChatColors.getChar(color?.rgb())?.let { builder.append(ProtocolDefinition.TEXT_COMPONENT_FORMATTING_PREFIX).append(it) }
ChatColors.getChar(color)?.let { builder.append(ProtocolDefinition.TEXT_COMPONENT_FORMATTING_PREFIX).append(it) }
for (formattingCode in this.formatting) {
builder.append(ProtocolDefinition.TEXT_COMPONENT_FORMATTING_PREFIX)
builder.append(formattingCode.char)

View File

@ -49,10 +49,8 @@ object ChatColors {
var index = 0
for (field in this::class.java.declaredFields) {
val color = field.get(null)
if (color !is RGBAColor) {
continue
}
if (field.type != Int::class.java) continue // inlined datatype
val color = RGBAColor(field.getInt(null))
VALUES[index] = color
CHAR_MAP[color] = index
nameMap[field.name.lowercase()] = color
@ -60,6 +58,7 @@ object ChatColors {
}
NAME_MAP = nameMap
if (NAME_MAP.size != 16) throw IllegalStateException("No colors found?")
CHAR_MAP.defaultReturnValue(-1)
}
@ -72,14 +71,12 @@ object ChatColors {
return VALUES.getOrNull(id)
}
operator fun get(name: String): RGBAColor? {
return when (name) {
"dark_grey" -> DARK_GRAY
else -> NAME_MAP[name]
}
operator fun get(name: String) = when (name) {
"dark_grey" -> DARK_GRAY
else -> NAME_MAP[name]
}
fun getChar(color: RGBColor?): String? {
fun getChar(color: RGBAColor?): String? {
if (color == null) return null
val char = CHAR_MAP.getInt(color)
if (char < 0) return null

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.data.text.formatting.color
import de.bixilon.kotlinglm.func.common.clamp
import de.bixilon.kutil.ansi.ANSI
interface Color {
@ -40,6 +41,8 @@ interface Color {
const val TIMES = VALUES * VALUES
fun Int.clamp() = this.clamp(MIN, MAX)
fun toFloat(value: Int) = value * (1.0f / MAX)
fun fromFloat(value: Float) = (value * MAX).toInt()
}

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.data.text.formatting.color
import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.kotlinglm.vec4.Vec4
import de.bixilon.kutil.primitive.IntUtil.toHex
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.text.formatting.TextFormattable
@ -22,13 +23,14 @@ import de.bixilon.minosoft.data.text.formatting.color.Color.Companion.BITS
import de.bixilon.minosoft.data.text.formatting.color.Color.Companion.MASK
import de.bixilon.minosoft.data.text.formatting.color.Color.Companion.MAX
import de.bixilon.minosoft.data.text.formatting.color.Color.Companion.TIMES
import de.bixilon.minosoft.data.text.formatting.color.Color.Companion.clamp
@JvmInline
value class RGBAColor(val argb: Int) : Color, TextFormattable {
constructor(red: Int, green: Int, blue: Int) : this(red, green, blue, MAX)
constructor(red: Int, green: Int, blue: Int, alpha: Int) : this(((alpha and MASK) shl ALPHA_SHIFT) or ((red and MASK) shl RED_SHIFT) or ((green and MASK) shl GREEN_SHIFT) or ((blue and MASK) shl BLUE_SHIFT))
constructor(red: Int, green: Int, blue: Int, alpha: Int) : this(((alpha.clamp() and MASK) shl ALPHA_SHIFT) or ((red.clamp() and MASK) shl RED_SHIFT) or ((green.clamp() and MASK) shl GREEN_SHIFT) or ((blue.clamp() and MASK) shl BLUE_SHIFT))
constructor(red: Float, green: Float, blue: Float) : this(Color.fromFloat(red), Color.fromFloat(green), Color.fromFloat(blue))
constructor(red: Float, green: Float, blue: Float, alpha: Float) : this(Color.fromFloat(red), Color.fromFloat(green), Color.fromFloat(blue), Color.fromFloat(alpha))
@ -73,11 +75,15 @@ value class RGBAColor(val argb: Int) : Color, TextFormattable {
fun toVec4() = Vec4(redf, greenf, bluef, alphaf)
override fun toString(): String {
return if (alpha != MAX) {
String.format("#%08X", rgba)
} else {
String.format("#%06X", rgb)
val builder = StringBuilder(9)
builder.append('#')
builder.append(red.toHex(2))
builder.append(green.toHex(2))
builder.append(blue.toHex(2))
if (alpha != MAX) {
builder.append(alpha.toHex(2))
}
return builder.toString()
}
override fun toText(): ChatComponent {
@ -93,7 +99,7 @@ value class RGBAColor(val argb: Int) : Color, TextFormattable {
fun Vec4.color() = RGBAColor(r, g, b, a)
inline fun Int.rgba() = RGBAColor(this shl BITS or (this ushr (BITS * 3)))
inline fun Int.rgba() = RGBAColor(this shr BITS or (this and BITS shl (BITS * 3)))
inline fun Int.argb() = RGBColor(this)

View File

@ -45,7 +45,7 @@ class RGB8Buffer(
}
override fun setRGB(x: Int, y: Int, value: RGBColor) = setRGB(x, y, value.red, value.green, value.blue)
override fun setRGBA(x: Int, y: Int, value: RGBAColor) = setRGBA(x, y, value.red, value.green, value.blue, value.alpha)
override fun setRGBA(x: Int, y: Int, value: RGBAColor) = setRGB(x, y, value.red, value.green, value.blue)
override fun copy() = RGB8Buffer(Vec2i(size), data.duplicate())

View File

@ -54,7 +54,7 @@ class RGBA8Buffer(
override fun getRGBA(x: Int, y: Int): RGBAColor {
val stride = stride(x, y)
return RGBAColor(this[stride + 0], this[stride + 1], this[stride + 2], this[stride + 3] shl 0)
return RGBAColor(this[stride + 0], this[stride + 1], this[stride + 2], this[stride + 3])
}
override fun getRGB(x: Int, y: Int): RGBColor {

View File

@ -111,7 +111,7 @@ object TextureUtil {
}
}
mkdirParent()
file.mkdirParent()
ImageIO.write(bufferedImage, "png", file)
}

View File

@ -334,9 +334,9 @@ internal class ChatComponentTest {
private fun assertEquals(expected: ChatComponent, actual: ChatComponent) {
when (expected) {
is BaseComponent -> {
if (actual !is BaseComponent) throw AssertionFailedError("Type mismatch", "BaseComponent", actual::class.java.name)
if (actual !is BaseComponent) assert("Type mismatch", "BaseComponent", actual::class.java.name)
if (expected.parts.size != actual.parts.size) throw AssertionFailedError("Count of parts does not match", expected.parts, actual.parts)
if (expected.parts.size != actual.parts.size) assert("Count of parts does not match", expected.parts, actual.parts)
for (index in expected.parts.indices) {
val first = expected.parts[index]
@ -347,20 +347,20 @@ internal class ChatComponentTest {
}
is TextComponent -> {
if (actual !is TextComponent) throw AssertionFailedError("Type mismatch", "TextComponent", actual::class.java.name)
if (expected.message != actual.message) {
throw AssertionFailedError("Message mismatch", expected.message, actual.message)
}
if (expected.clickEvent != actual.clickEvent) {
throw AssertionFailedError("Click event mismatch: $expected", expected.clickEvent, actual.clickEvent)
}
if (expected.hoverEvent != actual.hoverEvent) {
throw AssertionFailedError("Click event mismatch: $expected", expected.hoverEvent, actual.hoverEvent)
}
if (actual !is TextComponent) assert("Type mismatch", "TextComponent", actual::class.java.name)
if (expected.message != actual.message) assert("Message mismatch", expected.message, actual.message)
if (expected.clickEvent != actual.clickEvent) assert("Click event mismatch: $expected", expected.clickEvent, actual.clickEvent)
if (expected.hoverEvent != actual.hoverEvent) assert("Click event mismatch: $expected", expected.hoverEvent, actual.hoverEvent)
if (expected.color != actual.color) assert("Color mismatch", expected.color, actual.color)
if (expected.font != actual.font) assert("Font mismatch: $expected", expected.font, actual.font)
if (expected.formatting != actual.formatting) assert("Formatting mismatch: $expected", expected.formatting, actual.formatting)
assertEquals(expected as Any, actual)
}
else -> assertEquals(expected as Any, actual)
}
}
private fun assert(message: String, expected: Any?, actual: Any?): Nothing = throw AssertionFailedError("$message: expected=$expected, actual=$actual", expected, actual)
}

View File

@ -0,0 +1,89 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.text.formatting.color
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class RGBAColorTest {
@Test
fun `color int components`() {
val color = RGBAColor(0x12, 0x34, 0x56, 0x78)
assertEquals(color.red, 0x12)
assertEquals(color.green, 0x34)
assertEquals(color.blue, 0x56)
assertEquals(color.alpha, 0x78)
}
@Test
fun `color float components`() {
val color = RGBAColor(0x12, 0x34, 0x56, 0x78)
assertEquals(color.redf, 0x12 / 255.0f, 0.01f)
assertEquals(color.greenf, 0x34 / 255.0f, 0.01f)
assertEquals(color.bluef, 0x56 / 255.0f, 0.01f)
assertEquals(color.alphaf, 0x78 / 255.0f, 0.01f)
}
@Test
fun `color swizzles`() {
val color = RGBAColor(0x12, 0x34, 0x56, 0x78)
assertEquals(color.rgba, 0x12345678)
assertEquals(color.rgb, 0x123456)
assertEquals(color.argb, 0x78123456)
}
@Test
fun `default alpha`() {
val color = RGBAColor(0x12, 0x34, 0x56)
assertEquals(color.alpha, 0xFF)
}
@Test
fun `out of bounds clamping positive`() {
val color = RGBAColor(0x145, 0x145, 0x145, 0x145)
assertEquals(color.red, 0xFF)
assertEquals(color.green, 0xFF)
assertEquals(color.blue, 0xFF)
assertEquals(color.alpha, 0xFF)
}
@Test
fun `out of bounds clamping negative`() {
val color = RGBAColor(-123, -123, -123, -123)
assertEquals(color.red, 0)
assertEquals(color.green, 0)
assertEquals(color.blue, 0)
assertEquals(color.alpha, 0)
}
@Test
fun `operations minus`() {
val a = RGBAColor(0x12, 0x34, 0x56, 0x78)
val b = RGBAColor(0x05, 0x06, 0x07, 0x08)
assertEquals(a - b, RGBAColor(0x0D, 0x2e, 0x4F, 0x70))
assertEquals(a - 3, RGBAColor(0x0F, 0x31, 0x53, 0x75))
assertEquals(a - 0.1f, RGBAColor(0x00, 0x1b, 0x3d, 0x5f))
}
@Test
fun `conversion rgb`() {
val color = RGBAColor(0x12, 0x34, 0x56, 0x78)
assertEquals(color.rgb(), RGBColor(0x12, 0x34, 0x56))
}
// TODO: operations (plus, times), conversion, toString, Int::rgb, Int::rgba, mix
}

View File

@ -0,0 +1,77 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.text.formatting.color
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class RGBColorTest {
@Test
fun `color int components`() {
val color = RGBColor(0x12, 0x34, 0x56)
assertEquals(color.red, 0x12)
assertEquals(color.green, 0x34)
assertEquals(color.blue, 0x56)
}
@Test
fun `color float components`() {
val color = RGBColor(0x12, 0x34, 0x56)
assertEquals(color.redf, 0x12 / 255.0f, 0.01f)
assertEquals(color.greenf, 0x34 / 255.0f, 0.01f)
assertEquals(color.bluef, 0x56 / 255.0f, 0.01f)
}
@Test
fun `color swizzles`() {
val color = RGBColor(0x12, 0x34, 0x56)
assertEquals(color.rgb, 0x123456)
}
@Test
fun `out of bounds clamping positive`() {
val color = RGBColor(0x145, 0x145, 0x145)
assertEquals(color.red, 0xFF)
assertEquals(color.green, 0xFF)
assertEquals(color.blue, 0xFF)
}
@Test
fun `out of bounds clamping negative`() {
val color = RGBColor(-123, -123, -123)
assertEquals(color.red, 0)
assertEquals(color.green, 0)
assertEquals(color.blue, 0)
}
@Test
fun `operations minus`() {
val a = RGBColor(0x12, 0x34, 0x56)
val b = RGBColor(0x05, 0x06, 0x07)
assertEquals(a - b, RGBColor(0x0D, 0x2e, 0x4F))
assertEquals(a - 3, RGBColor(0x0F, 0x31, 0x53))
assertEquals(a - 0.1f, RGBColor(0x00, 0x1b, 0x3d))
}
@Test
fun `conversion rgba`() {
val color = RGBColor(0x12, 0x34, 0x56)
assertEquals(color.rgba(), RGBAColor(0x12, 0x34, 0x56, 0xFF))
}
// TODO: operations (plus, times), conversion, toString, Int::rgb, Int::rgba, mix
}