/*
 * Copyright (C) 2016 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.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SuppressAutoDoc;
import android.util.Pair;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.DoubleUnaryOperator;

{@usesMathJax}

A ColorSpace is used to identify a specific organization of colors. Each color space is characterized by a color model that defines how a color value is represented (for instance the RGB color model defines a color value as a triplet of numbers).

Each component of a color must fall within a valid range, specific to each color space, defined by getMinValue(int) and getMaxValue(int) This range is commonly \([0..1]\). While it is recommended to use values in the valid range, a color space always clamps input and output values when performing operations such as converting to a different color space.

Using color spaces

This implementation provides a pre-defined set of common color spaces described in the Named enum. To obtain an instance of one of the pre-defined color spaces, simply invoke get(Named):

ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB);

The get(Named) method always returns the same instance for a given name. Color spaces with an RGB color model can be safely cast to Rgb. Doing so gives you access to more APIs to query various properties of RGB color models: color gamut primaries, transfer functions, conversions to and from linear space, etc. Please refer to Rgb for more information.

The documentation of Named provides a detailed description of the various characteristics of each available color space.

Color space conversions

To allow conversion between color spaces, this implementation uses the CIE XYZ profile connection space (PCS). Color values can be converted to and from this PCS using toXyz(float[]) and fromXyz(float[]).

For color space with a non-RGB color model, the white point of the PCS must be the CIE standard illuminant D50. RGB color spaces use their native white point (D65 for sRGB for instance and must undergo chromatic adaptation as necessary.

Since the white point of the PCS is not defined for RGB color space, it is highly recommended to use the variants of the connect(ColorSpace, ColorSpace) method to perform conversions between color spaces. A color space can be manually adapted to a specific white point using adapt(ColorSpace, float[]). Please refer to the documentation of RGB color spaces for more information. Several common CIE standard illuminants are provided in this class as reference (see ILLUMINANT_D65 or ILLUMINANT_D50 for instance).

Here is an example of how to convert from a color space to another:

// Convert from DCI-P3 to Rec.2020
ColorSpace.Connector connector = ColorSpace.connect(
        ColorSpace.get(ColorSpace.Named.DCI_P3),
        ColorSpace.get(ColorSpace.Named.BT2020));
float[] bt2020 = connector.transform(p3r, p3g, p3b);

You can easily convert to sRGB by omitting the second parameter:

// Convert from DCI-P3 to sRGB
ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
float[] sRGB = connector.transform(p3r, p3g, p3b);

Conversions also work between color spaces with different color models:

// Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB)
ColorSpace.Connector connector = ColorSpace.connect(
        ColorSpace.get(ColorSpace.Named.CIE_LAB),
        ColorSpace.get(ColorSpace.Named.BT709));

Color spaces and multi-threading

Color spaces and other related classes (Connector for instance) are immutable and stateless. They can be safely used from multiple concurrent threads.

Public static methods provided by this class, such as get(Named) and connect(ColorSpace, ColorSpace), are also guaranteed to be thread-safe.

See Also:
/** * {@usesMathJax} * * <p>A {@link ColorSpace} is used to identify a specific organization of colors. * Each color space is characterized by a {@link Model color model} that defines * how a color value is represented (for instance the {@link Model#RGB RGB} color * model defines a color value as a triplet of numbers).</p> * * <p>Each component of a color must fall within a valid range, specific to each * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)} * This range is commonly \([0..1]\). While it is recommended to use values in the * valid range, a color space always clamps input and output values when performing * operations such as converting to a different color space.</p> * * <h3>Using color spaces</h3> * * <p>This implementation provides a pre-defined set of common color spaces * described in the {@link Named} enum. To obtain an instance of one of the * pre-defined color spaces, simply invoke {@link #get(Named)}:</p> * * <pre class="prettyprint"> * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB); * </pre> * * <p>The {@link #get(Named)} method always returns the same instance for a given * name. Color spaces with an {@link Model#RGB RGB} color model can be safely * cast to {@link Rgb}. Doing so gives you access to more APIs to query various * properties of RGB color models: color gamut primaries, transfer functions, * conversions to and from linear space, etc. Please refer to {@link Rgb} for * more information.</p> * * <p>The documentation of {@link Named} provides a detailed description of the * various characteristics of each available color space.</p> * * <h3>Color space conversions</h3> * <p>To allow conversion between color spaces, this implementation uses the CIE * XYZ profile connection space (PCS). Color values can be converted to and from * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p> * * <p>For color space with a non-RGB color model, the white point of the PCS * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their * native white point (D65 for {@link Named#SRGB sRGB} for instance and must * undergo {@link Adaptation chromatic adaptation} as necessary.</p> * * <p>Since the white point of the PCS is not defined for RGB color space, it is * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)} * method to perform conversions between color spaces. A color space can be * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}. * Please refer to the documentation of {@link Rgb RGB color spaces} for more * information. Several common CIE standard illuminants are provided in this * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50} * for instance).</p> * * <p>Here is an example of how to convert from a color space to another:</p> * * <pre class="prettyprint"> * // Convert from DCI-P3 to Rec.2020 * ColorSpace.Connector connector = ColorSpace.connect( * ColorSpace.get(ColorSpace.Named.DCI_P3), * ColorSpace.get(ColorSpace.Named.BT2020)); * * float[] bt2020 = connector.transform(p3r, p3g, p3b); * </pre> * * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second * parameter:</p> * * <pre class="prettyprint"> * // Convert from DCI-P3 to sRGB * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3)); * * float[] sRGB = connector.transform(p3r, p3g, p3b); * </pre> * * <p>Conversions also work between color spaces with different color models:</p> * * <pre class="prettyprint"> * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB) * ColorSpace.Connector connector = ColorSpace.connect( * ColorSpace.get(ColorSpace.Named.CIE_LAB), * ColorSpace.get(ColorSpace.Named.BT709)); * </pre> * * <h3>Color spaces and multi-threading</h3> * * <p>Color spaces and other related classes ({@link Connector} for instance) * are immutable and stateless. They can be safely used from multiple concurrent * threads.</p> * * <p>Public static methods provided by this class, such as {@link #get(Named)} * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be * thread-safe.</p> * * @see #get(Named) * @see Named * @see Model * @see Connector * @see Adaptation */
@AnyThread @SuppressWarnings("StaticInitializerReferencesSubClass") @SuppressAutoDoc public abstract class ColorSpace {
Standard CIE 1931 2° illuminant A, encoded in xyY. This illuminant has a color temperature of 2856K.
/** * Standard CIE 1931 2° illuminant A, encoded in xyY. * This illuminant has a color temperature of 2856K. */
public static final float[] ILLUMINANT_A = { 0.44757f, 0.40745f };
Standard CIE 1931 2° illuminant B, encoded in xyY. This illuminant has a color temperature of 4874K.
/** * Standard CIE 1931 2° illuminant B, encoded in xyY. * This illuminant has a color temperature of 4874K. */
public static final float[] ILLUMINANT_B = { 0.34842f, 0.35161f };
Standard CIE 1931 2° illuminant C, encoded in xyY. This illuminant has a color temperature of 6774K.
/** * Standard CIE 1931 2° illuminant C, encoded in xyY. * This illuminant has a color temperature of 6774K. */
public static final float[] ILLUMINANT_C = { 0.31006f, 0.31616f };
Standard CIE 1931 2° illuminant D50, encoded in xyY. This illuminant has a color temperature of 5003K. This illuminant is used by the profile connection space in ICC profiles.
/** * Standard CIE 1931 2° illuminant D50, encoded in xyY. * This illuminant has a color temperature of 5003K. This illuminant * is used by the profile connection space in ICC profiles. */
public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f };
Standard CIE 1931 2° illuminant D55, encoded in xyY. This illuminant has a color temperature of 5503K.
/** * Standard CIE 1931 2° illuminant D55, encoded in xyY. * This illuminant has a color temperature of 5503K. */
public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f };
Standard CIE 1931 2° illuminant D60, encoded in xyY. This illuminant has a color temperature of 6004K.
/** * Standard CIE 1931 2° illuminant D60, encoded in xyY. * This illuminant has a color temperature of 6004K. */
public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f };
Standard CIE 1931 2° illuminant D65, encoded in xyY. This illuminant has a color temperature of 6504K. This illuminant is commonly used in RGB color spaces such as sRGB, BT.209, etc.
/** * Standard CIE 1931 2° illuminant D65, encoded in xyY. * This illuminant has a color temperature of 6504K. This illuminant * is commonly used in RGB color spaces such as sRGB, BT.209, etc. */
public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
Standard CIE 1931 2° illuminant D75, encoded in xyY. This illuminant has a color temperature of 7504K.
/** * Standard CIE 1931 2° illuminant D75, encoded in xyY. * This illuminant has a color temperature of 7504K. */
public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f };
Standard CIE 1931 2° illuminant E, encoded in xyY. This illuminant has a color temperature of 5454K.
/** * Standard CIE 1931 2° illuminant E, encoded in xyY. * This illuminant has a color temperature of 5454K. */
public static final float[] ILLUMINANT_E = { 0.33333f, 0.33333f };
The minimum ID value a color space can have.
See Also:
  • getId()
/** * The minimum ID value a color space can have. * * @see #getId() */
public static final int MIN_ID = -1; // Do not change
The maximum ID value a color space can have.
See Also:
  • getId()
/** * The maximum ID value a color space can have. * * @see #getId() */
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 }; private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; @NonNull private final String mName; @NonNull private final Model mModel; @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
{@usesMathJax}

List of common, named color spaces. A corresponding instance of ColorSpace can be obtained by calling ColorSpace.get(Named):

ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);

The properties of each color space are described below (see sRGB for instance). When applicable, the color gamut of each color space is compared to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram shows the location of the color space's primaries and white point.

See Also:
/** * {@usesMathJax} * * <p>List of common, named color spaces. A corresponding instance of * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p> * * <pre class="prettyprint"> * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3); * </pre> * * <p>The properties of each color space are described below (see {@link #SRGB sRGB} * for instance). When applicable, the color gamut of each color space is compared * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram * shows the location of the color space's primaries and white point.</p> * * @see ColorSpace#get(Named) */
public enum Named { // NOTE: Do NOT change the order of the enum

RGB color space sRGB standardized as IEC 61966-2.1:1999.

ChromaticityRedGreenBlueWhite point
x0.6400.3000.1500.3127
y0.3300.6000.0600.3290
PropertyValue
NamesRGB IEC61966-2.1
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} \end{equation}\)
Range\([0..1]\)

sRGB

/** * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> * <figcaption style="text-align: center;">sRGB</figcaption> * </p> */
SRGB,

RGB color space sRGB standardized as IEC 61966-2.1:1999.

ChromaticityRedGreenBlueWhite point
x0.6400.3000.1500.3127
y0.3300.6000.0600.3290
PropertyValue
NamesRGB IEC61966-2.1 (Linear)
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(C_{sRGB} = C_{linear}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{sRGB}\)
Range\([0..1]\)

sRGB

/** * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{sRGB} = C_{linear}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{sRGB}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> * <figcaption style="text-align: center;">sRGB</figcaption> * </p> */
LINEAR_SRGB,

RGB color space scRGB-nl standardized as IEC 61966-2-2:2003.

ChromaticityRedGreenBlueWhite point
x0.6400.3000.1500.3127
y0.3300.6000.0600.3290
PropertyValue
NamescRGB-nl IEC 61966-2-2:2003
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| & \left| C_{linear} \right| \lt 0.0031308 \\ sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & \left| C_{linear} \right| \ge 0.0031308 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} & \left| C_{scRGB} \right| \lt 0.04045 \\ sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & \left| C_{scRGB} \right| \ge 0.04045 \end{cases} \end{equation}\)
Range\([-0.799..2.399[\)

Extended sRGB (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| & * \left| C_{linear} \right| \lt 0.0031308 \\ * sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & * \left| C_{linear} \right| \ge 0.0031308 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} & * \left| C_{scRGB} \right| \lt 0.04045 \\ * sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & * \left| C_{scRGB} \right| \ge 0.04045 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption> * </p> */
EXTENDED_SRGB,

RGB color space scRGB standardized as IEC 61966-2-2:2003.

ChromaticityRedGreenBlueWhite point
x0.6400.3000.1500.3127
y0.3300.6000.0600.3290
PropertyValue
NamescRGB IEC 61966-2-2:2003
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(C_{scRGB} = C_{linear}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{scRGB}\)
Range\([-0.5..7.499[\)

Extended sRGB (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{scRGB} = C_{linear}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{scRGB}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption> * </p> */
LINEAR_EXTENDED_SRGB,

RGB color space BT.709 standardized as Rec. ITU-R BT.709-5.

ChromaticityRedGreenBlueWhite point
x0.6400.3000.1500.3127
y0.3300.6000.0600.3290
PropertyValue
NameRec. ITU-R BT.709-5
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} \end{equation}\)
Range\([0..1]\)

BT.709

/** * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" /> * <figcaption style="text-align: center;">BT.709</figcaption> * </p> */
BT709,

RGB color space BT.2020 standardized as Rec. ITU-R BT.2020-1.

ChromaticityRedGreenBlueWhite point
x0.7080.1700.1310.3127
y0.2920.7970.0460.3290
PropertyValue
NameRec. ITU-R BT.2020-1
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\ 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\ \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases} \end{equation}\)
Range\([0..1]\)

BT.2020 (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\ * 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\ * \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" /> * <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption> * </p> */
BT2020,

RGB color space DCI-P3 standardized as SMPTE RP 431-2-2007.

ChromaticityRedGreenBlueWhite point
x0.6800.2650.1500.314
y0.3200.6900.0600.351
PropertyValue
NameSMPTE RP 431-2-2007 DCI (P3)
CIE standard illuminantN/A
Opto-electronic transfer function (OETF) \(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{P3}^{2.6}\)
Range\([0..1]\)

DCI-P3 (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr> * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" /> * <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption> * </p> */
DCI_P3,

RGB color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.

ChromaticityRedGreenBlueWhite point
x0.6800.2650.1500.3127
y0.3200.6900.0600.3290
PropertyValue
NameDisplay P3
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\ 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\ \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases} \end{equation}\)
Range\([0..1]\)

Display P3 (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Display P3</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\ * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\ * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" /> * <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption> * </p> */
DISPLAY_P3,

RGB color space NTSC, 1953 standard.

ChromaticityRedGreenBlueWhite point
x0.670.210.140.310
y0.330.710.080.316
PropertyValue
NameNTSC (1953)
CIE standard illuminantC
Opto-electronic transfer function (OETF) \(\begin{equation} C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} \end{equation}\)
Range\([0..1]\)

NTSC 1953 (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr> * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" /> * <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption> * </p> */
NTSC_1953,

RGB color space SMPTE C.

ChromaticityRedGreenBlueWhite point
x0.6300.3100.1550.3127
y0.3400.5950.0700.3290
PropertyValue
NameSMPTE-C RGB
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(\begin{equation} C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} \end{equation}\)
Range\([0..1]\)

SMPTE-C (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" /> * <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption> * </p> */
SMPTE_C,

RGB color space Adobe RGB (1998).

ChromaticityRedGreenBlueWhite point
x0.640.210.150.3127
y0.330.710.060.3290
PropertyValue
NameAdobe RGB (1998)
CIE standard illuminantD65
Opto-electronic transfer function (OETF) \(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{RGB}^{2.2}\)
Range\([0..1]\)

Adobe RGB (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr> * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" /> * <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption> * </p> */
ADOBE_RGB,

RGB color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.

ChromaticityRedGreenBlueWhite point
x0.73470.15960.03660.3457
y0.26530.84040.00010.3585
PropertyValue
NameROMM RGB ISO 22028-2:2013
CIE standard illuminantD50
Opto-electronic transfer function (OETF) \(\begin{equation} C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\ C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases} \end{equation}\)
Electro-optical transfer function (EOTF) \(\begin{equation} C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\ C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases} \end{equation}\)
Range\([0..1]\)

ProPhoto RGB (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr> * <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\ * C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\ * C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases} * \end{equation}\) * </td> * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" /> * <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption> * </p> */
PRO_PHOTO_RGB,

RGB color space ACES standardized as SMPTE ST 2065-1:2012.

ChromaticityRedGreenBlueWhite point
x0.734700.000000.000100.32168
y0.265301.00000-0.077000.33767
PropertyValue
NameSMPTE ST 2065-1:2012 ACES
CIE standard illuminantD60
Opto-electronic transfer function (OETF) \(C_{ACES} = C_{linear}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{ACES}\)
Range\([-65504.0, 65504.0]\)

ACES (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr> * <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{ACES} = C_{linear}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{ACES}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" /> * <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption> * </p> */
ACES,

RGB color space ACEScg standardized as Academy S-2014-004.

ChromaticityRedGreenBlueWhite point
x0.7130.1650.1280.32168
y0.2930.8300.0440.33767
PropertyValue
NameAcademy S-2014-004 ACEScg
CIE standard illuminantD60
Opto-electronic transfer function (OETF) \(C_{ACEScg} = C_{linear}\)
Electro-optical transfer function (EOTF) \(C_{linear} = C_{ACEScg}\)
Range\([-65504.0, 65504.0]\)

ACEScg (orange) vs sRGB (white)

/** * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p> * <table summary="Color space definition"> * <tr> * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> * </tr> * <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr> * <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td> * </tr> * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> * </table> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" /> * <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption> * </p> */
ACESCG,

XYZ color space CIE XYZ. This color space assumes standard illuminant D50 as its white point.

PropertyValue
NameGeneric XYZ
CIE standard illuminantD50
Range\([-2.0, 2.0]\)
/** * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard * illuminant D50 as its white point.</p> * <table summary="Color space definition"> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> * <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr> * </table> */
CIE_XYZ,

Lab color space CIE L*a*b*. This color space uses CIE XYZ D50 as a profile conversion space.

PropertyValue
NameGeneric L*a*b*
CIE standard illuminantD50
Range\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)
/** * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50 * as a profile conversion space.</p> * <table summary="Color space definition"> * <tr><th>Property</th><th colspan="4">Value</th></tr> * <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr> * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> * <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr> * </table> */
CIE_LAB // Update the initialization block next to #get(Named) when adding new values }

A render intent determines how a connector maps colors from one color space to another. The choice of mapping is important when the source color space has a larger color gamut than the destination color space.

