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:

+ * + *

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:

+ * + * + * + * + * + * + * + * + * + *
ComponentNameSizeRange
AAlpha8 bits\([0..255]\)
RRed8 bits\([0..255]\)
GGreen8 bits\([0..255]\)
BBlue8 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:

+ * + * + *

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.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ComponentNameSizeRange
{@link ColorSpace.Model#RGB RGB} color model
RRed16 bits\([-65504.0, 65504.0]\)
GGreen16 bits\([-65504.0, 65504.0]\)
BBlue16 bits\([-65504.0, 65504.0]\)
AAlpha10 bits\([0..1023]\)
Color space6 bits\([0..63]\)
{@link ColorSpace.Named#SRGB sRGB} color space
AAlpha8 bits\([0..255]\)
RRed8 bits\([0..255]\)
GGreen8 bits\([0..255]\)
BBlue8 bits\([0..255]\)
XUnused32 bits\(0\)
{@link ColorSpace.Model#XYZ XYZ} color model
XX16 bits\([-65504.0, 65504.0]\)
YY16 bits\([-65504.0, 65504.0]\)
ZZ16 bits\([-65504.0, 65504.0]\)
AAlpha10 bits\([0..1023]\)
Color space6 bits\([0..63]\)
{@link ColorSpace.Model#XYZ Lab} color model
LL16 bits\([-65504.0, 65504.0]\)
aa16 bits\([-65504.0, 65504.0]\)
bb16 bits\([-65504.0, 65504.0]\)
AAlpha10 bits\([0..1023]\)
Color space6 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:

+ * + * + *

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:

+ * + * + *

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:

+ * + * + *

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:

+ * + * + * + *

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] + * + * @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. + * * @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] + * * 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] + * * 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;