diff --git a/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt
index 19082060d..43ef1bfac 100644
--- a/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt
+++ b/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt
@@ -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)
diff --git a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/ChatColors.kt b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/ChatColors.kt
index 11cf3dab9..7af6b5c67 100644
--- a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/ChatColors.kt
+++ b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/ChatColors.kt
@@ -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
diff --git a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/Color.kt b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/Color.kt
index fc640ef21..baea5582c 100644
--- a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/Color.kt
+++ b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/Color.kt
@@ -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()
}
diff --git a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColor.kt b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColor.kt
index b408261c3..d1f5f8ad7 100644
--- a/src/main/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColor.kt
+++ b/src/main/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColor.kt
@@ -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)
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGB8Buffer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGB8Buffer.kt
index 03a51976f..fcba232c4 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGB8Buffer.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGB8Buffer.kt
@@ -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())
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGBA8Buffer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGBA8Buffer.kt
index 0ba132a11..7e6522b02 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGBA8Buffer.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/data/buffer/RGBA8Buffer.kt
@@ -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 {
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureUtil.kt
index 89f8d322f..a66877bcf 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureUtil.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureUtil.kt
@@ -111,7 +111,7 @@ object TextureUtil {
}
}
- mkdirParent()
+ file.mkdirParent()
ImageIO.write(bufferedImage, "png", file)
}
diff --git a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt
index dd73877c0..dee35f85c 100644
--- a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt
+++ b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt
@@ -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)
}
diff --git a/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColorTest.kt b/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColorTest.kt
new file mode 100644
index 000000000..615d0a483
--- /dev/null
+++ b/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBAColorTest.kt
@@ -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 .
+ *
+ * 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
+}
diff --git a/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBColorTest.kt b/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBColorTest.kt
new file mode 100644
index 000000000..4268aa724
--- /dev/null
+++ b/src/test/java/de/bixilon/minosoft/data/text/formatting/color/RGBColorTest.kt
@@ -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 .
+ *
+ * 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
+}