See Also:
/** * <p>A render intent determines how a {@link ColorSpace.Connector connector} * maps colors from one color space to another. The choice of mapping is * important when the source color space has a larger color gamut than the * destination color space.</p> * * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) */
public enum RenderIntent {

Compresses the source gamut into the destination gamut. This render intent affects all colors, inside and outside of destination gamut. The goal of this render intent is to preserve the visual relationship between colors.

This render intent is currently not implemented and behaves like RELATIVE.

/** * <p>Compresses the source gamut into the destination gamut. * This render intent affects all colors, inside and outside * of destination gamut. The goal of this render intent is * to preserve the visual relationship between colors.</p> * * <p class="note">This render intent is currently not * implemented and behaves like {@link #RELATIVE}.</p> */
PERCEPTUAL,
Similar to the ABSOLUTE render intent, this render intent matches the closest color in the destination gamut but makes adjustments for the destination white point.
/** * Similar to the {@link #ABSOLUTE} render intent, this render * intent matches the closest color in the destination gamut * but makes adjustments for the destination white point. */
RELATIVE,

Attempts to maintain the relative saturation of colors from the source gamut to the destination gamut, to keep highly saturated colors as saturated as possible.

This render intent is currently not implemented and behaves like RELATIVE.

/** * <p>Attempts to maintain the relative saturation of colors * from the source gamut to the destination gamut, to keep * highly saturated colors as saturated as possible.</p> * * <p class="note">This render intent is currently not * implemented and behaves like {@link #RELATIVE}.</p> */
SATURATION,
Colors that are in the destination gamut are left unchanged. Colors that fall outside of the destination gamut are mapped to the closest possible color within the gamut of the destination color space (they are clipped).
/** * Colors that are in the destination gamut are left unchanged. * Colors that fall outside of the destination gamut are mapped * to the closest possible color within the gamut of the destination * color space (they are clipped). */
ABSOLUTE }
{@usesMathJax}

List of adaptation matrices that can be used for chromatic adaptation using the von Kries transform. These matrices are used to convert values in the CIE XYZ space to values in the LMS space (Long Medium Short).

Given an adaptation matrix \(A\), the conversion from XYZ to LMS is straightforward:

$$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] = A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$

The complete von Kries transform \(T\) uses a diagonal matrix noted \(D\) to perform the adaptation in LMS space. In addition to \(A\) and \(D\), the source white point \(W1\) and the destination white point \(W2\) must be specified:

$$\begin{align*} \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &= A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\ \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &= A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\ D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\ 0 & \frac{M_2}{M_1} & 0 \\ 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\ T &= A^{-1}.D.A \end{align*}$$

As an example, the resulting matrix \(T\) can then be used to perform the chromatic adaptation of sRGB XYZ transform from D65 to D50:

$$sRGB_{D50} = T.sRGB_{D65}$$
See Also:
/** * {@usesMathJax} * * <p>List of adaptation matrices that can be used for chromatic adaptation * using the von Kries transform. These matrices are used to convert values * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p> * * <p>Given an adaptation matrix \(A\), the conversion from XYZ to * LMS is straightforward:</p> * * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] = * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$ * * <p>The complete von Kries transform \(T\) uses a diagonal matrix * noted \(D\) to perform the adaptation in LMS space. In addition * to \(A\) and \(D\), the source white point \(W1\) and the destination * white point \(W2\) must be specified:</p> * * $$\begin{align*} * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &= * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\ * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &= * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\ * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\ * 0 & \frac{M_2}{M_1} & 0 \\ * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\ * T &= A^{-1}.D.A * \end{align*}$$ * * <p>As an example, the resulting matrix \(T\) can then be used to * perform the chromatic adaptation of sRGB XYZ transform from D65 * to D50:</p> * * $$sRGB_{D50} = T.sRGB_{D65}$$ * * @see ColorSpace.Connector * @see ColorSpace#connect(ColorSpace, ColorSpace) */
public enum Adaptation {
Bradford chromatic adaptation transform, as defined in the CIECAM97s color appearance model.
/** * Bradford chromatic adaptation transform, as defined in the * CIECAM97s color appearance model. */
BRADFORD(new float[] { 0.8951f, -0.7502f, 0.0389f, 0.2664f, 1.7135f, -0.0685f, -0.1614f, 0.0367f, 1.0296f }),
von Kries chromatic adaptation transform.
/** * von Kries chromatic adaptation transform. */
VON_KRIES(new float[] { 0.40024f, -0.22630f, 0.00000f, 0.70760f, 1.16532f, 0.00000f, -0.08081f, 0.04570f, 0.91822f }),
CIECAT02 chromatic adaption transform, as defined in the CIECAM02 color appearance model.
/** * CIECAT02 chromatic adaption transform, as defined in the * CIECAM02 color appearance model. */
CIECAT02(new float[] { 0.7328f, -0.7036f, 0.0030f, 0.4296f, 1.6975f, 0.0136f, -0.1624f, 0.0061f, 0.9834f }); final float[] mTransform; Adaptation(@NonNull @Size(9) float[] transform) { mTransform = transform; } }
A color model is required by a ColorSpace to describe the way colors can be represented as tuples of numbers. A common color model is the RGB color model which defines a color as represented by a tuple of 3 numbers (red, green and blue).
/** * A color model is required by a {@link ColorSpace} to describe the * way colors can be represented as tuples of numbers. A common color * model is the {@link #RGB RGB} color model which defines a color * as represented by a tuple of 3 numbers (red, green and blue). */
public enum Model {
The RGB model is a color model with 3 components that refer to the three additive primiaries: red, green andd blue.
/** * The RGB model is a color model with 3 components that * refer to the three additive primiaries: red, green * andd blue. */
RGB(3),
The XYZ model is a color model with 3 components that are used to model human color vision on a basic sensory level.
/** * The XYZ model is a color model with 3 components that * are used to model human color vision on a basic sensory * level. */
XYZ(3),
The Lab model is a color model with 3 components used to describe a color space that is more perceptually uniform than XYZ.
/** * The Lab model is a color model with 3 components used * to describe a color space that is more perceptually * uniform than XYZ. */
LAB(3),
The CMYK model is a color model with 4 components that refer to four inks used in color printing: cyan, magenta, yellow and black (or key). CMYK is a subtractive color model.
/** * The CMYK model is a color model with 4 components that * refer to four inks used in color printing: cyan, magenta, * yellow and black (or key). CMYK is a subtractive color * model. */
CMYK(4); private final int mComponentCount; Model(@IntRange(from = 1, to = 4) int componentCount) { mComponentCount = componentCount; }
Returns the number of components for this color model.
Returns:An integer between 1 and 4
/** * Returns the number of components for this color model. * * @return An integer between 1 and 4 */
@IntRange(from = 1, to = 4) public int getComponentCount() { return mComponentCount; } } private ColorSpace( @NonNull String name, @NonNull Model model, @IntRange(from = MIN_ID, to = MAX_ID) int id) { if (name == null || name.length() < 1) { throw new IllegalArgumentException("The name of a color space cannot be null and " + "must contain at least 1 character"); } if (model == null) { throw new IllegalArgumentException("A color space must have a model"); } if (id < MIN_ID || id > MAX_ID) { throw new IllegalArgumentException("The id must be between " + MIN_ID + " and " + MAX_ID); } mName = name; mModel = model; mId = id; }

Returns the name of this color space. The name is never null and contains always at least 1 character.

Color space names are recommended to be unique but are not guaranteed to be. There is no defined format but the name usually falls in one of the following categories:

  • Generic names used to identify color spaces in non-RGB color models. For instance: Generic L*a*b*.
  • Names tied to a particular specification. For instance: sRGB IEC61966-2.1 or SMPTE ST 2065-1:2012 ACES.
  • Ad-hoc names, often generated procedurally or by the user during a calibration workflow. These names often contain the make and model of the display.

Because the format of color space names is not defined, it is not recommended to programmatically identify a color space by its name alone. Names can be used as a first approximation.

It is however perfectly acceptable to display color space names to users in a UI, or in debuggers and logs. When displaying a color space name to the user, it is recommended to add extra information to avoid ambiguities: color model, a representation of the color space's gamut, white point, etc.

Returns:A non-null String of length >= 1
/** * <p>Returns the name of this color space. The name is never null * and contains always at least 1 character.</p> * * <p>Color space names are recommended to be unique but are not * guaranteed to be. There is no defined format but the name usually * falls in one of the following categories:</p> * <ul> * <li>Generic names used to identify color spaces in non-RGB * color models. For instance: {@link Named#CIE_LAB Generic L*a*b*}.</li> * <li>Names tied to a particular specification. For instance: * {@link Named#SRGB sRGB IEC61966-2.1} or * {@link Named#ACES SMPTE ST 2065-1:2012 ACES}.</li> * <li>Ad-hoc names, often generated procedurally or by the user * during a calibration workflow. These names often contain the * make and model of the display.</li> * </ul> * * <p>Because the format of color space names is not defined, it is * not recommended to programmatically identify a color space by its * name alone. Names can be used as a first approximation.</p> * * <p>It is however perfectly acceptable to display color space names to * users in a UI, or in debuggers and logs. When displaying a color space * name to the user, it is recommended to add extra information to avoid * ambiguities: color model, a representation of the color space's gamut, * white point, etc.</p> * * @return A non-null String of length >= 1 */
@NonNull public String getName() { return mName; }
Returns the ID of this color space. Positive IDs match the color spaces enumerated in Named. A negative ID indicates a color space created by calling one of the public constructors.
Returns:An integer between MIN_ID and MAX_ID
/** * Returns the ID of this color space. Positive IDs match the color * spaces enumerated in {@link Named}. A negative ID indicates a * color space created by calling one of the public constructors. * * @return An integer between {@link #MIN_ID} and {@link #MAX_ID} */
@IntRange(from = MIN_ID, to = MAX_ID) public int getId() { return mId; }
Return the color model of this color space.
See Also:
Returns:A non-null Model
/** * Return the color model of this color space. * * @return A non-null {@link Model} * * @see Model * @see #getComponentCount() */
@NonNull public Model getModel() { return mModel; }
Returns the number of components that form a color value according to this color space's color model.
See Also:
Returns:An integer between 1 and 4
/** * Returns the number of components that form a color value according * to this color space's color model. * * @return An integer between 1 and 4 * * @see Model * @see #getModel() */
@IntRange(from = 1, to = 4) public int getComponentCount() { return mModel.getComponentCount(); }
Returns whether this color space is a wide-gamut color space. An RGB color space is wide-gamut if its gamut entirely contains the sRGB gamut and if the area of its gamut is 90% of greater than the area of the NTSC gamut.
Returns:True if this color space is a wide-gamut color space, false otherwise
/** * Returns whether this color space is a wide-gamut color space. * An RGB color space is wide-gamut if its gamut entirely contains * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC} * gamut. * * @return True if this color space is a wide-gamut color space, * false otherwise */
public abstract boolean isWideGamut();

Indicates whether this color space is the sRGB color space or equivalent to the sRGB color space.

A color space is considered sRGB if it meets all the following conditions:

  • Its color model is Model.RGB.
  • Its primaries are within 1e-3 of the true sRGB primaries.
  • Its white point is withing 1e-3 of the CIE standard illuminant D65.
  • Its opto-electronic transfer function is not linear.
  • Its electro-optical transfer function is not linear.
  • Its range is \([0..1]\).

This method always returns true for Named.SRGB.

Returns:True if this color space is the sRGB color space (or a close approximation), false otherwise
/** * <p>Indicates whether this color space is the sRGB color space or * equivalent to the sRGB color space.</p> * <p>A color space is considered sRGB if it meets all the following * conditions:</p> * <ul> * <li>Its color model is {@link Model#RGB}.</li> * <li> * Its primaries are within 1e-3 of the true * {@link Named#SRGB sRGB} primaries. * </li> * <li> * Its white point is withing 1e-3 of the CIE standard * illuminant {@link #ILLUMINANT_D65 D65}. * </li> * <li>Its opto-electronic transfer function is not linear.</li> * <li>Its electro-optical transfer function is not linear.</li> * <li>Its range is \([0..1]\).</li> * </ul> * <p>This method always returns true for {@link Named#SRGB}.</p> * * @return True if this color space is the sRGB color space (or a * close approximation), false otherwise */
public boolean isSrgb() { return false; }
Returns the minimum valid value for the specified component of this color space's color model.
Params:
  • component – The index of the component
See Also:
Returns:A floating point value less than getMaxValue(int)
/** * Returns the minimum valid value for the specified component of this * color space's color model. * * @param component The index of the component * @return A floating point value less than {@link #getMaxValue(int)} * * @see #getMaxValue(int) * @see Model#getComponentCount() */
public abstract float getMinValue(@IntRange(from = 0, to = 3) int component);
Returns the maximum valid value for the specified component of this color space's color model.
Params:
  • component – The index of the component
See Also:
Returns:A floating point value greater than getMinValue(int)
/** * Returns the maximum valid value for the specified component of this * color space's color model. * * @param component The index of the component * @return A floating point value greater than {@link #getMinValue(int)} * * @see #getMinValue(int) * @see Model#getComponentCount() */
public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component);

Converts a color value from this color space's model to tristimulus CIE XYZ values. If the color model of this color space is not RGB, it is assumed that the target CIE XYZ space uses a D50 standard illuminant.

This method is a convenience for color spaces with a model of 3 components (RGB or Model.LAB for instance). With color spaces using fewer or more components, use toXyz(float[]) instead

.
Params:
  • r – The first component of the value to convert from (typically R in RGB)
  • g – The second component of the value to convert from (typically G in RGB)
  • b – The third component of the value to convert from (typically B in RGB)
See Also:
Returns:A new array of 3 floats, containing tristimulus XYZ values
/** * <p>Converts a color value from this color space's model to * tristimulus CIE XYZ values. If the color model of this color * space is not {@link Model#RGB RGB}, it is assumed that the * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} * standard illuminant.</p> * * <p>This method is a convenience for color spaces with a model * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB} * for instance). With color spaces using fewer or more components, * use {@link #toXyz(float[])} instead</p>. * * @param r The first component of the value to convert from (typically R in RGB) * @param g The second component of the value to convert from (typically G in RGB) * @param b The third component of the value to convert from (typically B in RGB) * @return A new array of 3 floats, containing tristimulus XYZ values * * @see #toXyz(float[]) * @see #fromXyz(float, float, float) */
@NonNull @Size(3) public float[] toXyz(float r, float g, float b) { return toXyz(new float[] { r, g, b }); }

Converts a color value from this color space's model to tristimulus CIE XYZ values. If the color model of this color space is not RGB, it is assumed that the target CIE XYZ space uses a D50 standard illuminant.

The specified array's length must be at least equal to to the number of color components as returned by Model.getComponentCount().

Params:
  • v – An array of color components containing the color space's color value to convert to XYZ, and large enough to hold the resulting tristimulus XYZ values
See Also:
Returns:The array passed in parameter
/** * <p>Converts a color value from this color space's model to * tristimulus CIE XYZ values. If the color model of this color * space is not {@link Model#RGB RGB}, it is assumed that the * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} * standard illuminant.</p> * * <p class="note">The specified array's length must be at least * equal to to the number of color components as returned by * {@link Model#getComponentCount()}.</p> * * @param v An array of color components containing the color space's * color value to convert to XYZ, and large enough to hold * the resulting tristimulus XYZ values * @return The array passed in parameter * * @see #toXyz(float, float, float) * @see #fromXyz(float[]) */
@NonNull @Size(min = 3) public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v);

Converts tristimulus values from the CIE XYZ space to this color space's color model.

Params:
  • x – The X component of the color value
  • y – The Y component of the color value
  • z – The Z component of the color value
See Also:
Returns:A new array whose size is equal to the number of color components as returned by Model.getComponentCount()
/** * <p>Converts tristimulus values from the CIE XYZ space to this * color space's color model.</p> * * @param x The X component of the color value * @param y The Y component of the color value * @param z The Z component of the color value * @return A new array whose size is equal to the number of color * components as returned by {@link Model#getComponentCount()} * * @see #fromXyz(float[]) * @see #toXyz(float, float, float) */
@NonNull @Size(min = 3) public float[] fromXyz(float x, float y, float z) { float[] xyz = new float[mModel.getComponentCount()]; xyz[0] = x; xyz[1] = y; xyz[2] = z; return fromXyz(xyz); }

Converts tristimulus values from the CIE XYZ space to this color 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 Model.getComponentCount(), and its first 3 values must be the XYZ components to convert from.

Params:
  • v – An array of color components containing the XYZ values to convert from, and large enough to hold the number of components of this color space's model
See Also:
Returns:The array passed in parameter
/** * <p>Converts tristimulus values from the CIE XYZ space to this color * space's color model. The resulting value is passed back in the specified * array.</p> * * <p class="note">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.</p> * * @param v An array of color components containing the XYZ values * to convert from, and large enough to hold the number * of components of this color space's model * @return The array passed in parameter * * @see #fromXyz(float, float, float) * @see #toXyz(float[]) */
@NonNull @Size(min = 3) public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v);

Returns a string representation of the object. This method returns a string equal to the value of:

getName() + "(id=" + getId() + ", model=" + getModel() + ")"

For instance, the string representation of the sRGB color space is equal to the following value:

sRGB IEC61966-2.1 (id=0, model=RGB)
Returns:A string representation of the object
/** * <p>Returns a string representation of the object. This method returns * a string equal to the value of:</p> * * <pre class="prettyprint"> * getName() + "(id=" + getId() + ", model=" + getModel() + ")" * </pre> * * <p>For instance, the string representation of the {@link Named#SRGB sRGB} * color space is equal to the following value:</p> * * <pre> * sRGB IEC61966-2.1 (id=0, model=RGB) * </pre> * * @return A string representation of the object */
@Override @NonNull public String toString() { return mName + " (id=" + mId + ", model=" + mModel + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ColorSpace that = (ColorSpace) o; if (mId != that.mId) return false; //noinspection SimplifiableIfStatement if (!mName.equals(that.mName)) return false; return mModel == that.mModel; } @Override public int hashCode() { int result = mName.hashCode(); result = 31 * result + mModel.hashCode(); result = 31 * result + mId; return result; }

Connects two color spaces to allow conversion from the source color space to the destination color space. If the source and destination color spaces do not have the same profile connection space (CIE XYZ with the same white point), they are chromatically adapted to use the CIE standard illuminant D50 as needed.

If the source and destination are the same, an optimized connector is returned to avoid unnecessary computations and loss of precision.

Colors are mapped from the source color space to the destination color space using the perceptual render intent.

Params:
  • source – The color space to convert colors from
  • destination – The color space to convert colors to
See Also:
Returns:A non-null connector between the two specified color spaces
/** * <p>Connects two color spaces to allow conversion from the source color * space to the destination color space. If the source and destination * color spaces do not have the same profile connection space (CIE XYZ * with the same white point), they are chromatically adapted to use the * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> * * <p>If the source and destination are the same, an optimized connector * is returned to avoid unnecessary computations and loss of precision.</p> * * <p>Colors are mapped from the source color space to the destination color * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> * * @param source The color space to convert colors from * @param destination The color space to convert colors to * @return A non-null connector between the two specified color spaces * * @see #connect(ColorSpace) * @see #connect(ColorSpace, RenderIntent) * @see #connect(ColorSpace, ColorSpace, RenderIntent) */
@NonNull public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) { return connect(source, destination, RenderIntent.PERCEPTUAL); }

