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 +}