From 00fed477bc9373d3def3a2cea2a0a9a8fa32953f Mon Sep 17 00:00:00 2001 From: Riley Park Date: Sat, 7 Dec 2024 19:44:44 -0800 Subject: [PATCH] shadow color changes --- .../adventure/text/format/ShadowColor.java | 103 ++++++++---------- .../text/format/ShadowColorImpl.java | 2 +- .../kyori/adventure/text/format/Style.java | 8 +- .../adventure/text/format/StyleImpl.java | 4 +- .../adventure/text/format/TextColor.java | 9 +- .../java/net/kyori/adventure/util/ARGB.java | 59 ++++++++++ .../net/kyori/adventure/util/ARGBTest.java | 65 +++++++++++ 7 files changed, 188 insertions(+), 62 deletions(-) create mode 100644 api/src/main/java/net/kyori/adventure/util/ARGB.java create mode 100644 api/src/test/java/net/kyori/adventure/util/ARGBTest.java diff --git a/api/src/main/java/net/kyori/adventure/text/format/ShadowColor.java b/api/src/main/java/net/kyori/adventure/text/format/ShadowColor.java index 4cb14994d..c04631d26 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/ShadowColor.java +++ b/api/src/main/java/net/kyori/adventure/text/format/ShadowColor.java @@ -26,6 +26,7 @@ import net.kyori.adventure.util.ARGBLike; import net.kyori.adventure.util.RGBLike; import org.intellij.lang.annotations.Pattern; +import net.kyori.adventure.util.ARGB; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,35 +41,6 @@ * @sinceMinecraft 1.21.4 */ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { - /** - * Linearly interpolates between {@code a} and {@code b} by {@code t}. - * - *

This returns a color blended between color {@code a}, at {@code t=0.0}, and color {@code b}, at {@code t=1.0}.

- * - * @param t the interpolation value, between {@code 0.0} and {@code 1.0} (both inclusive) - * @param a the lower bound ({@code t=0.0}) - * @param b the upper bound ({@code t=1.0}) - * @return the interpolated value, a color between the two input colors {@code a} and {@code b} - * @since 4.18.0 - */ - static @NotNull ShadowColor lerp(final float t, final @NotNull ARGBLike a, final @NotNull ARGBLike b) { - final float clampedT = Math.min(1.0f, Math.max(0.0f, t)); // clamp between 0 and 1 - final int ar = a.red(); - final int br = b.red(); - final int ag = a.green(); - final int bg = b.green(); - final int ab = a.blue(); - final int bb = b.blue(); - final int aa = a.alpha(); - final int ba = b.alpha(); - return shadowColor( - Math.round(ar + clampedT * (br - ar)), - Math.round(ag + clampedT * (bg - ag)), - Math.round(ab + clampedT * (bb - ab)), - Math.round(aa + clampedT * (ba - aa)) - ); - } - /** * Return a shadow color that will disable the shadow on a component. * @@ -84,15 +56,14 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { * *

This int will be in the format {@code 0xAARRGGBB}

* - * @param value the int-packed ARGB value + * @param argb the int-packed ARGB value * @return a shadow color * @since 4.18.0 */ @Contract(pure = true) - static @NotNull ShadowColor shadowColor(final int value) { - if (value == ShadowColorImpl.NONE_VALUE) return none(); - - return new ShadowColorImpl(value); + static @NotNull ShadowColor shadowColor(final int argb) { + if (argb == ShadowColorImpl.NONE_VALUE) return none(); + return new ShadowColorImpl(argb); } /** @@ -112,27 +83,20 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { final @Range(from = 0x0, to = 0xff) int blue, final @Range(from = 0x0, to = 0xff) int alpha ) { - final int value = - (alpha & 0xff) << 24 - | (red & 0xff) << 16 - | (green & 0xff) << 8 - | (blue & 0xff); - - if (value == ShadowColorImpl.NONE_VALUE) return none(); - return new ShadowColorImpl(value); + return shadowColor(ARGB.argb(red, green, blue, alpha)); } /** * Create a shadow color from an existing colour plus an alpha value. * - * @param color the existing color + * @param rgb the existing color * @param alpha the alpha * @return a shadow colour * @since 4.18.0 */ @Contract(pure = true) - static @NotNull ShadowColor shadowColor(final @NotNull RGBLike color, final @Range(from = 0x0, to = 0xff) int alpha) { - return shadowColor(color.red(), color.green(), color.blue(), alpha); + static @NotNull ShadowColor shadowColor(final @NotNull RGBLike rgb, final @Range(from = 0x0, to = 0xff) int alpha) { + return shadowColor(rgb.red(), rgb.green(), rgb.blue(), alpha); } /** @@ -169,8 +133,8 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { final int g = Integer.parseInt(value.substring(3, 5), 16); final int b = Integer.parseInt(value.substring(5, 7), 16); final int a = Integer.parseInt(value.substring(7, 9), 16); - return new ShadowColorImpl((a << 24) | (r << 16) | (g << 8) | b); - } catch (NumberFormatException ignored) { + return new ShadowColorImpl(ARGB.argb(a, r, g, b)); + } catch (final NumberFormatException ignored) { return null; } } @@ -185,10 +149,10 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ default @NotNull String asHexString() { final int argb = this.value(); - final int a = (argb >> 24) & 0xFF; - final int r = (argb >> 16) & 0xFF; - final int g = (argb >> 8) & 0xFF; - final int b = argb & 0xFF; + final int a = ARGB.alpha(argb); + final int r = ARGB.red(argb); + final int g = ARGB.green(argb); + final int b = ARGB.blue(argb); return String.format("#%02X%02X%02X%02X", r, g, b, a); } @@ -200,7 +164,7 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ @Override default @Range(from = 0x0, to = 0xff) int red() { - return (this.value() >> 16) & 0xff; + return ARGB.red(this.value()); } /** @@ -211,7 +175,7 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ @Override default @Range(from = 0x0, to = 0xff) int green() { - return (this.value() >> 8) & 0xff; + return ARGB.green(this.value()); } /** @@ -222,7 +186,7 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ @Override default @Range(from = 0x0, to = 0xff) int blue() { - return this.value() & 0xff; + return ARGB.blue(this.value()); } /** @@ -233,7 +197,7 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ @Override default @Range(from = 0x0, to = 0xff) int alpha() { - return (this.value() >> 24) & 0xff; + return ARGB.alpha(this.value()); } /** @@ -244,6 +208,35 @@ public interface ShadowColor extends StyleBuilderApplicable, ARGBLike { */ int value(); + /** + * Linearly interpolates between {@code a} and {@code b} by {@code t}. + * + *

This returns a color blended between color {@code a}, at {@code t=0.0}, and color {@code b}, at {@code t=1.0}.

+ * + * @param t the interpolation value, between {@code 0.0} and {@code 1.0} (both inclusive) + * @param a the lower bound ({@code t=0.0}) + * @param b the upper bound ({@code t=1.0}) + * @return the interpolated value, a color between the two input colors {@code a} and {@code b} + * @since 4.18.0 + */ + static @NotNull ShadowColor lerp(final float t, final @NotNull ARGBLike a, final @NotNull ARGBLike b) { + final float clampedT = Math.min(1.0f, Math.max(0.0f, t)); // clamp between 0 and 1 + final int ar = a.red(); + final int br = b.red(); + final int ag = a.green(); + final int bg = b.green(); + final int ab = a.blue(); + final int bb = b.blue(); + final int aa = a.alpha(); + final int ba = b.alpha(); + return shadowColor( + Math.round(ar + clampedT * (br - ar)), + Math.round(ag + clampedT * (bg - ag)), + Math.round(ab + clampedT * (bb - ab)), + Math.round(aa + clampedT * (ba - aa)) + ); + } + @Override default void styleApply(final Style.@NotNull Builder style) { style.shadowColor(this); diff --git a/api/src/main/java/net/kyori/adventure/text/format/ShadowColorImpl.java b/api/src/main/java/net/kyori/adventure/text/format/ShadowColorImpl.java index 22fee2076..4136014c0 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/ShadowColorImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/format/ShadowColorImpl.java @@ -35,7 +35,7 @@ final class ShadowColorImpl implements ShadowColor, Examinable { static final int NONE_VALUE = 0; static final ShadowColorImpl NONE = new ShadowColorImpl(NONE_VALUE); - private final int value; + private final int value; // ARGB ShadowColorImpl(final int value) { this.value = value; diff --git a/api/src/main/java/net/kyori/adventure/text/format/Style.java b/api/src/main/java/net/kyori/adventure/text/format/Style.java index efac65fda..c15a80fac 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/Style.java +++ b/api/src/main/java/net/kyori/adventure/text/format/Style.java @@ -562,11 +562,17 @@ default boolean hasDecoration(final @NotNull TextDecoration decoration) { */ enum Merge { /** - * Merges {@link Style#color()} and {@link Style#shadowColor()}. + * Merges {@link Style#color()}. * * @since 4.0.0 */ COLOR, + /** + * Merges {@link Style#shadowColor()}. + * + * @since 4.0.0 + */ + SHADOW_COLOR, /** * Merges {@link Style#decorations()}. * diff --git a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java index 65d106565..d4b006410 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java @@ -426,7 +426,10 @@ static final class BuilderImpl implements Builder { this.color(color); } } + } + + if (merges.contains(Merge.SHADOW_COLOR)) { final ShadowColor shadowColor = that.shadowColor(); if (shadowColor != null) { if (strategy == Merge.Strategy.ALWAYS || (strategy == Merge.Strategy.IF_ABSENT_ON_TARGET && this.shadowColor == null)) { @@ -434,7 +437,6 @@ static final class BuilderImpl implements Builder { } } } - if (merges.contains(Merge.DECORATIONS)) { for (int i = 0, length = DecorationMap.DECORATIONS.length; i < length; i++) { final TextDecoration decoration = DecorationMap.DECORATIONS[i]; diff --git a/api/src/main/java/net/kyori/adventure/text/format/TextColor.java b/api/src/main/java/net/kyori/adventure/text/format/TextColor.java index 33989354a..f0d9ec951 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/TextColor.java +++ b/api/src/main/java/net/kyori/adventure/text/format/TextColor.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.stream.Stream; +import net.kyori.adventure.util.ARGB; import net.kyori.adventure.util.HSVLike; import net.kyori.adventure.util.RGBLike; import net.kyori.examination.Examinable; @@ -68,7 +69,7 @@ public interface TextColor extends Comparable, Examinable, RGBLike, S * @since 4.0.0 */ static @NotNull TextColor color(final int value) { - final int truncatedValue = value & 0xffffff; + final int truncatedValue = ARGB.rgb(value); final NamedTextColor named = NamedTextColor.namedColor(truncatedValue); return named != null ? named : new TextColorImpl(truncatedValue); } @@ -226,7 +227,7 @@ public interface TextColor extends Comparable, Examinable, RGBLike, S */ @Override default @Range(from = 0x0, to = 0xff) int red() { - return (this.value() >> 16) & 0xff; + return ARGB.red(this.value()); } /** @@ -237,7 +238,7 @@ public interface TextColor extends Comparable, Examinable, RGBLike, S */ @Override default @Range(from = 0x0, to = 0xff) int green() { - return (this.value() >> 8) & 0xff; + return ARGB.green(this.value()); } /** @@ -248,7 +249,7 @@ public interface TextColor extends Comparable, Examinable, RGBLike, S */ @Override default @Range(from = 0x0, to = 0xff) int blue() { - return this.value() & 0xff; + return ARGB.blue(this.value()); } /** diff --git a/api/src/main/java/net/kyori/adventure/util/ARGB.java b/api/src/main/java/net/kyori/adventure/util/ARGB.java new file mode 100644 index 000000000..a89141d96 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/util/ARGB.java @@ -0,0 +1,59 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2024 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.util; + +public interface ARGB { + static int argb(final int alpha, final int red, final int green, final int blue) { + return alpha << 24 | red << 16 | green << 8 | blue; + } + + static int argb(final float alpha, final float red, final float green, final float blue) { + return argb( + (int) Math.floor(alpha * 255f), + (int) Math.floor(red * 255f), + (int) Math.floor(green * 255f), + (int) Math.floor(blue * 255f) + ); + } + + static int rgb(final int argb) { + return argb & 0xffffff; + } + + static int alpha(final int argb) { + return argb >>> 24; + } + + static int red(final int argb) { + return (argb >> 16) & 0xff; + } + + static int green(final int argb) { + return (argb >> 8) & 0xff; + } + + static int blue(final int argb) { + return argb & 0xff; + } +} diff --git a/api/src/test/java/net/kyori/adventure/util/ARGBTest.java b/api/src/test/java/net/kyori/adventure/util/ARGBTest.java new file mode 100644 index 000000000..e96832d62 --- /dev/null +++ b/api/src/test/java/net/kyori/adventure/util/ARGBTest.java @@ -0,0 +1,65 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2024 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ARGBTest { + @Test + void testARGB() { + assertEquals(0xffef323d, ARGB.argb(255, 239, 50, 61)); + assertEquals(0xff7087ff, ARGB.argb(1f, 0.44f, 0.53f, 1f)); + } + + @Test + void testRGB() { + assertEquals(0xffef32, ARGB.rgb(ARGB.argb(255, 239, 50, 61))); + } + + @Test + void testAlpha() { + assertEquals(255, ARGB.alpha(ARGB.argb(255, 239, 50, 61))); + assertEquals(255, ARGB.alpha(ARGB.argb(1f, 0.44f, 0.53f, 1f))); + } + + @Test + void testRed() { + assertEquals(239, ARGB.red(ARGB.argb(255, 239, 50, 61))); + assertEquals(112, ARGB.red(ARGB.argb(1f, 0.44f, 0.53f, 1f))); + } + + @Test + void testGreen() { + assertEquals(50, ARGB.green(ARGB.argb(255, 239, 50, 61))); + assertEquals(135, ARGB.green(ARGB.argb(1f, 0.44f, 0.53f, 1f))); + } + + @Test + void testBlue() { + assertEquals(61, ARGB.blue(ARGB.argb(255, 239, 50, 61))); + assertEquals(255, ARGB.blue(ARGB.argb(1f, 0.44f, 0.53f, 1f))); + } +}