Connects two color spaces to allow conversion from the source color space to the destination color space. If the source and destination color spaces do not have the same profile connection space (CIE XYZ with the same white point), they are chromatically adapted to use the CIE standard illuminant D50 as needed.

If the source and destination are the same, an optimized connector is returned to avoid unnecessary computations and loss of precision.

Params:
  • source – The color space to convert colors from
  • destination – The color space to convert colors to
  • intent – The render intent to map colors from the source to the destination
See Also:
Returns:A non-null connector between the two specified color spaces
/** * <p>Connects two color spaces to allow conversion from the source color * space to the destination color space. If the source and destination * color spaces do not have the same profile connection space (CIE XYZ * with the same white point), they are chromatically adapted to use the * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> * * <p>If the source and destination are the same, an optimized connector * is returned to avoid unnecessary computations and loss of precision.</p> * * @param source The color space to convert colors from * @param destination The color space to convert colors to * @param intent The render intent to map colors from the source to the destination * @return A non-null connector between the two specified color spaces * * @see #connect(ColorSpace) * @see #connect(ColorSpace, RenderIntent) * @see #connect(ColorSpace, ColorSpace) */
@NonNull @SuppressWarnings("ConstantConditions") public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent) { if (source.equals(destination)) return Connector.identity(source); if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) { return new Connector.Rgb((Rgb) source, (Rgb) destination, intent); } return new Connector(source, destination, intent); }

Connects the specified color spaces to sRGB. If the source color space does not use CIE XYZ D65 as its profile connection space, the two spaces are chromatically adapted to use the CIE standard illuminant D50 as needed.

If the source is the sRGB color space, an optimized connector is returned to avoid unnecessary computations and loss of precision.

Colors are mapped from the source color space to the destination color space using the perceptual render intent.

Params:
  • source – The color space to convert colors from
See Also:
Returns:A non-null connector between the specified color space and sRGB
/** * <p>Connects the specified color spaces to sRGB. * If the source color space does not use CIE XYZ D65 as its profile * connection space, the two spaces are chromatically adapted to use the * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> * * <p>If the source is the sRGB color space, an optimized connector * is returned to avoid unnecessary computations and loss of precision.</p> * * <p>Colors are mapped from the source color space to the destination color * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> * * @param source The color space to convert colors from * @return A non-null connector between the specified color space and sRGB * * @see #connect(ColorSpace, RenderIntent) * @see #connect(ColorSpace, ColorSpace) * @see #connect(ColorSpace, ColorSpace, RenderIntent) */
@NonNull public static Connector connect(@NonNull ColorSpace source) { return connect(source, RenderIntent.PERCEPTUAL); }

Connects the specified color spaces to sRGB. If the source color space does not use CIE XYZ D65 as its profile connection space, the two spaces are chromatically adapted to use the CIE standard illuminant D50 as needed.

If the source is the sRGB color space, an optimized connector is returned to avoid unnecessary computations and loss of precision.

Params:
  • source – The color space to convert colors from
  • intent – The render intent to map colors from the source to the destination
See Also:
Returns:A non-null connector between the specified color space and sRGB
/** * <p>Connects the specified color spaces to sRGB. * If the source color space does not use CIE XYZ D65 as its profile * connection space, the two spaces are chromatically adapted to use the * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> * * <p>If the source is the sRGB color space, an optimized connector * is returned to avoid unnecessary computations and loss of precision.</p> * * @param source The color space to convert colors from * @param intent The render intent to map colors from the source to the destination * @return A non-null connector between the specified color space and sRGB * * @see #connect(ColorSpace) * @see #connect(ColorSpace, ColorSpace) * @see #connect(ColorSpace, ColorSpace, RenderIntent) */
@NonNull public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) { if (source.isSrgb()) return Connector.identity(source); if (source.getModel() == Model.RGB) { return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent); } return new Connector(source, get(Named.SRGB), intent); }

Performs the chromatic adaptation of a color space from its native white point to the specified white point.

The chromatic adaptation is performed using the Adaptation.BRADFORD matrix.

The color space returned by this method always has an ID of MIN_ID.

Params:
  • colorSpace – The color space to chromatically adapt
  • whitePoint – The new white point
See Also:
Returns:A ColorSpace instance with the same name, primaries, transfer functions and range as the specified color space
/** * <p>Performs the chromatic adaptation of a color space from its native * white point to the specified white point.</p> * * <p>The chromatic adaptation is performed using the * {@link Adaptation#BRADFORD} matrix.</p> * * <p class="note">The color space returned by this method always has * an ID of {@link #MIN_ID}.</p> * * @param colorSpace The color space to chromatically adapt * @param whitePoint The new white point * @return A {@link ColorSpace} instance with the same name, primaries, * transfer functions and range as the specified color space * * @see Adaptation * @see #adapt(ColorSpace, float[], Adaptation) */
@NonNull public static ColorSpace adapt(@NonNull ColorSpace colorSpace, @NonNull @Size(min = 2, max = 3) float[] whitePoint) { return adapt(colorSpace, whitePoint, Adaptation.BRADFORD); }

Performs the chromatic adaptation of a color space from its native white point to the specified white point. If the specified color space does not have an RGB color model, or if the color space already has the target white point, the color space is returned unmodified.

The chromatic adaptation is performed using the von Kries method described in the documentation of Adaptation.

The color space returned by this method always has an ID of MIN_ID.

Params:
  • colorSpace – The color space to chromatically adapt
  • whitePoint – The new white point
  • adaptation – The adaptation matrix
See Also:
Returns:A new color space if the specified color space has an RGB model and a white point different from the specified white point; the specified color space otherwise
/** * <p>Performs the chromatic adaptation of a color space from its native * white point to the specified white point. If the specified color space * does not have an {@link Model#RGB RGB} color model, or if the color * space already has the target white point, the color space is returned * unmodified.</p> * * <p>The chromatic adaptation is performed using the von Kries method * described in the documentation of {@link Adaptation}.</p> * * <p class="note">The color space returned by this method always has * an ID of {@link #MIN_ID}.</p> * * @param colorSpace The color space to chromatically adapt * @param whitePoint The new white point * @param adaptation The adaptation matrix * @return A new color space if the specified color space has an RGB * model and a white point different from the specified white * point; the specified color space otherwise * * @see Adaptation * @see #adapt(ColorSpace, float[]) */
@NonNull public static ColorSpace adapt(@NonNull ColorSpace colorSpace, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull Adaptation adaptation) { if (colorSpace.getModel() == Model.RGB) { ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace; float[] xyz = whitePoint.length == 3 ? Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint); float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform, xyYToXyz(rgb.getWhitePoint()), xyz); float[] transform = mul3x3(adaptationTransform, rgb.mTransform); return new ColorSpace.Rgb(rgb, transform, whitePoint); } return colorSpace; }

Returns an instance of ColorSpace whose ID matches the specified ID.

This method always returns the same instance for a given ID.

This method is thread-safe.

Params:
Throws:
Returns:A non-null ColorSpace instance
/** * <p>Returns an instance of {@link ColorSpace} whose ID matches the * specified ID.</p> * * <p>This method always returns the same instance for a given ID.</p> * * <p>This method is thread-safe.</p> * * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID} * @return A non-null {@link ColorSpace} instance * @throws IllegalArgumentException If the ID does not match the ID of one of the * {@link Named named color spaces} */
@NonNull static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) { if (index < 0 || index > Named.values().length) { throw new IllegalArgumentException("Invalid ID, must be in the range [0.." + Named.values().length + "]"); } return sNamedColorSpaces[index]; }

Returns an instance of ColorSpace identified by the specified name. The list of names provided in the Named enum gives access to a variety of common RGB color spaces.

This method always returns the same instance for a given name.

This method is thread-safe.

Params:
  • name – The name of the color space to get an instance of
Returns:A non-null ColorSpace instance
/** * <p>Returns an instance of {@link ColorSpace} identified by the specified * name. The list of names provided in the {@link Named} enum gives access * to a variety of common RGB color spaces.</p> * * <p>This method always returns the same instance for a given name.</p> * * <p>This method is thread-safe.</p> * * @param name The name of the color space to get an instance of * @return A non-null {@link ColorSpace} instance */
@NonNull public static ColorSpace get(@NonNull Named name) { return sNamedColorSpaces[name.ordinal()]; }

Returns a Named instance of ColorSpace that matches the specified RGB to CIE XYZ transform and transfer functions. If no instance can be found, this method returns null.

The color transform matrix is assumed to target the CIE XYZ space a D50 standard illuminant.

Params:
  • toXYZD50 – 3x3 column-major transform matrix from RGB to the profile connection space CIE XYZ as an array of 9 floats, cannot be null
  • function – Parameters for the transfer functions
Returns:A non-null ColorSpace if a match is found, null otherwise
/** * <p>Returns a {@link Named} instance of {@link ColorSpace} that matches * the specified RGB to CIE XYZ transform and transfer functions. If no * instance can be found, this method returns null.</p> * * <p>The color transform matrix is assumed to target the CIE XYZ space * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p> * * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile * connection space CIE XYZ as an array of 9 floats, cannot be null * @param function Parameters for the transfer functions * @return A non-null {@link ColorSpace} if a match is found, null otherwise */
@Nullable public static ColorSpace match( @NonNull @Size(9) float[] toXYZD50, @NonNull Rgb.TransferParameters function) { for (ColorSpace colorSpace : sNamedColorSpaces) { if (colorSpace.getModel() == Model.RGB) { ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ); if (compare(toXYZD50, rgb.mTransform) && compare(function, rgb.mTransferParameters)) { return colorSpace; } } } return null; }

Creates a new Renderer that can be used to visualize and debug color spaces. See the documentation of Renderer for more information.

See Also:
Returns:A new non-null Renderer instance
@hide
/** * <p>Creates a new {@link Renderer} that can be used to visualize and * debug color spaces. See the documentation of {@link Renderer} for * more information.</p> * * @return A new non-null {@link Renderer} instance * * @see Renderer * * @hide */
@NonNull public static Renderer createRenderer() { return new Renderer(); } static { sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb( "sRGB IEC61966-2.1", SRGB_PRIMARIES, ILLUMINANT_D65, new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), Named.SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( "sRGB IEC61966-2.1 (Linear)", SRGB_PRIMARIES, ILLUMINANT_D65, 1.0, 0.0f, 1.0f, Named.LINEAR_SRGB.ordinal() ); sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB-nl IEC 61966-2-2:2003", SRGB_PRIMARIES, ILLUMINANT_D65, x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), -0.799f, 2.399f, Named.EXTENDED_SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB IEC 61966-2-2:2003", SRGB_PRIMARIES, ILLUMINANT_D65, 1.0, -0.5f, 7.499f, Named.LINEAR_EXTENDED_SRGB.ordinal() ); sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.709-5", new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, ILLUMINANT_D65, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.BT709.ordinal() ); sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.2020-1", new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, ILLUMINANT_D65, new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), Named.BT2020.ordinal() ); sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( "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 }, 2.6, 0.0f, 1.0f, Named.DCI_P3.ordinal() ); sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4), Named.DISPLAY_P3.ordinal() ); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( "NTSC (1953)", NTSC_1953_PRIMARIES, ILLUMINANT_C, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.NTSC_1953.ordinal() ); sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( "SMPTE-C RGB", new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, ILLUMINANT_D65, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.SMPTE_C.ordinal() ); sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( "Adobe RGB (1998)", new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f }, ILLUMINANT_D65, 2.2, 0.0f, 1.0f, Named.ADOBE_RGB.ordinal() ); sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( "ROMM RGB ISO 22028-2:2013", new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, ILLUMINANT_D50, new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8), Named.PRO_PHOTO_RGB.ordinal() ); sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb( "SMPTE ST 2065-1:2012 ACES", new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f }, ILLUMINANT_D60, 1.0, -65504.0f, 65504.0f, Named.ACES.ordinal() ); sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb( "Academy S-2014-004 ACEScg", new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f }, ILLUMINANT_D60, 1.0, -65504.0f, 65504.0f, Named.ACESCG.ordinal() ); sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz( "Generic XYZ", Named.CIE_XYZ.ordinal() ); sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab( "Generic L*a*b*", Named.CIE_LAB.ordinal() ); } // Reciprocal piecewise gamma response private static double rcpResponse(double x, double a, double b, double c, double d, double g) { return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c; } // Piecewise gamma response private static double response(double x, double a, double b, double c, double d, double g) { return x >= d ? Math.pow(a * x + b, g) : c * x; } // Reciprocal piecewise gamma response private static double rcpResponse(double x, double a, double b, double c, double d, double e, double f, double g) { return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c; } // Piecewise gamma response private static double response(double x, double a, double b, double c, double d, double e, double f, double g) { return x >= d ? Math.pow(a * x + b, g) + e : c * x + f; } // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color // spaces that allow negative values @SuppressWarnings("SameParameterValue") private static double absRcpResponse(double x, double a, double b, double c, double d, double g) { return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x); } // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that // allow negative values @SuppressWarnings("SameParameterValue") private static double absResponse(double x, double a, double b, double c, double d, double g) { return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x); }
Compares two sets of parametric transfer functions parameters with a precision of 1e-3.
Params:
  • a – The first set of parameters to compare
  • b – The second set of parameters to compare
Returns:True if the two sets are equal, false otherwise
/** * Compares two sets of parametric transfer functions parameters with a precision of 1e-3. * * @param a The first set of parameters to compare * @param b The second set of parameters to compare * @return True if the two sets are equal, false otherwise */
private static boolean compare( @Nullable Rgb.TransferParameters a, @Nullable Rgb.TransferParameters b) { //noinspection SimplifiableIfStatement if (a == null && b == null) return true; return a != null && b != null && Math.abs(a.a - b.a) < 1e-3 && Math.abs(a.b - b.b) < 1e-3 && Math.abs(a.c - b.c) < 1e-3 && Math.abs(a.d - b.d) < 2e-3 && // Special case for variations in sRGB OETF/EOTF Math.abs(a.e - b.e) < 1e-3 && Math.abs(a.f - b.f) < 1e-3 && Math.abs(a.g - b.g) < 1e-3; }
Compares two arrays of float with a precision of 1e-3.
Params:
  • a – The first array to compare
  • b – The second array to compare
Returns:True if the two arrays are equal, false otherwise
/** * Compares two arrays of float with a precision of 1e-3. * * @param a The first array to compare * @param b The second array to compare * @return True if the two arrays are equal, false otherwise */
private static boolean compare(@NonNull float[] a, @NonNull float[] b) { if (a == b) return true; for (int i = 0; i < a.length; i++) { if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false; } return true; }
Inverts a 3x3 matrix. This method assumes the matrix is invertible.
Params:
  • m – A 3x3 matrix as a non-null array of 9 floats
Returns:A new array of 9 floats containing the inverse of the input matrix
/** * Inverts a 3x3 matrix. This method assumes the matrix is invertible. * * @param m A 3x3 matrix as a non-null array of 9 floats * @return A new array of 9 floats containing the inverse of the input matrix */
@NonNull @Size(9) private static float[] inverse3x3(@NonNull @Size(9) float[] m) { float a = m[0]; float b = m[3]; float c = m[6]; float d = m[1]; float e = m[4]; float f = m[7]; float g = m[2]; float h = m[5]; float i = m[8]; float A = e * i - f * h; float B = f * g - d * i; float C = d * h - e * g; float det = a * A + b * B + c * C; float inverted[] = new float[m.length]; inverted[0] = A / det; inverted[1] = B / det; inverted[2] = C / det; inverted[3] = (c * h - b * i) / det; inverted[4] = (a * i - c * g) / det; inverted[5] = (b * g - a * h) / det; inverted[6] = (b * f - c * e) / det; inverted[7] = (c * d - a * f) / det; inverted[8] = (a * e - b * d) / det; return inverted; }
Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
Params:
  • lhs – 3x3 matrix, as a non-null array of 9 floats
  • rhs – 3x3 matrix, as a non-null array of 9 floats
Returns:A new array of 9 floats containing the result of the multiplication of rhs by lhs
/** * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats. * * @param lhs 3x3 matrix, as a non-null array of 9 floats * @param rhs 3x3 matrix, as a non-null array of 9 floats * @return A new array of 9 floats containing the result of the multiplication * of rhs by lhs */
@NonNull @Size(9) private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { float[] r = new float[9]; r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2]; r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2]; r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2]; r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5]; r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5]; r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5]; r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8]; r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8]; r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8]; return r; }
Multiplies a vector of 3 components by a 3x3 matrix and stores the result in the input vector.
Params:
  • lhs – 3x3 matrix, as a non-null array of 9 floats
  • rhs – Vector of 3 components, as a non-null array of 3 floats
Returns:The array of 3 passed as the rhs parameter
/** * Multiplies a vector of 3 components by a 3x3 matrix and stores the * result in the input vector. * * @param lhs 3x3 matrix, as a non-null array of 9 floats * @param rhs Vector of 3 components, as a non-null array of 3 floats * @return The array of 3 passed as the rhs parameter */
@NonNull @Size(min = 3) private static float[] mul3x3Float3( @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) { float r0 = rhs[0]; float r1 = rhs[1]; float r2 = rhs[2]; rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2; rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2; rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2; return rhs; }
Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats, by a 3x3 matrix represented as an array of 9 floats.
Params:
  • lhs – Diagonal 3x3 matrix, as a non-null array of 3 floats
  • rhs – 3x3 matrix, as a non-null array of 9 floats
Returns:A new array of 9 floats containing the result of the multiplication of rhs by lhs
/** * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats, * by a 3x3 matrix represented as an array of 9 floats. * * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats * @param rhs 3x3 matrix, as a non-null array of 9 floats * @return A new array of 9 floats containing the result of the multiplication * of rhs by lhs */
@NonNull @Size(9) private static float[] mul3x3Diag( @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) { return new float[] { lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2], lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5], lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8] }; }
Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the input xyY array only contains the x and y components.
Params:
  • xyY – The xyY value to convert to XYZ, cannot be null, length must be 2
