/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.Nullable; import android.annotation.Size; import android.annotation.SuppressAutoDoc; 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; /** * {@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 @SuppressAutoDoc public class Color { @ColorInt public static final int BLACK = 0xFF000000; @ColorInt public static final int DKGRAY = 0xFF444444; @ColorInt public static final int GRAY = 0xFF888888; @ColorInt public static final int LTGRAY = 0xFFCCCCCC; @ColorInt public static final int WHITE = 0xFFFFFFFF; @ColorInt public static final int RED = 0xFFFF0000; @ColorInt public static final int GREEN = 0xFF00FF00; @ColorInt public static final int BLUE = 0xFF0000FF; @ColorInt public static final int YELLOW = 0xFFFFFF00; @ColorInt public static final int CYAN = 0xFF00FFFF; @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 The integer 4 or 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() - 1). * * @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, mComponents.length); } /** * Copies this color's components in the supplied array. The last element of the * array is always the alpha component. * * @param components An array of floats whose size must be at least * {@link #getComponentCount()}, can be null * @return The array passed as a parameter if not null, or a new array of length * {@link #getComponentCount()} * * @see #getComponent(int) * * @throws IllegalArgumentException If the specified array's length is less than * {@link #getComponentCount()} */ @NonNull @Size(min = 4) public float[] getComponents(@Nullable @Size(min = 4) float[] components) { if (components == null) { return Arrays.copyOf(mComponents, mComponents.length); } if (components.length < mComponents.length) { throw new IllegalArgumentException("The specified array's length must be at " + "least " + mComponents.length); } System.arraycopy(mComponents, 0, components, 0, mComponents.length); return components; } /** *

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 * @throws IllegalArgumentException If the encoded color space is invalid or unknown * * @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 * @throws IllegalArgumentException If the encoded color space is invalid or unknown * * @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 * @throws IllegalArgumentException If the encoded color space is invalid or unknown * * @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 * @throws IllegalArgumentException If the encoded color space is invalid or unknown */ @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} * @throws IllegalArgumentException If the encoded color space is invalid or unknown */ @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.toHalf(red); @HalfFloat short g = Half.toHalf(green); @HalfFloat short b = Half.toHalf(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 * @throws IllegalArgumentException If the encoded color space is invalid or unknown */ @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 * is unknown or 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; } /** * 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; } /** * 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; } /** * 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 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 */ @ColorInt 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 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 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 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. *

* Assumes sRGB encoding. 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) */ public static float luminance(@ColorInt int color) { ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB); DoubleUnaryOperator eotf = cs.getEotf(); 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. * If the string cannot be parsed, throws an IllegalArgumentException * 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) { if (colorString.charAt(0) == '#') { // Use a long to avoid rollovers on #ffXXXXXX long color = Long.parseLong(colorString.substring(1), 16); if (colorString.length() == 7) { // Set the alpha value color |= 0x00000000ff000000; } else if (colorString.length() != 9) { throw new IllegalArgumentException("Unknown color"); } return (int)color; } else { Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT)); if (color != null) { return color; } } throw new IllegalArgumentException("Unknown color"); } /** * Convert RGB components to HSV. * * @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( @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"); } nativeRGBToHSV(red, green, blue, hsv); } /** * 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. */ public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) { RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv); } /** * Convert HSV components to an ARGB color. Alpha set to 0xFF. * * 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); } /** * Convert HSV components to an ARGB color. The alpha component is passed * through unchanged. * * 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 */ @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"); } return nativeHSVToColor(alpha, hsv); } private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]); private static native int nativeHSVToColor(int alpha, float hsv[]); /** * Converts an HTML color (named or numeric) to an integer RGB value. * * @param color Non-null color string. * * @return A color value, or {@code -1} if the color string could not be interpreted. * * @hide */ @ColorInt public static int getHtmlColor(@NonNull String color) { Integer i = sColorNameMap.get(color.toLowerCase(Locale.ROOT)); if (i != null) { return i; } else { try { return XmlUtils.convertValueToInt(color, -1); } catch (NumberFormatException nfe) { return -1; } } } private static final HashMap sColorNameMap; static { sColorNameMap = new HashMap<>(); sColorNameMap.put("black", BLACK); sColorNameMap.put("darkgray", DKGRAY); sColorNameMap.put("gray", GRAY); sColorNameMap.put("lightgray", LTGRAY); sColorNameMap.put("white", WHITE); sColorNameMap.put("red", RED); sColorNameMap.put("green", GREEN); sColorNameMap.put("blue", BLUE); sColorNameMap.put("yellow", YELLOW); sColorNameMap.put("cyan", CYAN); sColorNameMap.put("magenta", MAGENTA); sColorNameMap.put("aqua", 0xFF00FFFF); sColorNameMap.put("fuchsia", 0xFFFF00FF); sColorNameMap.put("darkgrey", DKGRAY); sColorNameMap.put("grey", GRAY); sColorNameMap.put("lightgrey", LTGRAY); sColorNameMap.put("lime", 0xFF00FF00); sColorNameMap.put("maroon", 0xFF800000); sColorNameMap.put("navy", 0xFF000080); sColorNameMap.put("olive", 0xFF808000); sColorNameMap.put("purple", 0xFF800080); sColorNameMap.put("silver", 0xFFC0C0C0); sColorNameMap.put("teal", 0xFF008080); } }