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 ee2e982d0..8a35d9b85 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 @@ -24,6 +24,7 @@ package net.kyori.adventure.text.format; import java.util.stream.Stream; +import net.kyori.adventure.util.HSVLike; import net.kyori.adventure.util.RGBLike; import net.kyori.examination.Examinable; import net.kyori.examination.ExaminableProperty; @@ -63,10 +64,48 @@ public interface TextColor extends Comparable, Examinable, RGBLike, S * @return a new text colour * @since 4.0.0 */ - static @NonNull TextColor color(final RGBLike rgb) { + static @NonNull TextColor color(final @NonNull RGBLike rgb) { return color(rgb.red(), rgb.green(), rgb.blue()); } + /** + * Creates a new text color, converting the provided {@link HSVLike} to the RGB color space. + * + * @param hsv the hsv value + * @return a new text color + * @see https://en.wikipedia.org/wiki/HSL_and_HSV + * @since 4.6.0 + */ + static @NonNull TextColor color(final @NonNull HSVLike hsv) { + final float s = hsv.s(); + final float v = hsv.v(); + if(s == 0) { + // achromatic (grey) + return color(v, v, v); + } + + final float h = hsv.h() * 6; // sector 0 to 5 + final int i = (int) Math.floor(h); + final float f = h - i; // factorial part of h + final float p = v * (1 - s); + final float q = v * (1 - s * f); + final float t = v * (1 - s * (1 - f)); + + if(i == 0) { + return color(v, t, p); + } else if(i == 1) { + return color(q, v, p); + } else if(i == 2) { + return color(p, v, t); + } else if(i == 3) { + return color(p, q, v); + } else if(i == 4) { + return color(t, p, v); + } else { + return color(v, p, q); + } + } + /** * Create a new text colour with the red, green, and blue components individually. * diff --git a/api/src/test/java/net/kyori/adventure/util/HSVLikeTest.java b/api/src/test/java/net/kyori/adventure/util/HSVLikeTest.java index 373c9fbc2..7c65f21df 100644 --- a/api/src/test/java/net/kyori/adventure/util/HSVLikeTest.java +++ b/api/src/test/java/net/kyori/adventure/util/HSVLikeTest.java @@ -24,15 +24,26 @@ package net.kyori.adventure.util; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; import org.checkerframework.checker.nullness.qual.NonNull; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.awt.Color; import java.math.BigDecimal; import java.math.RoundingMode; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + class HSVLikeTest { + @Test + void roundTripRgbHsvTest() { + NamedTextColor.NAMES.values().forEach(namedTextColor -> { + final TextColor roundTripped = TextColor.color(namedTextColor.asHSV()); + assertEquals(namedTextColor, roundTripped); + }); + } + @Test void compareRgbToHsvConversionToJavaAwtColor() { NamedTextColor.NAMES.values().forEach(HSVLikeTest::assertRgbToHsvConversionRoughlyMatchesJavaAwtColor); @@ -40,7 +51,7 @@ void compareRgbToHsvConversionToJavaAwtColor() { private static void assertRgbToHsvConversionRoughlyMatchesJavaAwtColor(final @NonNull RGBLike rgb) { final HSVLike hsv = rgb.asHSV(); - Assertions.assertArrayEquals( + assertArrayEquals( roundFloats(Color.RGBtoHSB(rgb.red(), rgb.green(), rgb.blue(), null)), roundFloats(new float[]{hsv.h(), hsv.s(), hsv.v()}), rgb.toString()