Returns:A new float array of length 3 containing XYZ values
/** * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the * input xyY array only contains the x and y components. * * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2 * @return A new float array of length 3 containing XYZ values */
@NonNull @Size(3) private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) { return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] }; }
Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the input xyY array only contains the x and y components. After this method returns, the xyY array contains the converted u and v components.
Params:
  • xyY – The xyY value to convert to XYZ, cannot be null, length must be a multiple of 2
/** * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the * input xyY array only contains the x and y components. After this method * returns, the xyY array contains the converted u and v components. * * @param xyY The xyY value to convert to XYZ, cannot be null, * length must be a multiple of 2 */
private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) { for (int i = 0; i < xyY.length; i += 2) { float x = xyY[i]; float y = xyY[i + 1]; float d = -2.0f * x + 12.0f * y + 3; float u = (4.0f * x) / d; float v = (9.0f * y) / d; xyY[i] = u; xyY[i + 1] = v; } }

Computes the chromatic adaptation transform from the specified source white point to the specified destination white point.

The transform is computed using the von Kries method, described in more details in the documentation of Adaptation. The Adaptation enum provides different matrices that can be used to perform the adaptation.

Params:
  • matrix – The adaptation matrix
  • srcWhitePoint – The white point to adapt from, *will be modified*
  • dstWhitePoint – The white point to adapt to, *will be modified*
Returns:A 3x3 matrix as a non-null array of 9 floats
/** * <p>Computes the chromatic adaptation transform from the specified * source white point to the specified destination white point.</p> * * <p>The transform is computed using the von Kries method, described * in more details in the documentation of {@link Adaptation}. The * {@link Adaptation} enum provides different matrices that can be * used to perform the adaptation.</p> * * @param matrix The adaptation matrix * @param srcWhitePoint The white point to adapt from, *will be modified* * @param dstWhitePoint The white point to adapt to, *will be modified* * @return A 3x3 matrix as a non-null array of 9 floats */
@NonNull @Size(9) private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix, @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) { float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint); float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint); // LMS is a diagonal matrix stored as a float[3] float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] }; return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix)); }
Implementation of the CIE XYZ color space. Assumes the white point is D50.
/** * 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); } @Override public boolean isWideGamut() { return true; } @Override public float getMinValue(@IntRange(from = 0, to = 3) int component) { return -2.0f; } @Override public float getMaxValue(@IntRange(from = 0, to = 3) int component) { return 2.0f; } @Override public float[] toXyz(@NonNull @Size(min = 3) float[] v) { v[0] = clamp(v[0]); v[1] = clamp(v[1]); v[2] = clamp(v[2]); return v; } @Override public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { v[0] = clamp(v[0]); v[1] = clamp(v[1]); v[2] = clamp(v[2]); return v; } private static float clamp(float x) { return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x; } }
Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ with a white point of D50.
/** * 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; private static final float C = 4.0f / 29.0f; private static final float D = 6.0f / 29.0f; private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { super(name, Model.LAB, id); } @Override public boolean isWideGamut() { return true; } @Override public float getMinValue(@IntRange(from = 0, to = 3) int component) { return component == 0 ? 0.0f : -128.0f; } @Override public float getMaxValue(@IntRange(from = 0, to = 3) int component) { return component == 0 ? 100.0f : 128.0f; } @Override public float[] toXyz(@NonNull @Size(min = 3) float[] v) { v[0] = clamp(v[0], 0.0f, 100.0f); v[1] = clamp(v[1], -128.0f, 128.0f); v[2] = clamp(v[2], -128.0f, 128.0f); float fy = (v[0] + 16.0f) / 116.0f; float fx = fy + (v[1] * 0.002f); float fz = fy - (v[2] * 0.005f); float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C); float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C); float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C); v[0] = X * ILLUMINANT_D50_XYZ[0]; v[1] = Y * ILLUMINANT_D50_XYZ[1]; v[2] = Z * ILLUMINANT_D50_XYZ[2]; return v; } @Override public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { float X = v[0] / ILLUMINANT_D50_XYZ[0]; float Y = v[1] / ILLUMINANT_D50_XYZ[1]; float Z = v[2] / ILLUMINANT_D50_XYZ[2]; float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C; float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C; float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C; float L = 116.0f * fy - 16.0f; float a = 500.0f * (fx - fy); float b = 200.0f * (fy - fz); v[0] = clamp(L, 0.0f, 100.0f); v[1] = clamp(a, -128.0f, 128.0f); v[2] = clamp(b, -128.0f, 128.0f); return v; } private static float clamp(float x, float min, float max) { return x < min ? min : x > max ? max : x; } }
{@usesMathJax}

An RGB color space is an additive color space using the RGB color model (a color is therefore represented by a tuple of 3 numbers).

A specific RGB color space is defined by the following properties:

  • Three chromaticities of the red, green and blue primaries, which define the gamut of the color space.
  • A white point chromaticity that defines the stimulus to which color space values are normalized (also just called "white").
  • An opto-electronic transfer function, also called opto-electronic conversion function or often, and approximately, gamma function.
  • An electro-optical transfer function, also called electo-optical conversion function or often, and approximately, gamma function.
  • A range of valid RGB values (most commonly \([0..1]\)).

The most commonly used RGB color space is sRGB.

Primaries and white point chromaticities

In this implementation, the chromaticity of the primaries and the white point of an RGB color space is defined in the CIE xyY color space. This color space separates the chromaticity of a color, the x and y components, and its luminance, the Y component. Since the primaries and the white point have full brightness, the Y component is assumed to be 1 and only the x and y components are needed to encode them.

For convenience, this implementation also allows to define the primaries and white point in the CIE XYZ space. The tristimulus XYZ values are internally converted to xyY.

sRGB primaries and white point

Transfer functions

A transfer function is a color component conversion function, defined as a single variable, monotonic mathematical function. It is applied to each individual component of a color. They are used to perform the mapping between linear tristimulus values and non-linear electronic signal value.

The opto-electronic transfer function (OETF or OECF) encodes tristimulus values in a scene to a non-linear electronic signal value. An OETF is often expressed as a power function with an exponent between 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).

The electro-optical transfer function (EOTF or EOCF) decodes a non-linear electronic signal value to a tristimulus value at the display. An EOTF is often expressed as a power function with an exponent between 1.8 and 2.6.

Transfer functions are used as a compression scheme. For instance, linear sRGB values would normally require 11 to 12 bits of precision to store all values that can be perceived by the human eye. When encoding sRGB values using the appropriate OETF (see sRGB for an exact mathematical description of that OETF), the values can be compressed to only 8 bits precision.

When manipulating RGB values, particularly sRGB values, it is safe to assume that these values have been encoded with the appropriate OETF (unless noted otherwise). Encoded values are often said to be in "gamma space". They are therefore defined in a non-linear space. This in turns means that any linear operation applied to these values is going to yield mathematically incorrect results (any linear interpolation such as gradient generation for instance, most image processing functions such as blurs, etc.).

To properly process encoded RGB values you must first apply the EOTF to decode the value into linear space. After processing, the RGB value must be encoded back to non-linear ("gamma") space. Here is a formal description of the process, where \(f\) is the processing function to apply:

$$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$

If the transfer functions of the color space can be expressed as an ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters can be retrieved by calling getTransferParameters(). This can be useful to match color spaces for instance.

Some RGB color spaces, such as Named.ACES and scRGB, are said to be linear because their transfer functions are the identity function: \(f(x) = x\). If the source and/or destination are known to be linear, it is not necessary to invoke the transfer functions.

Range

Most RGB color spaces allow RGB values in the range \([0..1]\). There are however a few RGB color spaces that allow much larger ranges. For instance, scRGB is used to manipulate the range \([-0.5..7.5]\) while ACES can be used throughout the range \([-65504, 65504]\).

Extended sRGB and its large range

Converting between RGB color spaces

Conversion between two color spaces is achieved by using an intermediate color space called the profile connection space (PCS). The PCS used by this implementation is CIE XYZ. The conversion operation is defined as such:

$$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$

Where \(T_{src}\) is the RGB to XYZ transform of the source color space and \(T_{dst}^{-1}\) the XYZ to RGB transform of the destination color space.

Many RGB color spaces commonly used with electronic devices use the standard illuminant D65. Care must be take however when converting between two RGB color spaces if their white points do not match. This can be achieved by either calling ColorSpace.adapt(ColorSpace, float[]) to adapt one or both color spaces to a single common white point. This can be achieved automatically by calling ColorSpace.connect(ColorSpace, ColorSpace), which also handles non-RGB color spaces.

To learn more about the white point adaptation process, refer to the documentation of Adaptation.

/** * {@usesMathJax} * * <p>An RGB color space is an additive color space using the * {@link Model#RGB RGB} color model (a color is therefore represented * by a tuple of 3 numbers).</p> * * <p>A specific RGB color space is defined by the following properties:</p> * <ul> * <li>Three chromaticities of the red, green and blue primaries, which * define the gamut of the color space.</li> * <li>A white point chromaticity that defines the stimulus to which * color space values are normalized (also just called "white").</li> * <li>An opto-electronic transfer function, also called opto-electronic * conversion function or often, and approximately, gamma function.</li> * <li>An electro-optical transfer function, also called electo-optical * conversion function or often, and approximately, gamma function.</li> * <li>A range of valid RGB values (most commonly \([0..1]\)).</li> * </ul> * * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p> * * <h3>Primaries and white point chromaticities</h3> * <p>In this implementation, the chromaticity of the primaries and the white * point of an RGB color space is defined in the CIE xyY color space. This * color space separates the chromaticity of a color, the x and y components, * and its luminance, the Y component. Since the primaries and the white * point have full brightness, the Y component is assumed to be 1 and only * the x and y components are needed to encode them.</p> * <p>For convenience, this implementation also allows to define the * primaries and white point in the CIE XYZ space. The tristimulus XYZ values * are internally converted to xyY.</p> * * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> * <figcaption style="text-align: center;">sRGB primaries and white point</figcaption> * </p> * * <h3>Transfer functions</h3> * <p>A transfer function is a color component conversion function, defined as * a single variable, monotonic mathematical function. It is applied to each * individual component of a color. They are used to perform the mapping * between linear tristimulus values and non-linear electronic signal value.</p> * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes * tristimulus values in a scene to a non-linear electronic signal value. * An OETF is often expressed as a power function with an exponent between * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p> * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes * a non-linear electronic signal value to a tristimulus value at the display. * An EOTF is often expressed as a power function with an exponent between * 1.8 and 2.6.</p> * <p>Transfer functions are used as a compression scheme. For instance, * linear sRGB values would normally require 11 to 12 bits of precision to * store all values that can be perceived by the human eye. When encoding * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for * an exact mathematical description of that OETF), the values can be * compressed to only 8 bits precision.</p> * <p>When manipulating RGB values, particularly sRGB values, it is safe * to assume that these values have been encoded with the appropriate * OETF (unless noted otherwise). Encoded values are often said to be in * "gamma space". They are therefore defined in a non-linear space. This * in turns means that any linear operation applied to these values is * going to yield mathematically incorrect results (any linear interpolation * such as gradient generation for instance, most image processing functions * such as blurs, etc.).</p> * <p>To properly process encoded RGB values you must first apply the * EOTF to decode the value into linear space. After processing, the RGB * value must be encoded back to non-linear ("gamma") space. Here is a * formal description of the process, where \(f\) is the processing * function to apply:</p> * * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$ * * <p>If the transfer functions of the color space can be expressed as an * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters * can be retrieved by calling {@link #getTransferParameters()}. This can * be useful to match color spaces for instance.</p> * * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because * their transfer functions are the identity function: \(f(x) = x\). * If the source and/or destination are known to be linear, it is not * necessary to invoke the transfer functions.</p> * * <h3>Range</h3> * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There * are however a few RGB color spaces that allow much larger ranges. For * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout * the range \([-65504, 65504]\).</p> * * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> * <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption> * </p> * * <h3>Converting between RGB color spaces</h3> * <p>Conversion between two color spaces is achieved by using an intermediate * color space called the profile connection space (PCS). The PCS used by * this implementation is CIE XYZ. The conversion operation is defined * as such:</p> * * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$ * * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform} * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform() * XYZ to RGB transform} of the destination color space.</p> * <p>Many RGB color spaces commonly used with electronic devices use the * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however * when converting between two RGB color spaces if their white points do not * match. This can be achieved by either calling * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to * a single common white point. This can be achieved automatically by calling * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles * non-RGB color spaces.</p> * <p>To learn more about the white point adaptation process, refer to the * documentation of {@link Adaptation}.</p> */
@AnyThread public static class Rgb extends ColorSpace {
{@usesMathJax}

Defines the parameters for the ICC parametric curve type 4, as defined in ICC.1:2004-10, section 10.15.

The EOTF is of the form:

\(\begin{equation} Y = \begin{cases}c X + f & X \lt d \\ \left( a X + b \right) ^{g} + e & X \ge d \end{cases} \end{equation}\)

The corresponding OETF is simply the inverse function.

The parameters defined by this class form a valid transfer function only if all the following conditions are met:

  • No parameter is a Not-a-Number
  • \(d\) is in the range \([0..1]\)
  • The function is not constant
  • The function is positive and increasing
/** * {@usesMathJax} * * <p>Defines the parameters for the ICC parametric curve type 4, as * defined in ICC.1:2004-10, section 10.15.</p> * * <p>The EOTF is of the form:</p> * * \(\begin{equation} * Y = \begin{cases}c X + f & X \lt d \\ * \left( a X + b \right) ^{g} + e & X \ge d \end{cases} * \end{equation}\) * * <p>The corresponding OETF is simply the inverse function.</p> * * <p>The parameters defined by this class form a valid transfer * function only if all the following conditions are met:</p> * <ul> * <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li> * <li>\(d\) is in the range \([0..1]\)</li> * <li>The function is not constant</li> * <li>The function is positive and increasing</li> * </ul> */
public static class TransferParameters {
Variable \(a\) in the equation of the EOTF described above.
/** Variable \(a\) in the equation of the EOTF described above. */
public final double a;
Variable \(b\) in the equation of the EOTF described above.
/** Variable \(b\) in the equation of the EOTF described above. */
public final double b;
Variable \(c\) in the equation of the EOTF described above.
/** Variable \(c\) in the equation of the EOTF described above. */
public final double c;
Variable \(d\) in the equation of the EOTF described above.
/** Variable \(d\) in the equation of the EOTF described above. */
public final double d;
Variable \(e\) in the equation of the EOTF described above.
/** Variable \(e\) in the equation of the EOTF described above. */
public final double e;
Variable \(f\) in the equation of the EOTF described above.
/** Variable \(f\) in the equation of the EOTF described above. */
public final double f;
Variable \(g\) in the equation of the EOTF described above.
/** Variable \(g\) in the equation of the EOTF described above. */
public final double g;

Defines the parameters for the ICC parametric curve type 3, as defined in ICC.1:2004-10, section 10.15.

The EOTF is of the form:

\(\begin{equation} Y = \begin{cases}c X & X \lt d \\ \left( a X + b \right) ^{g} & X \ge d \end{cases} \end{equation}\)

This constructor is equivalent to setting \(e\) and \(f\) to 0.

Params:
  • a – The value of \(a\) in the equation of the EOTF described above
  • b – The value of \(b\) in the equation of the EOTF described above
  • c – The value of \(c\) in the equation of the EOTF described above
  • d – The value of \(d\) in the equation of the EOTF described above
  • g – The value of \(g\) in the equation of the EOTF described above
Throws:
/** * <p>Defines the parameters for the ICC parametric curve type 3, as * defined in ICC.1:2004-10, section 10.15.</p> * * <p>The EOTF is of the form:</p> * * \(\begin{equation} * Y = \begin{cases}c X & X \lt d \\ * \left( a X + b \right) ^{g} & X \ge d \end{cases} * \end{equation}\) * * <p>This constructor is equivalent to setting \(e\) and \(f\) to 0.</p> * * @param a The value of \(a\) in the equation of the EOTF described above * @param b The value of \(b\) in the equation of the EOTF described above * @param c The value of \(c\) in the equation of the EOTF described above * @param d The value of \(d\) in the equation of the EOTF described above * @param g The value of \(g\) in the equation of the EOTF described above * * @throws IllegalArgumentException If the parameters form an invalid transfer function */
public TransferParameters(double a, double b, double c, double d, double g) { this(a, b, c, d, 0.0, 0.0, g); }

Defines the parameters for the ICC parametric curve type 4, as defined in ICC.1:2004-10, section 10.15.

Params:
  • a – The value of \(a\) in the equation of the EOTF described above
  • b – The value of \(b\) in the equation of the EOTF described above
  • c – The value of \(c\) in the equation of the EOTF described above
  • d – The value of \(d\) in the equation of the EOTF described above
  • e – The value of \(e\) in the equation of the EOTF described above
  • f – The value of \(f\) in the equation of the EOTF described above
  • g – The value of \(g\) in the equation of the EOTF described above
Throws:
/** * <p>Defines the parameters for the ICC parametric curve type 4, as * defined in ICC.1:2004-10, section 10.15.</p> * * @param a The value of \(a\) in the equation of the EOTF described above * @param b The value of \(b\) in the equation of the EOTF described above * @param c The value of \(c\) in the equation of the EOTF described above * @param d The value of \(d\) in the equation of the EOTF described above * @param e The value of \(e\) in the equation of the EOTF described above * @param f The value of \(f\) in the equation of the EOTF described above * @param g The value of \(g\) in the equation of the EOTF described above * * @throws IllegalArgumentException If the parameters form an invalid transfer function */
public TransferParameters(double a, double b, double c, double d, double e, double f, double g) { if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) || Double.isNaN(g)) { throw new IllegalArgumentException("Parameters cannot be NaN"); } // Next representable float after 1.0 // We use doubles here but the representation inside our native code is often floats if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) { throw new IllegalArgumentException("Parameter d must be in the range [0..1], " + "was " + d); } if (d == 0.0 && (a == 0.0 || g == 0.0)) { throw new IllegalArgumentException( "Parameter a or g is zero, the transfer function is constant"); } if (d >= 1.0 && c == 0.0) { throw new IllegalArgumentException( "Parameter c is zero, the transfer function is constant"); } if ((a == 0.0 || g == 0.0) && c == 0.0) { throw new IllegalArgumentException("Parameter a or g is zero," + " and c is zero, the transfer function is constant"); } if (c < 0.0) { throw new IllegalArgumentException("The transfer function must be increasing"); } if (a < 0.0 || g < 0.0) { throw new IllegalArgumentException("The transfer function must be " + "positive or increasing"); } this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; this.g = g; } @SuppressWarnings("SimplifiableIfStatement") @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransferParameters that = (TransferParameters) o; if (Double.compare(that.a, a) != 0) return false; if (Double.compare(that.b, b) != 0) return false; if (Double.compare(that.c, c) != 0) return false; if (Double.compare(that.d, d) != 0) return false; if (Double.compare(that.e, e) != 0) return false; if (Double.compare(that.f, f) != 0) return false; return Double.compare(that.g, g) == 0; } @Override public int hashCode() { int result; long temp; temp = Double.doubleToLongBits(a); result = (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(b); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(c); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(d); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(e); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(f); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(g); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } @NonNull private final float[] mWhitePoint; @NonNull private final float[] mPrimaries; @NonNull private final float[] mTransform; @NonNull private final float[] mInverseTransform; @NonNull private final DoubleUnaryOperator mOetf; @NonNull private final DoubleUnaryOperator mEotf; @NonNull private final DoubleUnaryOperator mClampedOetf; @NonNull private final DoubleUnaryOperator mClampedEotf; private final float mMin; private final float mMax; private final boolean mIsWideGamut; private final boolean mIsSrgb; @Nullable private TransferParameters mTransferParameters;

Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix must convert from the RGB space to the profile connection space CIE XYZ.

The range of the color space is imposed to be \([0..1]\).

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • toXYZ – 3x3 column-major transform matrix from RGB to the profile connection space CIE XYZ as an array of 9 floats, cannot be null
  • oetf – Opto-electronic transfer function, cannot be null
  • eotf – Electro-optical transfer function, cannot be null
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The OETF is null or the EOTF is null.
    • The minimum valid value is >= the maximum valid value.
See Also:
/** * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. * The transform matrix must convert from the RGB space to the profile connection * space CIE XYZ.</p> * * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile * connection space CIE XYZ as an array of 9 floats, cannot be null * @param oetf Opto-electronic transfer function, cannot be null * @param eotf Electro-optical transfer function, cannot be null * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The OETF is null or the EOTF is null.</li> * <li>The minimum valid value is >= the maximum valid value.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf) { this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), oetf, eotf, 0.0f, 1.0f, MIN_ID); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

The ID, areturned by ColorSpace.getId(), of an object created by this constructor is always ColorSpace.MIN_ID.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • oetf – Opto-electronic transfer function, cannot be null
  • eotf – Electro-optical transfer function, cannot be null
  • min – The minimum valid value in this color space's RGB range
  • max – The maximum valid value in this color space's RGB range
Throws:
  • IllegalArgumentException

    If any of the following conditions is met:

    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • The OETF is null or the EOTF is null.
    • The minimum valid value is >= the maximum valid value.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * <p class="note">The ID, areturned by {@link #getId()}, of an object created by * this constructor is always {@link #MIN_ID}.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param oetf Opto-electronic transfer function, cannot be null * @param eotf Electro-optical transfer function, cannot be null * @param min The minimum valid value in this color space's RGB range * @param max The maximum valid value in this color space's RGB range * * @throws IllegalArgumentException <p>If any of the following conditions is met:</p> * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>The OETF is null or the EOTF is null.</li> * <li>The minimum valid value is >= the maximum valid value.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max) { this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID); }

Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix must convert from the RGB space to the profile connection space CIE XYZ.

The range of the color space is imposed to be \([0..1]\).

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • toXYZ – 3x3 column-major transform matrix from RGB to the profile connection space CIE XYZ as an array of 9 floats, cannot be null
  • function – Parameters for the transfer functions
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • Gamma is negative.
See Also:
/** * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. * The transform matrix must convert from the RGB space to the profile connection * space CIE XYZ.</p> * * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile * connection space CIE XYZ as an array of 9 floats, cannot be null * @param function Parameters for the transfer functions * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>Gamma is negative.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull TransferParameters function) { this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • function – Parameters for the transfer functions
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • The transfer parameters are invalid.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param function Parameters for the transfer functions * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>The transfer parameters are invalid.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function) { this(name, primaries, whitePoint, function, MIN_ID); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • function – Parameters for the transfer functions
  • id – ID of this color space as an integer between ColorSpace.MIN_ID and ColorSpace.MAX_ID
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • The ID is not between ColorSpace.MIN_ID and ColorSpace.MAX_ID.
    • The transfer parameters are invalid.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param function Parameters for the transfer functions * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> * <li>The transfer parameters are invalid.</li> * </ul> * * @see #get(Named) */
private Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id) { this(name, primaries, whitePoint, function.e == 0.0 && function.f == 0.0 ? x -> rcpResponse(x, function.a, function.b, function.c, function.d, function.g) : x -> rcpResponse(x, function.a, function.b, function.c, function.d, function.e, function.f, function.g), function.e == 0.0 && function.f == 0.0 ? x -> response(x, function.a, function.b, function.c, function.d, function.g) : x -> response(x, function.a, function.b, function.c, function.d, function.e, function.f, function.g), 0.0f, 1.0f, id); mTransferParameters = function; }

Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix must convert from the RGB space to the profile connection space CIE XYZ.

