Added new class Color
This commit is contained in:
parent
1b5e436d09
commit
de90c9780b
@ -17,13 +17,322 @@
|
||||
// <https://www.gnu.org/licenses/> or write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.pixelgamelibrary.api.graphics;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents a color in the RGBA format where each component is a float value
|
||||
* between 0 and 1. This class provides methods to manipulate color values,
|
||||
* including normalization, blending, and conversion to various formats (e.g.,
|
||||
* hexadecimal, integer).
|
||||
* <p>
|
||||
* The color components are represented as:
|
||||
* <ul>
|
||||
* <li><b>red</b> - The intensity of the red component, ranging from 0 to
|
||||
* 1.</li>
|
||||
* <li><b>green</b> - The intensity of the green component, ranging from 0 to
|
||||
* 1.</li>
|
||||
* <li><b>blue</b> - The intensity of the blue component, ranging from 0 to
|
||||
* 1.</li>
|
||||
* <li><b>alpha</b> - The transparency level, where 0 is fully transparent and 1
|
||||
* is fully opaque.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author robertvokac
|
||||
* <p>
|
||||
* This class is immutable. Each method that modifies a color returns a new
|
||||
* instance instead of modifying the original.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Author:</b> robertvokac
|
||||
* </p>
|
||||
*/
|
||||
public interface Color {
|
||||
|
||||
@Data
|
||||
public final class Color {
|
||||
|
||||
private float red, green, blue, alpha;
|
||||
|
||||
/**
|
||||
* Ensures that the color components are confined within the valid range [0,
|
||||
* 1]. If any component exceeds this range, it will be clamped to fit within
|
||||
* the specified boundaries. This method supports method chaining by
|
||||
* returning the current instance.
|
||||
*
|
||||
* @return the current instance of this Color for chaining
|
||||
*/
|
||||
public Color normalize() {
|
||||
red = Math.max(0, Math.min(red, 1));
|
||||
green = Math.max(0, Math.min(green, 1));
|
||||
blue = Math.max(0, Math.min(blue, 1));
|
||||
alpha = Math.max(0, Math.min(alpha, 1));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Default alpha value for colors
|
||||
public static final float DEFAULT_ALPHA = 1f;
|
||||
|
||||
// Default alpha int value for colors
|
||||
public static final int DEFAULT_ALPHA_INT = (int) DEFAULT_ALPHA;
|
||||
|
||||
// Default constructor initializes color to black with full opacity
|
||||
public Color() {
|
||||
this.red = 0;
|
||||
this.green = 0;
|
||||
this.blue = 0;
|
||||
this.alpha = DEFAULT_ALPHA;
|
||||
}
|
||||
|
||||
// Constructor initializes color with specified RGB values, defaulting alpha to full opacity
|
||||
public Color(float red, float green, float blue) {
|
||||
this(red, green, blue, DEFAULT_ALPHA);
|
||||
}
|
||||
|
||||
// Constructor initializes color with specified RGBA values and normalizes them
|
||||
public Color(float red, float green, float blue, float alpha) {
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.alpha = alpha;
|
||||
normalize();
|
||||
}
|
||||
|
||||
// Constructor initializes color with specified RGB values, defaulting alpha to full opacity
|
||||
public Color(int red, int green, int blue) {
|
||||
this(red, green, blue, DEFAULT_ALPHA_INT);
|
||||
}
|
||||
|
||||
// Constructor initializes color with specified RGBA values, converting from integers to floats
|
||||
public Color(int red, int green, int blue, int alpha) {
|
||||
this((float) red, (float) green, (float) blue, (float) alpha);
|
||||
}
|
||||
|
||||
// Copy constructor creates a new Color instance from an existing one
|
||||
public Color(Color color) {
|
||||
set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's components to match the provided color.
|
||||
*
|
||||
* @param color the Color instance to copy from
|
||||
* @return this color instance for method chaining
|
||||
*/
|
||||
public Color set(Color color) {
|
||||
return set(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's components using the provided RGB values, defaulting
|
||||
* alpha to full opacity.
|
||||
*
|
||||
* @param red the red component
|
||||
* @param green the green component
|
||||
* @param blue the blue component
|
||||
* @return this color instance for method chaining
|
||||
*/
|
||||
public Color set(float red, float green, float blue) {
|
||||
return set(red, green, blue, DEFAULT_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's components using the provided RGBA values.
|
||||
*
|
||||
* @param red the red component
|
||||
* @param green the green component
|
||||
* @param blue the blue component
|
||||
* @param alpha the alpha component
|
||||
* @return this color instance for method chaining
|
||||
*/
|
||||
public Color set(float red, float green, float blue, float alpha) {
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.alpha = alpha;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's components using the provided RGB values, defaulting
|
||||
* alpha to full opacity.
|
||||
*
|
||||
* @param red the red component
|
||||
* @param green the green component
|
||||
* @param blue the blue component
|
||||
* @return this color instance for method chaining
|
||||
*/
|
||||
public Color set(int red, int green, int blue) {
|
||||
return set(red, green, blue, DEFAULT_ALPHA_INT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's components using the provided RGBA values.
|
||||
*
|
||||
* @param red the red component
|
||||
* @param green the green component
|
||||
* @param blue the blue component
|
||||
* @param alpha the alpha component
|
||||
* @return this color instance for method chaining
|
||||
*/
|
||||
public Color set(int red, int green, int blue, int alpha) {
|
||||
return set(
|
||||
(float) red,
|
||||
(float) green,
|
||||
(float) blue,
|
||||
(float) alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blends this color with the specified color by multiplying their
|
||||
* respective components.
|
||||
*
|
||||
* @param other the color to blend with
|
||||
* @return the updated color instance for method chaining
|
||||
*/
|
||||
public Color multiply(Color other) {
|
||||
this.red *= other.red;
|
||||
this.green *= other.green;
|
||||
this.blue *= other.blue;
|
||||
this.alpha *= other.alpha;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the intensity of this color by multiplying each component by the
|
||||
* specified factor.
|
||||
*
|
||||
* @param factor the scalar value to multiply with
|
||||
* @return the updated color instance for method chaining
|
||||
*/
|
||||
public Color multiply(float factor) {
|
||||
this.red *= factor;
|
||||
this.green *= factor;
|
||||
this.blue *= factor;
|
||||
this.alpha *= factor;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the intensity of this color by adding the corresponding values
|
||||
* from another color.
|
||||
*
|
||||
* @param other the color whose components will be added
|
||||
* @return the updated color instance for method chaining
|
||||
*/
|
||||
public Color add(Color other) {
|
||||
this.red += other.red;
|
||||
this.green += other.green;
|
||||
this.blue += other.blue;
|
||||
this.alpha += other.alpha;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the intensity of this color by subtracting the corresponding
|
||||
* values from another color.
|
||||
*
|
||||
* @param other the color whose components will be subtracted
|
||||
* @return the updated color instance for method chaining
|
||||
*/
|
||||
public Color subtract(Color other) {
|
||||
this.red -= other.red;
|
||||
this.green -= other.green;
|
||||
this.blue -= other.blue;
|
||||
this.alpha -= other.alpha;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Check for reference equality
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure the other object is non-null and of the same class
|
||||
if (obj == null || !(obj instanceof Color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cast and compare the packed integer representations of the two colors
|
||||
Color otherColor = (Color) obj;
|
||||
return this.toInt() == otherColor.toInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Calculate a hash code based on the color's components
|
||||
int hash = (red != 0.0f ? Float.floatToIntBits(red) : 0);
|
||||
hash = 31 * hash + (green != 0.0f ? Float.floatToIntBits(green) : 0);
|
||||
hash = 31 * hash + (blue != 0.0f ? Float.floatToIntBits(blue) : 0);
|
||||
hash = 31 * hash + (alpha != 0.0f ? Float.floatToIntBits(alpha) : 0);
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the color to an integer representation (0xAARRGGBB).
|
||||
*
|
||||
* @return the integer representation of this color
|
||||
*/
|
||||
public int toInt() {
|
||||
int a = Math.round(alpha * 255);
|
||||
int r = Math.round(red * 255);
|
||||
int g = Math.round(green * 255);
|
||||
int b = Math.round(blue * 255);
|
||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public static int alpha(float alpha) {
|
||||
return Math.round(alpha * 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the color to a hexadecimal string representation (e.g.,
|
||||
* "#AARRGGBB").
|
||||
*
|
||||
* @return the hexadecimal string representation of this color
|
||||
*/
|
||||
public String toHexString() {
|
||||
return String.format("#%08X", toInt());
|
||||
}
|
||||
|
||||
public static Color valueOf(String hexString) {
|
||||
// Remove '#' if present
|
||||
if (hexString.startsWith("#")) {
|
||||
hexString = hexString.substring(1);
|
||||
}
|
||||
|
||||
// Ensure the hex string is the correct length
|
||||
if (hexString.length() != 8 && hexString.length() != 6) {
|
||||
throw new IllegalArgumentException("Hex string must be in the format #AARRGGBB or RRGGBB");
|
||||
}
|
||||
|
||||
int r, g, b, a = 255; // Default alpha is fully opaque
|
||||
|
||||
// Parse the hex values
|
||||
if (hexString.length() == 8) { // Format: AARRGGBB
|
||||
a = Integer.parseInt(hexString.substring(0, 2), 16);
|
||||
r = Integer.parseInt(hexString.substring(2, 4), 16);
|
||||
g = Integer.parseInt(hexString.substring(4, 6), 16);
|
||||
b = Integer.parseInt(hexString.substring(6, 8), 16);
|
||||
} else { // Format: RRGGBB
|
||||
r = Integer.parseInt(hexString.substring(0, 2), 16);
|
||||
g = Integer.parseInt(hexString.substring(2, 4), 16);
|
||||
b = Integer.parseInt(hexString.substring(4, 6), 16);
|
||||
}
|
||||
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
public Color cpy() {
|
||||
return new Color(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Provide a string representation of the color in RGBA format
|
||||
return String.format("Color{red=%f, green=%f, blue=%f, alpha=%f}", red, green, blue, alpha);
|
||||
}
|
||||
|
||||
}
|
||||
|
188
src/test/java/com/pixelgamelibrary/api/graphics/ColorTest.java
Normal file
188
src/test/java/com/pixelgamelibrary/api/graphics/ColorTest.java
Normal file
@ -0,0 +1,188 @@
|
||||
package com.pixelgamelibrary.api.graphics;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ColorTest {
|
||||
|
||||
@Test
|
||||
public void testDefaultConstructor() {
|
||||
Color color = new Color();
|
||||
assertEquals(0.0f, color.getRed(), 0.001);
|
||||
assertEquals(0.0f, color.getGreen(), 0.001);
|
||||
assertEquals(0.0f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRgbConstructor() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f);
|
||||
assertEquals(0.5f, color.getRed(), 0.001);
|
||||
assertEquals(0.5f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRgbaConstructor() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f, 0.5f);
|
||||
assertEquals(0.5f, color.getRed(), 0.001);
|
||||
assertEquals(0.5f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(0.5f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntConstructor() {
|
||||
Color color = new Color(128, 64, 32);
|
||||
assertEquals(128 / 255f, color.getRed(), 0.001);
|
||||
assertEquals(64 / 255f, color.getGreen(), 0.001);
|
||||
assertEquals(32 / 255f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntRgbaConstructor() {
|
||||
Color color = new Color(128, 64, 32, 128);
|
||||
assertEquals(128 / 255f, color.getRed(), 0.001);
|
||||
assertEquals(64 / 255f, color.getGreen(), 0.001);
|
||||
assertEquals(32 / 255f, color.getBlue(), 0.001);
|
||||
assertEquals(128 / 255f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalization() {
|
||||
Color color = new Color(1.2f, -0.2f, 0.5f, 1.5f);
|
||||
color.normalize();
|
||||
assertEquals(1.0f, color.getRed(), 0.001);
|
||||
assertEquals(0.0f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplyWithAnotherColor() {
|
||||
Color color1 = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
Color color2 = new Color(0.2f, 0.4f, 0.6f, 1.0f);
|
||||
color1.multiply(color2);
|
||||
assertEquals(0.1f, color1.getRed(), 0.001);
|
||||
assertEquals(0.2f, color1.getGreen(), 0.001);
|
||||
assertEquals(0.3f, color1.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color1.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplyWithFactor() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
color.multiply(2.0f);
|
||||
assertEquals(1.0f, color.getRed(), 0.001);
|
||||
assertEquals(1.0f, color.getGreen(), 0.001);
|
||||
assertEquals(1.0f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
Color color1 = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
Color color2 = new Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
color1.add(color2);
|
||||
assertEquals(0.8f, color1.getRed(), 0.001);
|
||||
assertEquals(0.8f, color1.getGreen(), 0.001);
|
||||
assertEquals(0.8f, color1.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color1.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubtract() {
|
||||
Color color1 = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
Color color2 = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
color1.subtract(color2);
|
||||
assertEquals(0.3f, color1.getRed(), 0.001);
|
||||
assertEquals(0.3f, color1.getGreen(), 0.001);
|
||||
assertEquals(0.3f, color1.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color1.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsAndHashCode() {
|
||||
Color color1 = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
Color color2 = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
assertEquals(color1, color2);
|
||||
assertEquals(color1.hashCode(), color2.hashCode());
|
||||
|
||||
Color color3 = new Color(0.5f, 0.5f, 0.5f, 0.8f);
|
||||
assertNotEquals(color1, color3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToInt() {
|
||||
Color color = new Color(1.0f, 0.5f, 0.25f, 1.0f);
|
||||
int expected = 0xFFFF7F40; // (0xAARRGGBB) with alpha = 255, red = 255, green = 127, blue = 64
|
||||
assertEquals(expected, color.toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToHexString() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
assertEquals("#FF7F7F7F", color.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueOfHex() {
|
||||
Color color = Color.valueOf("#FF7F7F7F");
|
||||
assertEquals(0.5f, color.getRed(), 0.001);
|
||||
assertEquals(0.5f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueOfHexWithoutHash() {
|
||||
Color color = Color.valueOf("FF7F7F7F");
|
||||
assertEquals(0.5f, color.getRed(), 0.001);
|
||||
assertEquals(0.5f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(1.0f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueOfHexWithAlpha() {
|
||||
Color color = Color.valueOf("#80FF7F7F");
|
||||
assertEquals(0.5f, color.getRed(), 0.001);
|
||||
assertEquals(0.5f, color.getGreen(), 0.001);
|
||||
assertEquals(0.5f, color.getBlue(), 0.001);
|
||||
assertEquals(0.5f, color.getAlpha(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlphaConversion() {
|
||||
assertEquals(255, Color.alpha(1.0f));
|
||||
assertEquals(128, Color.alpha(0.5f));
|
||||
assertEquals(0, Color.alpha(0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
Color copy = color.copy();
|
||||
assertEquals(color, copy);
|
||||
assertNotSame(color, copy); // Ensure they are different objects
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
Color color = new Color(0.5f, 0.5f, 0.5f, 0.75f);
|
||||
assertEquals("Color{red=0.500000, green=0.500000, blue=0.500000, alpha=0.750000}", color.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentColors() {
|
||||
Color color1 = new Color(1.0f, 0.0f, 0.0f, 1.0f); // Red
|
||||
Color color2 = new Color(0.0f, 1.0f, 0.0f, 1.0f); // Green
|
||||
Color color3 = new Color(0.0f, 0.0f, 1.0f, 1.0f); // Blue
|
||||
|
||||
assertNotEquals(color1, color2);
|
||||
assertNotEquals(color2, color3);
|
||||
assertNotEquals(color3, color1);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user