From 77b161e0b14372e3eb124ed19321a9639aeb4271 Mon Sep 17 00:00:00 2001
From: Romain Guy
Date: Mon, 9 Jan 2017 21:38:57 -0800
Subject: [PATCH] Color API to create/store wide-gamut colors in a long
This change also includes the ability to create instances of
the Color class to represent more complex colors (any number
of components, etc.).
Test: CtsGraphicsTestCase
Bug: 32984164
Change-Id: I59399547c4aa2580901c93dd16e626fe07f8f23a
---
api/current.txt | 44 +-
api/system-current.txt | 44 +-
api/test-current.txt | 44 +-
graphics/java/android/graphics/Color.java | 1328 ++++++++++++++++-
.../java/android/graphics/ColorSpace.java | 40 +-
5 files changed, 1424 insertions(+), 76 deletions(-)
diff --git a/api/current.txt b/api/current.txt
index 00bd41f7328c9..bb287bab361c0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12091,15 +12091,57 @@ package android.graphics {
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12171,7 +12213,7 @@ package android.graphics {
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
diff --git a/api/system-current.txt b/api/system-current.txt
index c25c84c1af043..3ac97029d630d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12589,15 +12589,57 @@ package android.graphics {
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12669,7 +12711,7 @@ package android.graphics {
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 3637897a712fe..6645505267bc3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12122,15 +12122,57 @@ package android.graphics {
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12202,7 +12244,7 @@ package android.graphics {
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index a2c104a0b4bb6..ff21cac85a239 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -16,27 +16,278 @@
package android.graphics;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.HalfFloat;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Size;
+import android.util.Half;
import com.android.internal.util.XmlUtils;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.function.DoubleUnaryOperator;
/**
- * The Color class defines methods for creating and converting color ints.
- * Colors are represented as packed ints, made up of 4 bytes: alpha, red,
- * green, blue. The values are unpremultiplied, meaning any transparency is
- * stored solely in the alpha component, and not in the color components. The
- * components are stored as follows (alpha << 24) | (red << 16) |
- * (green << 8) | blue. Each component ranges between 0..255 with 0
- * meaning no contribution for that component, and 255 meaning 100%
- * contribution. Thus opaque-black would be 0xFF000000 (100% opaque but
- * no contributions from red, green, or blue), and opaque-white would be
- * 0xFFFFFFFF
+ * {@usesMathJax}
+ *
+ * The Color class provides methods for creating, converting and
+ * manipulating colors. Colors have three different representations:
+ *
+ * - Color ints, the most common representation
+ * - Color longs
+ * Color instances
+ *
+ * The section below describe each representation in detail.
+ *
+ * Color ints
+ * Color ints are the most common representation of colors on Android and
+ * have been used since {@link android.os.Build.VERSION_CODES#BASE API level 1}.
+ *
+ * A color int always defines a color in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space using 4 components packed in a single 32 bit integer value:
+ *
+ *
+ *
+ * | Component | Name | Size | Range |
+ *
+ * | A | Alpha | 8 bits | \([0..255]\) |
+ * | R | Red | 8 bits | \([0..255]\) |
+ * | G | Green | 8 bits | \([0..255]\) |
+ * | B | Blue | 8 bits | \([0..255]\) |
+ *
+ *
+ * The components in this table are listed in encoding order (see below),
+ * which is why color ints are called ARGB colors.
+ *
+ * Usage in code
+ * To avoid confusing color ints with arbitrary integer values, it is a
+ * good practice to annotate them with the @ColorInt annotation
+ * found in the Android Support Library.
+ *
+ * Encoding
+ * The four components of a color int are encoded in the following way:
+ *
+ * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 16 | (B & 0xff);
+ *
+ *
+ * Because of this encoding, color ints can easily be described as an integer
+ * constant in source. For instance, opaque blue is 0xff0000ff
+ * and yellow is 0xffffff00.
+ *
+ * To easily encode color ints, it is recommended to use the static methods
+ * {@link #argb(int, int, int, int)} and {@link #rgb(int, int, int)}. The second
+ * method omits the alpha component and assumes the color is opaque (alpha is 255).
+ * As a convenience this class also offers methods to encode color ints from components
+ * defined in the \([0..1]\) range: {@link #argb(float, float, float, float)} and
+ * {@link #rgb(float, float, float)}.
+ *
+ * Color longs (defined below) can be easily converted to color ints by invoking
+ * the {@link #toArgb(long)} method. This method performs a color space conversion
+ * if needed.
+ *
+ * It is also possible to create a color int by invoking the method {@link #toArgb()}
+ * on a color instance.
+ *
+ * Decoding
+ * The four ARGB components can be individually extracted from a color int
+ * using the following expressions:
+ *
+ * int A = (color >> 24) & 0xff; // or color >>> 24
+ * int R = (color >> 16) & 0xff;
+ * int G = (color >> 8) & 0xff;
+ * int B = (color ) & 0xff;
+ *
+ *
+ * This class offers convenience methods to easily extract these components:
+ *
+ * - {@link #alpha(int)} to extract the alpha component
+ * - {@link #red(int)} to extract the red component
+ * - {@link #green(int)} to extract the green component
+ * - {@link #blue(int)} to extract the blue component
+ *
+ *
+ * Color longs
+ * Color longs are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than color ints.
+ *
+ * A color long always defines a color using 4 components packed in a single
+ * 64 bit long value. One of these components is always alpha while the other
+ * three components depend on the color space's {@link ColorSpace.Model color model}.
+ * The most common color model is the {@link ColorSpace.Model#RGB RGB} model in
+ * which the components represent red, green and blue values.
+ *
+ * Component ranges: the ranges defined in the tables
+ * below indicate the ranges that can be encoded in a color long. They do not
+ * represent the actual ranges as they may differ per color space. For instance,
+ * the RGB components of a color in the {@link ColorSpace.Named#DISPLAY_P3 Display P3}
+ * color space use the \([0..1]\) range. Please refer to the documentation of the
+ * various {@link ColorSpace.Named color spaces} to find their respective ranges.
+ *
+ * Alpha range: while alpha is encoded in a color long using
+ * a 10 bit integer (thus using a range of \([0..1023]\)), it is converted to and
+ * from \([0..1]\) float values when decoding and encoding color longs.
+ *
+ * sRGB color space: for compatibility reasons and ease of
+ * use, color longs encoding {@link ColorSpace.Named#SRGB sRGB} colors do not
+ * use the same encoding as other color longs.
+ *
+ *
+ *
+ * | Component | Name | Size | Range |
+ *
+ * | {@link ColorSpace.Model#RGB RGB} color model |
+ * | R | Red | 16 bits | \([-65504.0, 65504.0]\) |
+ * | G | Green | 16 bits | \([-65504.0, 65504.0]\) |
+ * | B | Blue | 16 bits | \([-65504.0, 65504.0]\) |
+ * | A | Alpha | 10 bits | \([0..1023]\) |
+ * | Color space | 6 bits | \([0..63]\) |
+ * | {@link ColorSpace.Named#SRGB sRGB} color space |
+ * | A | Alpha | 8 bits | \([0..255]\) |
+ * | R | Red | 8 bits | \([0..255]\) |
+ * | G | Green | 8 bits | \([0..255]\) |
+ * | B | Blue | 8 bits | \([0..255]\) |
+ * | X | Unused | 32 bits | \(0\) |
+ * | {@link ColorSpace.Model#XYZ XYZ} color model |
+ * | X | X | 16 bits | \([-65504.0, 65504.0]\) |
+ * | Y | Y | 16 bits | \([-65504.0, 65504.0]\) |
+ * | Z | Z | 16 bits | \([-65504.0, 65504.0]\) |
+ * | A | Alpha | 10 bits | \([0..1023]\) |
+ * | Color space | 6 bits | \([0..63]\) |
+ * | {@link ColorSpace.Model#XYZ Lab} color model |
+ * | L | L | 16 bits | \([-65504.0, 65504.0]\) |
+ * | a | a | 16 bits | \([-65504.0, 65504.0]\) |
+ * | b | b | 16 bits | \([-65504.0, 65504.0]\) |
+ * | A | Alpha | 10 bits | \([0..1023]\) |
+ * | Color space | 6 bits | \([0..63]\) |
+ * | {@link ColorSpace.Model#CMYK CMYK} color model |
+ * | Unsupported |
+ *
+ *
+ * The components in this table are listed in encoding order (see below),
+ * which is why color longs in the RGB model are called RGBA colors (even if
+ * this doesn't quite hold for the special case of sRGB colors).
+ *
+ * The color long encoding relies on half-precision float values (fp16). If you
+ * wish to know more about the limitations of half-precision float values, please
+ * refer to the documentation of the {@link Half} class.
+ *
+ * Usage in code
+ * To avoid confusing color longs with arbitrary long values, it is a
+ * good practice to annotate them with the @ColorLong annotation
+ * found in the Android Support Library.
+ *
+ * Encoding
+ *
+ * Given the complex nature of color longs, it is strongly encouraged to use
+ * the various methods provided by this class to encode them.
+ *
+ * The most flexible way to encode a color long is to use the method
+ * {@link #pack(float, float, float, float, ColorSpace)}. This method allows you
+ * to specify three color components (typically RGB), an alpha component and a
+ * color space. To encode sRGB colors, use {@link #pack(float, float, float)}
+ * and {@link #pack(float, float, float, float)} which are the
+ * equivalent of {@link #rgb(int, int, int)} and {@link #argb(int, int, int, int)}
+ * for color ints. If you simply need to convert a color int into a color long,
+ * use {@link #pack(int)}.
+ *
+ * It is also possible to create a color long value by invoking the method
+ * {@link #pack()} on a color instance.
+ *
+ * Decoding
+ *
+ * This class offers convenience methods to easily extract the components
+ * of a color long:
+ *
+ * - {@link #alpha(long)} to extract the alpha component
+ * - {@link #red(long)} to extract the red/X/L component
+ * - {@link #green(long)} to extract the green/Y/a component
+ * - {@link #blue(long)} to extract the blue/Z/b component
+ *
+ *
+ * The values returned by these methods depend on the color space encoded
+ * in the color long. The values are however typically in the \([0..1]\) range
+ * for RGB colors. Please refer to the documentation of the various
+ * {@link ColorSpace.Named color spaces} for the exact ranges.
+ *
+ * Color instances
+ * Color instances are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than both color ints and
+ * color longs. Color instances also offer the ability to store more than 4
+ * components if necessary.
+ *
+ * Colors instances are immutable and can be created using one of the various
+ * valueOf methods. For instance:
+ *
+ * // sRGB
+ * Color opaqueRed = Color.valueOf(0xffff0000); // from a color int
+ * Color translucentRed = Color.valueOf(1.0f, 0.0f, 0.0f, 0.5f);
+ *
+ * // Wide gamut color
+ * {@literal @}ColorLong long p3 = pack(1.0f, 1.0f, 0.0f, 1.0f, colorSpaceP3);
+ * Color opaqueYellow = Color.valueOf(p3); // from a color long
+ *
+ * // CIE L*a*b* color space
+ * ColorSpace lab = ColorSpace.get(ColorSpace.Named.LAB);
+ * Color green = Color.valueOf(100.0f, -128.0f, 128.0f, 1.0f, lab);
+ *
+ *
+ * Color instances can be converted to color ints ({@link #toArgb()}) or
+ * color longs ({@link #pack()}). They also offer easy access to their various
+ * components using the following methods:
+ *
+ * - {@link #alpha()}, returns the alpha component value
+ * - {@link #red()}, returns the red component value (or first
+ * component value in non-RGB models)
+ * - {@link #green()}, returns the green component value (or second
+ * component value in non-RGB models)
+ * - {@link #blue()}, returns the blue component value (or third
+ * component value in non-RGB models)
+ * - {@link #getComponent(int)}, returns a specific component value
+ * - {@link #getComponents()}, returns all component values as an array
+ *
+ *
+ * Color space conversions
+ * You can convert colors from one color space to another using
+ * {@link ColorSpace#connect(ColorSpace, ColorSpace)} and its variants. However,
+ * the Color class provides a few convenience methods to simplify
+ * the process. Here is a brief description of some of them:
+ *
+ * - {@link #convert(ColorSpace)} to convert a color instance in a color
+ * space to a new color instance in a different color space
+ * - {@link #convert(float, float, float, float, ColorSpace, ColorSpace)} to
+ * convert a color from a source color space to a destination color space
+ * - {@link #convert(long, ColorSpace)} to convert a color long from its
+ * built-in color space to a destination color space
+ * - {@link #convert(int, ColorSpace)} to convert a color int from sRGB
+ * to a destination color space
+ *
+ *
+ * Please refere to the {@link ColorSpace} documentation for more
+ * information.
+ *
+ * Alpha and transparency
+ * The alpha component of a color defines the level of transparency of a
+ * color. When the alpha component is 0, the color is completely transparent.
+ * When the alpha is component is 1 (in the \([0..1]\) range) or 255 (in the
+ * \([0..255]\) range), the color is completely opaque.
+ *
+ * The color representations described above do not use pre-multiplied
+ * color components (a pre-multiplied color component is a color component
+ * that has been multiplied by the value of the alpha component).
+ * For instance, the color int representation of opaque red is
+ * 0xffff0000. For semi-transparent (50%) red, the
+ * representation becomes 0x80ff0000. The equivalent color
+ * instance representations would be (1.0, 0.0, 0.0, 1.0)
+ * and (1.0, 0.0, 0.0, 0.5).
*/
+@AnyThread
public class Color {
@ColorInt public static final int BLACK = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
@@ -51,10 +302,905 @@ public class Color {
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
@ColorInt public static final int TRANSPARENT = 0;
+ @NonNull
+ @Size(min = 4, max = 5)
+ private final float[] mComponents;
+
+ @NonNull
+ private final ColorSpace mColorSpace;
+
+ /**
+ * Creates a new color instance set to opaque black in the
+ * {@link ColorSpace.Named#SRGB sRGB} color space.
+ *
+ * @see #valueOf(float, float, float)
+ * @see #valueOf(float, float, float, float)
+ * @see #valueOf(float, float, float, float, ColorSpace)
+ * @see #valueOf(float[], ColorSpace)
+ * @see #valueOf(int)
+ * @see #valueOf(long)
+ */
+ public Color() {
+ // This constructor is required for compatibility with previous APIs
+ mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
+ mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+
+ /**
+ * Creates a new color instance in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @param r The value of the red channel, must be in [0..1] range
+ * @param g The value of the green channel, must be in [0..1] range
+ * @param b The value of the blue channel, must be in [0..1] range
+ * @param a The value of the alpha channel, must be in [0..1] range
+ */
+ private Color(float r, float g, float b, float a) {
+ this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Creates a new color instance in the specified color space. The color space
+ * must have a 3 components model.
+ *
+ * @param r The value of the red channel, must be in the color space defined range
+ * @param g The value of the green channel, must be in the color space defined range
+ * @param b The value of the blue channel, must be in the color space defined range
+ * @param a The value of the alpha channel, must be in [0..1] range
+ * @param colorSpace This color's color space, cannot be null
+ */
+ private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+ mComponents = new float[] { r, g, b, a };
+ mColorSpace = colorSpace;
+ }
+
+ /**
+ * Creates a new color instance in the specified color space.
+ *
+ * @param components An array of color components, plus alpha
+ * @param colorSpace This color's color space, cannot be null
+ */
+ private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
+ mComponents = components;
+ mColorSpace = colorSpace;
+ }
+
+ /**
+ * Returns this color's color space.
+ *
+ * @return A non-null instance of {@link ColorSpace}
+ */
+ @NonNull
+ public ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ /**
+ * Returns the color model of this color.
+ *
+ * @return A non-null {@link ColorSpace.Model}
+ */
+ public ColorSpace.Model getModel() {
+ return mColorSpace.getModel();
+ }
+
+ /**
+ * Indicates whether this color color is in a wide-gamut color space.
+ * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+ * color space.
+ *
+ * @return True if this color is in a wide-gamut color space, false otherwise
+ *
+ * @see #isSrgb()
+ * @see ColorSpace#isWideGamut()
+ */
+ public boolean isWideGamut() {
+ return getColorSpace().isWideGamut();
+ }
+
+ /**
+ * Indicates whether this color is in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @return True if this color is in the sRGB color space, false otherwise
+ *
+ * @see #isWideGamut()
+ */
+ public boolean isSrgb() {
+ return getColorSpace().isSrgb();
+ }
+
+ /**
+ * Returns the number of components that form a color value according
+ * to this color space's color model, plus one extra component for
+ * alpha.
+ *
+ * @return An integer between 4 and 5
+ */
+ @IntRange(from = 4, to = 5)
+ public int getComponentCount() {
+ return mColorSpace.getComponentCount() + 1;
+ }
+
+ /**
+ * Packs this color into a color long. See the documentation of this class
+ * for a description of the color long format.
+ *
+ * @return A color long
+ *
+ * @throws IllegalArgumentException If this color's color space has the id
+ * {@link ColorSpace#MIN_ID} or if this color has more than 4 components
+ */
+ @ColorLong
+ public long pack() {
+ return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
+ }
+
+ /**
+ * Converts this color from its color space to the specified color space.
+ * The conversion is done using the default rendering intent as specified
+ * by {@link ColorSpace#connect(ColorSpace, ColorSpace)}.
+ *
+ * @param colorSpace The destination color space, cannot be null
+ *
+ * @return A non-null color instance in the specified color space
+ */
+ @NonNull
+ public Color convert(@NonNull ColorSpace colorSpace) {
+ ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
+ float[] color = new float[] {
+ mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+ };
+ connector.transform(color);
+ return new Color(color, colorSpace);
+ }
+
+ /**
+ * Converts this color to an ARGB color int. A color int is always in
+ * the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+ * a color space conversion is applied if needed.
+ *
+ * @return An ARGB color in the sRGB color space
+ */
+ @ColorInt
+ public int toArgb() {
+ if (mColorSpace.isSrgb()) {
+ return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
+ ((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
+ ((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
+ (int) (mComponents[2] * 255.0f + 0.5f);
+ }
+
+ float[] color = new float[] {
+ mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+ };
+ // The transformation saturates the output
+ ColorSpace.connect(mColorSpace).transform(color);
+
+ return ((int) (color[3] * 255.0f + 0.5f) << 24) |
+ ((int) (color[0] * 255.0f + 0.5f) << 16) |
+ ((int) (color[1] * 255.0f + 0.5f) << 8) |
+ (int) (color[2] * 255.0f + 0.5f);
+ }
+
+ /**
+ * Returns the value of the red component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).
+ *
+ * If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to getComponent(0).
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float red() {
+ return mComponents[0];
+ }
+
+ /**
+ * Returns the value of the green component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).
+ *
+ * If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to getComponent(1).
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float green() {
+ return mComponents[1];
+ }
+
+ /**
+ * Returns the value of the blue component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).
+ *
+ * If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to getComponent(2).
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float blue() {
+ return mComponents[2];
+ }
+
+ /**
+ * Returns the value of the alpha component in the range \([0..1]\).
+ * Calling this method is equivalent to
+ * getComponent(getComponentCount()).
+ *
+ * @see #red()
+ * @see #green()
+ * @see #blue()
+ * @see #getComponents()
+ * @see #getComponent(int)
+ */
+ public float alpha() {
+ return mComponents[mComponents.length - 1];
+ }
+
+ /**
+ * Returns this color's components as a new array. The last element of the
+ * array is always the alpha component.
+ *
+ * @return A new, non-null array whose size is equal to {@link #getComponentCount()}
+ *
+ * @see #getComponent(int)
+ */
+ @NonNull
+ @Size(min = 4, max = 5)
+ public float[] getComponents() {
+ return Arrays.copyOf(mComponents, mColorSpace.getComponentCount() + 1);
+ }
+
+ /**
+ * Returns the value of the specified component in the range defined by
+ * this color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).
+ *
+ * If the requested component index is {@link #getComponentCount()},
+ * this method returns the alpha component, always in the range
+ * \([0..1\).
+ *
+ * @see #getComponents()
+ *
+ * @throws ArrayIndexOutOfBoundsException If the specified component index
+ * is < 0 or >= {@link #getComponentCount()}
+ */
+ public float getComponent(@IntRange(from = 0, to = 4) int component) {
+ return mComponents[component];
+ }
+
+ /**
+ * Returns the relative luminance of this color.
+ *
+ * Based on the formula for relative luminance defined in WCAG 2.0,
+ * W3C Recommendation 11 December 2008.
+ *
+ * @return A value between 0 (darkest black) and 1 (lightest white)
+ *
+ * @throws IllegalArgumentException If the this color's color space
+ * does not use the {@link ColorSpace.Model#RGB RGB} color model
+ */
+ public float luminance() {
+ if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
+ throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+ "color space. The supplied color space is " + mColorSpace.getModel());
+ }
+
+ DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
+ double r = eotf.applyAsDouble(mComponents[0]);
+ double g = eotf.applyAsDouble(mComponents[1]);
+ double b = eotf.applyAsDouble(mComponents[2]);
+
+ return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Color color = (Color) o;
+
+ //noinspection SimplifiableIfStatement
+ if (!Arrays.equals(mComponents, color.mComponents)) return false;
+ return mColorSpace.equals(color.mColorSpace);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mComponents);
+ result = 31 * result + mColorSpace.hashCode();
+ return result;
+ }
+
+ /**
+ * Returns a string representation of the object. This method returns
+ * a string equal to the value of:
+ *
+ *
+ * "Color(" + r + ", " + g + ", " + b + ", " + a +
+ * ", " + getColorSpace().getName + ')'
+ *
+ *
+ * For instance, the string representation of opaque black in the sRGB
+ * color space is equal to the following value:
+ *
+ *
+ * Color(0.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1)
+ *
+ *
+ * @return A non-null string representation of the object
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ StringBuilder b = new StringBuilder("Color(");
+ for (float c : mComponents) {
+ b.append(c).append(", ");
+ }
+ b.append(mColorSpace.getName());
+ b.append(')');
+ return b.toString();
+ }
+
+ /**
+ * Returns the color space encoded in the specified color long.
+ *
+ * @param color The color long whose color space to extract
+ * @return A non-null color space instance. If the color long encodes
+ * an unknown or invalid color space, the {@link ColorSpace.Named#SRGB sRGB}
+ * color space is returned
+ *
+ * @see #red(long)
+ * @see #green(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ @NonNull
+ public static ColorSpace colorSpace(@ColorLong long color) {
+ return ColorSpace.get((int) (color & 0x3fL));
+ }
+
+ /**
+ * Returns the red component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose red channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #green(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ public static float red(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 48) & 0xffff));
+ }
+
+ /**
+ * Returns the green component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose green channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ public static float green(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 32) & 0xffff));
+ }
+
+ /**
+ * Returns the blue component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose blue channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #green(long)
+ * @see #alpha(long)
+ */
+ public static float blue(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 16) & 0xffff));
+ }
+
+ /**
+ * Returns the alpha component encoded in the specified color long.
+ * The returned value is always in the range \([0..1]\).
+ *
+ * @param color The color long whose blue channel to extract
+ * @return A float value in the range \([0..1]\)
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #green(long)
+ * @see #blue(long)
+ */
+ public static float alpha(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
+ return ((color >> 6) & 0x3ff) / 1023.0f;
+ }
+
+ /**
+ * Indicates whether the specified color is in the
+ * {@link ColorSpace.Named#SRGB sRGB} color space.
+ *
+ * @param color The color to test
+ * @return True if the color is in the sRGB color space, false otherwise
+ *
+ * @see #isInColorSpace(long, ColorSpace)
+ * @see #isWideGamut(long)
+ */
+ public static boolean isSrgb(@ColorLong long color) {
+ return colorSpace(color).isSrgb();
+ }
+
+ /**
+ * Indicates whether the specified color is in a wide-gamut color space.
+ * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+ * color space.
+ *
+ * @param color The color to test
+ * @return True if the color is in a wide-gamut color space, false otherwise
+ *
+ * @see #isInColorSpace(long, ColorSpace)
+ * @see #isSrgb(long)
+ * @see ColorSpace#isWideGamut()
+ */
+ public static boolean isWideGamut(@ColorLong long color) {
+ return colorSpace(color).isWideGamut();
+ }
+
+ /**
+ * Indicates whether the specified color is in the specified color space.
+ *
+ * @param color The color to test
+ * @param colorSpace The color space to test against
+ * @return True if the color is in the specified color space, false otherwise
+ *
+ * @see #isSrgb(long)
+ * @see #isWideGamut(long)
+ */
+ public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+ return (int) (color & 0x3fL) == colorSpace.getId();
+ }
+
+ /**
+ * Converts the specified color long to an ARGB color int. A color int is
+ * always in the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+ * a color space conversion is applied if needed.
+ *
+ * @return An ARGB color in the sRGB color space
+ */
+ @ColorInt
+ public static int toArgb(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return (int) (color >> 32);
+
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+
+ // The transformation saturates the output
+ float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
+
+ return ((int) (a * 255.0f + 0.5f) << 24) |
+ ((int) (c[0] * 255.0f + 0.5f) << 16) |
+ ((int) (c[1] * 255.0f + 0.5f) << 8) |
+ (int) (c[2] * 255.0f + 0.5f);
+ }
+
+ /**
+ * Creates a new Color instance from an ARGB color int.
+ * The resulting color is in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @param color The ARGB color int to create a Color from
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(@ColorInt int color) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color ) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Creates a new Color instance from a color long.
+ * The resulting color is in the same color space as the specified color long.
+ *
+ * @param color The color long to create a Color from
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(@ColorLong long color) {
+ return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
+ }
+
+ /**
+ * Creates a new opaque Color in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space with the specified red, green and blue component values. The component
+ * values must be in the range \([0..1]\).
+ *
+ * @param r The red component of the opaque sRGB color to create, in \([0..1]\)
+ * @param g The green component of the opaque sRGB color to create, in \([0..1]\)
+ * @param b The blue component of the opaque sRGB color to create, in \([0..1]\)
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b) {
+ return new Color(r, g, b, 1.0f);
+ }
+
+ /**
+ * Creates a new Color in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space with the specified red, green, blue and alpha component values.
+ * The component values must be in the range \([0..1]\).
+ *
+ * @param r The red component of the sRGB color to create, in \([0..1]\)
+ * @param g The green component of the sRGB color to create, in \([0..1]\)
+ * @param b The blue component of the sRGB color to create, in \([0..1]\)
+ * @param a The alpha component of the sRGB color to create, in \([0..1]\)
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b, float a) {
+ return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
+ }
+
+ /**
+ * Creates a new Color in the specified color space with the
+ * specified red, green, blue and alpha component values. The range of the
+ * components is defined by {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}. The values passed to this method
+ * must be in the proper range.
+ *
+ * @param r The red component of the color to create
+ * @param g The green component of the color to create
+ * @param b The blue component of the color to create
+ * @param a The alpha component of the color to create, in \([0..1]\)
+ * @param colorSpace The color space of the color to create
+ * @return A non-null instance of {@link Color}
+ *
+ * @throws IllegalArgumentException If the specified color space uses a
+ * color model with more than 3 components
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+ if (colorSpace.getComponentCount() > 3) {
+ throw new IllegalArgumentException("The specified color space must use a color model " +
+ "with at most 3 color components");
+ }
+ return new Color(r, g, b, a, colorSpace);
+ }
+
+ /**
+ * Creates a new Color in the specified color space with the
+ * specified component values. The range of the components is defined by
+ * {@link ColorSpace#getMinValue(int)} and {@link ColorSpace#getMaxValue(int)}.
+ * The values passed to this method must be in the proper range. The alpha
+ * component is always in the range \([0..1]\).
+ *
+ * The length of the array of components must be at least
+ * {@link ColorSpace#getComponentCount()} + 1. The component at index
+ * {@link ColorSpace#getComponentCount()} is always alpha.
+ *
+ * @param components The components of the color to create, with alpha as the last component
+ * @param colorSpace The color space of the color to create
+ * @return A non-null instance of {@link Color}
+ *
+ * @throws IllegalArgumentException If the array of components is smaller than
+ * required by the color space
+ */
+ @NonNull
+ public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
+ @NonNull ColorSpace colorSpace) {
+ if (components.length < colorSpace.getComponentCount() + 1) {
+ throw new IllegalArgumentException("Received a component array of length " +
+ components.length + " but the color model requires " +
+ (colorSpace.getComponentCount() + 1) + " (including alpha)");
+ }
+ return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
+ }
+
+ /**
+ * Converts the specified ARGB color int to an RGBA color long in the sRGB
+ * color space. See the documentation of this class for a description of
+ * the color long format.
+ *
+ * @param color The ARGB color int to convert to an RGBA color long in sRGB
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(@ColorInt int color) {
+ return (color & 0xffffffffL) << 32;
+ }
+
+ /**
+ * Packs the sRGB color defined by the specified red, green and blue component
+ * values into an RGBA color long in the sRGB color space. The alpha component
+ * is set to 1.0. See the documentation of this class for a description of the
+ * color long format.
+ *
+ * @param red The red component of the sRGB color to create, in \([0..1]\)
+ * @param green The green component of the sRGB color to create, in \([0..1]\)
+ * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue) {
+ return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Packs the sRGB color defined by the specified red, green, blue and alpha
+ * component values into an RGBA color long in the sRGB color space. See the
+ * documentation of this class for a description of the color long format.
+ *
+ * @param red The red component of the sRGB color to create, in \([0..1]\)
+ * @param green The green component of the sRGB color to create, in \([0..1]\)
+ * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+ * @param alpha The alpha component of the sRGB color to create, in \([0..1]\)
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue, float alpha) {
+ return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Packs the 3 component color defined by the specified red, green, blue and
+ * alpha component values into a color long in the specified color space. See the
+ * documentation of this class for a description of the color long format.
+ *
+ * The red, green and blue components must be in the range defined by the
+ * specified color space. See {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}.
+ *
+ * @param red The red component of the color to create
+ * @param green The green component of the color to create
+ * @param blue The blue component of the color to create
+ * @param alpha The alpha component of the color to create, in \([0..1]\)
+ *
+ * @return A color long
+ *
+ * @throws IllegalArgumentException If the color space's id is {@link ColorSpace#MIN_ID}
+ * or if the color space's color model has more than 3 components
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue, float alpha,
+ @NonNull ColorSpace colorSpace) {
+ if (colorSpace.isSrgb()) {
+ int argb =
+ ((int) (alpha * 255.0f + 0.5f) << 24) |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ return (argb & 0xffffffffL) << 32;
+ }
+
+ int id = colorSpace.getId();
+ if (id == ColorSpace.MIN_ID) {
+ throw new IllegalArgumentException(
+ "Unknown color space, please use a color space returned by ColorSpace.get()");
+ }
+ if (colorSpace.getComponentCount() > 3) {
+ throw new IllegalArgumentException(
+ "The color space must use a color model with at most 3 components");
+ }
+
+ @HalfFloat short r = Half.valueOf(red);
+ @HalfFloat short g = Half.valueOf(green);
+ @HalfFloat short b = Half.valueOf(blue);
+
+ int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
+
+ // Suppress sign extension
+ return (r & 0xffffL) << 48 |
+ (g & 0xffffL) << 32 |
+ (b & 0xffffL) << 16 |
+ (a & 0x3ffL ) << 6 |
+ id & 0x3fL;
+ }
+
+ /**
+ * Converts the specified ARGB color int from the {@link ColorSpace.Named#SRGB sRGB}
+ * color space into the specified destination color space. The resulting color is
+ * returned as a color long. See the documentation of this class for a description
+ * of the color long format.
+ *
+ * @param color The sRGB color int to convert
+ * @param colorSpace The destination color space
+ * @return A color long in the destination color space
+ */
+ @ColorLong
+ public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color ) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
+ return convert(r, g, b, a, source, colorSpace);
+ }
+
+ /**
+ * Converts the specified color long from its color space into the specified
+ * destination color space. The resulting color is returned as a color long. See
+ * the documentation of this class for a description of the color long format.
+ *
+ * When converting several colors in a row, it is recommended to use
+ * {@link #convert(long, ColorSpace.Connector)} instead to
+ * avoid the creation of a {@link ColorSpace.Connector} on every invocation.
+ *
+ * @param color The color long to convert
+ * @param colorSpace The destination color space
+ * @return A color long in the destination color space
+ */
+ @ColorLong
+ public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+ ColorSpace source = colorSpace(color);
+ return convert(r, g, b, a, source, colorSpace);
+ }
+
+ /**
+ * Converts the specified 3 component color from the source color space to the
+ * destination color space. The resulting color is returned as a color long. See
+ * the documentation of this class for a description of the color long format.
+ *
+ * When converting multiple colors in a row, it is recommended to use
+ * {@link #convert(float, float, float, float, ColorSpace.Connector)} instead to
+ * avoid the creation of a {@link ColorSpace.Connector} on every invocation.
+ *
+ * The red, green and blue components must be in the range defined by the
+ * specified color space. See {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}.
+ *
+ * @param r The red component of the color to convert
+ * @param g The green component of the color to convert
+ * @param b The blue component of the color to convert
+ * @param a The alpha component of the color to convert, in \([0..1]\)
+ * @param source The source color space, cannot be null
+ * @param destination The destination color space, cannot be null
+ * @return A color long in the destination color space
+ *
+ * @see #convert(float, float, float, float, ColorSpace.Connector)
+ */
+ @ColorLong
+ public static long convert(float r, float g, float b, float a,
+ @NonNull ColorSpace source, @NonNull ColorSpace destination) {
+ float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
+ return pack(c[0], c[1], c[2], a, destination);
+ }
+
+ /**
+ * Converts the specified color long from a color space to another using the
+ * specified color space {@link ColorSpace.Connector connector}. The resulting
+ * color is returned as a color long. See the documentation of this class for a
+ * description of the color long format.
+ *
+ * When converting several colors in a row, this method is preferable to
+ * {@link #convert(long, ColorSpace)} as it prevents a new connector from being
+ * created on every invocation.
+ *
+ * The connector's source color space should match the color long's
+ * color space.
+ *
+ * @param color The color long to convert
+ * @param connector A color space connector, cannot be null
+ * @return A color long in the destination color space of the connector
+ */
+ @ColorLong
+ public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+ return convert(r, g, b, a, connector);
+ }
+
+ /**
+ * Converts the specified 3 component color from a color space to another using
+ * the specified color space {@link ColorSpace.Connector connector}. The resulting
+ * color is returned as a color long. See the documentation of this class for a
+ * description of the color long format.
+ *
+ * When converting several colors in a row, this method is preferable to
+ * {@link #convert(float, float, float, float, ColorSpace, ColorSpace)} as
+ * it prevents a new connector from being created on every invocation.
+ *
+ * The red, green and blue components must be in the range defined by the
+ * source color space of the connector. See {@link ColorSpace#getMinValue(int)}
+ * and {@link ColorSpace#getMaxValue(int)}.
+ *
+ * @param r The red component of the color to convert
+ * @param g The green component of the color to convert
+ * @param b The blue component of the color to convert
+ * @param a The alpha component of the color to convert, in \([0..1]\)
+ * @param connector A color space connector, cannot be null
+ * @return A color long in the destination color space of the connector
+ *
+ * @see #convert(float, float, float, float, ColorSpace, ColorSpace)
+ */
+ @ColorLong
+ public static long convert(float r, float g, float b, float a,
+ @NonNull ColorSpace.Connector connector) {
+ float[] c = connector.transform(r, g, b);
+ return pack(c[0], c[1], c[2], a, connector.getDestination());
+ }
+
+ /**
+ * Returns the relative luminance of a color.
+ *
+ * Based on the formula for relative luminance defined in WCAG 2.0,
+ * W3C Recommendation 11 December 2008.
+ *
+ * @return A value between 0 (darkest black) and 1 (lightest white)
+ *
+ * @throws IllegalArgumentException If the specified color's color space
+ * does not use the {@link ColorSpace.Model#RGB RGB} color model
+ */
+ public static float luminance(@ColorLong long color) {
+ ColorSpace colorSpace = colorSpace(color);
+ if (colorSpace.getModel() != ColorSpace.Model.RGB) {
+ throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+ "color space. The supplied color space is " + colorSpace.getModel());
+ }
+
+ DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
+ double r = eotf.applyAsDouble(red(color));
+ double g = eotf.applyAsDouble(green(color));
+ double b = eotf.applyAsDouble(blue(color));
+
+ return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+ }
+
+ private static float saturate(float v) {
+ return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
+ }
+
/**
* Return the alpha component of a color int. This is the same as saying
* color >>> 24
*/
+ @IntRange(from = 0, to = 255)
public static int alpha(int color) {
return color >>> 24;
}
@@ -63,6 +1209,7 @@ public class Color {
* Return the red component of a color int. This is the same as saying
* (color >> 16) & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int red(int color) {
return (color >> 16) & 0xFF;
}
@@ -71,6 +1218,7 @@ public class Color {
* Return the green component of a color int. This is the same as saying
* (color >> 8) & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int green(int color) {
return (color >> 8) & 0xFF;
}
@@ -79,40 +1227,85 @@ public class Color {
* Return the blue component of a color int. This is the same as saying
* color & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int blue(int color) {
return color & 0xFF;
}
/**
* Return a color-int from red, green, blue components.
- * The alpha component is implicity 255 (fully opaque).
- * These component values should be [0..255], but there is no
+ * The alpha component is implicitly 255 (fully opaque).
+ * These component values should be \([0..255]\), but there is no
* range check performed, so if they are out of range, the
* returned color is undefined.
- * @param red Red component [0..255] of the color
- * @param green Green component [0..255] of the color
- * @param blue Blue component [0..255] of the color
+ *
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
*/
@ColorInt
- public static int rgb(int red, int green, int blue) {
+ public static int rgb(
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue) {
return 0xff000000 | (red << 16) | (green << 8) | blue;
}
/**
- * Return a color-int from alpha, red, green, blue components.
- * These component values should be [0..255], but there is no
- * range check performed, so if they are out of range, the
+ * Return a color-int from red, green, blue float components
+ * in the range \([0..1]\). The alpha component is implicitly
+ * 1.0 (fully opaque). If the components are out of range, the
* returned color is undefined.
- * @param alpha Alpha component [0..255] of the color
- * @param red Red component [0..255] of the color
- * @param green Green component [0..255] of the color
- * @param blue Blue component [0..255] of the color
+ *
+ * @param red Red component \([0..1]\) of the color
+ * @param green Green component \([0..1]\) of the color
+ * @param blue Blue component \([0..1]\) of the color
*/
@ColorInt
- public static int argb(int alpha, int red, int green, int blue) {
+ public static int rgb(float red, float green, float blue) {
+ return 0xff000000 |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ }
+
+ /**
+ * Return a color-int from alpha, red, green, blue components.
+ * These component values should be \([0..255]\), but there is no
+ * range check performed, so if they are out of range, the
+ * returned color is undefined.
+ * @param alpha Alpha component \([0..255]\) of the color
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
+ */
+ @ColorInt
+ public static int argb(
+ @IntRange(from = 0, to = 255) int alpha,
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
+ /**
+ * Return a color-int from alpha, red, green, blue float components
+ * in the range \([0..1]\). If the components are out of range, the
+ * returned color is undefined.
+ *
+ * @param alpha Alpha component \([0..1]\) of the color
+ * @param red Red component \([0..1]\) of the color
+ * @param green Green component \([0..1]\) of the color
+ * @param blue Blue component \([0..1]\) of the color
+ */
+ @ColorInt
+ public static int argb(float alpha, float red, float green, float blue) {
+ return ((int) (alpha * 255.0f + 0.5f) << 24) |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ }
+
/**
* Returns the relative luminance of a color.
*
@@ -124,23 +1317,31 @@ public class Color {
public static float luminance(@ColorInt int color) {
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
DoubleUnaryOperator eotf = cs.getEotf();
- double red = eotf.applyAsDouble(Color.red(color) / 255.0);
- double green = eotf.applyAsDouble(Color.green(color) / 255.0);
- double blue = eotf.applyAsDouble(Color.blue(color) / 255.0);
- return (float) ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue));
+
+ double r = eotf.applyAsDouble(red(color) / 255.0);
+ double g = eotf.applyAsDouble(green(color) / 255.0);
+ double b = eotf.applyAsDouble(blue(color) / 255.0);
+
+ return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
}
/**
- * Parse the color string, and return the corresponding color-int.
+ *
Parse the color string, and return the corresponding color-int.
* If the string cannot be parsed, throws an IllegalArgumentException
- * exception. Supported formats are:
- * #RRGGBB
- * #AARRGGBB
- * or one of the following names:
- * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta',
- * 'yellow', 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey',
- * 'aqua', 'fuchsia', 'lime', 'maroon', 'navy', 'olive', 'purple',
- * 'silver', 'teal'.
+ * exception. Supported formats are:
+ *
+ *
+ * #RRGGBB
+ * #AARRGGBB
+ *
+ *
+ * The following names are also accepted: red, blue,
+ * green, black, white, gray,
+ * cyan, magenta, yellow, lightgray,
+ * darkgray, grey, lightgrey, darkgrey,
+ * aqua, fuchsia, lime, maroon,
+ * navy, olive, purple, silver,
+ * and teal.
*/
@ColorInt
public static int parseColor(@Size(min=1) String colorString) {
@@ -165,15 +1366,20 @@ public class Color {
/**
* Convert RGB components to HSV.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
- * @param red red component value [0..255]
- * @param green green component value [0..255]
- * @param blue blue component value [0..255]
+ *
+ * hsv[0] is Hue \([0..360[\)
+ * hsv[1] is Saturation \([0...1]\)
+ * hsv[2] is Value \([0...1]\)
+ *
+ * @param red red component value \([0..255]\)
+ * @param green green component value \([0..255]\)
+ * @param blue blue component value \([0..255]\)
* @param hsv 3 element array which holds the resulting HSV components.
*/
- public static void RGBToHSV(int red, int green, int blue, @Size(3) float hsv[]) {
+ public static void RGBToHSV(
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
if (hsv.length < 3) {
throw new RuntimeException("3 components required for hsv");
}
@@ -181,10 +1387,12 @@ public class Color {
}
/**
- * Convert the argb color to its HSV components.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ * Convert the ARGB color to its HSV components.
+ *
+ * hsv[0] is Hue \([0..360[\)
+ * hsv[1] is Saturation \([0...1]\)
+ * hsv[2] is Value \([0...1]\)
+ *
* @param color the argb color to convert. The alpha component is ignored.
* @param hsv 3 element array which holds the resulting HSV components.
*/
@@ -194,13 +1402,16 @@ public class Color {
/**
* Convert HSV components to an ARGB color. Alpha set to 0xFF.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ *
+ * hsv[0] is Hue \([0..360[\)
+ * hsv[1] is Saturation \([0...1]\)
+ * hsv[2] is Value \([0...1]\)
+ *
* If hsv values are out of range, they are pinned.
* @param hsv 3 element array which holds the input HSV components.
* @return the resulting argb color
*/
+ @ColorInt
public static int HSVToColor(@Size(3) float hsv[]) {
return HSVToColor(0xFF, hsv);
}
@@ -208,15 +1419,18 @@ public class Color {
/**
* Convert HSV components to an ARGB color. The alpha component is passed
* through unchanged.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ *
+ * hsv[0] is Hue \([0..360[\)
+ * hsv[1] is Saturation \([0...1]\)
+ * hsv[2] is Value \([0...1]\)
+ *
* If hsv values are out of range, they are pinned.
* @param alpha the alpha component of the returned argb color.
* @param hsv 3 element array which holds the input HSV components.
* @return the resulting argb color
- */
- public static int HSVToColor(int alpha, @Size(3) float hsv[]) {
+ */
+ @ColorInt
+ public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
if (hsv.length < 3) {
throw new RuntimeException("3 components required for hsv");
}
@@ -236,7 +1450,7 @@ public class Color {
* @hide
*/
@ColorInt
- public static int getHtmlColor(String color) {
+ public static int getHtmlColor(@NonNull String color) {
Integer i = sColorNameMap.get(color.toLowerCase(Locale.ROOT));
if (i != null) {
return i;
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index d968516f36bc3..ec00c453c6d41 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -126,7 +127,8 @@ import java.util.function.DoubleUnaryOperator;
*
* To visualize and debug color spaces, you can call {@link #createRenderer()}.
* The {@link Renderer} created by calling this method can be used to compare
- * color spaces and locate specific colors on a CIE 1931 chromaticity diagram.
+ * color spaces and locate specific colors on a CIE 1931 or CIE 1976 UCS
+ * chromaticity diagram.
*
* The following code snippet shows how to render a bitmap that compares
* the color gamuts and white points of {@link Named#DCI_P3} and
@@ -155,6 +157,7 @@ import java.util.function.DoubleUnaryOperator;
* @see Adaptation
* @see Renderer
*/
+@AnyThread
@SuppressWarnings("StaticInitializerReferencesSubClass")
public abstract class ColorSpace {
/**
@@ -216,7 +219,7 @@ public abstract class ColorSpace {
*
* @see #getId()
*/
- public static final int MAX_ID = 64; // Do not change, used to encode in longs
+ public static final int MAX_ID = 63; // Do not change, used to encode in longs
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
@@ -341,11 +344,11 @@ public abstract class ColorSpace {
* \end{equation}\)
*
*
- *
| Range | \([-0.5..7.5[\) |
+ * | Range | \([-0.799..2.399[\) |
*
*
*
- * Extended RGB (orange) vs sRGB (white)
+ * Extended sRGB (orange) vs sRGB (white)
*
*/
EXTENDED_SRGB,
@@ -368,11 +371,11 @@ public abstract class ColorSpace {
* Electro-optical transfer function |
* \(C_{linear} = C_{scRGB}\) |
*
- * | Range | \([-0.5..7.5[\) |
+ * | Range | \([-0.5..7.499[\) |
*
*
*
- * Extended RGB (orange) vs sRGB (white)
+ * Extended sRGB (orange) vs sRGB (white)
*
*/
LINEAR_EXTENDED_SRGB,
@@ -1090,7 +1093,7 @@ public abstract class ColorSpace {
* space's color model. The resulting value is passed back in the specified
* array.
*
- * The specified array's length must be at least equal to
* to the number of color components as returned by
* {@link Model#getComponentCount()}, and its first 3 values must
* be the XYZ components to convert from.
@@ -1125,6 +1128,7 @@ public abstract class ColorSpace {
* @return A string representation of the object
*/
@Override
+ @NonNull
public String toString() {
return mName + " (id=" + mId + ", model=" + mModel + ")";
}
@@ -1403,7 +1407,7 @@ public abstract class ColorSpace {
ILLUMINANT_D65,
x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
- -0.5f, 7.5f,
+ -0.799f, 2.399f,
Named.EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1412,7 +1416,7 @@ public abstract class ColorSpace {
ILLUMINANT_D65,
DoubleUnaryOperator.identity(),
DoubleUnaryOperator.identity(),
- -0.5f, 7.5f,
+ -0.5f, 7.499f,
Named.LINEAR_EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
@@ -1437,8 +1441,8 @@ public abstract class ColorSpace {
"SMPTE RP 431-2-2007 DCI (P3)",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
new float[] { 0.314f, 0.351f },
- x -> Math.pow(x, 1 / 2.6),
- x -> Math.pow(x, 2.6),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.6),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 2.6),
0.0f, 1.0f,
Named.DCI_P3.ordinal()
);
@@ -1473,8 +1477,8 @@ public abstract class ColorSpace {
"Adobe RGB (1998)",
new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
ILLUMINANT_D65,
- x -> Math.pow(x, 1 / 2.2),
- x -> Math.pow(x, 2.2),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.2),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 2.2),
0.0f, 1.0f,
Named.ADOBE_RGB.ordinal()
);
@@ -1720,6 +1724,7 @@ public abstract class ColorSpace {
/**
* Implementation of the CIE XYZ color space. Assumes the white point is D50.
*/
+ @AnyThread
private static final class Xyz extends ColorSpace {
private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
super(name, Model.XYZ, id);
@@ -1765,6 +1770,7 @@ public abstract class ColorSpace {
* Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
* with a white point of D50.
*/
+ @AnyThread
private static final class Lab extends ColorSpace {
private static final float A = 216.0f / 24389.0f;
private static final float B = 841.0f / 108.0f;
@@ -1949,6 +1955,7 @@ public abstract class ColorSpace {
* To learn more about the white point adaptation process, refer to the
* documentation of {@link Adaptation}.
*/
+ @AnyThread
public static class Rgb extends ColorSpace {
@NonNull private final float[] mWhitePoint;
@NonNull private final float[] mPrimaries;
@@ -2337,7 +2344,7 @@ public abstract class ColorSpace {
* to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
* are frequently used because many OETFs can be closely approximated using
* a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
- * approximation of the {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\)
+ * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
* for instance).
*
* @return A transfer function that converts from linear space to "gamma space"
@@ -2346,7 +2353,7 @@ public abstract class ColorSpace {
*/
@NonNull
public DoubleUnaryOperator getOetf() {
- return mOetf;
+ return mClampedOetf;
}
/**
@@ -2369,7 +2376,7 @@ public abstract class ColorSpace {
*/
@NonNull
public DoubleUnaryOperator getEotf() {
- return mEotf;
+ return mClampedEotf;
}
@Override
@@ -2924,6 +2931,7 @@ public abstract class ColorSpace {
* @see ColorSpace#connect(ColorSpace, RenderIntent)
* @see ColorSpace#connect(ColorSpace)
*/
+ @AnyThread
public static class Connector {
@NonNull private final ColorSpace mSource;
@NonNull private final ColorSpace mDestination;