The range of the color space is imposed to be \([0..1]\).

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • toXYZ – 3x3 column-major transform matrix from RGB to the profile connection space CIE XYZ as an array of 9 floats, cannot be null
  • gamma – Gamma to use as the transfer function
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • Gamma is negative.
See Also:
/** * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. * The transform matrix must convert from the RGB space to the profile connection * space CIE XYZ.</p> * * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile * connection space CIE XYZ as an array of 9 floats, cannot be null * @param gamma Gamma to use as the transfer function * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>Gamma is negative.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(9) float[] toXYZ, double gamma) { this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • gamma – Gamma to use as the transfer function
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • Gamma is negative.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param gamma Gamma to use as the transfer function * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>Gamma is negative.</li> * </ul> * * @see #get(Named) */
public Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, double gamma) { this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • gamma – Gamma to use as the transfer function
  • min – The minimum valid value in this color space's RGB range
  • max – The maximum valid value in this color space's RGB range
  • id – ID of this color space as an integer between ColorSpace.MIN_ID and ColorSpace.MAX_ID
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • The minimum valid value is >= the maximum valid value.
    • The ID is not between ColorSpace.MIN_ID and ColorSpace.MAX_ID.
    • Gamma is negative.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param gamma Gamma to use as the transfer function * @param min The minimum valid value in this color space's RGB range * @param max The maximum valid value in this color space's RGB range * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>The minimum valid value is >= the maximum valid value.</li> * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> * <li>Gamma is negative.</li> * </ul> * * @see #get(Named) */
private Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, double gamma, float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { this(name, primaries, whitePoint, gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma), gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, gamma), min, max, id); mTransferParameters = gamma == 1.0 ? new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0f), gamma) : new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma); }

Creates a new RGB color space using a specified set of primaries and a specified white point.

The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of the arrays depends on the chosen space:

SpacePrimaries lengthWhite point length
xyY62
XYZ93

When the primaries and/or white point are specified in xyY, the Y component does not need to be specified and is assumed to be 1.0. Only the xy components are required.

Params:
  • name – Name of the color space, cannot be null, its length must be >= 1
  • primaries – RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
  • whitePoint – Reference white as an array of 2 (xy) or 3 (XYZ) floats
  • oetf – Opto-electronic transfer function, cannot be null
  • eotf – Electro-optical transfer function, cannot be null
  • min – The minimum valid value in this color space's RGB range
  • max – The maximum valid value in this color space's RGB range
  • id – ID of this color space as an integer between ColorSpace.MIN_ID and ColorSpace.MAX_ID
Throws:
  • IllegalArgumentException – If any of the following conditions is met:
    • The name is null or has a length of 0.
    • The primaries array is null or has a length that is neither 6 or 9.
    • The white point array is null or has a length that is neither 2 or 3.
    • The OETF is null or the EOTF is null.
    • The minimum valid value is >= the maximum valid value.
    • The ID is not between ColorSpace.MIN_ID and ColorSpace.MAX_ID.
See Also:
/** * <p>Creates a new RGB color space using a specified set of primaries * and a specified white point.</p> * * <p>The primaries and white point can be specified in the CIE xyY space * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> * * <table summary="Parameters length"> * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> * <tr><td>xyY</td><td>6</td><td>2</td></tr> * <tr><td>XYZ</td><td>9</td><td>3</td></tr> * </table> * * <p>When the primaries and/or white point are specified in xyY, the Y component * does not need to be specified and is assumed to be 1.0. Only the xy components * are required.</p> * * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats * @param oetf Opto-electronic transfer function, cannot be null * @param eotf Electro-optical transfer function, cannot be null * @param min The minimum valid value in this color space's RGB range * @param max The maximum valid value in this color space's RGB range * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * * @throws IllegalArgumentException If any of the following conditions is met: * <ul> * <li>The name is null or has a length of 0.</li> * <li>The primaries array is null or has a length that is neither 6 or 9.</li> * <li>The white point array is null or has a length that is neither 2 or 3.</li> * <li>The OETF is null or the EOTF is null.</li> * <li>The minimum valid value is >= the maximum valid value.</li> * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> * </ul> * * @see #get(Named) */
private Rgb( @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { super(name, Model.RGB, id); if (primaries == null || (primaries.length != 6 && primaries.length != 9)) { throw new IllegalArgumentException("The color space's primaries must be " + "defined as an array of 6 floats in xyY or 9 floats in XYZ"); } if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) { throw new IllegalArgumentException("The color space's white point must be " + "defined as an array of 2 floats in xyY or 3 float in XYZ"); } if (oetf == null || eotf == null) { throw new IllegalArgumentException("The transfer functions of a color space " + "cannot be null"); } if (min >= max) { throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max + "; min must be strictly < max"); } mWhitePoint = xyWhitePoint(whitePoint); mPrimaries = xyPrimaries(primaries); mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); mInverseTransform = inverse3x3(mTransform); mOetf = oetf; mEotf = eotf; mMin = min; mMax = max; DoubleUnaryOperator clamp = this::clamp; mClampedOetf = oetf.andThen(clamp); mClampedEotf = clamp.andThen(eotf); // A color space is wide-gamut if its area is >90% of NTSC 1953 and // if it entirely contains the Color space definition in xyY mIsWideGamut = isWideGamut(mPrimaries, min, max); mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); }
Creates a copy of the specified color space with a new transform.
Params:
  • colorSpace – The color space to create a copy of
/** * Creates a copy of the specified color space with a new transform. * * @param colorSpace The color space to create a copy of */
private Rgb(Rgb colorSpace, @NonNull @Size(9) float[] transform, @NonNull @Size(min = 2, max = 3) float[] whitePoint) { super(colorSpace.getName(), Model.RGB, -1); mWhitePoint = xyWhitePoint(whitePoint); mPrimaries = colorSpace.mPrimaries; mTransform = transform; mInverseTransform = inverse3x3(transform); mMin = colorSpace.mMin; mMax = colorSpace.mMax; mOetf = colorSpace.mOetf; mEotf = colorSpace.mEotf; mClampedOetf = colorSpace.mClampedOetf; mClampedEotf = colorSpace.mClampedEotf; mIsWideGamut = colorSpace.mIsWideGamut; mIsSrgb = colorSpace.mIsSrgb; mTransferParameters = colorSpace.mTransferParameters; }
Copies the non-adapted CIE xyY white point of this color space in specified array. The Y component is assumed to be 1 and is therefore not copied into the destination. The x and y components are written in the array at positions 0 and 1 respectively.
Params:
  • whitePoint – The destination array, cannot be null, its length must be >= 2
See Also:
Returns:The destination array passed as a parameter
/** * Copies the non-adapted CIE xyY white point of this color space in * specified array. The Y component is assumed to be 1 and is therefore * not copied into the destination. The x and y components are written * in the array at positions 0 and 1 respectively. * * @param whitePoint The destination array, cannot be null, its length * must be >= 2 * * @return The destination array passed as a parameter * * @see #getWhitePoint(float[]) */
@NonNull @Size(min = 2) public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) { whitePoint[0] = mWhitePoint[0]; whitePoint[1] = mWhitePoint[1]; return whitePoint; }
Returns the non-adapted CIE xyY white point of this color space as a new array of 2 floats. The Y component is assumed to be 1 and is therefore not copied into the destination. The x and y components are written in the array at positions 0 and 1 respectively.
See Also:
Returns:A new non-null array of 2 floats
/** * Returns the non-adapted CIE xyY white point of this color space as * a new array of 2 floats. The Y component is assumed to be 1 and is * therefore not copied into the destination. The x and y components * are written in the array at positions 0 and 1 respectively. * * @return A new non-null array of 2 floats * * @see #getWhitePoint() */
@NonNull @Size(2) public float[] getWhitePoint() { return Arrays.copyOf(mWhitePoint, mWhitePoint.length); }
Copies the primaries of this color space in specified array. The Y component is assumed to be 1 and is therefore not copied into the destination. The x and y components of the first primary are written in the array at positions 0 and 1 respectively.
Params:
  • primaries – The destination array, cannot be null, its length must be >= 6
See Also:
Returns:The destination array passed as a parameter
/** * Copies the primaries of this color space in specified array. The Y * component is assumed to be 1 and is therefore not copied into the * destination. The x and y components of the first primary are written * in the array at positions 0 and 1 respectively. * * @param primaries The destination array, cannot be null, its length * must be >= 6 * * @return The destination array passed as a parameter * * @see #getPrimaries(float[]) */
@NonNull @Size(min = 6) public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) { System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length); return primaries; }
Returns the primaries of this color space as a new array of 6 floats. The Y component is assumed to be 1 and is therefore not copied into the destination. The x and y components of the first primary are written in the array at positions 0 and 1 respectively.
See Also:
Returns:A new non-null array of 2 floats
/** * Returns the primaries of this color space as a new array of 6 floats. * The Y component is assumed to be 1 and is therefore not copied into * the destination. The x and y components of the first primary are * written in the array at positions 0 and 1 respectively. * * @return A new non-null array of 2 floats * * @see #getWhitePoint() */
@NonNull @Size(6) public float[] getPrimaries() { return Arrays.copyOf(mPrimaries, mPrimaries.length); }

Copies the transform of this color space in specified array. The transform is used to convert from RGB to XYZ (with the same white point as this color space). To connect color spaces, you must first adapt them to the same white point.

It is recommended to use ColorSpace.connect(ColorSpace, ColorSpace) to convert between color spaces.

Params:
  • transform – The destination array, cannot be null, its length must be >= 9
See Also:
Returns:The destination array passed as a parameter
/** * <p>Copies the transform of this color space in specified array. The * transform is used to convert from RGB to XYZ (with the same white * point as this color space). To connect color spaces, you must first * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the * same white point.</p> * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} * to convert between color spaces.</p> * * @param transform The destination array, cannot be null, its length * must be >= 9 * * @return The destination array passed as a parameter * * @see #getInverseTransform() */
@NonNull @Size(min = 9) public float[] getTransform(@NonNull @Size(min = 9) float[] transform) { System.arraycopy(mTransform, 0, transform, 0, mTransform.length); return transform; }

Returns the transform of this color space as a new array. The transform is used to convert from RGB to XYZ (with the same white point as this color space). To connect color spaces, you must first adapt them to the same white point.

It is recommended to use ColorSpace.connect(ColorSpace, ColorSpace) to convert between color spaces.

See Also:
Returns:A new array of 9 floats
/** * <p>Returns the transform of this color space as a new array. The * transform is used to convert from RGB to XYZ (with the same white * point as this color space). To connect color spaces, you must first * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the * same white point.</p> * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} * to convert between color spaces.</p> * * @return A new array of 9 floats * * @see #getInverseTransform(float[]) */
@NonNull @Size(9) public float[] getTransform() { return Arrays.copyOf(mTransform, mTransform.length); }

Copies the inverse transform of this color space in specified array. The inverse transform is used to convert from XYZ to RGB (with the same white point as this color space). To connect color spaces, you must first adapt them to the same white point.

It is recommended to use ColorSpace.connect(ColorSpace, ColorSpace) to convert between color spaces.

Params:
  • inverseTransform – The destination array, cannot be null, its length must be >= 9
See Also:
Returns:The destination array passed as a parameter
/** * <p>Copies the inverse transform of this color space in specified array. * The inverse transform is used to convert from XYZ to RGB (with the * same white point as this color space). To connect color spaces, you * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them * to the same white point.</p> * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} * to convert between color spaces.</p> * * @param inverseTransform The destination array, cannot be null, its length * must be >= 9 * * @return The destination array passed as a parameter * * @see #getTransform() */
@NonNull @Size(min = 9) public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) { System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length); return inverseTransform; }

Returns the inverse transform of this color space as a new array. The inverse transform is used to convert from XYZ to RGB (with the same white point as this color space). To connect color spaces, you must first adapt them to the same white point.

It is recommended to use ColorSpace.connect(ColorSpace, ColorSpace) to convert between color spaces.

See Also:
Returns:A new array of 9 floats
/** * <p>Returns the inverse transform of this color space as a new array. * The inverse transform is used to convert from XYZ to RGB (with the * same white point as this color space). To connect color spaces, you * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them * to the same white point.</p> * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} * to convert between color spaces.</p> * * @return A new array of 9 floats * * @see #getTransform(float[]) */
@NonNull @Size(9) public float[] getInverseTransform() { return Arrays.copyOf(mInverseTransform, mInverseTransform.length); }

Returns the opto-electronic transfer function (OETF) of this color space. The inverse function is the electro-optical transfer function (EOTF) returned by getEotf(). These functions are defined to satisfy the following equality for \(x \in [0..1]\):

$$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$

For RGB colors, this function can be used to convert from linear space 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 sRGB OETF uses \(\gamma=2.2\) for instance).

See Also:
Returns:A transfer function that converts from linear space to "gamma space"
/** * <p>Returns the opto-electronic transfer function (OETF) of this color space. * The inverse function is the electro-optical transfer function (EOTF) returned * by {@link #getEotf()}. These functions are defined to satisfy the following * equality for \(x \in [0..1]\):</p> * * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ * * <p>For RGB colors, this function can be used to convert from linear space * 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} OETF uses \(\gamma=2.2\) * for instance).</p> * * @return A transfer function that converts from linear space to "gamma space" * * @see #getEotf() * @see #getTransferParameters() */
@NonNull public DoubleUnaryOperator getOetf() { return mClampedOetf; }

