diff --git a/paper-api/src/main/java/org/bukkit/Color.java b/paper-api/src/main/java/org/bukkit/Color.java index d2f8f5a8e4..e088390ec3 100644 --- a/paper-api/src/main/java/org/bukkit/Color.java +++ b/paper-api/src/main/java/org/bukkit/Color.java @@ -8,6 +8,7 @@ import java.util.Objects; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.SerializableAs; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * A container for a color palette. This class is immutable; the set methods @@ -17,6 +18,7 @@ import org.jetbrains.annotations.NotNull; @SerializableAs("Color") public final class Color implements ConfigurationSerializable { private static final int BIT_MASK = 0xff; + private static final int DEFAULT_ALPHA = 255; /** * White, or (0xFF,0xFF,0xFF) in (R,G,B) @@ -103,10 +105,26 @@ public final class Color implements ConfigurationSerializable { */ public static final Color ORANGE = fromRGB(0xFFA500); + private final byte alpha; private final byte red; private final byte green; private final byte blue; + /** + * Creates a new Color object from an alpha, red, green, and blue + * + * @param alpha integer from 0-255 + * @param red integer from 0-255 + * @param green integer from 0-255 + * @param blue integer from 0-255 + * @return a new Color object for the alpha, red, green, blue + * @throws IllegalArgumentException if any value is strictly {@literal >255 or <0} + */ + @NotNull + public static Color fromARGB(int alpha, int red, int green, int blue) throws IllegalArgumentException { + return new Color(alpha, red, green, blue); + } + /** * Creates a new Color object from a red, green, and blue * @@ -118,7 +136,7 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException { - return new Color(red, green, blue); + return new Color(DEFAULT_ALPHA, red, green, blue); } /** @@ -132,7 +150,7 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException { - return new Color(red, green, blue); + return new Color(DEFAULT_ALPHA, red, green, blue); } /** @@ -146,8 +164,20 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public static Color fromRGB(int rgb) throws IllegalArgumentException { - Preconditions.checkArgument((rgb >> 24) == 0, "Extrenuous data in: ", rgb); - return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb >> 0 & BIT_MASK); + Preconditions.checkArgument((rgb >> 24) == 0, "Extraneous data in: %s", rgb); + return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb & BIT_MASK); + } + + /** + * Creates a new color object from an integer that contains the alpha, red, + * green, and blue bytes. + * + * @param argb the integer storing the alpha, red, green, and blue values + * @return a new color object for specified values + */ + @NotNull + public static Color fromARGB(int argb) { + return fromARGB(argb >> 24 & BIT_MASK, argb >> 16 & BIT_MASK, argb >> 8 & BIT_MASK, argb & BIT_MASK); } /** @@ -161,20 +191,46 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public static Color fromBGR(int bgr) throws IllegalArgumentException { - Preconditions.checkArgument((bgr >> 24) == 0, "Extrenuous data in: ", bgr); - return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr >> 0 & BIT_MASK); + Preconditions.checkArgument((bgr >> 24) == 0, "Extrenuous data in: %s", bgr); + return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr & BIT_MASK); } private Color(int red, int green, int blue) { - Preconditions.checkArgument(red >= 0 && red <= BIT_MASK, "Red is not between 0-255: ", red); - Preconditions.checkArgument(green >= 0 && green <= BIT_MASK, "Green is not between 0-255: ", green); - Preconditions.checkArgument(blue >= 0 && blue <= BIT_MASK, "Blue is not between 0-255: ", blue); + this(DEFAULT_ALPHA, red, green, blue); + } + private Color(int alpha, int red, int green, int blue) { + Preconditions.checkArgument(alpha >= 0 && alpha <= BIT_MASK, "Alpha[%s] is not between 0-255", alpha); + Preconditions.checkArgument(red >= 0 && red <= BIT_MASK, "Red[%s] is not between 0-255", red); + Preconditions.checkArgument(green >= 0 && green <= BIT_MASK, "Green[%s] is not between 0-255", green); + Preconditions.checkArgument(blue >= 0 && blue <= BIT_MASK, "Blue[%s] is not between 0-255", blue); + + this.alpha = (byte) alpha; this.red = (byte) red; this.green = (byte) green; this.blue = (byte) blue; } + /** + * Gets the alpha component + * + * @return alpha component, from 0 to 255 + */ + public int getAlpha() { + return BIT_MASK & alpha; + } + + /** + * Creates a new Color object with specified component + * + * @param alpha the alpha component, from 0 to 255 + * @return a new color object with the red component + */ + @NotNull + public Color setAlpha(int alpha) { + return fromARGB(alpha, getRed(), getGreen(), getBlue()); + } + /** * Gets the red component * @@ -192,7 +248,7 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public Color setRed(int red) { - return fromRGB(red, getGreen(), getBlue()); + return fromARGB(getAlpha(), red, getGreen(), getBlue()); } /** @@ -212,7 +268,7 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public Color setGreen(int green) { - return fromRGB(getRed(), green, getBlue()); + return fromARGB(getAlpha(), getRed(), green, getBlue()); } /** @@ -232,7 +288,7 @@ public final class Color implements ConfigurationSerializable { */ @NotNull public Color setBlue(int blue) { - return fromRGB(getRed(), getGreen(), blue); + return fromARGB(getAlpha(), getRed(), getGreen(), blue); } /** @@ -241,7 +297,16 @@ public final class Color implements ConfigurationSerializable { * @return An integer representation of this color, as 0xRRGGBB */ public int asRGB() { - return getRed() << 16 | getGreen() << 8 | getBlue() << 0; + return getRed() << 16 | getGreen() << 8 | getBlue(); + } + + /** + * Gets the color as an ARGB integer. + * + * @return An integer representation of this color, as 0xAARRGGBB + */ + public int asARGB() { + return getAlpha() << 24 | getRed() << 16 | getGreen() << 8 | getBlue(); } /** @@ -250,7 +315,7 @@ public final class Color implements ConfigurationSerializable { * @return An integer representation of this color, as 0xBBGGRR */ public int asBGR() { - return getBlue() << 16 | getGreen() << 8 | getRed() << 0; + return getBlue() << 16 | getGreen() << 8 | getRed(); } /** @@ -263,7 +328,7 @@ public final class Color implements ConfigurationSerializable { // TODO: Javadoc what this method does, not what it mimics. API != Implementation @NotNull public Color mixDyes(@NotNull DyeColor... colors) { - Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "Colors cannot be null"); + Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "DyeColor cannot be null or contain null values"); Color[] toPass = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { @@ -275,7 +340,10 @@ public final class Color implements ConfigurationSerializable { /** * Creates a new color with its RGB components changed as if it was dyed - * with the colors passed in, replicating vanilla workbench dyeing + * with the colors passed in, replicating vanilla workbench dyeing. + * + * Note that this method does not currently take into account alpha + * components. * * @param colors The colors to dye with * @return A new color with the changed rgb components @@ -313,36 +381,42 @@ public final class Color implements ConfigurationSerializable { return false; } final Color that = (Color) o; - return this.blue == that.blue && this.green == that.green && this.red == that.red; + return this.alpha == that.alpha && this.blue == that.blue && this.green == that.green && this.red == that.red; } @Override public int hashCode() { - return asRGB() ^ Color.class.hashCode(); + return asARGB() ^ Color.class.hashCode(); } @Override @NotNull public Map serialize() { - return ImmutableMap.of( - "RED", getRed(), - "BLUE", getBlue(), - "GREEN", getGreen() + return ImmutableMap.of( + "ALPHA", getAlpha(), + "RED", getRed(), + "BLUE", getBlue(), + "GREEN", getGreen() ); } @SuppressWarnings("javadoc") @NotNull public static Color deserialize(@NotNull Map map) { - return fromRGB( - asInt("RED", map), - asInt("GREEN", map), - asInt("BLUE", map) + return fromARGB( + asInt("ALPHA", map, DEFAULT_ALPHA), + asInt("RED", map), + asInt("GREEN", map), + asInt("BLUE", map) ); } private static int asInt(@NotNull String string, @NotNull Map map) { - Object value = map.get(string); + return asInt(string, map, null); + } + + private static int asInt(@NotNull String string, @NotNull Map map, @Nullable Object defaultValue) { + Object value = map.getOrDefault(string, defaultValue); if (value == null) { throw new IllegalArgumentException(string + " not in map " + map); } @@ -354,6 +428,6 @@ public final class Color implements ConfigurationSerializable { @Override public String toString() { - return "Color:[rgb0x" + Integer.toHexString(getRed()).toUpperCase() + Integer.toHexString(getGreen()).toUpperCase() + Integer.toHexString(getBlue()).toUpperCase() + "]"; + return "Color:[argb0x" + Integer.toHexString(asARGB()).toUpperCase() + "]"; } } diff --git a/paper-api/src/test/java/org/bukkit/ColorTest.java b/paper-api/src/test/java/org/bukkit/ColorTest.java index 70183fdb7f..1f722fb249 100644 --- a/paper-api/src/test/java/org/bukkit/ColorTest.java +++ b/paper-api/src/test/java/org/bukkit/ColorTest.java @@ -10,19 +10,27 @@ public class ColorTest { static class TestColor { static int id = 0; final String name; + final int argb; final int rgb; final int bgr; + final int a; final int r; final int g; final int b; TestColor(int rgb, int bgr, int r, int g, int b) { + this((255 << 24 | r << 16 | g << 8 | b), rgb, bgr, 255, r, g, b); + } + + TestColor(int argb, int rgb, int bgr, int a, int r, int g, int b) { + this.argb = argb; this.rgb = rgb; this.bgr = bgr; + this.a = a; this.r = r; this.g = g; this.b = b; - this.name = id + ":" + Integer.toHexString(rgb).toUpperCase() + "_" + Integer.toHexString(bgr).toUpperCase() + "-r" + Integer.toHexString(r).toUpperCase() + "-g" + Integer.toHexString(g).toUpperCase() + "-b" + Integer.toHexString(b).toUpperCase(); + this.name = id + ":" + Integer.toHexString(argb).toUpperCase() + "_" + Integer.toHexString(rgb).toUpperCase() + "_" + Integer.toHexString(bgr).toUpperCase() + "-a" + Integer.toHexString(a).toUpperCase() + "-r" + Integer.toHexString(r).toUpperCase() + "-g" + Integer.toHexString(g).toUpperCase() + "-b" + Integer.toHexString(b).toUpperCase(); } } @@ -32,7 +40,10 @@ public class ColorTest { new TestColor(0xFFFFAA, 0xAAFFFF, 0xFF, 0xFF, 0xAA), new TestColor(0xFF00FF, 0xFF00FF, 0xFF, 0x00, 0xFF), new TestColor(0x67FF22, 0x22FF67, 0x67, 0xFF, 0x22), - new TestColor(0x000000, 0x000000, 0x00, 0x00, 0x00) + new TestColor(0x000000, 0x000000, 0x00, 0x00, 0x00), + /* 0xAARRGGBB, 0xRRGGBB, 0xBBGGRR, 0xAA, 0xRR, 0xGG, 0xBB */ + new TestColor(0xFF559922, 0x559922, 0x229955, 0xFF, 0x55, 0x99, 0x22), + new TestColor(0x00000000, 0x000000, 0x000000, 0x00, 0x00, 0x00, 0x00) }; @Test @@ -55,11 +66,15 @@ public class ColorTest { @Test public void testEqualities() { for (TestColor testColor : examples) { + Color fromARGB = Color.fromARGB(testColor.argb); + Color fromARGBs = Color.fromARGB(testColor.a, testColor.r, testColor.g, testColor.b); Color fromRGB = Color.fromRGB(testColor.rgb); Color fromBGR = Color.fromBGR(testColor.bgr); Color fromRGBs = Color.fromRGB(testColor.r, testColor.g, testColor.b); Color fromBGRs = Color.fromBGR(testColor.b, testColor.g, testColor.r); + assertThat(testColor.name, fromARGB, is(fromARGB)); + assertThat(testColor.name, fromARGBs, is(fromARGBs)); assertThat(testColor.name, fromRGB, is(fromRGBs)); assertThat(testColor.name, fromRGB, is(fromBGR)); assertThat(testColor.name, fromRGB, is(fromBGRs)); @@ -73,20 +88,29 @@ public class ColorTest { public void testInequalities() { for (int i = 1; i < examples.length; i++) { TestColor testFrom = examples[i]; - Color from = Color.fromRGB(testFrom.rgb); + Color from = Color.fromARGB(testFrom.argb); for (int j = i - 1; j >= 0; j--) { TestColor testTo = examples[j]; - Color to = Color.fromRGB(testTo.rgb); + Color to = Color.fromARGB(testTo.argb); String name = testFrom.name + " to " + testTo.name; assertThat(name, from, is(not(to))); - Color transform = from.setRed(testTo.r).setBlue(testTo.b).setGreen(testTo.g); + Color transform = from.setAlpha(testTo.a).setRed(testTo.r).setBlue(testTo.b).setGreen(testTo.g); assertThat(name, transform, is(not(sameInstance(from)))); assertThat(name, transform, is(to)); } } } + // ARGB tests + @Test + public void testARGB() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromARGB(testColor.argb).asARGB(), is(testColor.argb)); + assertThat(testColor.name, Color.fromARGB(testColor.a, testColor.r, testColor.g, testColor.b).asARGB(), is(testColor.argb)); + } + } + // RGB tests @Test public void testRGB() { @@ -149,6 +173,74 @@ public class ColorTest { Color.fromBGR(-1); } + // Alpha tests + @Test + public void testAlpha() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromARGB(testColor.argb).getAlpha(), is(testColor.a)); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA01() { + Color.fromARGB(-1, 0x00, 0x00, 0x00); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA02() { + Color.fromARGB(Integer.MAX_VALUE, 0x00, 0x00, 0x00); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA03() { + Color.fromARGB(Integer.MIN_VALUE, 0x00, 0x00, 0x00); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA04() { + Color.fromARGB(0x100, 0x00, 0x00, 0x00); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA05() { + Color.fromBGR(0x00, 0x00, 0x00).setAlpha(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA06() { + Color.fromBGR(0x00, 0x00, 0x00).setAlpha(Integer.MAX_VALUE); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA07() { + Color.fromBGR(0x00, 0x00, 0x00).setAlpha(Integer.MIN_VALUE); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA08() { + Color.fromBGR(0x00, 0x00, 0x00).setAlpha(0x100); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA09() { + Color.WHITE.setAlpha(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA10() { + Color.WHITE.setAlpha(Integer.MAX_VALUE); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA11() { + Color.WHITE.setAlpha(Integer.MIN_VALUE); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidA12() { + Color.WHITE.setAlpha(0x100); + } + // Red tests @Test public void testRed() {