Returns the electro-optical transfer function (EOTF) of this color space. The inverse function is the opto-electronic transfer function (OETF) returned by getOetf(). These functions are defined to satisfy the following equality for \(x \in [0..1]\):

$$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$

For RGB colors, this function can be used to convert from "gamma space" (gamma encoded) to linear space. The terms gamma space and gamma encoded are frequently used because many EOTFs can be closely approximated using a simple power function of the form \(x^\gamma\) (the approximation of the sRGB EOTF uses \(\gamma=2.2\) for instance).

See Also:
Returns:A transfer function that converts from "gamma space" to linear space
/** * <p>Returns the electro-optical transfer function (EOTF) of this color space. * The inverse function is the opto-electronic transfer function (OETF) * returned by {@link #getOetf()}. These functions are defined to satisfy the * following equality for \(x \in [0..1]\):</p> * * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ * * <p>For RGB colors, this function can be used to convert from "gamma space" * (gamma encoded) to linear space. The terms gamma space and gamma encoded * are frequently used because many EOTFs can be closely approximated using * a simple power function of the form \(x^\gamma\) (the approximation of the * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p> * * @return A transfer function that converts from "gamma space" to linear space * * @see #getOetf() * @see #getTransferParameters() */
@NonNull public DoubleUnaryOperator getEotf() { return mClampedEotf; }

Returns the parameters used by the electro-optical and opto-electronic transfer functions. If the transfer functions do not match the ICC parametric curves defined in ICC.1:2004-10 (section 10.15), this method returns null.

See TransferParameters for a full description of the transfer functions.

Returns:An instance of TransferParameters or null if this color space's transfer functions do not match the equation defined in TransferParameters
/** * <p>Returns the parameters used by the {@link #getEotf() electro-optical} * and {@link #getOetf() opto-electronic} transfer functions. If the transfer * functions do not match the ICC parametric curves defined in ICC.1:2004-10 * (section 10.15), this method returns null.</p> * * <p>See {@link TransferParameters} for a full description of the transfer * functions.</p> * * @return An instance of {@link TransferParameters} or null if this color * space's transfer functions do not match the equation defined in * {@link TransferParameters} */
@Nullable public TransferParameters getTransferParameters() { return mTransferParameters; } @Override public boolean isSrgb() { return mIsSrgb; } @Override public boolean isWideGamut() { return mIsWideGamut; } @Override public float getMinValue(int component) { return mMin; } @Override public float getMaxValue(int component) { return mMax; }

Decodes an RGB value to linear space. This is achieved by applying this color space's electro-optical transfer function to the supplied values.

Refer to the documentation of Rgb for more information about transfer functions and their use for encoding and decoding RGB values.

Params:
  • r – The red component to decode to linear space
  • g – The green component to decode to linear space
  • b – The blue component to decode to linear space
See Also:
Returns:A new array of 3 floats containing linear RGB values
/** * <p>Decodes an RGB value to linear space. This is achieved by * applying this color space's electro-optical transfer function * to the supplied values.</p> * * <p>Refer to the documentation of {@link ColorSpace.Rgb} for * more information about transfer functions and their use for * encoding and decoding RGB values.</p> * * @param r The red component to decode to linear space * @param g The green component to decode to linear space * @param b The blue component to decode to linear space * @return A new array of 3 floats containing linear RGB values * * @see #toLinear(float[]) * @see #fromLinear(float, float, float) */
@NonNull @Size(3) public float[] toLinear(float r, float g, float b) { return toLinear(new float[] { r, g, b }); }

Decodes an RGB value to linear space. This is achieved by applying this color space's electro-optical transfer function to the first 3 values of the supplied array. The result is stored back in the input array.

Refer to the documentation of Rgb for more information about transfer functions and their use for encoding and decoding RGB values.

Params:
  • v – A non-null array of non-linear RGB values, its length must be at least 3
See Also:
Returns:The specified array
/** * <p>Decodes an RGB value to linear space. This is achieved by * applying this color space's electro-optical transfer function * to the first 3 values of the supplied array. The result is * stored back in the input array.</p> * * <p>Refer to the documentation of {@link ColorSpace.Rgb} for * more information about transfer functions and their use for * encoding and decoding RGB values.</p> * * @param v A non-null array of non-linear RGB values, its length * must be at least 3 * @return The specified array * * @see #toLinear(float, float, float) * @see #fromLinear(float[]) */
@NonNull @Size(min = 3) public float[] toLinear(@NonNull @Size(min = 3) float[] v) { v[0] = (float) mClampedEotf.applyAsDouble(v[0]); v[1] = (float) mClampedEotf.applyAsDouble(v[1]); v[2] = (float) mClampedEotf.applyAsDouble(v[2]); return v; }

Encodes an RGB value from linear space to this color space's "gamma space". This is achieved by applying this color space's opto-electronic transfer function to the supplied values.

Refer to the documentation of Rgb for more information about transfer functions and their use for encoding and decoding RGB values.

Params:
  • r – The red component to encode from linear space
  • g – The green component to encode from linear space
  • b – The blue component to encode from linear space
See Also:
Returns:A new array of 3 floats containing non-linear RGB values
/** * <p>Encodes an RGB value from linear space to this color space's * "gamma space". This is achieved by applying this color space's * opto-electronic transfer function to the supplied values.</p> * * <p>Refer to the documentation of {@link ColorSpace.Rgb} for * more information about transfer functions and their use for * encoding and decoding RGB values.</p> * * @param r The red component to encode from linear space * @param g The green component to encode from linear space * @param b The blue component to encode from linear space * @return A new array of 3 floats containing non-linear RGB values * * @see #fromLinear(float[]) * @see #toLinear(float, float, float) */
@NonNull @Size(3) public float[] fromLinear(float r, float g, float b) { return fromLinear(new float[] { r, g, b }); }

Encodes an RGB value from linear space to this color space's "gamma space". This is achieved by applying this color space's opto-electronic transfer function to the first 3 values of the supplied array. The result is stored back in the input array.

Refer to the documentation of Rgb for more information about transfer functions and their use for encoding and decoding RGB values.

Params:
  • v – A non-null array of linear RGB values, its length must be at least 3
See Also:
Returns:A new array of 3 floats containing non-linear RGB values
/** * <p>Encodes an RGB value from linear space to this color space's * "gamma space". This is achieved by applying this color space's * opto-electronic transfer function to the first 3 values of the * supplied array. The result is stored back in the input array.</p> * * <p>Refer to the documentation of {@link ColorSpace.Rgb} for * more information about transfer functions and their use for * encoding and decoding RGB values.</p> * * @param v A non-null array of linear RGB values, its length * must be at least 3 * @return A new array of 3 floats containing non-linear RGB values * * @see #fromLinear(float[]) * @see #toLinear(float, float, float) */
@NonNull @Size(min = 3) public float[] fromLinear(@NonNull @Size(min = 3) float[] v) { v[0] = (float) mClampedOetf.applyAsDouble(v[0]); v[1] = (float) mClampedOetf.applyAsDouble(v[1]); v[2] = (float) mClampedOetf.applyAsDouble(v[2]); return v; } @Override @NonNull @Size(min = 3) public float[] toXyz(@NonNull @Size(min = 3) float[] v) { v[0] = (float) mClampedEotf.applyAsDouble(v[0]); v[1] = (float) mClampedEotf.applyAsDouble(v[1]); v[2] = (float) mClampedEotf.applyAsDouble(v[2]); return mul3x3Float3(mTransform, v); } @Override @NonNull @Size(min = 3) public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { mul3x3Float3(mInverseTransform, v); v[0] = (float) mClampedOetf.applyAsDouble(v[0]); v[1] = (float) mClampedOetf.applyAsDouble(v[1]); v[2] = (float) mClampedOetf.applyAsDouble(v[2]); return v; } private double clamp(double x) { return x < mMin ? mMin : x > mMax ? mMax : x; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; Rgb rgb = (Rgb) o; if (Float.compare(rgb.mMin, mMin) != 0) return false; if (Float.compare(rgb.mMax, mMax) != 0) return false; if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false; if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false; if (mTransferParameters != null) { return mTransferParameters.equals(rgb.mTransferParameters); } else if (rgb.mTransferParameters == null) { return true; } //noinspection SimplifiableIfStatement if (!mOetf.equals(rgb.mOetf)) return false; return mEotf.equals(rgb.mEotf); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + Arrays.hashCode(mWhitePoint); result = 31 * result + Arrays.hashCode(mPrimaries); result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0); result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0); result = 31 * result + (mTransferParameters != null ? mTransferParameters.hashCode() : 0); if (mTransferParameters == null) { result = 31 * result + mOetf.hashCode(); result = 31 * result + mEotf.hashCode(); } return result; }
Computes whether a color space is the sRGB color space or at least a close approximation.
Params:
  • primaries – The set of RGB primaries in xyY as an array of 6 floats
  • whitePoint – The white point in xyY as an array of 2 floats
  • OETF – The opto-electronic transfer function
  • EOTF – The electro-optical transfer function
  • min – The minimum value of the color space's range
  • max – The minimum value of the color space's range
  • id – The ID of the color space
See Also:
Returns:True if the color space can be considered as the sRGB color space
/** * Computes whether a color space is the sRGB color space or at least * a close approximation. * * @param primaries The set of RGB primaries in xyY as an array of 6 floats * @param whitePoint The white point in xyY as an array of 2 floats * @param OETF The opto-electronic transfer function * @param EOTF The electro-optical transfer function * @param min The minimum value of the color space's range * @param max The minimum value of the color space's range * @param id The ID of the color space * @return True if the color space can be considered as the sRGB color space * * @see #isSrgb() */
@SuppressWarnings("RedundantIfStatement") private static boolean isSrgb( @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint, @NonNull DoubleUnaryOperator OETF, @NonNull DoubleUnaryOperator EOTF, float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { if (id == 0) return true; if (!compare(primaries, SRGB_PRIMARIES)) { return false; } if (!compare(whitePoint, ILLUMINANT_D65)) { return false; } if (OETF.applyAsDouble(0.5) < 0.5001) return false; if (EOTF.applyAsDouble(0.5) > 0.5001) return false; if (min != 0.0f) return false; if (max != 1.0f) return false; return true; }
Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form a wide color gamut. A color gamut is considered wide if its area is > 90% of the area of NTSC 1953 and if it contains the sRGB color gamut entirely. If the conditions above are not met, the color space is considered as having a wide color gamut if its range is larger than [0..1].
Params:
  • primaries – RGB primaries in CIE xyY as an array of 6 floats
  • min – The minimum value of the color space's range
  • max – The minimum value of the color space's range
See Also:
Returns:True if the color space has a wide gamut, false otherwise
/** * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form * a wide color gamut. A color gamut is considered wide if its area is &gt; 90% * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely. * If the conditions above are not met, the color space is considered as having * a wide color gamut if its range is larger than [0..1]. * * @param primaries RGB primaries in CIE xyY as an array of 6 floats * @param min The minimum value of the color space's range * @param max The minimum value of the color space's range * @return True if the color space has a wide gamut, false otherwise * * @see #isWideGamut() * @see #area(float[]) */
private static boolean isWideGamut(@NonNull @Size(6) float[] primaries, float min, float max) { return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f && contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f); }
Computes the area of the triangle represented by a set of RGB primaries in the CIE xyY space.
Params:
  • primaries – The triangle's vertices, as RGB primaries in an array of 6 floats
See Also:
Returns:The area of the triangle
/** * Computes the area of the triangle represented by a set of RGB primaries * in the CIE xyY space. * * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats * @return The area of the triangle * * @see #isWideGamut(float[], float, float) */
private static float area(@NonNull @Size(6) float[] primaries) { float Rx = primaries[0]; float Ry = primaries[1]; float Gx = primaries[2]; float Gy = primaries[3]; float Bx = primaries[4]; float By = primaries[5]; float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By; float r = 0.5f * det; return r < 0.0f ? -r : r; }
Computes the cross product of two 2D vectors.
Params:
  • ax – The x coordinate of the first vector
  • ay – The y coordinate of the first vector
  • bx – The x coordinate of the second vector
  • by – The y coordinate of the second vector
Returns:The result of a x b
/** * Computes the cross product of two 2D vectors. * * @param ax The x coordinate of the first vector * @param ay The y coordinate of the first vector * @param bx The x coordinate of the second vector * @param by The y coordinate of the second vector * @return The result of a x b */
private static float cross(float ax, float ay, float bx, float by) { return ax * by - ay * bx; }
Decides whether a 2D triangle, identified by the 6 coordinates of its 3 vertices, is contained within another 2D triangle, also identified by the 6 coordinates of its 3 vertices. In the illustration below, we want to test whether the RGB triangle is contained within the triangle XYZ formed by the 3 vertices at the "+" locations. Y . . + . . .. . . . . . G * * * ** * * ** * * ** * * * * * ** * * * * ** ** * R ... * * ..... * ***** .. ** ************ . + B * ************ . X ......***** . ...... . . .. + . Z . RGB is contained within XYZ if all the following conditions are true (with "x" the cross product operator): --> --> GR x RX >= 0 --> --> RX x BR >= 0 --> --> RG x GY >= 0 --> --> GY x RG >= 0 --> --> RB x BZ >= 0 --> --> BZ x GB >= 0
Params:
  • p1 – The enclosing triangle
  • p2 – The enclosed triangle
See Also:
Returns:True if the triangle p1 contains the triangle p2
/** * Decides whether a 2D triangle, identified by the 6 coordinates of its * 3 vertices, is contained within another 2D triangle, also identified * by the 6 coordinates of its 3 vertices. * * In the illustration below, we want to test whether the RGB triangle * is contained within the triangle XYZ formed by the 3 vertices at * the "+" locations. * * Y . * . + . * . .. * . . * . . * . G * * * * * * ** * * * ** * * * * ** * * * * * * * * ** * * * * * * ** * ** * R ... * * * ..... * * ***** .. * ** ************ . + * B * ************ . X * ......***** . * ...... . . * .. * + . * Z . * * RGB is contained within XYZ if all the following conditions are true * (with "x" the cross product operator): * * --> --> * GR x RX >= 0 * --> --> * RX x BR >= 0 * --> --> * RG x GY >= 0 * --> --> * GY x RG >= 0 * --> --> * RB x BZ >= 0 * --> --> * BZ x GB >= 0 * * @param p1 The enclosing triangle * @param p2 The enclosed triangle * @return True if the triangle p1 contains the triangle p2 * * @see #isWideGamut(float[], float, float) */
@SuppressWarnings("RedundantIfStatement") private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) { // Translate the vertices p1 in the coordinates system // with the vertices p2 as the origin float[] p0 = new float[] { p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2], p1[3] - p2[3], p1[4] - p2[4], p1[5] - p2[5], }; // Check the first vertex of p1 if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 || cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) { return false; } // Check the second vertex of p1 if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 || cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) { return false; } // Check the third vertex of p1 if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 || cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) { return false; } return true; }
Computes the primaries of a color space identified only by its RGB->XYZ transform matrix. This method assumes that the range of the color space is [0..1].
Params:
  • toXYZ – The color space's 3x3 transform matrix to XYZ
Returns:A new array of 6 floats containing the color space's primaries in CIE xyY
/** * Computes the primaries of a color space identified only by * its RGB->XYZ transform matrix. This method assumes that the * range of the color space is [0..1]. * * @param toXYZ The color space's 3x3 transform matrix to XYZ * @return A new array of 6 floats containing the color space's * primaries in CIE xyY */
@NonNull @Size(6) private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) { float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f }); float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f }); float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f }); float rSum = r[0] + r[1] + r[2]; float gSum = g[0] + g[1] + g[2]; float bSum = b[0] + b[1] + b[2]; return new float[] { r[0] / rSum, r[1] / rSum, g[0] / gSum, g[1] / gSum, b[0] / bSum, b[1] / bSum, }; }
Computes the white point of a color space identified only by its RGB->XYZ transform matrix. This method assumes that the range of the color space is [0..1].
Params:
  • toXYZ – The color space's 3x3 transform matrix to XYZ
Returns:A new array of 2 floats containing the color space's white point in CIE xyY
/** * Computes the white point of a color space identified only by * its RGB->XYZ transform matrix. This method assumes that the * range of the color space is [0..1]. * * @param toXYZ The color space's 3x3 transform matrix to XYZ * @return A new array of 2 floats containing the color space's * white point in CIE xyY */
@NonNull @Size(2) private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) { float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f }); float sum = w[0] + w[1] + w[2]; return new float[] { w[0] / sum, w[1] / sum }; }
Converts the specified RGB primaries point to xyY if needed. The primaries can be specified as an array of 6 floats (in CIE xyY) or 9 floats (in CIE XYZ). If no conversion is needed, the input array is copied.
Params:
  • primaries – The primaries in xyY or XYZ
Returns:A new array of 6 floats containing the primaries in xyY
/** * Converts the specified RGB primaries point to xyY if needed. The primaries * can be specified as an array of 6 floats (in CIE xyY) or 9 floats * (in CIE XYZ). If no conversion is needed, the input array is copied. * * @param primaries The primaries in xyY or XYZ * @return A new array of 6 floats containing the primaries in xyY */
@NonNull @Size(6) private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) { float[] xyPrimaries = new float[6]; // XYZ to xyY if (primaries.length == 9) { float sum; sum = primaries[0] + primaries[1] + primaries[2]; xyPrimaries[0] = primaries[0] / sum; xyPrimaries[1] = primaries[1] / sum; sum = primaries[3] + primaries[4] + primaries[5]; xyPrimaries[2] = primaries[3] / sum; xyPrimaries[3] = primaries[4] / sum; sum = primaries[6] + primaries[7] + primaries[8]; xyPrimaries[4] = primaries[6] / sum; xyPrimaries[5] = primaries[7] / sum; } else { System.arraycopy(primaries, 0, xyPrimaries, 0, 6); } return xyPrimaries; }
Converts the specified white point to xyY if needed. The white point can be specified as an array of 2 floats (in CIE xyY) or 3 floats (in CIE XYZ). If no conversion is needed, the input array is copied.
Params:
  • whitePoint – The white point in xyY or XYZ
Returns:A new array of 2 floats containing the white point in xyY
/** * Converts the specified white point to xyY if needed. The white point * can be specified as an array of 2 floats (in CIE xyY) or 3 floats * (in CIE XYZ). If no conversion is needed, the input array is copied. * * @param whitePoint The white point in xyY or XYZ * @return A new array of 2 floats containing the white point in xyY */
@NonNull @Size(2) private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) { float[] xyWhitePoint = new float[2]; // XYZ to xyY if (whitePoint.length == 3) { float sum = whitePoint[0] + whitePoint[1] + whitePoint[2]; xyWhitePoint[0] = whitePoint[0] / sum; xyWhitePoint[1] = whitePoint[1] / sum; } else { System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2); } return xyWhitePoint; }
Computes the matrix that converts from RGB to XYZ based on RGB primaries and a white point, both specified in the CIE xyY space. The Y component of the primaries and white point is implied to be 1.
Params:
  • primaries – The RGB primaries in xyY, as an array of 6 floats
  • whitePoint – The white point in xyY, as an array of 2 floats
Returns:A 3x3 matrix as a new array of 9 floats
/** * Computes the matrix that converts from RGB to XYZ based on RGB * primaries and a white point, both specified in the CIE xyY space. * The Y component of the primaries and white point is implied to be 1. * * @param primaries The RGB primaries in xyY, as an array of 6 floats * @param whitePoint The white point in xyY, as an array of 2 floats * @return A 3x3 matrix as a new array of 9 floats */
@NonNull @Size(9) private static float[] computeXYZMatrix( @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { float Rx = primaries[0]; float Ry = primaries[1]; float Gx = primaries[2]; float Gy = primaries[3]; float Bx = primaries[4]; float By = primaries[5]; float Wx = whitePoint[0]; float Wy = whitePoint[1]; float oneRxRy = (1 - Rx) / Ry; float oneGxGy = (1 - Gx) / Gy; float oneBxBy = (1 - Bx) / By; float oneWxWy = (1 - Wx) / Wy; float RxRy = Rx / Ry; float GxGy = Gx / Gy; float BxBy = Bx / By; float WxWy = Wx / Wy; float BY = ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); float RY = 1 - GY - BY; float RYRy = RY / Ry; float GYGy = GY / Gy; float BYBy = BY / By; return new float[] { RYRy * Rx, RY, RYRy * (1 - Rx - Ry), GYGy * Gx, GY, GYGy * (1 - Gx - Gy), BYBy * Bx, BY, BYBy * (1 - Bx - By) }; } }
{@usesMathJax}

A connector transforms colors from a source color space to a destination color space.

A source color space is connected to a destination color space using the color transform \(C\) computed from their respective transforms noted \(T_{src}\) and \(T_{dst}\) in the following equation:

$$C = T^{-1}_{dst} . T_{src}$$

The transform \(C\) shown above is only valid when the source and destination color spaces have the same profile connection space (PCS). We know that instances of ColorSpace always use CIE XYZ as their PCS but their white points might differ. When they do, we must perform a chromatic adaptation of the color spaces' transforms. To do so, we use the von Kries method described in the documentation of Adaptation, using the CIE standard illuminant D50 as the target white point.

Example of conversion from sRGB to DCI-P3:

ColorSpace.Connector connector = ColorSpace.connect(
        ColorSpace.get(ColorSpace.Named.SRGB),
        ColorSpace.get(ColorSpace.Named.DCI_P3));
float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
// p3 contains { 0.9473, 0.2740, 0.2076 }
See Also:
/** * {@usesMathJax} * * <p>A connector transforms colors from a source color space to a destination * color space.</p> * * <p>A source color space is connected to a destination color space using the * color transform \(C\) computed from their respective transforms noted * \(T_{src}\) and \(T_{dst}\) in the following equation:</p> * * $$C = T^{-1}_{dst} . T_{src}$$ * * <p>The transform \(C\) shown above is only valid when the source and * destination color spaces have the same profile connection space (PCS). * We know that instances of {@link ColorSpace} always use CIE XYZ as their * PCS but their white points might differ. When they do, we must perform * a chromatic adaptation of the color spaces' transforms. To do so, we * use the von Kries method described in the documentation of {@link Adaptation}, * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50} * as the target white point.</p> * * <p>Example of conversion from {@link Named#SRGB sRGB} to * {@link Named#DCI_P3 DCI-P3}:</p> * * <pre class="prettyprint"> * ColorSpace.Connector connector = ColorSpace.connect( * ColorSpace.get(ColorSpace.Named.SRGB), * ColorSpace.get(ColorSpace.Named.DCI_P3)); * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f); * // p3 contains { 0.9473, 0.2740, 0.2076 } * </pre> * * @see Adaptation * @see ColorSpace#adapt(ColorSpace, float[], Adaptation) * @see ColorSpace#adapt(ColorSpace, float[]) * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) * @see ColorSpace#connect(ColorSpace, 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; @NonNull private final ColorSpace mTransformSource; @NonNull private final ColorSpace mTransformDestination; @NonNull private final RenderIntent mIntent; @NonNull @Size(3) private final float[] mTransform;
Creates a new connector between a source and a destination color space.
Params:
  • source – The source color space, cannot be null
  • destination – The destination color space, cannot be null
  • intent – The render intent to use when compressing gamuts
/** * Creates a new connector between a source and a destination color space. * * @param source The source color space, cannot be null * @param destination The destination color space, cannot be null * @param intent The render intent to use when compressing gamuts */
Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent) { this(source, destination, source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source, destination.getModel() == Model.RGB ? adapt(destination, ILLUMINANT_D50_XYZ) : destination, intent, computeTransform(source, destination, intent)); }
To connect between color spaces, we might need to use adapted transforms. This should be transparent to the user so this constructor takes the original source and destinations (returned by the getters), as well as possibly adapted color spaces used by transform().
/** * To connect between color spaces, we might need to use adapted transforms. * This should be transparent to the user so this constructor takes the * original source and destinations (returned by the getters), as well as * possibly adapted color spaces used by transform(). */
private Connector( @NonNull ColorSpace source, @NonNull ColorSpace destination, @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) { mSource = source; mDestination = destination; mTransformSource = transformSource; mTransformDestination = transformDestination; mIntent = intent; mTransform = transform; }
Computes an extra transform to apply in XYZ space depending on the selected rendering intent.
/** * Computes an extra transform to apply in XYZ space depending on the * selected rendering intent. */
@Nullable private static float[] computeTransform(@NonNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent) { if (intent != RenderIntent.ABSOLUTE) return null; boolean srcRGB = source.getModel() == Model.RGB; boolean dstRGB = destination.getModel() == Model.RGB; if (srcRGB && dstRGB) return null; if (srcRGB || dstRGB) { ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination); float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; return new float[] { srcXYZ[0] / dstXYZ[0], srcXYZ[1] / dstXYZ[1], srcXYZ[2] / dstXYZ[2], }; } return null; }
Returns the source color space this connector will convert from.
See Also:
Returns:A non-null instance of ColorSpace
/** * Returns the source color space this connector will convert from. * * @return A non-null instance of {@link ColorSpace} * * @see #getDestination() */
@NonNull public ColorSpace getSource() { return mSource; }
Returns the destination color space this connector will convert to.
See Also:
Returns:A non-null instance of ColorSpace
/** * Returns the destination color space this connector will convert to. * * @return A non-null instance of {@link ColorSpace} * * @see #getSource() */
@NonNull public ColorSpace getDestination() { return mDestination; }
Returns the render intent this connector will use when mapping the source color space to the destination color space.
See Also:
Returns:A non-null RenderIntent
/** * Returns the render intent this connector will use when mapping the * source color space to the destination color space. * * @return A non-null {@link RenderIntent} * * @see RenderIntent */
public RenderIntent getRenderIntent() { return mIntent; }

Transforms the specified color from the source color space to a color in the destination color space. This convenience method assumes a source color model with 3 components (typically RGB). To transform from color models with more than 3 components, such as CMYK, use transform(float[]) instead.

Params:
  • r – The red component of the color to transform
  • g – The green component of the color to transform
  • b – The blue component of the color to transform
See Also:
Returns:A new array of 3 floats containing the specified color transformed from the source space to the destination space
/** * <p>Transforms the specified color from the source color space * to a color in the destination color space. This convenience * method assumes a source color model with 3 components * (typically RGB). To transform from color models with more than * 3 components, such as {@link Model#CMYK CMYK}, use * {@link #transform(float[])} instead.</p> * * @param r The red component of the color to transform * @param g The green component of the color to transform * @param b The blue component of the color to transform * @return A new array of 3 floats containing the specified color * transformed from the source space to the destination space * * @see #transform(float[]) */
@NonNull @Size(3) public float[] transform(float r, float g, float b) { return transform(new float[] { r, g, b }); }

Transforms the specified color from the source color space to a color in the destination color space.

Params:
  • v – A non-null array of 3 floats containing the value to transform and that will hold the result of the transform
See Also:
Returns:The v array passed as a parameter, containing the specified color transformed from the source space to the destination space
/** * <p>Transforms the specified color from the source color space * to a color in the destination color space.</p> * * @param v A non-null array of 3 floats containing the value to transform * and that will hold the result of the transform * @return The v array passed as a parameter, containing the specified color * transformed from the source space to the destination space * * @see #transform(float, float, float) */
@NonNull @Size(min = 3) public float[] transform(@NonNull @Size(min = 3) float[] v) { float[] xyz = mTransformSource.toXyz(v); if (mTransform != null) { xyz[0] *= mTransform[0]; xyz[1] *= mTransform[1]; xyz[2] *= mTransform[2]; } return mTransformDestination.fromXyz(xyz); }
Optimized connector for RGB->RGB conversions.
/** * Optimized connector for RGB->RGB conversions. */
private static class Rgb extends Connector { @NonNull private final ColorSpace.Rgb mSource; @NonNull private final ColorSpace.Rgb mDestination; @NonNull private final float[] mTransform; Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent) { super(source, destination, source, destination, intent, null); mSource = source; mDestination = destination; mTransform = computeTransform(source, destination, intent); } @Override public float[] transform(@NonNull @Size(min = 3) float[] rgb) { rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]); rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]); rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]); mul3x3Float3(mTransform, rgb); rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]); rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]); rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]); return rgb; }

Computes the color transform that connects two RGB color spaces.

We can only connect color spaces if they use the same profile connection space. We assume the connection space is always CIE XYZ but we maye need to perform a chromatic adaptation to match the white points. If an adaptation is needed, we use the CIE standard illuminant D50. The unmatched color space is adapted using the von Kries transform and the Adaptation.BRADFORD matrix.

Params:
  • source – The source color space, cannot be null
  • destination – The destination color space, cannot be null
  • intent – The render intent to use when compressing gamuts
Returns:An array of 9 floats containing the 3x3 matrix transform
/** * <p>Computes the color transform that connects two RGB color spaces.</p> * * <p>We can only connect color spaces if they use the same profile * connection space. We assume the connection space is always * CIE XYZ but we maye need to perform a chromatic adaptation to * match the white points. If an adaptation is needed, we use the * CIE standard illuminant D50. The unmatched color space is adapted * using the von Kries transform and the {@link Adaptation#BRADFORD} * matrix.</p> * * @param source The source color space, cannot be null * @param destination The destination color space, cannot be null * @param intent The render intent to use when compressing gamuts * @return An array of 9 floats containing the 3x3 matrix transform */
@NonNull @Size(9) private static float[] computeTransform( @NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent) { if (compare(source.mWhitePoint, destination.mWhitePoint)) { // RGB->RGB using the PCS of both color spaces since they have the same return mul3x3(destination.mInverseTransform, source.mTransform); } else { // RGB->RGB using CIE XYZ D50 as the PCS float[] transform = source.mTransform; float[] inverseTransform = destination.mInverseTransform; float[] srcXYZ = xyYToXyz(source.mWhitePoint); float[] dstXYZ = xyYToXyz(destination.mWhitePoint); if (!compare(source.mWhitePoint, ILLUMINANT_D50)) { float[] srcAdaptation = chromaticAdaptation( Adaptation.BRADFORD.mTransform, srcXYZ, Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); transform = mul3x3(srcAdaptation, source.mTransform); } if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) { float[] dstAdaptation = chromaticAdaptation( Adaptation.BRADFORD.mTransform, dstXYZ, Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform)); } if (intent == RenderIntent.ABSOLUTE) { transform = mul3x3Diag( new float[] { srcXYZ[0] / dstXYZ[0], srcXYZ[1] / dstXYZ[1], srcXYZ[2] / dstXYZ[2], }, transform); } return mul3x3(inverseTransform, transform); } } }
Returns the identity connector for a given color space.
Params:
  • source – The source and destination color space
See Also:
Returns:A non-null connector that does not perform any transform
/** * Returns the identity connector for a given color space. * * @param source The source and destination color space * @return A non-null connector that does not perform any transform * * @see ColorSpace#connect(ColorSpace, ColorSpace) */
static Connector identity(ColorSpace source) { return new Connector(source, source, RenderIntent.RELATIVE) { @Override public float[] transform(@NonNull @Size(min = 3) float[] v) { return v; } }; } }

A color space renderer can be used to visualize and compare the gamut and white point of one or more color spaces. The output is an sRGB Bitmap showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.

The following code snippet shows how to compare the Named.SRGB and Named.DCI_P3 color spaces in a CIE 1931 diagram:

Bitmap bitmap = ColorSpace.createRenderer()
    .size(768)
    .clip(true)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .render();

sRGB vs DCI-P3

A renderer can also be used to show the location of specific colors, associated with a color space, in the CIE 1931 xyY chromaticity diagram. See add(ColorSpace, float, float, float, int) for more information.

See Also:
@hide
/** * <p>A color space renderer can be used to visualize and compare the gamut and * white point of one or more color spaces. The output is an sRGB {@link Bitmap} * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p> * * <p>The following code snippet shows how to compare the {@link Named#SRGB} * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p> * * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .size(768) * .clip(true) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> * </p> * * <p>A renderer can also be used to show the location of specific colors, * associated with a color space, in the CIE 1931 xyY chromaticity diagram. * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p> * * @see ColorSpace#createRenderer() * * @hide */
public static class Renderer { private static final int NATIVE_SIZE = 1440; private static final float UCS_SCALE = 9.0f / 6.0f; // Number of subdivision of the inside of the spectral locus private static final int CHROMATICITY_RESOLUTION = 32; private static final double ONE_THIRD = 1.0 / 3.0; @IntRange(from = 128, to = Integer.MAX_VALUE) private int mSize = 1024; private boolean mShowWhitePoint = true; private boolean mClip = false; private boolean mUcs = false; private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2); private final List<Point> mPoints = new ArrayList<>(0); private Renderer() { }

Defines whether the chromaticity diagram should be clipped by the first registered color space. The default value is false.

The following code snippet and image show the default behavior:

Bitmap bitmap = ColorSpace.createRenderer()
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .render();

Clipping disabled

Here is the same example with clipping enabled:

Bitmap bitmap = ColorSpace.createRenderer()
    .clip(true)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .render();

Clipping enabled

Params:
  • clip – True to clip the chromaticity diagram to the first registered color space, false otherwise
Returns:This instance of Renderer
/** * <p>Defines whether the chromaticity diagram should be clipped by the first * registered color space. The default value is false.</p> * * <p>The following code snippet and image show the default behavior:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> * <figcaption style="text-align: center;">Clipping disabled</figcaption> * </p> * * <p>Here is the same example with clipping enabled:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .clip(true) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> * <figcaption style="text-align: center;">Clipping enabled</figcaption> * </p> * * @param clip True to clip the chromaticity diagram to the first registered color space, * false otherwise * @return This instance of {@link Renderer} */
@NonNull public Renderer clip(boolean clip) { mClip = clip; return this; }

Defines whether the chromaticity diagram should use the uniform chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale is used, the distance between two points on the diagram is approximately proportional to the perceived color difference.

The following code snippet shows how to enable the uniform chromaticity scale. The image below shows the result:

Bitmap bitmap = ColorSpace.createRenderer()
    .uniformChromaticityScale(true)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .render();

CIE 1976 UCS diagram

Params:
  • ucs – True to render the chromaticity diagram as the CIE 1976 UCS diagram
Returns:This instance of Renderer
/** * <p>Defines whether the chromaticity diagram should use the uniform * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale * is used, the distance between two points on the diagram is approximately * proportional to the perceived color difference.</p> * * <p>The following code snippet shows how to enable the uniform chromaticity * scale. The image below shows the result:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .uniformChromaticityScale(true) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" /> * <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption> * </p> * * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram * @return This instance of {@link Renderer} */
@NonNull public Renderer uniformChromaticityScale(boolean ucs) { mUcs = ucs; return this; }
Sets the dimensions (width and height) in pixels of the output bitmap. The size must be at least 128px and defaults to 1024px.
Params:
  • size – The size in pixels of the output bitmap
Returns:This instance of Renderer
/** * Sets the dimensions (width and height) in pixels of the output bitmap. * The size must be at least 128px and defaults to 1024px. * * @param size The size in pixels of the output bitmap * @return This instance of {@link Renderer} */
@NonNull public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) { mSize = Math.max(128, size); return this; }
Shows or hides the white point of each color space in the output bitmap. The default is true.
Params:
  • show – True to show the white point of each color space, false otherwise
Returns:This instance of Renderer
/** * Shows or hides the white point of each color space in the output bitmap. * The default is true. * * @param show True to show the white point of each color space, false * otherwise * @return This instance of {@link Renderer} */
@NonNull public Renderer showWhitePoint(boolean show) { mShowWhitePoint = show; return this; }

Adds a color space to represent on the output CIE 1931 chromaticity diagram. The color space is represented as a triangle showing the footprint of its color gamut and, optionally, the location of its white point.

Color spaces with a color model that is not RGB are accepted but ignored.

The following code snippet and image show an example of calling this method to compare sRGB and DCI-P3:

Bitmap bitmap = ColorSpace.createRenderer()
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .render();

sRGB vs DCI-P3

Adding a color space extending beyond the boundaries of the spectral locus will alter the size of the diagram within the output bitmap as shown in this example:

Bitmap bitmap = ColorSpace.createRenderer()
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
    .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9)
    .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000)
    .render();

sRGB, DCI-P3, ACES and scRGB

Params:
  • colorSpace – The color space whose gamut to render on the diagram
  • color – The sRGB color to use to render the color space's gamut and white point
See Also:
Returns:This instance of Renderer
/** * <p>Adds a color space to represent on the output CIE 1931 chromaticity * diagram. The color space is represented as a triangle showing the * footprint of its color gamut and, optionally, the location of its * white point.</p> * * <p class="note">Color spaces with a color model that is not RGB are * accepted but ignored.</p> * * <p>The following code snippet and image show an example of calling this * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> * </p> * * <p>Adding a color space extending beyond the boundaries of the * spectral locus will alter the size of the diagram within the output * bitmap as shown in this example:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) * .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9) * .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" /> * <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption> * </p> * * @param colorSpace The color space whose gamut to render on the diagram * @param color The sRGB color to use to render the color space's gamut and white point * @return This instance of {@link Renderer} * * @see #clip(boolean) * @see #showWhitePoint(boolean) */
@NonNull public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) { mColorSpaces.add(new Pair<>(colorSpace, color)); return this; }

Adds a color to represent as a point on the chromaticity diagram. The color is associated with a color space which will be used to perform the conversion to CIE XYZ and compute the location of the point on the diagram. The point is rendered as a colored circle.

The following code snippet and image show an example of calling this method to render the location of several sRGB colors as white circles:

Bitmap bitmap = ColorSpace.createRenderer()
    .clip(true)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff)
    .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff)
    .render();

Locating colors on the chromaticity diagram

Params:
  • colorSpace – The color space of the color to locate on the diagram
  • r – The first component of the color to locate on the diagram
  • g – The second component of the color to locate on the diagram
  • b – The third component of the color to locate on the diagram
  • pointColor – The sRGB color to use to render the point on the diagram
Returns:This instance of Renderer
/** * <p>Adds a color to represent as a point on the chromaticity diagram. * The color is associated with a color space which will be used to * perform the conversion to CIE XYZ and compute the location of the point * on the diagram. The point is rendered as a colored circle.</p> * * <p>The following code snippet and image show an example of calling this * method to render the location of several sRGB colors as white circles:</p> * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() * .clip(true) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff) * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff) * .render(); * </pre> * <p> * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_points.png" /> * <figcaption style="text-align: center;"> * Locating colors on the chromaticity diagram * </figcaption> * </p> * * @param colorSpace The color space of the color to locate on the diagram * @param r The first component of the color to locate on the diagram * @param g The second component of the color to locate on the diagram * @param b The third component of the color to locate on the diagram * @param pointColor The sRGB color to use to render the point on the diagram * @return This instance of {@link Renderer} */
@NonNull public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b, @ColorInt int pointColor) { mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor)); return this; }

Renders the color spaces and points registered with this renderer. The output bitmap is an sRGB image with the dimensions specified by calling size(int) (1204x1024px by default).

Returns:A new non-null Bitmap with the dimensions specified by size(int) (1024x1024 by default)
/** * <p>Renders the {@link #add(ColorSpace, int) color spaces} and * {@link #add(ColorSpace, float, float, float, int) points} registered * with this renderer. The output bitmap is an sRGB image with the * dimensions specified by calling {@link #size(int)} (1204x1024px by * default).</p> * * @return A new non-null {@link Bitmap} with the dimensions specified * by {@link #size(int)} (1024x1024 by default) */
@NonNull public Bitmap render() { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); float[] primaries = new float[6]; float[] whitePoint = new float[2]; int width = NATIVE_SIZE; int height = NATIVE_SIZE; Path path = new Path(); setTransform(canvas, width, height, primaries); drawBox(canvas, width, height, paint, path); setUcsTransform(canvas, height); drawLocus(canvas, width, height, paint, path, primaries); drawGamuts(canvas, width, height, paint, path, primaries, whitePoint); drawPoints(canvas, width, height, paint); return bitmap; }
Draws registered points at their correct position in the xyY coordinates. Each point is positioned according to its associated color space.
Params:
  • canvas – The canvas to transform
  • width – Width in pixel of the final image
  • height – Height in pixel of the final image
  • paint – A pre-allocated paint used to avoid temporary allocations
/** * Draws registered points at their correct position in the xyY coordinates. * Each point is positioned according to its associated color space. * * @param canvas The canvas to transform * @param width Width in pixel of the final image * @param height Height in pixel of the final image * @param paint A pre-allocated paint used to avoid temporary allocations */
private void drawPoints(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint) { paint.setStyle(Paint.Style.FILL); float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); float[] v = new float[3]; float[] xy = new float[2]; for (final Point point : mPoints) { v[0] = point.mRgb[0]; v[1] = point.mRgb[1]; v[2] = point.mRgb[2]; point.mColorSpace.toXyz(v); paint.setColor(point.mColor); // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed float sum = v[0] + v[1] + v[2]; xy[0] = v[0] / sum; xy[1] = v[1] / sum; if (mUcs) xyYToUv(xy); canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint); } }
Draws the color gamuts and white points of all the registered color spaces. Only color spaces with an RGB color model are rendered, the others are ignored.
Params:
  • canvas – The canvas to transform
  • width – Width in pixel of the final image
  • height – Height in pixel of the final image
  • paint – A pre-allocated paint used to avoid temporary allocations
  • path – A pre-allocated path used to avoid temporary allocations
  • primaries – A pre-allocated array of 6 floats to avoid temporary allocations
  • whitePoint – A pre-allocated array of 2 floats to avoid temporary allocations
/** * Draws the color gamuts and white points of all the registered color * spaces. Only color spaces with an RGB color model are rendered, the * others are ignored. * * @param canvas The canvas to transform * @param width Width in pixel of the final image * @param height Height in pixel of the final image * @param paint A pre-allocated paint used to avoid temporary allocations * @param path A pre-allocated path used to avoid temporary allocations * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations */
private void drawGamuts( @NonNull Canvas canvas, int width, int height, @NonNull Paint paint, @NonNull Path path, @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); for (final Pair<ColorSpace, Integer> item : mColorSpaces) { ColorSpace colorSpace = item.first; int color = item.second; if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; getPrimaries(rgb, primaries, mUcs); path.rewind(); path.moveTo(width * primaries[0], height - height * primaries[1]); path.lineTo(width * primaries[2], height - height * primaries[3]); path.lineTo(width * primaries[4], height - height * primaries[5]); path.close(); paint.setStyle(Paint.Style.STROKE); paint.setColor(color); canvas.drawPath(path, paint); // Draw the white point if (mShowWhitePoint) { rgb.getWhitePoint(whitePoint); if (mUcs) xyYToUv(whitePoint); paint.setStyle(Paint.Style.FILL); paint.setColor(color); canvas.drawCircle( width * whitePoint[0], height - height * whitePoint[1], radius, paint); } } }
Returns the primaries of the specified RGB color space. This method handles the special case of the Named.EXTENDED_SRGB family of color spaces.
Params:
  • rgb – The color space whose primaries to extract
  • primaries – A pre-allocated array of 6 floats that will hold the result
  • asUcs – True if the primaries should be returned in Luv, false for xyY
/** * Returns the primaries of the specified RGB color space. This method handles * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces. * * @param rgb The color space whose primaries to extract * @param primaries A pre-allocated array of 6 floats that will hold the result * @param asUcs True if the primaries should be returned in Luv, false for xyY */
@NonNull @Size(6) private static void getPrimaries(@NonNull Rgb rgb, @NonNull @Size(6) float[] primaries, boolean asUcs) { // TODO: We should find a better way to handle these cases if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) || rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) { primaries[0] = 1.41f; primaries[1] = 0.33f; primaries[2] = 0.27f; primaries[3] = 1.24f; primaries[4] = -0.23f; primaries[5] = -0.57f; } else { rgb.getPrimaries(primaries); } if (asUcs) xyYToUv(primaries); }
Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside. This method respect the clip parameter.
Params:
  • canvas – The canvas to transform
  • width – Width in pixel of the final image
  • height – Height in pixel of the final image
  • paint – A pre-allocated paint used to avoid temporary allocations
  • path – A pre-allocated path used to avoid temporary allocations
  • primaries – A pre-allocated array of 6 floats to avoid temporary allocations
/** * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside. * This method respect the clip parameter. * * @param canvas The canvas to transform * @param width Width in pixel of the final image * @param height Height in pixel of the final image * @param paint A pre-allocated paint used to avoid temporary allocations * @param path A pre-allocated path used to avoid temporary allocations * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations */
private void drawLocus( @NonNull Canvas canvas, int width, int height, @NonNull Paint paint, @NonNull Path path, @NonNull @Size(6) float[] primaries) { int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6; float[] vertices = new float[vertexCount * 2]; int[] colors = new int[vertices.length]; computeChromaticityMesh(vertices, colors); if (mUcs) xyYToUv(vertices); for (int i = 0; i < vertices.length; i += 2) { vertices[i] *= width; vertices[i + 1] = height - vertices[i + 1] * height; } // Draw the spectral locus if (mClip && mColorSpaces.size() > 0) { for (final Pair<ColorSpace, Integer> item : mColorSpaces) { ColorSpace colorSpace = item.first; if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; getPrimaries(rgb, primaries, mUcs); break; } path.rewind(); path.moveTo(width * primaries[0], height - height * primaries[1]); path.lineTo(width * primaries[2], height - height * primaries[3]); path.lineTo(width * primaries[4], height - height * primaries[5]); path.close(); int[] solid = new int[colors.length]; Arrays.fill(solid, 0xff6c6c6c); canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, null, 0, solid, 0, null, 0, 0, paint); canvas.save(); canvas.clipPath(path); canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, null, 0, colors, 0, null, 0, 0, paint); canvas.restore(); } else { canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, null, 0, colors, 0, null, 0, 0, paint); } // Draw the non-spectral locus int index = (CHROMATICITY_RESOLUTION - 1) * 12; path.reset(); path.moveTo(vertices[index], vertices[index + 1]); for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) { index += CHROMATICITY_RESOLUTION * 12; path.lineTo(vertices[index], vertices[index + 1]); } path.close(); paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f)); paint.setStyle(Paint.Style.STROKE); paint.setColor(0xff000000); canvas.drawPath(path, paint); }
Draws the diagram box, including borders, tick marks, grid lines and axis labels.
Params:
  • canvas – The canvas to transform
  • width – Width in pixel of the final image
  • height – Height in pixel of the final image
  • paint – A pre-allocated paint used to avoid temporary allocations
  • path – A pre-allocated path used to avoid temporary allocations
/** * Draws the diagram box, including borders, tick marks, grid lines * and axis labels. * * @param canvas The canvas to transform * @param width Width in pixel of the final image * @param height Height in pixel of the final image * @param paint A pre-allocated paint used to avoid temporary allocations * @param path A pre-allocated path used to avoid temporary allocations */
private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint, @NonNull Path path) { int lineCount = 10; float scale = 1.0f; if (mUcs) { lineCount = 7; scale = UCS_SCALE; } // Draw the unit grid paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2.0f); paint.setColor(0xffc0c0c0); for (int i = 1; i < lineCount - 1; i++) { float v = i / 10.0f; float x = (width * v) * scale; float y = height - (height * v) * scale; canvas.drawLine(0.0f, y, 0.9f * width, y, paint); canvas.drawLine(x, height, x, 0.1f * height, paint); } // Draw tick marks paint.setStrokeWidth(4.0f); paint.setColor(0xff000000); for (int i = 1; i < lineCount - 1; i++) { float v = i / 10.0f; float x = (width * v) * scale; float y = height - (height * v) * scale; canvas.drawLine(0.0f, y, width / 100.0f, y, paint); canvas.drawLine(x, height, x, height - (height / 100.0f), paint); } // Draw the axis labels paint.setStyle(Paint.Style.FILL); paint.setTextSize(36.0f); paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); Rect bounds = new Rect(); for (int i = 1; i < lineCount - 1; i++) { String text = "0." + i; paint.getTextBounds(text, 0, text.length(), bounds); float v = i / 10.0f; float x = (width * v) * scale; float y = height - (height * v) * scale; canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); canvas.drawText(text, x - bounds.width() / 2.0f, height + bounds.height() + 16, paint); } paint.setStyle(Paint.Style.STROKE); // Draw the diagram box path.moveTo(0.0f, height); path.lineTo(0.9f * width, height); path.lineTo(0.9f * width, 0.1f * height); path.lineTo(0.0f, 0.1f * height); path.close(); canvas.drawPath(path, paint); }
Computes and applies the Canvas transforms required to make the color gamut of each color space visible in the final image.
Params:
  • canvas – The canvas to transform
  • width – Width in pixel of the final image
  • height – Height in pixel of the final image
  • primaries – Array of 6 floats used to avoid temporary allocations
/** * Computes and applies the Canvas transforms required to make the color * gamut of each color space visible in the final image. * * @param canvas The canvas to transform * @param width Width in pixel of the final image * @param height Height in pixel of the final image * @param primaries Array of 6 floats used to avoid temporary allocations */
private void setTransform(@NonNull Canvas canvas, int width, int height, @NonNull @Size(6) float[] primaries) { RectF primariesBounds = new RectF(); for (final Pair<ColorSpace, Integer> item : mColorSpaces) { ColorSpace colorSpace = item.first; if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; getPrimaries(rgb, primaries, mUcs); primariesBounds.left = Math.min(primariesBounds.left, primaries[4]); primariesBounds.top = Math.min(primariesBounds.top, primaries[5]); primariesBounds.right = Math.max(primariesBounds.right, primaries[0]); primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]); } float max = mUcs ? 0.6f : 0.9f; primariesBounds.left = Math.min(0.0f, primariesBounds.left); primariesBounds.top = Math.min(0.0f, primariesBounds.top); primariesBounds.right = Math.max(max, primariesBounds.right); primariesBounds.bottom = Math.max(max, primariesBounds.bottom); float scaleX = max / primariesBounds.width(); float scaleY = max / primariesBounds.height(); float scale = Math.min(scaleX, scaleY); canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE); canvas.scale(scale, scale); canvas.translate( (primariesBounds.width() - max) * width / 2.0f, (primariesBounds.height() - max) * height / 2.0f); // The spectrum extends ~0.85 vertically and ~0.65 horizontally // We shift the canvas a little bit to get nicer margins canvas.translate(0.05f * width, -0.05f * height); }
Computes and applies the Canvas transforms required to render the CIE 197 UCS chromaticity diagram.
Params:
  • canvas – The canvas to transform
  • height – Height in pixel of the final image
/** * Computes and applies the Canvas transforms required to render the CIE * 197 UCS chromaticity diagram. * * @param canvas The canvas to transform * @param height Height in pixel of the final image */
private void setUcsTransform(@NonNull Canvas canvas, int height) { if (mUcs) { canvas.translate(0.0f, (height - height * UCS_SCALE)); canvas.scale(UCS_SCALE, UCS_SCALE); } } // X coordinates of the spectral locus in CIE 1931 private static final float[] SPECTRUM_LOCUS_X = { 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f, 0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f, 0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f, 0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f, 0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f, 0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f, 0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f, 0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f, 0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f, 0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f, 0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f, 0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f, 0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f, 0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f, 0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f, 0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f, 0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f, 0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f, 0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f }; // Y coordinates of the spectral locus in CIE 1931 private static final float[] SPECTRUM_LOCUS_Y = { 0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f, 0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f, 0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f, 0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f, 0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f, 0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f, 0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f, 0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f, 0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f, 0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f, 0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f, 0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f, 0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f, 0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f, 0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f, 0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f, 0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f, 0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f, 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f };
Computes a 2D mesh representation of the CIE 1931 chromaticity diagram.
Params:
  • vertices – Array of floats that will hold the mesh vertices
  • colors – Array of floats that will hold the mesh colors
/** * Computes a 2D mesh representation of the CIE 1931 chromaticity * diagram. * * @param vertices Array of floats that will hold the mesh vertices * @param colors Array of floats that will hold the mesh colors */
private static void computeChromaticityMesh(@NonNull float[] vertices, @NonNull int[] colors) { ColorSpace colorSpace = get(Named.SRGB); float[] color = new float[3]; int vertexIndex = 0; int colorIndex = 0; for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) { int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1; float a1 = (float) Math.atan2( SPECTRUM_LOCUS_Y[x] - ONE_THIRD, SPECTRUM_LOCUS_X[x] - ONE_THIRD); float a2 = (float) Math.atan2( SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD, SPECTRUM_LOCUS_X[nextX] - ONE_THIRD); float radius1 = (float) Math.pow( sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) + sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD), 0.5); float radius2 = (float) Math.pow( sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) + sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD), 0.5); // Compute patches; each patch is a quad with a different // color associated with each vertex for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) { float f1 = c / (float) CHROMATICITY_RESOLUTION; float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION; double cr1 = radius1 * Math.cos(a1); double sr1 = radius1 * Math.sin(a1); double cr2 = radius2 * Math.cos(a2); double sr2 = radius2 * Math.sin(a2); // Compute the XYZ coordinates of the 4 vertices of the patch float v1x = (float) (ONE_THIRD + cr1 * f1); float v1y = (float) (ONE_THIRD + sr1 * f1); float v1z = 1 - v1x - v1y; float v2x = (float) (ONE_THIRD + cr1 * f2); float v2y = (float) (ONE_THIRD + sr1 * f2); float v2z = 1 - v2x - v2y; float v3x = (float) (ONE_THIRD + cr2 * f2); float v3y = (float) (ONE_THIRD + sr2 * f2); float v3z = 1 - v3x - v3y; float v4x = (float) (ONE_THIRD + cr2 * f1); float v4y = (float) (ONE_THIRD + sr2 * f1); float v4z = 1 - v4x - v4y; // Compute the sRGB representation of each XYZ coordinate of the patch colors[colorIndex ] = computeColor(color, v1x, v1y, v1z, colorSpace); colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace); colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace); colors[colorIndex + 3] = colors[colorIndex]; colors[colorIndex + 4] = colors[colorIndex + 2]; colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace); colorIndex += 6; // Flip the mesh upside down to match Canvas' coordinates system vertices[vertexIndex++] = v1x; vertices[vertexIndex++] = v1y; vertices[vertexIndex++] = v2x; vertices[vertexIndex++] = v2y; vertices[vertexIndex++] = v3x; vertices[vertexIndex++] = v3y; vertices[vertexIndex++] = v1x; vertices[vertexIndex++] = v1y; vertices[vertexIndex++] = v3x; vertices[vertexIndex++] = v3y; vertices[vertexIndex++] = v4x; vertices[vertexIndex++] = v4y; } } } @ColorInt private static int computeColor(@NonNull @Size(3) float[] color, float x, float y, float z, @NonNull ColorSpace cs) { color[0] = x; color[1] = y; color[2] = z; cs.fromXyz(color); return 0xff000000 | (((int) (color[0] * 255.0f) & 0xff) << 16) | (((int) (color[1] * 255.0f) & 0xff) << 8) | (((int) (color[2] * 255.0f) & 0xff) ); } private static double sqr(double v) { return v * v; } private static class Point { @NonNull final ColorSpace mColorSpace; @NonNull final float[] mRgb; final int mColor; Point(@NonNull ColorSpace colorSpace, @NonNull @Size(3) float[] rgb, @ColorInt int color) { mColorSpace = colorSpace; mRgb = rgb; mColor = color; } } } }