Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.graphics;
     18 
     19 import android.annotation.AnyThread;
     20 import android.annotation.ColorInt;
     21 import android.annotation.IntRange;
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.annotation.Size;
     25 import android.annotation.SuppressAutoDoc;
     26 import android.util.Pair;
     27 
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.List;
     31 import java.util.function.DoubleUnaryOperator;
     32 
     33 /**
     34  * {@usesMathJax}
     35  *
     36  * <p>A {@link ColorSpace} is used to identify a specific organization of colors.
     37  * Each color space is characterized by a {@link Model color model} that defines
     38  * how a color value is represented (for instance the {@link Model#RGB RGB} color
     39  * model defines a color value as a triplet of numbers).</p>
     40  *
     41  * <p>Each component of a color must fall within a valid range, specific to each
     42  * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)}
     43  * This range is commonly \([0..1]\). While it is recommended to use values in the
     44  * valid range, a color space always clamps input and output values when performing
     45  * operations such as converting to a different color space.</p>
     46  *
     47  * <h3>Using color spaces</h3>
     48  *
     49  * <p>This implementation provides a pre-defined set of common color spaces
     50  * described in the {@link Named} enum. To obtain an instance of one of the
     51  * pre-defined color spaces, simply invoke {@link #get(Named)}:</p>
     52  *
     53  * <pre class="prettyprint">
     54  * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB);
     55  * </pre>
     56  *
     57  * <p>The {@link #get(Named)} method always returns the same instance for a given
     58  * name. Color spaces with an {@link Model#RGB RGB} color model can be safely
     59  * cast to {@link Rgb}. Doing so gives you access to more APIs to query various
     60  * properties of RGB color models: color gamut primaries, transfer functions,
     61  * conversions to and from linear space, etc. Please refer to {@link Rgb} for
     62  * more information.</p>
     63  *
     64  * <p>The documentation of {@link Named} provides a detailed description of the
     65  * various characteristics of each available color space.</p>
     66  *
     67  * <h3>Color space conversions</h3>
     68 
     69  * <p>To allow conversion between color spaces, this implementation uses the CIE
     70  * XYZ profile connection space (PCS). Color values can be converted to and from
     71  * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p>
     72  *
     73  * <p>For color space with a non-RGB color model, the white point of the PCS
     74  * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their
     75  * native white point (D65 for {@link Named#SRGB sRGB} for instance and must
     76  * undergo {@link Adaptation chromatic adaptation} as necessary.</p>
     77  *
     78  * <p>Since the white point of the PCS is not defined for RGB color space, it is
     79  * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)}
     80  * method to perform conversions between color spaces. A color space can be
     81  * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}.
     82  * Please refer to the documentation of {@link Rgb RGB color spaces} for more
     83  * information. Several common CIE standard illuminants are provided in this
     84  * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50}
     85  * for instance).</p>
     86  *
     87  * <p>Here is an example of how to convert from a color space to another:</p>
     88  *
     89  * <pre class="prettyprint">
     90  * // Convert from DCI-P3 to Rec.2020
     91  * ColorSpace.Connector connector = ColorSpace.connect(
     92  *         ColorSpace.get(ColorSpace.Named.DCI_P3),
     93  *         ColorSpace.get(ColorSpace.Named.BT2020));
     94  *
     95  * float[] bt2020 = connector.transform(p3r, p3g, p3b);
     96  * </pre>
     97  *
     98  * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second
     99  * parameter:</p>
    100  *
    101  * <pre class="prettyprint">
    102  * // Convert from DCI-P3 to sRGB
    103  * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
    104  *
    105  * float[] sRGB = connector.transform(p3r, p3g, p3b);
    106  * </pre>
    107  *
    108  * <p>Conversions also work between color spaces with different color models:</p>
    109  *
    110  * <pre class="prettyprint">
    111  * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB)
    112  * ColorSpace.Connector connector = ColorSpace.connect(
    113  *         ColorSpace.get(ColorSpace.Named.CIE_LAB),
    114  *         ColorSpace.get(ColorSpace.Named.BT709));
    115  * </pre>
    116  *
    117  * <h3>Color spaces and multi-threading</h3>
    118  *
    119  * <p>Color spaces and other related classes ({@link Connector} for instance)
    120  * are immutable and stateless. They can be safely used from multiple concurrent
    121  * threads.</p>
    122  *
    123  * <p>Public static methods provided by this class, such as {@link #get(Named)}
    124  * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be
    125  * thread-safe.</p>
    126  *
    127  * @see #get(Named)
    128  * @see Named
    129  * @see Model
    130  * @see Connector
    131  * @see Adaptation
    132  */
    133 @AnyThread
    134 @SuppressWarnings("StaticInitializerReferencesSubClass")
    135 @SuppressAutoDoc
    136 public abstract class ColorSpace {
    137     /**
    138      * Standard CIE 1931 2 illuminant A, encoded in xyY.
    139      * This illuminant has a color temperature of 2856K.
    140      */
    141     public static final float[] ILLUMINANT_A   = { 0.44757f, 0.40745f };
    142     /**
    143      * Standard CIE 1931 2 illuminant B, encoded in xyY.
    144      * This illuminant has a color temperature of 4874K.
    145      */
    146     public static final float[] ILLUMINANT_B   = { 0.34842f, 0.35161f };
    147     /**
    148      * Standard CIE 1931 2 illuminant C, encoded in xyY.
    149      * This illuminant has a color temperature of 6774K.
    150      */
    151     public static final float[] ILLUMINANT_C   = { 0.31006f, 0.31616f };
    152     /**
    153      * Standard CIE 1931 2 illuminant D50, encoded in xyY.
    154      * This illuminant has a color temperature of 5003K. This illuminant
    155      * is used by the profile connection space in ICC profiles.
    156      */
    157     public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f };
    158     /**
    159      * Standard CIE 1931 2 illuminant D55, encoded in xyY.
    160      * This illuminant has a color temperature of 5503K.
    161      */
    162     public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f };
    163     /**
    164      * Standard CIE 1931 2 illuminant D60, encoded in xyY.
    165      * This illuminant has a color temperature of 6004K.
    166      */
    167     public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f };
    168     /**
    169      * Standard CIE 1931 2 illuminant D65, encoded in xyY.
    170      * This illuminant has a color temperature of 6504K. This illuminant
    171      * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
    172      */
    173     public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
    174     /**
    175      * Standard CIE 1931 2 illuminant D75, encoded in xyY.
    176      * This illuminant has a color temperature of 7504K.
    177      */
    178     public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f };
    179     /**
    180      * Standard CIE 1931 2 illuminant E, encoded in xyY.
    181      * This illuminant has a color temperature of 5454K.
    182      */
    183     public static final float[] ILLUMINANT_E   = { 0.33333f, 0.33333f };
    184 
    185     /**
    186      * The minimum ID value a color space can have.
    187      *
    188      * @see #getId()
    189      */
    190     public static final int MIN_ID = -1; // Do not change
    191     /**
    192      * The maximum ID value a color space can have.
    193      *
    194      * @see #getId()
    195      */
    196     public static final int MAX_ID = 63; // Do not change, used to encode in longs
    197 
    198     private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
    199     private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
    200     private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
    201 
    202     // See static initialization block next to #get(Named)
    203     private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
    204 
    205     @NonNull private final String mName;
    206     @NonNull private final Model mModel;
    207     @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
    208 
    209     /**
    210      * {@usesMathJax}
    211      *
    212      * <p>List of common, named color spaces. A corresponding instance of
    213      * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p>
    214      *
    215      * <pre class="prettyprint">
    216      * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);
    217      * </pre>
    218      *
    219      * <p>The properties of each color space are described below (see {@link #SRGB sRGB}
    220      * for instance). When applicable, the color gamut of each color space is compared
    221      * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram
    222      * shows the location of the color space's primaries and white point.</p>
    223      *
    224      * @see ColorSpace#get(Named)
    225      */
    226     public enum Named {
    227         // NOTE: Do NOT change the order of the enum
    228         /**
    229          * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
    230          * <table summary="Color space definition">
    231          *     <tr>
    232          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    233          *     </tr>
    234          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
    235          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
    236          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    237          *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr>
    238          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    239          *     <tr>
    240          *         <td>Opto-electronic transfer function (OETF)</td>
    241          *         <td colspan="4">\(\begin{equation}
    242          *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\
    243          *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases}
    244          *             \end{equation}\)
    245          *         </td>
    246          *     </tr>
    247          *     <tr>
    248          *         <td>Electro-optical transfer function (EOTF)</td>
    249          *         <td colspan="4">\(\begin{equation}
    250          *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\
    251          *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
    252          *             \end{equation}\)
    253          *         </td>
    254          *     </tr>
    255          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    256          * </table>
    257          * <p>
    258          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
    259          *     <figcaption style="text-align: center;">sRGB</figcaption>
    260          * </p>
    261          */
    262         SRGB,
    263         /**
    264          * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
    265          * <table summary="Color space definition">
    266          *     <tr>
    267          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    268          *     </tr>
    269          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
    270          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
    271          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    272          *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr>
    273          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    274          *     <tr>
    275          *         <td>Opto-electronic transfer function (OETF)</td>
    276          *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
    277          *     </tr>
    278          *     <tr>
    279          *         <td>Electro-optical transfer function (EOTF)</td>
    280          *         <td colspan="4">\(C_{linear} = C_{sRGB}\)</td>
    281          *     </tr>
    282          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    283          * </table>
    284          * <p>
    285          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
    286          *     <figcaption style="text-align: center;">sRGB</figcaption>
    287          * </p>
    288          */
    289         LINEAR_SRGB,
    290         /**
    291          * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p>
    292          * <table summary="Color space definition">
    293          *     <tr>
    294          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    295          *     </tr>
    296          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
    297          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
    298          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    299          *     <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr>
    300          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    301          *     <tr>
    302          *         <td>Opto-electronic transfer function (OETF)</td>
    303          *         <td colspan="4">\(\begin{equation}
    304          *             C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
    305          *                      \left| C_{linear} \right| \lt 0.0031308 \\
    306          *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
    307          *                      \left| C_{linear} \right| \ge 0.0031308 \end{cases}
    308          *             \end{equation}\)
    309          *         </td>
    310          *     </tr>
    311          *     <tr>
    312          *         <td>Electro-optical transfer function (EOTF)</td>
    313          *         <td colspan="4">\(\begin{equation}
    314          *             C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
    315          *                  \left| C_{scRGB} \right| \lt 0.04045 \\
    316          *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
    317          *                  \left| C_{scRGB} \right| \ge 0.04045 \end{cases}
    318          *             \end{equation}\)
    319          *         </td>
    320          *     </tr>
    321          *     <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr>
    322          * </table>
    323          * <p>
    324          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
    325          *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
    326          * </p>
    327          */
    328         EXTENDED_SRGB,
    329         /**
    330          * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p>
    331          * <table summary="Color space definition">
    332          *     <tr>
    333          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    334          *     </tr>
    335          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
    336          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
    337          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    338          *     <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr>
    339          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    340          *     <tr>
    341          *         <td>Opto-electronic transfer function (OETF)</td>
    342          *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
    343          *     </tr>
    344          *     <tr>
    345          *         <td>Electro-optical transfer function (EOTF)</td>
    346          *         <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
    347          *     </tr>
    348          *     <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr>
    349          * </table>
    350          * <p>
    351          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
    352          *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
    353          * </p>
    354          */
    355         LINEAR_EXTENDED_SRGB,
    356         /**
    357          * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p>
    358          * <table summary="Color space definition">
    359          *     <tr>
    360          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    361          *     </tr>
    362          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
    363          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
    364          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    365          *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr>
    366          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    367          *     <tr>
    368          *         <td>Opto-electronic transfer function (OETF)</td>
    369          *         <td colspan="4">\(\begin{equation}
    370          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
    371          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
    372          *             \end{equation}\)
    373          *         </td>
    374          *     </tr>
    375          *     <tr>
    376          *         <td>Electro-optical transfer function (EOTF)</td>
    377          *         <td colspan="4">\(\begin{equation}
    378          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
    379          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
    380          *             \end{equation}\)
    381          *         </td>
    382          *     </tr>
    383          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    384          * </table>
    385          * <p>
    386          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" />
    387          *     <figcaption style="text-align: center;">BT.709</figcaption>
    388          * </p>
    389          */
    390         BT709,
    391         /**
    392          * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p>
    393          * <table summary="Color space definition">
    394          *     <tr>
    395          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    396          *     </tr>
    397          *     <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr>
    398          *     <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr>
    399          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    400          *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr>
    401          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    402          *     <tr>
    403          *         <td>Opto-electronic transfer function (OETF)</td>
    404          *         <td colspan="4">\(\begin{equation}
    405          *             C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\
    406          *             1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
    407          *             \end{equation}\)
    408          *         </td>
    409          *     </tr>
    410          *     <tr>
    411          *         <td>Electro-optical transfer function (EOTF)</td>
    412          *         <td colspan="4">\(\begin{equation}
    413          *             C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\
    414          *             \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
    415          *             \end{equation}\)
    416          *         </td>
    417          *     </tr>
    418          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    419          * </table>
    420          * <p>
    421          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" />
    422          *     <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption>
    423          * </p>
    424          */
    425         BT2020,
    426         /**
    427          * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p>
    428          * <table summary="Color space definition">
    429          *     <tr>
    430          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    431          *     </tr>
    432          *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr>
    433          *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr>
    434          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    435          *     <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr>
    436          *     <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr>
    437          *     <tr>
    438          *         <td>Opto-electronic transfer function (OETF)</td>
    439          *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
    440          *     </tr>
    441          *     <tr>
    442          *         <td>Electro-optical transfer function (EOTF)</td>
    443          *         <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td>
    444          *     </tr>
    445          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    446          * </table>
    447          * <p>
    448          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" />
    449          *     <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption>
    450          * </p>
    451          */
    452         DCI_P3,
    453         /**
    454          * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p>
    455          * <table summary="Color space definition">
    456          *     <tr>
    457          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    458          *     </tr>
    459          *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr>
    460          *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr>
    461          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    462          *     <tr><td>Name</td><td colspan="4">Display P3</td></tr>
    463          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    464          *     <tr>
    465          *         <td>Opto-electronic transfer function (OETF)</td>
    466          *         <td colspan="4">\(\begin{equation}
    467          *             C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\
    468          *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases}
    469          *             \end{equation}\)
    470          *         </td>
    471          *     </tr>
    472          *     <tr>
    473          *         <td>Electro-optical transfer function (EOTF)</td>
    474          *         <td colspan="4">\(\begin{equation}
    475          *             C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\
    476          *             \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases}
    477          *             \end{equation}\)
    478          *         </td>
    479          *     </tr>
    480          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    481          * </table>
    482          * <p>
    483          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" />
    484          *     <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption>
    485          * </p>
    486          */
    487         DISPLAY_P3,
    488         /**
    489          * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p>
    490          * <table summary="Color space definition">
    491          *     <tr>
    492          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    493          *     </tr>
    494          *     <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr>
    495          *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr>
    496          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    497          *     <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr>
    498          *     <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr>
    499          *     <tr>
    500          *         <td>Opto-electronic transfer function (OETF)</td>
    501          *         <td colspan="4">\(\begin{equation}
    502          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
    503          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
    504          *             \end{equation}\)
    505          *         </td>
    506          *     </tr>
    507          *     <tr>
    508          *         <td>Electro-optical transfer function (EOTF)</td>
    509          *         <td colspan="4">\(\begin{equation}
    510          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
    511          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
    512          *             \end{equation}\)
    513          *         </td>
    514          *     </tr>
    515          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    516          * </table>
    517          * <p>
    518          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" />
    519          *     <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption>
    520          * </p>
    521          */
    522         NTSC_1953,
    523         /**
    524          * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p>
    525          * <table summary="Color space definition">
    526          *     <tr>
    527          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    528          *     </tr>
    529          *     <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr>
    530          *     <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr>
    531          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    532          *     <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr>
    533          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    534          *     <tr>
    535          *         <td>Opto-electronic transfer function (OETF)</td>
    536          *         <td colspan="4">\(\begin{equation}
    537          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
    538          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
    539          *             \end{equation}\)
    540          *         </td>
    541          *     </tr>
    542          *     <tr>
    543          *         <td>Electro-optical transfer function (EOTF)</td>
    544          *         <td colspan="4">\(\begin{equation}
    545          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
    546          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
    547          *             \end{equation}\)
    548          *         </td>
    549          *     </tr>
    550          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    551          * </table>
    552          * <p>
    553          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" />
    554          *     <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption>
    555          * </p>
    556          */
    557         SMPTE_C,
    558         /**
    559          * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p>
    560          * <table summary="Color space definition">
    561          *     <tr>
    562          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    563          *     </tr>
    564          *     <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr>
    565          *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr>
    566          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    567          *     <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr>
    568          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
    569          *     <tr>
    570          *         <td>Opto-electronic transfer function (OETF)</td>
    571          *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
    572          *     </tr>
    573          *     <tr>
    574          *         <td>Electro-optical transfer function (EOTF)</td>
    575          *         <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td>
    576          *     </tr>
    577          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    578          * </table>
    579          * <p>
    580          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" />
    581          *     <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption>
    582          * </p>
    583          */
    584         ADOBE_RGB,
    585         /**
    586          * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p>
    587          * <table summary="Color space definition">
    588          *     <tr>
    589          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    590          *     </tr>
    591          *     <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr>
    592          *     <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr>
    593          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    594          *     <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr>
    595          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
    596          *     <tr>
    597          *         <td>Opto-electronic transfer function (OETF)</td>
    598          *         <td colspan="4">\(\begin{equation}
    599          *             C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\
    600          *             C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
    601          *             \end{equation}\)
    602          *         </td>
    603          *     </tr>
    604          *     <tr>
    605          *         <td>Electro-optical transfer function (EOTF)</td>
    606          *         <td colspan="4">\(\begin{equation}
    607          *             C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\
    608          *             C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
    609          *             \end{equation}\)
    610          *         </td>
    611          *     </tr>
    612          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
    613          * </table>
    614          * <p>
    615          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" />
    616          *     <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption>
    617          * </p>
    618          */
    619         PRO_PHOTO_RGB,
    620         /**
    621          * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p>
    622          * <table summary="Color space definition">
    623          *     <tr>
    624          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    625          *     </tr>
    626          *     <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr>
    627          *     <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr>
    628          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    629          *     <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr>
    630          *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
    631          *     <tr>
    632          *         <td>Opto-electronic transfer function (OETF)</td>
    633          *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
    634          *     </tr>
    635          *     <tr>
    636          *         <td>Electro-optical transfer function (EOTF)</td>
    637          *         <td colspan="4">\(C_{linear} = C_{ACES}\)</td>
    638          *     </tr>
    639          *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
    640          * </table>
    641          * <p>
    642          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" />
    643          *     <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption>
    644          * </p>
    645          */
    646         ACES,
    647         /**
    648          * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p>
    649          * <table summary="Color space definition">
    650          *     <tr>
    651          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
    652          *     </tr>
    653          *     <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr>
    654          *     <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr>
    655          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    656          *     <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr>
    657          *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
    658          *     <tr>
    659          *         <td>Opto-electronic transfer function (OETF)</td>
    660          *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
    661          *     </tr>
    662          *     <tr>
    663          *         <td>Electro-optical transfer function (EOTF)</td>
    664          *         <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td>
    665          *     </tr>
    666          *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
    667          * </table>
    668          * <p>
    669          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" />
    670          *     <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption>
    671          * </p>
    672          */
    673         ACESCG,
    674         /**
    675          * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard
    676          * illuminant D50 as its white point.</p>
    677          * <table summary="Color space definition">
    678          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    679          *     <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr>
    680          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
    681          *     <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr>
    682          * </table>
    683          */
    684         CIE_XYZ,
    685         /**
    686          * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50
    687          * as a profile conversion space.</p>
    688          * <table summary="Color space definition">
    689          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
    690          *     <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr>
    691          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
    692          *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
    693          * </table>
    694          */
    695         CIE_LAB
    696         // Update the initialization block next to #get(Named) when adding new values
    697     }
    698 
    699     /**
    700      * <p>A render intent determines how a {@link ColorSpace.Connector connector}
    701      * maps colors from one color space to another. The choice of mapping is
    702      * important when the source color space has a larger color gamut than the
    703      * destination color space.</p>
    704      *
    705      * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
    706      */
    707     public enum RenderIntent {
    708         /**
    709          * <p>Compresses the source gamut into the destination gamut.
    710          * This render intent affects all colors, inside and outside
    711          * of destination gamut. The goal of this render intent is
    712          * to preserve the visual relationship between colors.</p>
    713          *
    714          * <p class="note">This render intent is currently not
    715          * implemented and behaves like {@link #RELATIVE}.</p>
    716          */
    717         PERCEPTUAL,
    718         /**
    719          * Similar to the {@link #ABSOLUTE} render intent, this render
    720          * intent matches the closest color in the destination gamut
    721          * but makes adjustments for the destination white point.
    722          */
    723         RELATIVE,
    724         /**
    725          * <p>Attempts to maintain the relative saturation of colors
    726          * from the source gamut to the destination gamut, to keep
    727          * highly saturated colors as saturated as possible.</p>
    728          *
    729          * <p class="note">This render intent is currently not
    730          * implemented and behaves like {@link #RELATIVE}.</p>
    731          */
    732         SATURATION,
    733         /**
    734          * Colors that are in the destination gamut are left unchanged.
    735          * Colors that fall outside of the destination gamut are mapped
    736          * to the closest possible color within the gamut of the destination
    737          * color space (they are clipped).
    738          */
    739         ABSOLUTE
    740     }
    741 
    742     /**
    743      * {@usesMathJax}
    744      *
    745      * <p>List of adaptation matrices that can be used for chromatic adaptation
    746      * using the von Kries transform. These matrices are used to convert values
    747      * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p>
    748      *
    749      * <p>Given an adaptation matrix \(A\), the conversion from XYZ to
    750      * LMS is straightforward:</p>
    751      *
    752      * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] =
    753      * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$
    754      *
    755      * <p>The complete von Kries transform \(T\) uses a diagonal matrix
    756      * noted \(D\) to perform the adaptation in LMS space. In addition
    757      * to \(A\) and \(D\), the source white point \(W1\) and the destination
    758      * white point \(W2\) must be specified:</p>
    759      *
    760      * $$\begin{align*}
    761      * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
    762      *      A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\
    763      * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
    764      *      A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\
    765      * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\
    766      *      0 & \frac{M_2}{M_1} & 0 \\
    767      *      0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\
    768      * T &= A^{-1}.D.A
    769      * \end{align*}$$
    770      *
    771      * <p>As an example, the resulting matrix \(T\) can then be used to
    772      * perform the chromatic adaptation of sRGB XYZ transform from D65
    773      * to D50:</p>
    774      *
    775      * $$sRGB_{D50} = T.sRGB_{D65}$$
    776      *
    777      * @see ColorSpace.Connector
    778      * @see ColorSpace#connect(ColorSpace, ColorSpace)
    779      */
    780     public enum Adaptation {
    781         /**
    782          * Bradford chromatic adaptation transform, as defined in the
    783          * CIECAM97s color appearance model.
    784          */
    785         BRADFORD(new float[] {
    786                  0.8951f, -0.7502f,  0.0389f,
    787                  0.2664f,  1.7135f, -0.0685f,
    788                 -0.1614f,  0.0367f,  1.0296f
    789         }),
    790         /**
    791          * von Kries chromatic adaptation transform.
    792          */
    793         VON_KRIES(new float[] {
    794                  0.40024f, -0.22630f, 0.00000f,
    795                  0.70760f,  1.16532f, 0.00000f,
    796                 -0.08081f,  0.04570f, 0.91822f
    797         }),
    798         /**
    799          * CIECAT02 chromatic adaption transform, as defined in the
    800          * CIECAM02 color appearance model.
    801          */
    802         CIECAT02(new float[] {
    803                  0.7328f, -0.7036f,  0.0030f,
    804                  0.4296f,  1.6975f,  0.0136f,
    805                 -0.1624f,  0.0061f,  0.9834f
    806         });
    807 
    808         final float[] mTransform;
    809 
    810         Adaptation(@NonNull @Size(9) float[] transform) {
    811             mTransform = transform;
    812         }
    813     }
    814 
    815     /**
    816      * A color model is required by a {@link ColorSpace} to describe the
    817      * way colors can be represented as tuples of numbers. A common color
    818      * model is the {@link #RGB RGB} color model which defines a color
    819      * as represented by a tuple of 3 numbers (red, green and blue).
    820      */
    821     public enum Model {
    822         /**
    823          * The RGB model is a color model with 3 components that
    824          * refer to the three additive primiaries: red, green
    825          * andd blue.
    826          */
    827         RGB(3),
    828         /**
    829          * The XYZ model is a color model with 3 components that
    830          * are used to model human color vision on a basic sensory
    831          * level.
    832          */
    833         XYZ(3),
    834         /**
    835          * The Lab model is a color model with 3 components used
    836          * to describe a color space that is more perceptually
    837          * uniform than XYZ.
    838          */
    839         LAB(3),
    840         /**
    841          * The CMYK model is a color model with 4 components that
    842          * refer to four inks used in color printing: cyan, magenta,
    843          * yellow and black (or key). CMYK is a subtractive color
    844          * model.
    845          */
    846         CMYK(4);
    847 
    848         private final int mComponentCount;
    849 
    850         Model(@IntRange(from = 1, to = 4) int componentCount) {
    851             mComponentCount = componentCount;
    852         }
    853 
    854         /**
    855          * Returns the number of components for this color model.
    856          *
    857          * @return An integer between 1 and 4
    858          */
    859         @IntRange(from = 1, to = 4)
    860         public int getComponentCount() {
    861             return mComponentCount;
    862         }
    863     }
    864 
    865     private ColorSpace(
    866             @NonNull String name,
    867             @NonNull Model model,
    868             @IntRange(from = MIN_ID, to = MAX_ID) int id) {
    869 
    870         if (name == null || name.length() < 1) {
    871             throw new IllegalArgumentException("The name of a color space cannot be null and " +
    872                     "must contain at least 1 character");
    873         }
    874 
    875         if (model == null) {
    876             throw new IllegalArgumentException("A color space must have a model");
    877         }
    878 
    879         if (id < MIN_ID || id > MAX_ID) {
    880             throw new IllegalArgumentException("The id must be between " +
    881                     MIN_ID + " and " + MAX_ID);
    882         }
    883 
    884         mName = name;
    885         mModel = model;
    886         mId = id;
    887     }
    888 
    889     /**
    890      * <p>Returns the name of this color space. The name is never null
    891      * and contains always at least 1 character.</p>
    892      *
    893      * <p>Color space names are recommended to be unique but are not
    894      * guaranteed to be. There is no defined format but the name usually
    895      * falls in one of the following categories:</p>
    896      * <ul>
    897      *     <li>Generic names used to identify color spaces in non-RGB
    898      *     color models. For instance: {@link Named#CIE_LAB Generic L*a*b*}.</li>
    899      *     <li>Names tied to a particular specification. For instance:
    900      *     {@link Named#SRGB sRGB IEC61966-2.1} or
    901      *     {@link Named#ACES SMPTE ST 2065-1:2012 ACES}.</li>
    902      *     <li>Ad-hoc names, often generated procedurally or by the user
    903      *     during a calibration workflow. These names often contain the
    904      *     make and model of the display.</li>
    905      * </ul>
    906      *
    907      * <p>Because the format of color space names is not defined, it is
    908      * not recommended to programmatically identify a color space by its
    909      * name alone. Names can be used as a first approximation.</p>
    910      *
    911      * <p>It is however perfectly acceptable to display color space names to
    912      * users in a UI, or in debuggers and logs. When displaying a color space
    913      * name to the user, it is recommended to add extra information to avoid
    914      * ambiguities: color model, a representation of the color space's gamut,
    915      * white point, etc.</p>
    916      *
    917      * @return A non-null String of length >= 1
    918      */
    919     @NonNull
    920     public String getName() {
    921         return mName;
    922     }
    923 
    924     /**
    925      * Returns the ID of this color space. Positive IDs match the color
    926      * spaces enumerated in {@link Named}. A negative ID indicates a
    927      * color space created by calling one of the public constructors.
    928      *
    929      * @return An integer between {@link #MIN_ID} and {@link #MAX_ID}
    930      */
    931     @IntRange(from = MIN_ID, to = MAX_ID)
    932     public int getId() {
    933         return mId;
    934     }
    935 
    936     /**
    937      * Return the color model of this color space.
    938      *
    939      * @return A non-null {@link Model}
    940      *
    941      * @see Model
    942      * @see #getComponentCount()
    943      */
    944     @NonNull
    945     public Model getModel() {
    946         return mModel;
    947     }
    948 
    949     /**
    950      * Returns the number of components that form a color value according
    951      * to this color space's color model.
    952      *
    953      * @return An integer between 1 and 4
    954      *
    955      * @see Model
    956      * @see #getModel()
    957      */
    958     @IntRange(from = 1, to = 4)
    959     public int getComponentCount() {
    960         return mModel.getComponentCount();
    961     }
    962 
    963     /**
    964      * Returns whether this color space is a wide-gamut color space.
    965      * An RGB color space is wide-gamut if its gamut entirely contains
    966      * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is
    967      * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC}
    968      * gamut.
    969      *
    970      * @return True if this color space is a wide-gamut color space,
    971      *         false otherwise
    972      */
    973     public abstract boolean isWideGamut();
    974 
    975     /**
    976      * <p>Indicates whether this color space is the sRGB color space or
    977      * equivalent to the sRGB color space.</p>
    978      * <p>A color space is considered sRGB if it meets all the following
    979      * conditions:</p>
    980      * <ul>
    981      *     <li>Its color model is {@link Model#RGB}.</li>
    982      *     <li>
    983      *         Its primaries are within 1e-3 of the true
    984      *         {@link Named#SRGB sRGB} primaries.
    985      *     </li>
    986      *     <li>
    987      *         Its white point is withing 1e-3 of the CIE standard
    988      *         illuminant {@link #ILLUMINANT_D65 D65}.
    989      *     </li>
    990      *     <li>Its opto-electronic transfer function is not linear.</li>
    991      *     <li>Its electro-optical transfer function is not linear.</li>
    992      *     <li>Its range is \([0..1]\).</li>
    993      * </ul>
    994      * <p>This method always returns true for {@link Named#SRGB}.</p>
    995      *
    996      * @return True if this color space is the sRGB color space (or a
    997      *         close approximation), false otherwise
    998      */
    999     public boolean isSrgb() {
   1000         return false;
   1001     }
   1002 
   1003     /**
   1004      * Returns the minimum valid value for the specified component of this
   1005      * color space's color model.
   1006      *
   1007      * @param component The index of the component
   1008      * @return A floating point value less than {@link #getMaxValue(int)}
   1009      *
   1010      * @see #getMaxValue(int)
   1011      * @see Model#getComponentCount()
   1012      */
   1013     public abstract float getMinValue(@IntRange(from = 0, to = 3) int component);
   1014 
   1015     /**
   1016      * Returns the maximum valid value for the specified component of this
   1017      * color space's color model.
   1018      *
   1019      * @param component The index of the component
   1020      * @return A floating point value greater than {@link #getMinValue(int)}
   1021      *
   1022      * @see #getMinValue(int)
   1023      * @see Model#getComponentCount()
   1024      */
   1025     public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component);
   1026 
   1027     /**
   1028      * <p>Converts a color value from this color space's model to
   1029      * tristimulus CIE XYZ values. If the color model of this color
   1030      * space is not {@link Model#RGB RGB}, it is assumed that the
   1031      * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
   1032      * standard illuminant.</p>
   1033      *
   1034      * <p>This method is a convenience for color spaces with a model
   1035      * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB}
   1036      * for instance). With color spaces using fewer or more components,
   1037      * use {@link #toXyz(float[])} instead</p>.
   1038      *
   1039      * @param r The first component of the value to convert from (typically R in RGB)
   1040      * @param g The second component of the value to convert from (typically G in RGB)
   1041      * @param b The third component of the value to convert from (typically B in RGB)
   1042      * @return A new array of 3 floats, containing tristimulus XYZ values
   1043      *
   1044      * @see #toXyz(float[])
   1045      * @see #fromXyz(float, float, float)
   1046      */
   1047     @NonNull
   1048     @Size(3)
   1049     public float[] toXyz(float r, float g, float b) {
   1050         return toXyz(new float[] { r, g, b });
   1051     }
   1052 
   1053     /**
   1054      * <p>Converts a color value from this color space's model to
   1055      * tristimulus CIE XYZ values. If the color model of this color
   1056      * space is not {@link Model#RGB RGB}, it is assumed that the
   1057      * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
   1058      * standard illuminant.</p>
   1059      *
   1060      * <p class="note">The specified array's length  must be at least
   1061      * equal to to the number of color components as returned by
   1062      * {@link Model#getComponentCount()}.</p>
   1063      *
   1064      * @param v An array of color components containing the color space's
   1065      *          color value to convert to XYZ, and large enough to hold
   1066      *          the resulting tristimulus XYZ values
   1067      * @return The array passed in parameter
   1068      *
   1069      * @see #toXyz(float, float, float)
   1070      * @see #fromXyz(float[])
   1071      */
   1072     @NonNull
   1073     @Size(min = 3)
   1074     public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v);
   1075 
   1076     /**
   1077      * <p>Converts tristimulus values from the CIE XYZ space to this
   1078      * color space's color model.</p>
   1079      *
   1080      * @param x The X component of the color value
   1081      * @param y The Y component of the color value
   1082      * @param z The Z component of the color value
   1083      * @return A new array whose size is equal to the number of color
   1084      *         components as returned by {@link Model#getComponentCount()}
   1085      *
   1086      * @see #fromXyz(float[])
   1087      * @see #toXyz(float, float, float)
   1088      */
   1089     @NonNull
   1090     @Size(min = 3)
   1091     public float[] fromXyz(float x, float y, float z) {
   1092         float[] xyz = new float[mModel.getComponentCount()];
   1093         xyz[0] = x;
   1094         xyz[1] = y;
   1095         xyz[2] = z;
   1096         return fromXyz(xyz);
   1097     }
   1098 
   1099     /**
   1100      * <p>Converts tristimulus values from the CIE XYZ space to this color
   1101      * space's color model. The resulting value is passed back in the specified
   1102      * array.</p>
   1103      *
   1104      * <p class="note">The specified array's length  must be at least equal to
   1105      * to the number of color components as returned by
   1106      * {@link Model#getComponentCount()}, and its first 3 values must
   1107      * be the XYZ components to convert from.</p>
   1108      *
   1109      * @param v An array of color components containing the XYZ values
   1110      *          to convert from, and large enough to hold the number
   1111      *          of components of this color space's model
   1112      * @return The array passed in parameter
   1113      *
   1114      * @see #fromXyz(float, float, float)
   1115      * @see #toXyz(float[])
   1116      */
   1117     @NonNull
   1118     @Size(min = 3)
   1119     public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v);
   1120 
   1121     /**
   1122      * <p>Returns a string representation of the object. This method returns
   1123      * a string equal to the value of:</p>
   1124      *
   1125      * <pre class="prettyprint">
   1126      * getName() + "(id=" + getId() + ", model=" + getModel() + ")"
   1127      * </pre>
   1128      *
   1129      * <p>For instance, the string representation of the {@link Named#SRGB sRGB}
   1130      * color space is equal to the following value:</p>
   1131      *
   1132      * <pre>
   1133      * sRGB IEC61966-2.1 (id=0, model=RGB)
   1134      * </pre>
   1135      *
   1136      * @return A string representation of the object
   1137      */
   1138     @Override
   1139     @NonNull
   1140     public String toString() {
   1141         return mName + " (id=" + mId + ", model=" + mModel + ")";
   1142     }
   1143 
   1144     @Override
   1145     public boolean equals(Object o) {
   1146         if (this == o) return true;
   1147         if (o == null || getClass() != o.getClass()) return false;
   1148 
   1149         ColorSpace that = (ColorSpace) o;
   1150 
   1151         if (mId != that.mId) return false;
   1152         //noinspection SimplifiableIfStatement
   1153         if (!mName.equals(that.mName)) return false;
   1154         return mModel == that.mModel;
   1155 
   1156     }
   1157 
   1158     @Override
   1159     public int hashCode() {
   1160         int result = mName.hashCode();
   1161         result = 31 * result + mModel.hashCode();
   1162         result = 31 * result + mId;
   1163         return result;
   1164     }
   1165 
   1166     /**
   1167      * <p>Connects two color spaces to allow conversion from the source color
   1168      * space to the destination color space. If the source and destination
   1169      * color spaces do not have the same profile connection space (CIE XYZ
   1170      * with the same white point), they are chromatically adapted to use the
   1171      * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
   1172      *
   1173      * <p>If the source and destination are the same, an optimized connector
   1174      * is returned to avoid unnecessary computations and loss of precision.</p>
   1175      *
   1176      * <p>Colors are mapped from the source color space to the destination color
   1177      * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
   1178      *
   1179      * @param source The color space to convert colors from
   1180      * @param destination The color space to convert colors to
   1181      * @return A non-null connector between the two specified color spaces
   1182      *
   1183      * @see #connect(ColorSpace)
   1184      * @see #connect(ColorSpace, RenderIntent)
   1185      * @see #connect(ColorSpace, ColorSpace, RenderIntent)
   1186      */
   1187     @NonNull
   1188     public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) {
   1189         return connect(source, destination, RenderIntent.PERCEPTUAL);
   1190     }
   1191 
   1192     /**
   1193      * <p>Connects two color spaces to allow conversion from the source color
   1194      * space to the destination color space. If the source and destination
   1195      * color spaces do not have the same profile connection space (CIE XYZ
   1196      * with the same white point), they are chromatically adapted to use the
   1197      * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
   1198      *
   1199      * <p>If the source and destination are the same, an optimized connector
   1200      * is returned to avoid unnecessary computations and loss of precision.</p>
   1201      *
   1202      * @param source The color space to convert colors from
   1203      * @param destination The color space to convert colors to
   1204      * @param intent The render intent to map colors from the source to the destination
   1205      * @return A non-null connector between the two specified color spaces
   1206      *
   1207      * @see #connect(ColorSpace)
   1208      * @see #connect(ColorSpace, RenderIntent)
   1209      * @see #connect(ColorSpace, ColorSpace)
   1210      */
   1211     @NonNull
   1212     @SuppressWarnings("ConstantConditions")
   1213     public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination,
   1214             @NonNull RenderIntent intent) {
   1215         if (source.equals(destination)) return Connector.identity(source);
   1216 
   1217         if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) {
   1218             return new Connector.Rgb((Rgb) source, (Rgb) destination, intent);
   1219         }
   1220 
   1221         return new Connector(source, destination, intent);
   1222     }
   1223 
   1224     /**
   1225      * <p>Connects the specified color spaces to sRGB.
   1226      * If the source color space does not use CIE XYZ D65 as its profile
   1227      * connection space, the two spaces are chromatically adapted to use the
   1228      * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
   1229      *
   1230      * <p>If the source is the sRGB color space, an optimized connector
   1231      * is returned to avoid unnecessary computations and loss of precision.</p>
   1232      *
   1233      * <p>Colors are mapped from the source color space to the destination color
   1234      * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
   1235      *
   1236      * @param source The color space to convert colors from
   1237      * @return A non-null connector between the specified color space and sRGB
   1238      *
   1239      * @see #connect(ColorSpace, RenderIntent)
   1240      * @see #connect(ColorSpace, ColorSpace)
   1241      * @see #connect(ColorSpace, ColorSpace, RenderIntent)
   1242      */
   1243     @NonNull
   1244     public static Connector connect(@NonNull ColorSpace source) {
   1245         return connect(source, RenderIntent.PERCEPTUAL);
   1246     }
   1247 
   1248     /**
   1249      * <p>Connects the specified color spaces to sRGB.
   1250      * If the source color space does not use CIE XYZ D65 as its profile
   1251      * connection space, the two spaces are chromatically adapted to use the
   1252      * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
   1253      *
   1254      * <p>If the source is the sRGB color space, an optimized connector
   1255      * is returned to avoid unnecessary computations and loss of precision.</p>
   1256      *
   1257      * @param source The color space to convert colors from
   1258      * @param intent The render intent to map colors from the source to the destination
   1259      * @return A non-null connector between the specified color space and sRGB
   1260      *
   1261      * @see #connect(ColorSpace)
   1262      * @see #connect(ColorSpace, ColorSpace)
   1263      * @see #connect(ColorSpace, ColorSpace, RenderIntent)
   1264      */
   1265     @NonNull
   1266     public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) {
   1267         if (source.isSrgb()) return Connector.identity(source);
   1268 
   1269         if (source.getModel() == Model.RGB) {
   1270             return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent);
   1271         }
   1272 
   1273         return new Connector(source, get(Named.SRGB), intent);
   1274     }
   1275 
   1276     /**
   1277      * <p>Performs the chromatic adaptation of a color space from its native
   1278      * white point to the specified white point.</p>
   1279      *
   1280      * <p>The chromatic adaptation is performed using the
   1281      * {@link Adaptation#BRADFORD} matrix.</p>
   1282      *
   1283      * <p class="note">The color space returned by this method always has
   1284      * an ID of {@link #MIN_ID}.</p>
   1285      *
   1286      * @param colorSpace The color space to chromatically adapt
   1287      * @param whitePoint The new white point
   1288      * @return A {@link ColorSpace} instance with the same name, primaries,
   1289      *         transfer functions and range as the specified color space
   1290      *
   1291      * @see Adaptation
   1292      * @see #adapt(ColorSpace, float[], Adaptation)
   1293      */
   1294     @NonNull
   1295     public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
   1296             @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
   1297         return adapt(colorSpace, whitePoint, Adaptation.BRADFORD);
   1298     }
   1299 
   1300     /**
   1301      * <p>Performs the chromatic adaptation of a color space from its native
   1302      * white point to the specified white point. If the specified color space
   1303      * does not have an {@link Model#RGB RGB} color model, or if the color
   1304      * space already has the target white point, the color space is returned
   1305      * unmodified.</p>
   1306      *
   1307      * <p>The chromatic adaptation is performed using the von Kries method
   1308      * described in the documentation of {@link Adaptation}.</p>
   1309      *
   1310      * <p class="note">The color space returned by this method always has
   1311      * an ID of {@link #MIN_ID}.</p>
   1312      *
   1313      * @param colorSpace The color space to chromatically adapt
   1314      * @param whitePoint The new white point
   1315      * @param adaptation The adaptation matrix
   1316      * @return A new color space if the specified color space has an RGB
   1317      *         model and a white point different from the specified white
   1318      *         point; the specified color space otherwise
   1319      *
   1320      * @see Adaptation
   1321      * @see #adapt(ColorSpace, float[])
   1322      */
   1323     @NonNull
   1324     public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
   1325             @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   1326             @NonNull Adaptation adaptation) {
   1327         if (colorSpace.getModel() == Model.RGB) {
   1328             ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
   1329             if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace;
   1330 
   1331             float[] xyz = whitePoint.length == 3 ?
   1332                     Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint);
   1333             float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform,
   1334                     xyYToXyz(rgb.getWhitePoint()), xyz);
   1335             float[] transform = mul3x3(adaptationTransform, rgb.mTransform);
   1336 
   1337             return new ColorSpace.Rgb(rgb, transform, whitePoint);
   1338         }
   1339         return colorSpace;
   1340     }
   1341 
   1342     /**
   1343      * <p>Returns an instance of {@link ColorSpace} whose ID matches the
   1344      * specified ID.</p>
   1345      *
   1346      * <p>This method always returns the same instance for a given ID.</p>
   1347      *
   1348      * <p>This method is thread-safe.</p>
   1349      *
   1350      * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID}
   1351      * @return A non-null {@link ColorSpace} instance
   1352      * @throws IllegalArgumentException If the ID does not match the ID of one of the
   1353      *         {@link Named named color spaces}
   1354      */
   1355     @NonNull
   1356     static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
   1357         if (index < 0 || index > Named.values().length) {
   1358             throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
   1359                     Named.values().length + "]");
   1360         }
   1361         return sNamedColorSpaces[index];
   1362     }
   1363 
   1364     /**
   1365      * <p>Returns an instance of {@link ColorSpace} identified by the specified
   1366      * name. The list of names provided in the {@link Named} enum gives access
   1367      * to a variety of common RGB color spaces.</p>
   1368      *
   1369      * <p>This method always returns the same instance for a given name.</p>
   1370      *
   1371      * <p>This method is thread-safe.</p>
   1372      *
   1373      * @param name The name of the color space to get an instance of
   1374      * @return A non-null {@link ColorSpace} instance
   1375      */
   1376     @NonNull
   1377     public static ColorSpace get(@NonNull Named name) {
   1378         return sNamedColorSpaces[name.ordinal()];
   1379     }
   1380 
   1381     /**
   1382      * <p>Returns a {@link Named} instance of {@link ColorSpace} that matches
   1383      * the specified RGB to CIE XYZ transform and transfer functions. If no
   1384      * instance can be found, this method returns null.</p>
   1385      *
   1386      * <p>The color transform matrix is assumed to target the CIE XYZ space
   1387      * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p>
   1388      *
   1389      * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile
   1390      *                 connection space CIE XYZ as an array of 9 floats, cannot be null
   1391      * @param function Parameters for the transfer functions
   1392      * @return A non-null {@link ColorSpace} if a match is found, null otherwise
   1393      */
   1394     @Nullable
   1395     public static ColorSpace match(
   1396             @NonNull @Size(9) float[] toXYZD50,
   1397             @NonNull Rgb.TransferParameters function) {
   1398 
   1399         for (ColorSpace colorSpace : sNamedColorSpaces) {
   1400             if (colorSpace.getModel() == Model.RGB) {
   1401                 ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ);
   1402                 if (compare(toXYZD50, rgb.mTransform) &&
   1403                         compare(function, rgb.mTransferParameters)) {
   1404                     return colorSpace;
   1405                 }
   1406             }
   1407         }
   1408 
   1409         return null;
   1410     }
   1411 
   1412     /**
   1413      * <p>Creates a new {@link Renderer} that can be used to visualize and
   1414      * debug color spaces. See the documentation of {@link Renderer} for
   1415      * more information.</p>
   1416      *
   1417      * @return A new non-null {@link Renderer} instance
   1418      *
   1419      * @see Renderer
   1420      *
   1421      * @hide
   1422      */
   1423     @NonNull
   1424     public static Renderer createRenderer() {
   1425         return new Renderer();
   1426     }
   1427 
   1428     static {
   1429         sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
   1430                 "sRGB IEC61966-2.1",
   1431                 SRGB_PRIMARIES,
   1432                 ILLUMINANT_D65,
   1433                 new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
   1434                 Named.SRGB.ordinal()
   1435         );
   1436         sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
   1437                 "sRGB IEC61966-2.1 (Linear)",
   1438                 SRGB_PRIMARIES,
   1439                 ILLUMINANT_D65,
   1440                 1.0,
   1441                 0.0f, 1.0f,
   1442                 Named.LINEAR_SRGB.ordinal()
   1443         );
   1444         sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
   1445                 "scRGB-nl IEC 61966-2-2:2003",
   1446                 SRGB_PRIMARIES,
   1447                 ILLUMINANT_D65,
   1448                 x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
   1449                 x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
   1450                 -0.799f, 2.399f,
   1451                 Named.EXTENDED_SRGB.ordinal()
   1452         );
   1453         sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
   1454                 "scRGB IEC 61966-2-2:2003",
   1455                 SRGB_PRIMARIES,
   1456                 ILLUMINANT_D65,
   1457                 1.0,
   1458                 -0.5f, 7.499f,
   1459                 Named.LINEAR_EXTENDED_SRGB.ordinal()
   1460         );
   1461         sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
   1462                 "Rec. ITU-R BT.709-5",
   1463                 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
   1464                 ILLUMINANT_D65,
   1465                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
   1466                 Named.BT709.ordinal()
   1467         );
   1468         sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
   1469                 "Rec. ITU-R BT.2020-1",
   1470                 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
   1471                 ILLUMINANT_D65,
   1472                 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
   1473                 Named.BT2020.ordinal()
   1474         );
   1475         sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
   1476                 "SMPTE RP 431-2-2007 DCI (P3)",
   1477                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
   1478                 new float[] { 0.314f, 0.351f },
   1479                 2.6,
   1480                 0.0f, 1.0f,
   1481                 Named.DCI_P3.ordinal()
   1482         );
   1483         sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
   1484                 "Display P3",
   1485                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
   1486                 ILLUMINANT_D65,
   1487                 new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4),
   1488                 Named.DISPLAY_P3.ordinal()
   1489         );
   1490         sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
   1491                 "NTSC (1953)",
   1492                 NTSC_1953_PRIMARIES,
   1493                 ILLUMINANT_C,
   1494                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
   1495                 Named.NTSC_1953.ordinal()
   1496         );
   1497         sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
   1498                 "SMPTE-C RGB",
   1499                 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
   1500                 ILLUMINANT_D65,
   1501                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
   1502                 Named.SMPTE_C.ordinal()
   1503         );
   1504         sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
   1505                 "Adobe RGB (1998)",
   1506                 new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
   1507                 ILLUMINANT_D65,
   1508                 2.2,
   1509                 0.0f, 1.0f,
   1510                 Named.ADOBE_RGB.ordinal()
   1511         );
   1512         sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
   1513                 "ROMM RGB ISO 22028-2:2013",
   1514                 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
   1515                 ILLUMINANT_D50,
   1516                 new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8),
   1517                 Named.PRO_PHOTO_RGB.ordinal()
   1518         );
   1519         sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
   1520                 "SMPTE ST 2065-1:2012 ACES",
   1521                 new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
   1522                 ILLUMINANT_D60,
   1523                 1.0,
   1524                 -65504.0f, 65504.0f,
   1525                 Named.ACES.ordinal()
   1526         );
   1527         sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
   1528                 "Academy S-2014-004 ACEScg",
   1529                 new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
   1530                 ILLUMINANT_D60,
   1531                 1.0,
   1532                 -65504.0f, 65504.0f,
   1533                 Named.ACESCG.ordinal()
   1534         );
   1535         sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
   1536                 "Generic XYZ",
   1537                 Named.CIE_XYZ.ordinal()
   1538         );
   1539         sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
   1540                 "Generic L*a*b*",
   1541                 Named.CIE_LAB.ordinal()
   1542         );
   1543     }
   1544 
   1545     // Reciprocal piecewise gamma response
   1546     private static double rcpResponse(double x, double a, double b, double c, double d, double g) {
   1547         return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
   1548     }
   1549 
   1550     // Piecewise gamma response
   1551     private static double response(double x, double a, double b, double c, double d, double g) {
   1552         return x >= d ? Math.pow(a * x + b, g) : c * x;
   1553     }
   1554 
   1555     // Reciprocal piecewise gamma response
   1556     private static double rcpResponse(double x, double a, double b, double c, double d,
   1557             double e, double f, double g) {
   1558         return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c;
   1559     }
   1560 
   1561     // Piecewise gamma response
   1562     private static double response(double x, double a, double b, double c, double d,
   1563             double e, double f, double g) {
   1564         return x >= d ? Math.pow(a * x + b, g) + e : c * x + f;
   1565     }
   1566 
   1567     // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
   1568     // spaces that allow negative values
   1569     @SuppressWarnings("SameParameterValue")
   1570     private static double absRcpResponse(double x, double a, double b, double c, double d, double g) {
   1571         return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x);
   1572     }
   1573 
   1574     // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
   1575     // allow negative values
   1576     @SuppressWarnings("SameParameterValue")
   1577     private static double absResponse(double x, double a, double b, double c, double d, double g) {
   1578         return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x);
   1579     }
   1580 
   1581     /**
   1582      * Compares two sets of parametric transfer functions parameters with a precision of 1e-3.
   1583      *
   1584      * @param a The first set of parameters to compare
   1585      * @param b The second set of parameters to compare
   1586      * @return True if the two sets are equal, false otherwise
   1587      */
   1588     private static boolean compare(
   1589             @Nullable Rgb.TransferParameters a,
   1590             @Nullable Rgb.TransferParameters b) {
   1591         //noinspection SimplifiableIfStatement
   1592         if (a == null && b == null) return true;
   1593         return a != null && b != null &&
   1594                 Math.abs(a.a - b.a) < 1e-3 &&
   1595                 Math.abs(a.b - b.b) < 1e-3 &&
   1596                 Math.abs(a.c - b.c) < 1e-3 &&
   1597                 Math.abs(a.d - b.d) < 2e-3 && // Special case for variations in sRGB OETF/EOTF
   1598                 Math.abs(a.e - b.e) < 1e-3 &&
   1599                 Math.abs(a.f - b.f) < 1e-3 &&
   1600                 Math.abs(a.g - b.g) < 1e-3;
   1601     }
   1602 
   1603     /**
   1604      * Compares two arrays of float with a precision of 1e-3.
   1605      *
   1606      * @param a The first array to compare
   1607      * @param b The second array to compare
   1608      * @return True if the two arrays are equal, false otherwise
   1609      */
   1610     private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
   1611         if (a == b) return true;
   1612         for (int i = 0; i < a.length; i++) {
   1613             if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
   1614         }
   1615         return true;
   1616     }
   1617 
   1618     /**
   1619      * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
   1620      *
   1621      * @param m A 3x3 matrix as a non-null array of 9 floats
   1622      * @return A new array of 9 floats containing the inverse of the input matrix
   1623      */
   1624     @NonNull
   1625     @Size(9)
   1626     private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
   1627         float a = m[0];
   1628         float b = m[3];
   1629         float c = m[6];
   1630         float d = m[1];
   1631         float e = m[4];
   1632         float f = m[7];
   1633         float g = m[2];
   1634         float h = m[5];
   1635         float i = m[8];
   1636 
   1637         float A = e * i - f * h;
   1638         float B = f * g - d * i;
   1639         float C = d * h - e * g;
   1640 
   1641         float det = a * A + b * B + c * C;
   1642 
   1643         float inverted[] = new float[m.length];
   1644         inverted[0] = A / det;
   1645         inverted[1] = B / det;
   1646         inverted[2] = C / det;
   1647         inverted[3] = (c * h - b * i) / det;
   1648         inverted[4] = (a * i - c * g) / det;
   1649         inverted[5] = (b * g - a * h) / det;
   1650         inverted[6] = (b * f - c * e) / det;
   1651         inverted[7] = (c * d - a * f) / det;
   1652         inverted[8] = (a * e - b * d) / det;
   1653         return inverted;
   1654     }
   1655 
   1656     /**
   1657      * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
   1658      *
   1659      * @param lhs 3x3 matrix, as a non-null array of 9 floats
   1660      * @param rhs 3x3 matrix, as a non-null array of 9 floats
   1661      * @return A new array of 9 floats containing the result of the multiplication
   1662      *         of rhs by lhs
   1663      */
   1664     @NonNull
   1665     @Size(9)
   1666     private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
   1667         float[] r = new float[9];
   1668         r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
   1669         r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
   1670         r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
   1671         r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
   1672         r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
   1673         r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
   1674         r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
   1675         r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
   1676         r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
   1677         return r;
   1678     }
   1679 
   1680     /**
   1681      * Multiplies a vector of 3 components by a 3x3 matrix and stores the
   1682      * result in the input vector.
   1683      *
   1684      * @param lhs 3x3 matrix, as a non-null array of 9 floats
   1685      * @param rhs Vector of 3 components, as a non-null array of 3 floats
   1686      * @return The array of 3 passed as the rhs parameter
   1687      */
   1688     @NonNull
   1689     @Size(min = 3)
   1690     private static float[] mul3x3Float3(
   1691             @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
   1692         float r0 = rhs[0];
   1693         float r1 = rhs[1];
   1694         float r2 = rhs[2];
   1695         rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
   1696         rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
   1697         rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
   1698         return rhs;
   1699     }
   1700 
   1701     /**
   1702      * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
   1703      * by a 3x3 matrix represented as an array of 9 floats.
   1704      *
   1705      * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
   1706      * @param rhs 3x3 matrix, as a non-null array of 9 floats
   1707      * @return A new array of 9 floats containing the result of the multiplication
   1708      *         of rhs by lhs
   1709      */
   1710     @NonNull
   1711     @Size(9)
   1712     private static float[] mul3x3Diag(
   1713             @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
   1714         return new float[] {
   1715                 lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
   1716                 lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
   1717                 lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
   1718         };
   1719     }
   1720 
   1721     /**
   1722      * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
   1723      * input xyY array only contains the x and y components.
   1724      *
   1725      * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
   1726      * @return A new float array of length 3 containing XYZ values
   1727      */
   1728     @NonNull
   1729     @Size(3)
   1730     private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
   1731         return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
   1732     }
   1733 
   1734     /**
   1735      * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the
   1736      * input xyY array only contains the x and y components. After this method
   1737      * returns, the xyY array contains the converted u and v components.
   1738      *
   1739      * @param xyY The xyY value to convert to XYZ, cannot be null,
   1740      *            length must be a multiple of 2
   1741      */
   1742     private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) {
   1743         for (int i = 0; i < xyY.length; i += 2) {
   1744             float x = xyY[i];
   1745             float y = xyY[i + 1];
   1746 
   1747             float d = -2.0f * x + 12.0f * y + 3;
   1748             float u = (4.0f * x) / d;
   1749             float v = (9.0f * y) / d;
   1750 
   1751             xyY[i] = u;
   1752             xyY[i + 1] = v;
   1753         }
   1754     }
   1755 
   1756     /**
   1757      * <p>Computes the chromatic adaptation transform from the specified
   1758      * source white point to the specified destination white point.</p>
   1759      *
   1760      * <p>The transform is computed using the von Kries method, described
   1761      * in more details in the documentation of {@link Adaptation}. The
   1762      * {@link Adaptation} enum provides different matrices that can be
   1763      * used to perform the adaptation.</p>
   1764      *
   1765      * @param matrix The adaptation matrix
   1766      * @param srcWhitePoint The white point to adapt from, *will be modified*
   1767      * @param dstWhitePoint The white point to adapt to, *will be modified*
   1768      * @return A 3x3 matrix as a non-null array of 9 floats
   1769      */
   1770     @NonNull
   1771     @Size(9)
   1772     private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
   1773             @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
   1774         float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
   1775         float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
   1776         // LMS is a diagonal matrix stored as a float[3]
   1777         float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
   1778         return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
   1779     }
   1780 
   1781     /**
   1782      * Implementation of the CIE XYZ color space. Assumes the white point is D50.
   1783      */
   1784     @AnyThread
   1785     private static final class Xyz extends ColorSpace {
   1786         private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   1787             super(name, Model.XYZ, id);
   1788         }
   1789 
   1790         @Override
   1791         public boolean isWideGamut() {
   1792             return true;
   1793         }
   1794 
   1795         @Override
   1796         public float getMinValue(@IntRange(from = 0, to = 3) int component) {
   1797             return -2.0f;
   1798         }
   1799 
   1800         @Override
   1801         public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
   1802             return 2.0f;
   1803         }
   1804 
   1805         @Override
   1806         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
   1807             v[0] = clamp(v[0]);
   1808             v[1] = clamp(v[1]);
   1809             v[2] = clamp(v[2]);
   1810             return v;
   1811         }
   1812 
   1813         @Override
   1814         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
   1815             v[0] = clamp(v[0]);
   1816             v[1] = clamp(v[1]);
   1817             v[2] = clamp(v[2]);
   1818             return v;
   1819         }
   1820 
   1821         private static float clamp(float x) {
   1822             return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
   1823         }
   1824     }
   1825 
   1826     /**
   1827      * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
   1828      * with a white point of D50.
   1829      */
   1830     @AnyThread
   1831     private static final class Lab extends ColorSpace {
   1832         private static final float A = 216.0f / 24389.0f;
   1833         private static final float B = 841.0f / 108.0f;
   1834         private static final float C = 4.0f / 29.0f;
   1835         private static final float D = 6.0f / 29.0f;
   1836 
   1837         private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   1838             super(name, Model.LAB, id);
   1839         }
   1840 
   1841         @Override
   1842         public boolean isWideGamut() {
   1843             return true;
   1844         }
   1845 
   1846         @Override
   1847         public float getMinValue(@IntRange(from = 0, to = 3) int component) {
   1848             return component == 0 ? 0.0f : -128.0f;
   1849         }
   1850 
   1851         @Override
   1852         public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
   1853             return component == 0 ? 100.0f : 128.0f;
   1854         }
   1855 
   1856         @Override
   1857         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
   1858             v[0] = clamp(v[0], 0.0f, 100.0f);
   1859             v[1] = clamp(v[1], -128.0f, 128.0f);
   1860             v[2] = clamp(v[2], -128.0f, 128.0f);
   1861 
   1862             float fy = (v[0] + 16.0f) / 116.0f;
   1863             float fx = fy + (v[1] * 0.002f);
   1864             float fz = fy - (v[2] * 0.005f);
   1865             float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
   1866             float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
   1867             float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
   1868 
   1869             v[0] = X * ILLUMINANT_D50_XYZ[0];
   1870             v[1] = Y * ILLUMINANT_D50_XYZ[1];
   1871             v[2] = Z * ILLUMINANT_D50_XYZ[2];
   1872 
   1873             return v;
   1874         }
   1875 
   1876         @Override
   1877         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
   1878             float X = v[0] / ILLUMINANT_D50_XYZ[0];
   1879             float Y = v[1] / ILLUMINANT_D50_XYZ[1];
   1880             float Z = v[2] / ILLUMINANT_D50_XYZ[2];
   1881 
   1882             float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
   1883             float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
   1884             float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
   1885 
   1886             float L = 116.0f * fy - 16.0f;
   1887             float a = 500.0f * (fx - fy);
   1888             float b = 200.0f * (fy - fz);
   1889 
   1890             v[0] = clamp(L, 0.0f, 100.0f);
   1891             v[1] = clamp(a, -128.0f, 128.0f);
   1892             v[2] = clamp(b, -128.0f, 128.0f);
   1893 
   1894             return v;
   1895         }
   1896 
   1897         private static float clamp(float x, float min, float max) {
   1898             return x < min ? min : x > max ? max : x;
   1899         }
   1900     }
   1901 
   1902     /**
   1903      * {@usesMathJax}
   1904      *
   1905      * <p>An RGB color space is an additive color space using the
   1906      * {@link Model#RGB RGB} color model (a color is therefore represented
   1907      * by a tuple of 3 numbers).</p>
   1908      *
   1909      * <p>A specific RGB color space is defined by the following properties:</p>
   1910      * <ul>
   1911      *     <li>Three chromaticities of the red, green and blue primaries, which
   1912      *     define the gamut of the color space.</li>
   1913      *     <li>A white point chromaticity that defines the stimulus to which
   1914      *     color space values are normalized (also just called "white").</li>
   1915      *     <li>An opto-electronic transfer function, also called opto-electronic
   1916      *     conversion function or often, and approximately, gamma function.</li>
   1917      *     <li>An electro-optical transfer function, also called electo-optical
   1918      *     conversion function or often, and approximately, gamma function.</li>
   1919      *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
   1920      * </ul>
   1921      *
   1922      * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
   1923      *
   1924      * <h3>Primaries and white point chromaticities</h3>
   1925      * <p>In this implementation, the chromaticity of the primaries and the white
   1926      * point of an RGB color space is defined in the CIE xyY color space. This
   1927      * color space separates the chromaticity of a color, the x and y components,
   1928      * and its luminance, the Y component. Since the primaries and the white
   1929      * point have full brightness, the Y component is assumed to be 1 and only
   1930      * the x and y components are needed to encode them.</p>
   1931      * <p>For convenience, this implementation also allows to define the
   1932      * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
   1933      * are internally converted to xyY.</p>
   1934      *
   1935      * <p>
   1936      *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
   1937      *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
   1938      * </p>
   1939      *
   1940      * <h3>Transfer functions</h3>
   1941      * <p>A transfer function is a color component conversion function, defined as
   1942      * a single variable, monotonic mathematical function. It is applied to each
   1943      * individual component of a color. They are used to perform the mapping
   1944      * between linear tristimulus values and non-linear electronic signal value.</p>
   1945      * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
   1946      * tristimulus values in a scene to a non-linear electronic signal value.
   1947      * An OETF is often expressed as a power function with an exponent between
   1948      * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
   1949      * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
   1950      * a non-linear electronic signal value to a tristimulus value at the display.
   1951      * An EOTF is often expressed as a power function with an exponent between
   1952      * 1.8 and 2.6.</p>
   1953      * <p>Transfer functions are used as a compression scheme. For instance,
   1954      * linear sRGB values would normally require 11 to 12 bits of precision to
   1955      * store all values that can be perceived by the human eye. When encoding
   1956      * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
   1957      * an exact mathematical description of that OETF), the values can be
   1958      * compressed to only 8 bits precision.</p>
   1959      * <p>When manipulating RGB values, particularly sRGB values, it is safe
   1960      * to assume that these values have been encoded with the appropriate
   1961      * OETF (unless noted otherwise). Encoded values are often said to be in
   1962      * "gamma space". They are therefore defined in a non-linear space. This
   1963      * in turns means that any linear operation applied to these values is
   1964      * going to yield mathematically incorrect results (any linear interpolation
   1965      * such as gradient generation for instance, most image processing functions
   1966      * such as blurs, etc.).</p>
   1967      * <p>To properly process encoded RGB values you must first apply the
   1968      * EOTF to decode the value into linear space. After processing, the RGB
   1969      * value must be encoded back to non-linear ("gamma") space. Here is a
   1970      * formal description of the process, where \(f\) is the processing
   1971      * function to apply:</p>
   1972      *
   1973      * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
   1974      *
   1975      * <p>If the transfer functions of the color space can be expressed as an
   1976      * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters
   1977      * can be retrieved by calling {@link #getTransferParameters()}. This can
   1978      * be useful to match color spaces for instance.</p>
   1979      *
   1980      * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
   1981      * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
   1982      * their transfer functions are the identity function: \(f(x) = x\).
   1983      * If the source and/or destination are known to be linear, it is not
   1984      * necessary to invoke the transfer functions.</p>
   1985      *
   1986      * <h3>Range</h3>
   1987      * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
   1988      * are however a few RGB color spaces that allow much larger ranges. For
   1989      * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
   1990      * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
   1991      * the range \([-65504, 65504]\).</p>
   1992      *
   1993      * <p>
   1994      *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
   1995      *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
   1996      * </p>
   1997      *
   1998      * <h3>Converting between RGB color spaces</h3>
   1999      * <p>Conversion between two color spaces is achieved by using an intermediate
   2000      * color space called the profile connection space (PCS). The PCS used by
   2001      * this implementation is CIE XYZ. The conversion operation is defined
   2002      * as such:</p>
   2003      *
   2004      * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
   2005      *
   2006      * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
   2007      * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
   2008      * XYZ to RGB transform} of the destination color space.</p>
   2009      * <p>Many RGB color spaces commonly used with electronic devices use the
   2010      * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
   2011      * when converting between two RGB color spaces if their white points do not
   2012      * match. This can be achieved by either calling
   2013      * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
   2014      * a single common white point. This can be achieved automatically by calling
   2015      * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
   2016      * non-RGB color spaces.</p>
   2017      * <p>To learn more about the white point adaptation process, refer to the
   2018      * documentation of {@link Adaptation}.</p>
   2019      */
   2020     @AnyThread
   2021     public static class Rgb extends ColorSpace {
   2022         /**
   2023          * {@usesMathJax}
   2024          *
   2025          * <p>Defines the parameters for the ICC parametric curve type 4, as
   2026          * defined in ICC.1:2004-10, section 10.15.</p>
   2027          *
   2028          * <p>The EOTF is of the form:</p>
   2029          *
   2030          * \(\begin{equation}
   2031          * Y = \begin{cases}c X + f & X \lt d \\
   2032          * \left( a X + b \right) ^{g} + e & X \ge d \end{cases}
   2033          * \end{equation}\)
   2034          *
   2035          * <p>The corresponding OETF is simply the inverse function.</p>
   2036          *
   2037          * <p>The parameters defined by this class form a valid transfer
   2038          * function only if all the following conditions are met:</p>
   2039          * <ul>
   2040          *     <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li>
   2041          *     <li>\(d\) is in the range \([0..1]\)</li>
   2042          *     <li>The function is not constant</li>
   2043          *     <li>The function is positive and increasing</li>
   2044          * </ul>
   2045          */
   2046         public static class TransferParameters {
   2047             /** Variable \(a\) in the equation of the EOTF described above. */
   2048             public final double a;
   2049             /** Variable \(b\) in the equation of the EOTF described above. */
   2050             public final double b;
   2051             /** Variable \(c\) in the equation of the EOTF described above. */
   2052             public final double c;
   2053             /** Variable \(d\) in the equation of the EOTF described above. */
   2054             public final double d;
   2055             /** Variable \(e\) in the equation of the EOTF described above. */
   2056             public final double e;
   2057             /** Variable \(f\) in the equation of the EOTF described above. */
   2058             public final double f;
   2059             /** Variable \(g\) in the equation of the EOTF described above. */
   2060             public final double g;
   2061 
   2062             /**
   2063              * <p>Defines the parameters for the ICC parametric curve type 3, as
   2064              * defined in ICC.1:2004-10, section 10.15.</p>
   2065              *
   2066              * <p>The EOTF is of the form:</p>
   2067              *
   2068              * \(\begin{equation}
   2069              * Y = \begin{cases}c X & X \lt d \\
   2070              * \left( a X + b \right) ^{g} & X \ge d \end{cases}
   2071              * \end{equation}\)
   2072              *
   2073              * <p>This constructor is equivalent to setting  \(e\) and \(f\) to 0.</p>
   2074              *
   2075              * @param a The value of \(a\) in the equation of the EOTF described above
   2076              * @param b The value of \(b\) in the equation of the EOTF described above
   2077              * @param c The value of \(c\) in the equation of the EOTF described above
   2078              * @param d The value of \(d\) in the equation of the EOTF described above
   2079              * @param g The value of \(g\) in the equation of the EOTF described above
   2080              *
   2081              * @throws IllegalArgumentException If the parameters form an invalid transfer function
   2082              */
   2083             public TransferParameters(double a, double b, double c, double d, double g) {
   2084                 this(a, b, c, d, 0.0, 0.0, g);
   2085             }
   2086 
   2087             /**
   2088              * <p>Defines the parameters for the ICC parametric curve type 4, as
   2089              * defined in ICC.1:2004-10, section 10.15.</p>
   2090              *
   2091              * @param a The value of \(a\) in the equation of the EOTF described above
   2092              * @param b The value of \(b\) in the equation of the EOTF described above
   2093              * @param c The value of \(c\) in the equation of the EOTF described above
   2094              * @param d The value of \(d\) in the equation of the EOTF described above
   2095              * @param e The value of \(e\) in the equation of the EOTF described above
   2096              * @param f The value of \(f\) in the equation of the EOTF described above
   2097              * @param g The value of \(g\) in the equation of the EOTF described above
   2098              *
   2099              * @throws IllegalArgumentException If the parameters form an invalid transfer function
   2100              */
   2101             public TransferParameters(double a, double b, double c, double d, double e,
   2102                     double f, double g) {
   2103 
   2104                 if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
   2105                         Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
   2106                         Double.isNaN(g)) {
   2107                     throw new IllegalArgumentException("Parameters cannot be NaN");
   2108                 }
   2109 
   2110                 // Next representable float after 1.0
   2111                 // We use doubles here but the representation inside our native code is often floats
   2112                 if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
   2113                     throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
   2114                             "was " + d);
   2115                 }
   2116 
   2117                 if (d == 0.0 && (a == 0.0 || g == 0.0)) {
   2118                     throw new IllegalArgumentException(
   2119                             "Parameter a or g is zero, the transfer function is constant");
   2120                 }
   2121 
   2122                 if (d >= 1.0 && c == 0.0) {
   2123                     throw new IllegalArgumentException(
   2124                             "Parameter c is zero, the transfer function is constant");
   2125                 }
   2126 
   2127                 if ((a == 0.0 || g == 0.0) && c == 0.0) {
   2128                     throw new IllegalArgumentException("Parameter a or g is zero," +
   2129                             " and c is zero, the transfer function is constant");
   2130                 }
   2131 
   2132                 if (c < 0.0) {
   2133                     throw new IllegalArgumentException("The transfer function must be increasing");
   2134                 }
   2135 
   2136                 if (a < 0.0 || g < 0.0) {
   2137                     throw new IllegalArgumentException("The transfer function must be " +
   2138                             "positive or increasing");
   2139                 }
   2140 
   2141                 this.a = a;
   2142                 this.b = b;
   2143                 this.c = c;
   2144                 this.d = d;
   2145                 this.e = e;
   2146                 this.f = f;
   2147                 this.g = g;
   2148             }
   2149 
   2150             @SuppressWarnings("SimplifiableIfStatement")
   2151             @Override
   2152             public boolean equals(Object o) {
   2153                 if (this == o) return true;
   2154                 if (o == null || getClass() != o.getClass()) return false;
   2155 
   2156                 TransferParameters that = (TransferParameters) o;
   2157 
   2158                 if (Double.compare(that.a, a) != 0) return false;
   2159                 if (Double.compare(that.b, b) != 0) return false;
   2160                 if (Double.compare(that.c, c) != 0) return false;
   2161                 if (Double.compare(that.d, d) != 0) return false;
   2162                 if (Double.compare(that.e, e) != 0) return false;
   2163                 if (Double.compare(that.f, f) != 0) return false;
   2164                 return Double.compare(that.g, g) == 0;
   2165             }
   2166 
   2167             @Override
   2168             public int hashCode() {
   2169                 int result;
   2170                 long temp;
   2171                 temp = Double.doubleToLongBits(a);
   2172                 result = (int) (temp ^ (temp >>> 32));
   2173                 temp = Double.doubleToLongBits(b);
   2174                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2175                 temp = Double.doubleToLongBits(c);
   2176                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2177                 temp = Double.doubleToLongBits(d);
   2178                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2179                 temp = Double.doubleToLongBits(e);
   2180                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2181                 temp = Double.doubleToLongBits(f);
   2182                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2183                 temp = Double.doubleToLongBits(g);
   2184                 result = 31 * result + (int) (temp ^ (temp >>> 32));
   2185                 return result;
   2186             }
   2187         }
   2188 
   2189         @NonNull private final float[] mWhitePoint;
   2190         @NonNull private final float[] mPrimaries;
   2191         @NonNull private final float[] mTransform;
   2192         @NonNull private final float[] mInverseTransform;
   2193 
   2194         @NonNull private final DoubleUnaryOperator mOetf;
   2195         @NonNull private final DoubleUnaryOperator mEotf;
   2196         @NonNull private final DoubleUnaryOperator mClampedOetf;
   2197         @NonNull private final DoubleUnaryOperator mClampedEotf;
   2198 
   2199         private final float mMin;
   2200         private final float mMax;
   2201 
   2202         private final boolean mIsWideGamut;
   2203         private final boolean mIsSrgb;
   2204 
   2205         @Nullable private TransferParameters mTransferParameters;
   2206 
   2207         /**
   2208          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
   2209          * The transform matrix must convert from the RGB space to the profile connection
   2210          * space CIE XYZ.</p>
   2211          *
   2212          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
   2213          *
   2214          * @param name Name of the color space, cannot be null, its length must be >= 1
   2215          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
   2216          *              connection space CIE XYZ as an array of 9 floats, cannot be null
   2217          * @param oetf Opto-electronic transfer function, cannot be null
   2218          * @param eotf Electro-optical transfer function, cannot be null
   2219          *
   2220          * @throws IllegalArgumentException If any of the following conditions is met:
   2221          * <ul>
   2222          *     <li>The name is null or has a length of 0.</li>
   2223          *     <li>The OETF is null or the EOTF is null.</li>
   2224          *     <li>The minimum valid value is >= the maximum valid value.</li>
   2225          * </ul>
   2226          *
   2227          * @see #get(Named)
   2228          */
   2229         public Rgb(
   2230                 @NonNull @Size(min = 1) String name,
   2231                 @NonNull @Size(9) float[] toXYZ,
   2232                 @NonNull DoubleUnaryOperator oetf,
   2233                 @NonNull DoubleUnaryOperator eotf) {
   2234             this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ),
   2235                     oetf, eotf, 0.0f, 1.0f, MIN_ID);
   2236         }
   2237 
   2238         /**
   2239          * <p>Creates a new RGB color space using a specified set of primaries
   2240          * and a specified white point.</p>
   2241          *
   2242          * <p>The primaries and white point can be specified in the CIE xyY space
   2243          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2244          *
   2245          * <table summary="Parameters length">
   2246          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2247          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2248          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2249          * </table>
   2250          *
   2251          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2252          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2253          * are required.</p>
   2254          *
   2255          * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
   2256          * this constructor is always {@link #MIN_ID}.</p>
   2257          *
   2258          * @param name Name of the color space, cannot be null, its length must be >= 1
   2259          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2260          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2261          * @param oetf Opto-electronic transfer function, cannot be null
   2262          * @param eotf Electro-optical transfer function, cannot be null
   2263          * @param min The minimum valid value in this color space's RGB range
   2264          * @param max The maximum valid value in this color space's RGB range
   2265          *
   2266          * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
   2267          * <ul>
   2268          *     <li>The name is null or has a length of 0.</li>
   2269          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2270          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2271          *     <li>The OETF is null or the EOTF is null.</li>
   2272          *     <li>The minimum valid value is >= the maximum valid value.</li>
   2273          * </ul>
   2274          *
   2275          * @see #get(Named)
   2276          */
   2277         public Rgb(
   2278                 @NonNull @Size(min = 1) String name,
   2279                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2280                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2281                 @NonNull DoubleUnaryOperator oetf,
   2282                 @NonNull DoubleUnaryOperator eotf,
   2283                 float min,
   2284                 float max) {
   2285             this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
   2286         }
   2287 
   2288         /**
   2289          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
   2290          * The transform matrix must convert from the RGB space to the profile connection
   2291          * space CIE XYZ.</p>
   2292          *
   2293          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
   2294          *
   2295          * @param name Name of the color space, cannot be null, its length must be >= 1
   2296          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
   2297          *              connection space CIE XYZ as an array of 9 floats, cannot be null
   2298          * @param function Parameters for the transfer functions
   2299          *
   2300          * @throws IllegalArgumentException If any of the following conditions is met:
   2301          * <ul>
   2302          *     <li>The name is null or has a length of 0.</li>
   2303          *     <li>Gamma is negative.</li>
   2304          * </ul>
   2305          *
   2306          * @see #get(Named)
   2307          */
   2308         public Rgb(
   2309                 @NonNull @Size(min = 1) String name,
   2310                 @NonNull @Size(9) float[] toXYZ,
   2311                 @NonNull TransferParameters function) {
   2312             this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID);
   2313         }
   2314 
   2315         /**
   2316          * <p>Creates a new RGB color space using a specified set of primaries
   2317          * and a specified white point.</p>
   2318          *
   2319          * <p>The primaries and white point can be specified in the CIE xyY space
   2320          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2321          *
   2322          * <table summary="Parameters length">
   2323          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2324          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2325          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2326          * </table>
   2327          *
   2328          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2329          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2330          * are required.</p>
   2331          *
   2332          * @param name Name of the color space, cannot be null, its length must be >= 1
   2333          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2334          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2335          * @param function Parameters for the transfer functions
   2336          *
   2337          * @throws IllegalArgumentException If any of the following conditions is met:
   2338          * <ul>
   2339          *     <li>The name is null or has a length of 0.</li>
   2340          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2341          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2342          *     <li>The transfer parameters are invalid.</li>
   2343          * </ul>
   2344          *
   2345          * @see #get(Named)
   2346          */
   2347         public Rgb(
   2348                 @NonNull @Size(min = 1) String name,
   2349                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2350                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2351                 @NonNull TransferParameters function) {
   2352             this(name, primaries, whitePoint, function, MIN_ID);
   2353         }
   2354 
   2355         /**
   2356          * <p>Creates a new RGB color space using a specified set of primaries
   2357          * and a specified white point.</p>
   2358          *
   2359          * <p>The primaries and white point can be specified in the CIE xyY space
   2360          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2361          *
   2362          * <table summary="Parameters length">
   2363          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2364          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2365          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2366          * </table>
   2367          *
   2368          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2369          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2370          * are required.</p>
   2371          *
   2372          * @param name Name of the color space, cannot be null, its length must be >= 1
   2373          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2374          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2375          * @param function Parameters for the transfer functions
   2376          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
   2377          *
   2378          * @throws IllegalArgumentException If any of the following conditions is met:
   2379          * <ul>
   2380          *     <li>The name is null or has a length of 0.</li>
   2381          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2382          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2383          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
   2384          *     <li>The transfer parameters are invalid.</li>
   2385          * </ul>
   2386          *
   2387          * @see #get(Named)
   2388          */
   2389         private Rgb(
   2390                 @NonNull @Size(min = 1) String name,
   2391                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2392                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2393                 @NonNull TransferParameters function,
   2394                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   2395             this(name, primaries, whitePoint,
   2396                     function.e == 0.0 && function.f == 0.0 ?
   2397                             x -> rcpResponse(x, function.a, function.b,
   2398                                     function.c, function.d, function.g) :
   2399                             x -> rcpResponse(x, function.a, function.b, function.c,
   2400                                     function.d, function.e, function.f, function.g),
   2401                     function.e == 0.0 && function.f == 0.0 ?
   2402                             x -> response(x, function.a, function.b,
   2403                                     function.c, function.d, function.g) :
   2404                             x -> response(x, function.a, function.b, function.c,
   2405                                     function.d, function.e, function.f, function.g),
   2406                     0.0f, 1.0f, id);
   2407             mTransferParameters = function;
   2408         }
   2409 
   2410         /**
   2411          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
   2412          * The transform matrix must convert from the RGB space to the profile connection
   2413          * space CIE XYZ.</p>
   2414          *
   2415          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
   2416          *
   2417          * @param name Name of the color space, cannot be null, its length must be >= 1
   2418          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
   2419          *              connection space CIE XYZ as an array of 9 floats, cannot be null
   2420          * @param gamma Gamma to use as the transfer function
   2421          *
   2422          * @throws IllegalArgumentException If any of the following conditions is met:
   2423          * <ul>
   2424          *     <li>The name is null or has a length of 0.</li>
   2425          *     <li>Gamma is negative.</li>
   2426          * </ul>
   2427          *
   2428          * @see #get(Named)
   2429          */
   2430         public Rgb(
   2431                 @NonNull @Size(min = 1) String name,
   2432                 @NonNull @Size(9) float[] toXYZ,
   2433                 double gamma) {
   2434             this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID);
   2435         }
   2436 
   2437         /**
   2438          * <p>Creates a new RGB color space using a specified set of primaries
   2439          * and a specified white point.</p>
   2440          *
   2441          * <p>The primaries and white point can be specified in the CIE xyY space
   2442          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2443          *
   2444          * <table summary="Parameters length">
   2445          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2446          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2447          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2448          * </table>
   2449          *
   2450          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2451          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2452          * are required.</p>
   2453          *
   2454          * @param name Name of the color space, cannot be null, its length must be >= 1
   2455          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2456          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2457          * @param gamma Gamma to use as the transfer function
   2458          *
   2459          * @throws IllegalArgumentException If any of the following conditions is met:
   2460          * <ul>
   2461          *     <li>The name is null or has a length of 0.</li>
   2462          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2463          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2464          *     <li>Gamma is negative.</li>
   2465          * </ul>
   2466          *
   2467          * @see #get(Named)
   2468          */
   2469         public Rgb(
   2470                 @NonNull @Size(min = 1) String name,
   2471                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2472                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2473                 double gamma) {
   2474             this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID);
   2475         }
   2476 
   2477         /**
   2478          * <p>Creates a new RGB color space using a specified set of primaries
   2479          * and a specified white point.</p>
   2480          *
   2481          * <p>The primaries and white point can be specified in the CIE xyY space
   2482          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2483          *
   2484          * <table summary="Parameters length">
   2485          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2486          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2487          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2488          * </table>
   2489          *
   2490          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2491          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2492          * are required.</p>
   2493          *
   2494          * @param name Name of the color space, cannot be null, its length must be >= 1
   2495          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2496          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2497          * @param gamma Gamma to use as the transfer function
   2498          * @param min The minimum valid value in this color space's RGB range
   2499          * @param max The maximum valid value in this color space's RGB range
   2500          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
   2501          *
   2502          * @throws IllegalArgumentException If any of the following conditions is met:
   2503          * <ul>
   2504          *     <li>The name is null or has a length of 0.</li>
   2505          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2506          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2507          *     <li>The minimum valid value is >= the maximum valid value.</li>
   2508          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
   2509          *     <li>Gamma is negative.</li>
   2510          * </ul>
   2511          *
   2512          * @see #get(Named)
   2513          */
   2514         private Rgb(
   2515                 @NonNull @Size(min = 1) String name,
   2516                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2517                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2518                 double gamma,
   2519                 float min,
   2520                 float max,
   2521                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   2522             this(name, primaries, whitePoint,
   2523                     gamma == 1.0 ? DoubleUnaryOperator.identity() :
   2524                             x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
   2525                     gamma == 1.0 ? DoubleUnaryOperator.identity() :
   2526                             x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
   2527                     min, max, id);
   2528             mTransferParameters = gamma == 1.0 ?
   2529                     new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0f), gamma) :
   2530                     new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
   2531         }
   2532 
   2533         /**
   2534          * <p>Creates a new RGB color space using a specified set of primaries
   2535          * and a specified white point.</p>
   2536          *
   2537          * <p>The primaries and white point can be specified in the CIE xyY space
   2538          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
   2539          *
   2540          * <table summary="Parameters length">
   2541          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
   2542          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
   2543          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
   2544          * </table>
   2545          *
   2546          * <p>When the primaries and/or white point are specified in xyY, the Y component
   2547          * does not need to be specified and is assumed to be 1.0. Only the xy components
   2548          * are required.</p>
   2549          *
   2550          * @param name Name of the color space, cannot be null, its length must be >= 1
   2551          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
   2552          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
   2553          * @param oetf Opto-electronic transfer function, cannot be null
   2554          * @param eotf Electro-optical transfer function, cannot be null
   2555          * @param min The minimum valid value in this color space's RGB range
   2556          * @param max The maximum valid value in this color space's RGB range
   2557          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
   2558          *
   2559          * @throws IllegalArgumentException If any of the following conditions is met:
   2560          * <ul>
   2561          *     <li>The name is null or has a length of 0.</li>
   2562          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
   2563          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
   2564          *     <li>The OETF is null or the EOTF is null.</li>
   2565          *     <li>The minimum valid value is >= the maximum valid value.</li>
   2566          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
   2567          * </ul>
   2568          *
   2569          * @see #get(Named)
   2570          */
   2571         private Rgb(
   2572                 @NonNull @Size(min = 1) String name,
   2573                 @NonNull @Size(min = 6, max = 9) float[] primaries,
   2574                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
   2575                 @NonNull DoubleUnaryOperator oetf,
   2576                 @NonNull DoubleUnaryOperator eotf,
   2577                 float min,
   2578                 float max,
   2579                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   2580 
   2581             super(name, Model.RGB, id);
   2582 
   2583             if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
   2584                 throw new IllegalArgumentException("The color space's primaries must be " +
   2585                         "defined as an array of 6 floats in xyY or 9 floats in XYZ");
   2586             }
   2587 
   2588             if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
   2589                 throw new IllegalArgumentException("The color space's white point must be " +
   2590                         "defined as an array of 2 floats in xyY or 3 float in XYZ");
   2591             }
   2592 
   2593             if (oetf == null || eotf == null) {
   2594                 throw new IllegalArgumentException("The transfer functions of a color space " +
   2595                         "cannot be null");
   2596             }
   2597 
   2598             if (min >= max) {
   2599                 throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
   2600                         "; min must be strictly < max");
   2601             }
   2602 
   2603             mWhitePoint = xyWhitePoint(whitePoint);
   2604             mPrimaries =  xyPrimaries(primaries);
   2605 
   2606             mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
   2607             mInverseTransform = inverse3x3(mTransform);
   2608 
   2609             mOetf = oetf;
   2610             mEotf = eotf;
   2611 
   2612             mMin = min;
   2613             mMax = max;
   2614 
   2615             DoubleUnaryOperator clamp = this::clamp;
   2616             mClampedOetf = oetf.andThen(clamp);
   2617             mClampedEotf = clamp.andThen(eotf);
   2618 
   2619             // A color space is wide-gamut if its area is >90% of NTSC 1953 and
   2620             // if it entirely contains the Color space definition in xyY
   2621             mIsWideGamut = isWideGamut(mPrimaries, min, max);
   2622             mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
   2623         }
   2624 
   2625         /**
   2626          * Creates a copy of the specified color space with a new transform.
   2627          *
   2628          * @param colorSpace The color space to create a copy of
   2629          */
   2630         private Rgb(Rgb colorSpace,
   2631                 @NonNull @Size(9) float[] transform,
   2632                 @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
   2633             super(colorSpace.getName(), Model.RGB, -1);
   2634 
   2635             mWhitePoint = xyWhitePoint(whitePoint);
   2636             mPrimaries = colorSpace.mPrimaries;
   2637 
   2638             mTransform = transform;
   2639             mInverseTransform = inverse3x3(transform);
   2640 
   2641             mMin = colorSpace.mMin;
   2642             mMax = colorSpace.mMax;
   2643 
   2644             mOetf = colorSpace.mOetf;
   2645             mEotf = colorSpace.mEotf;
   2646 
   2647             mClampedOetf = colorSpace.mClampedOetf;
   2648             mClampedEotf = colorSpace.mClampedEotf;
   2649 
   2650             mIsWideGamut = colorSpace.mIsWideGamut;
   2651             mIsSrgb = colorSpace.mIsSrgb;
   2652 
   2653             mTransferParameters = colorSpace.mTransferParameters;
   2654         }
   2655 
   2656         /**
   2657          * Copies the non-adapted CIE xyY white point of this color space in
   2658          * specified array. The Y component is assumed to be 1 and is therefore
   2659          * not copied into the destination. The x and y components are written
   2660          * in the array at positions 0 and 1 respectively.
   2661          *
   2662          * @param whitePoint The destination array, cannot be null, its length
   2663          *                   must be >= 2
   2664          *
   2665          * @return The destination array passed as a parameter
   2666          *
   2667          * @see #getWhitePoint(float[])
   2668          */
   2669         @NonNull
   2670         @Size(min = 2)
   2671         public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
   2672             whitePoint[0] = mWhitePoint[0];
   2673             whitePoint[1] = mWhitePoint[1];
   2674             return whitePoint;
   2675         }
   2676 
   2677         /**
   2678          * Returns the non-adapted CIE xyY white point of this color space as
   2679          * a new array of 2 floats. The Y component is assumed to be 1 and is
   2680          * therefore not copied into the destination. The x and y components
   2681          * are written in the array at positions 0 and 1 respectively.
   2682          *
   2683          * @return A new non-null array of 2 floats
   2684          *
   2685          * @see #getWhitePoint()
   2686          */
   2687         @NonNull
   2688         @Size(2)
   2689         public float[] getWhitePoint() {
   2690             return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
   2691         }
   2692 
   2693         /**
   2694          * Copies the primaries of this color space in specified array. The Y
   2695          * component is assumed to be 1 and is therefore not copied into the
   2696          * destination. The x and y components of the first primary are written
   2697          * in the array at positions 0 and 1 respectively.
   2698          *
   2699          * @param primaries The destination array, cannot be null, its length
   2700          *                  must be >= 6
   2701          *
   2702          * @return The destination array passed as a parameter
   2703          *
   2704          * @see #getPrimaries(float[])
   2705          */
   2706         @NonNull
   2707         @Size(min = 6)
   2708         public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
   2709             System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
   2710             return primaries;
   2711         }
   2712 
   2713         /**
   2714          * Returns the primaries of this color space as a new array of 6 floats.
   2715          * The Y component is assumed to be 1 and is therefore not copied into
   2716          * the destination. The x and y components of the first primary are
   2717          * written in the array at positions 0 and 1 respectively.
   2718          *
   2719          * @return A new non-null array of 2 floats
   2720          *
   2721          * @see #getWhitePoint()
   2722          */
   2723         @NonNull
   2724         @Size(6)
   2725         public float[] getPrimaries() {
   2726             return Arrays.copyOf(mPrimaries, mPrimaries.length);
   2727         }
   2728 
   2729         /**
   2730          * <p>Copies the transform of this color space in specified array. The
   2731          * transform is used to convert from RGB to XYZ (with the same white
   2732          * point as this color space). To connect color spaces, you must first
   2733          * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
   2734          * same white point.</p>
   2735          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
   2736          * to convert between color spaces.</p>
   2737          *
   2738          * @param transform The destination array, cannot be null, its length
   2739          *                  must be >= 9
   2740          *
   2741          * @return The destination array passed as a parameter
   2742          *
   2743          * @see #getInverseTransform()
   2744          */
   2745         @NonNull
   2746         @Size(min = 9)
   2747         public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
   2748             System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
   2749             return transform;
   2750         }
   2751 
   2752         /**
   2753          * <p>Returns the transform of this color space as a new array. The
   2754          * transform is used to convert from RGB to XYZ (with the same white
   2755          * point as this color space). To connect color spaces, you must first
   2756          * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
   2757          * same white point.</p>
   2758          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
   2759          * to convert between color spaces.</p>
   2760          *
   2761          * @return A new array of 9 floats
   2762          *
   2763          * @see #getInverseTransform(float[])
   2764          */
   2765         @NonNull
   2766         @Size(9)
   2767         public float[] getTransform() {
   2768             return Arrays.copyOf(mTransform, mTransform.length);
   2769         }
   2770 
   2771         /**
   2772          * <p>Copies the inverse transform of this color space in specified array.
   2773          * The inverse transform is used to convert from XYZ to RGB (with the
   2774          * same white point as this color space). To connect color spaces, you
   2775          * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
   2776          * to the same white point.</p>
   2777          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
   2778          * to convert between color spaces.</p>
   2779          *
   2780          * @param inverseTransform The destination array, cannot be null, its length
   2781          *                  must be >= 9
   2782          *
   2783          * @return The destination array passed as a parameter
   2784          *
   2785          * @see #getTransform()
   2786          */
   2787         @NonNull
   2788         @Size(min = 9)
   2789         public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
   2790             System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
   2791             return inverseTransform;
   2792         }
   2793 
   2794         /**
   2795          * <p>Returns the inverse transform of this color space as a new array.
   2796          * The inverse transform is used to convert from XYZ to RGB (with the
   2797          * same white point as this color space). To connect color spaces, you
   2798          * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
   2799          * to the same white point.</p>
   2800          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
   2801          * to convert between color spaces.</p>
   2802          *
   2803          * @return A new array of 9 floats
   2804          *
   2805          * @see #getTransform(float[])
   2806          */
   2807         @NonNull
   2808         @Size(9)
   2809         public float[] getInverseTransform() {
   2810             return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
   2811         }
   2812 
   2813         /**
   2814          * <p>Returns the opto-electronic transfer function (OETF) of this color space.
   2815          * The inverse function is the electro-optical transfer function (EOTF) returned
   2816          * by {@link #getEotf()}. These functions are defined to satisfy the following
   2817          * equality for \(x \in [0..1]\):</p>
   2818          *
   2819          * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
   2820          *
   2821          * <p>For RGB colors, this function can be used to convert from linear space
   2822          * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
   2823          * are frequently used because many OETFs can be closely approximated using
   2824          * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
   2825          * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
   2826          * for instance).</p>
   2827          *
   2828          * @return A transfer function that converts from linear space to "gamma space"
   2829          *
   2830          * @see #getEotf()
   2831          * @see #getTransferParameters()
   2832          */
   2833         @NonNull
   2834         public DoubleUnaryOperator getOetf() {
   2835             return mClampedOetf;
   2836         }
   2837 
   2838         /**
   2839          * <p>Returns the electro-optical transfer function (EOTF) of this color space.
   2840          * The inverse function is the opto-electronic transfer function (OETF)
   2841          * returned by {@link #getOetf()}. These functions are defined to satisfy the
   2842          * following equality for \(x \in [0..1]\):</p>
   2843          *
   2844          * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
   2845          *
   2846          * <p>For RGB colors, this function can be used to convert from "gamma space"
   2847          * (gamma encoded) to linear space. The terms gamma space and gamma encoded
   2848          * are frequently used because many EOTFs can be closely approximated using
   2849          * a simple power function of the form \(x^\gamma\) (the approximation of the
   2850          * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
   2851          *
   2852          * @return A transfer function that converts from "gamma space" to linear space
   2853          *
   2854          * @see #getOetf()
   2855          * @see #getTransferParameters()
   2856          */
   2857         @NonNull
   2858         public DoubleUnaryOperator getEotf() {
   2859             return mClampedEotf;
   2860         }
   2861 
   2862         /**
   2863          * <p>Returns the parameters used by the {@link #getEotf() electro-optical}
   2864          * and {@link #getOetf() opto-electronic} transfer functions. If the transfer
   2865          * functions do not match the ICC parametric curves defined in ICC.1:2004-10
   2866          * (section 10.15), this method returns null.</p>
   2867          *
   2868          * <p>See {@link TransferParameters} for a full description of the transfer
   2869          * functions.</p>
   2870          *
   2871          * @return An instance of {@link TransferParameters} or null if this color
   2872          *         space's transfer functions do not match the equation defined in
   2873          *         {@link TransferParameters}
   2874          */
   2875         @Nullable
   2876         public TransferParameters getTransferParameters() {
   2877             return mTransferParameters;
   2878         }
   2879 
   2880         @Override
   2881         public boolean isSrgb() {
   2882             return mIsSrgb;
   2883         }
   2884 
   2885         @Override
   2886         public boolean isWideGamut() {
   2887             return mIsWideGamut;
   2888         }
   2889 
   2890         @Override
   2891         public float getMinValue(int component) {
   2892             return mMin;
   2893         }
   2894 
   2895         @Override
   2896         public float getMaxValue(int component) {
   2897             return mMax;
   2898         }
   2899 
   2900         /**
   2901          * <p>Decodes an RGB value to linear space. This is achieved by
   2902          * applying this color space's electro-optical transfer function
   2903          * to the supplied values.</p>
   2904          *
   2905          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
   2906          * more information about transfer functions and their use for
   2907          * encoding and decoding RGB values.</p>
   2908          *
   2909          * @param r The red component to decode to linear space
   2910          * @param g The green component to decode to linear space
   2911          * @param b The blue component to decode to linear space
   2912          * @return A new array of 3 floats containing linear RGB values
   2913          *
   2914          * @see #toLinear(float[])
   2915          * @see #fromLinear(float, float, float)
   2916          */
   2917         @NonNull
   2918         @Size(3)
   2919         public float[] toLinear(float r, float g, float b) {
   2920             return toLinear(new float[] { r, g, b });
   2921         }
   2922 
   2923         /**
   2924          * <p>Decodes an RGB value to linear space. This is achieved by
   2925          * applying this color space's electro-optical transfer function
   2926          * to the first 3 values of the supplied array. The result is
   2927          * stored back in the input array.</p>
   2928          *
   2929          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
   2930          * more information about transfer functions and their use for
   2931          * encoding and decoding RGB values.</p>
   2932          *
   2933          * @param v A non-null array of non-linear RGB values, its length
   2934          *          must be at least 3
   2935          * @return The specified array
   2936          *
   2937          * @see #toLinear(float, float, float)
   2938          * @see #fromLinear(float[])
   2939          */
   2940         @NonNull
   2941         @Size(min = 3)
   2942         public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
   2943             v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
   2944             v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
   2945             v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
   2946             return v;
   2947         }
   2948 
   2949         /**
   2950          * <p>Encodes an RGB value from linear space to this color space's
   2951          * "gamma space". This is achieved by applying this color space's
   2952          * opto-electronic transfer function to the supplied values.</p>
   2953          *
   2954          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
   2955          * more information about transfer functions and their use for
   2956          * encoding and decoding RGB values.</p>
   2957          *
   2958          * @param r The red component to encode from linear space
   2959          * @param g The green component to encode from linear space
   2960          * @param b The blue component to encode from linear space
   2961          * @return A new array of 3 floats containing non-linear RGB values
   2962          *
   2963          * @see #fromLinear(float[])
   2964          * @see #toLinear(float, float, float)
   2965          */
   2966         @NonNull
   2967         @Size(3)
   2968         public float[] fromLinear(float r, float g, float b) {
   2969             return fromLinear(new float[] { r, g, b });
   2970         }
   2971 
   2972         /**
   2973          * <p>Encodes an RGB value from linear space to this color space's
   2974          * "gamma space". This is achieved by applying this color space's
   2975          * opto-electronic transfer function to the first 3 values of the
   2976          * supplied array. The result is stored back in the input array.</p>
   2977          *
   2978          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
   2979          * more information about transfer functions and their use for
   2980          * encoding and decoding RGB values.</p>
   2981          *
   2982          * @param v A non-null array of linear RGB values, its length
   2983          *          must be at least 3
   2984          * @return A new array of 3 floats containing non-linear RGB values
   2985          *
   2986          * @see #fromLinear(float[])
   2987          * @see #toLinear(float, float, float)
   2988          */
   2989         @NonNull
   2990         @Size(min = 3)
   2991         public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
   2992             v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
   2993             v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
   2994             v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
   2995             return v;
   2996         }
   2997 
   2998         @Override
   2999         @NonNull
   3000         @Size(min = 3)
   3001         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
   3002             v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
   3003             v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
   3004             v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
   3005             return mul3x3Float3(mTransform, v);
   3006         }
   3007 
   3008         @Override
   3009         @NonNull
   3010         @Size(min = 3)
   3011         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
   3012             mul3x3Float3(mInverseTransform, v);
   3013             v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
   3014             v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
   3015             v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
   3016             return v;
   3017         }
   3018 
   3019         private double clamp(double x) {
   3020             return x < mMin ? mMin : x > mMax ? mMax : x;
   3021         }
   3022 
   3023         @Override
   3024         public boolean equals(Object o) {
   3025             if (this == o) return true;
   3026             if (o == null || getClass() != o.getClass()) return false;
   3027             if (!super.equals(o)) return false;
   3028 
   3029             Rgb rgb = (Rgb) o;
   3030 
   3031             if (Float.compare(rgb.mMin, mMin) != 0) return false;
   3032             if (Float.compare(rgb.mMax, mMax) != 0) return false;
   3033             if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
   3034             if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
   3035             if (mTransferParameters != null) {
   3036                 return mTransferParameters.equals(rgb.mTransferParameters);
   3037             } else if (rgb.mTransferParameters == null) {
   3038                 return true;
   3039             }
   3040             //noinspection SimplifiableIfStatement
   3041             if (!mOetf.equals(rgb.mOetf)) return false;
   3042             return mEotf.equals(rgb.mEotf);
   3043         }
   3044 
   3045         @Override
   3046         public int hashCode() {
   3047             int result = super.hashCode();
   3048             result = 31 * result + Arrays.hashCode(mWhitePoint);
   3049             result = 31 * result + Arrays.hashCode(mPrimaries);
   3050             result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
   3051             result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
   3052             result = 31 * result +
   3053                     (mTransferParameters != null ? mTransferParameters.hashCode() : 0);
   3054             if (mTransferParameters == null) {
   3055                 result = 31 * result + mOetf.hashCode();
   3056                 result = 31 * result + mEotf.hashCode();
   3057             }
   3058             return result;
   3059         }
   3060 
   3061         /**
   3062          * Computes whether a color space is the sRGB color space or at least
   3063          * a close approximation.
   3064          *
   3065          * @param primaries The set of RGB primaries in xyY as an array of 6 floats
   3066          * @param whitePoint The white point in xyY as an array of 2 floats
   3067          * @param OETF The opto-electronic transfer function
   3068          * @param EOTF The electro-optical transfer function
   3069          * @param min The minimum value of the color space's range
   3070          * @param max The minimum value of the color space's range
   3071          * @param id The ID of the color space
   3072          * @return True if the color space can be considered as the sRGB color space
   3073          *
   3074          * @see #isSrgb()
   3075          */
   3076         @SuppressWarnings("RedundantIfStatement")
   3077         private static boolean isSrgb(
   3078                 @NonNull @Size(6) float[] primaries,
   3079                 @NonNull @Size(2) float[] whitePoint,
   3080                 @NonNull DoubleUnaryOperator OETF,
   3081                 @NonNull DoubleUnaryOperator EOTF,
   3082                 float min,
   3083                 float max,
   3084                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
   3085             if (id == 0) return true;
   3086             if (!compare(primaries, SRGB_PRIMARIES)) {
   3087                 return false;
   3088             }
   3089             if (!compare(whitePoint, ILLUMINANT_D65)) {
   3090                 return false;
   3091             }
   3092             if (OETF.applyAsDouble(0.5) < 0.5001) return false;
   3093             if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
   3094             if (min != 0.0f) return false;
   3095             if (max != 1.0f) return false;
   3096             return true;
   3097         }
   3098 
   3099         /**
   3100          * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
   3101          * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
   3102          * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
   3103          * If the conditions above are not met, the color space is considered as having
   3104          * a wide color gamut if its range is larger than [0..1].
   3105          *
   3106          * @param primaries RGB primaries in CIE xyY as an array of 6 floats
   3107          * @param min The minimum value of the color space's range
   3108          * @param max The minimum value of the color space's range
   3109          * @return True if the color space has a wide gamut, false otherwise
   3110          *
   3111          * @see #isWideGamut()
   3112          * @see #area(float[])
   3113          */
   3114         private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
   3115                 float min, float max) {
   3116             return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
   3117                             contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
   3118         }
   3119 
   3120         /**
   3121          * Computes the area of the triangle represented by a set of RGB primaries
   3122          * in the CIE xyY space.
   3123          *
   3124          * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
   3125          * @return The area of the triangle
   3126          *
   3127          * @see #isWideGamut(float[], float, float)
   3128          */
   3129         private static float area(@NonNull @Size(6) float[] primaries) {
   3130             float Rx = primaries[0];
   3131             float Ry = primaries[1];
   3132             float Gx = primaries[2];
   3133             float Gy = primaries[3];
   3134             float Bx = primaries[4];
   3135             float By = primaries[5];
   3136             float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
   3137             float r = 0.5f * det;
   3138             return r < 0.0f ? -r : r;
   3139         }
   3140 
   3141         /**
   3142          * Computes the cross product of two 2D vectors.
   3143          *
   3144          * @param ax The x coordinate of the first vector
   3145          * @param ay The y coordinate of the first vector
   3146          * @param bx The x coordinate of the second vector
   3147          * @param by The y coordinate of the second vector
   3148          * @return The result of a x b
   3149          */
   3150         private static float cross(float ax, float ay, float bx, float by) {
   3151             return ax * by - ay * bx;
   3152         }
   3153 
   3154         /**
   3155          * Decides whether a 2D triangle, identified by the 6 coordinates of its
   3156          * 3 vertices, is contained within another 2D triangle, also identified
   3157          * by the 6 coordinates of its 3 vertices.
   3158          *
   3159          * In the illustration below, we want to test whether the RGB triangle
   3160          * is contained within the triangle XYZ formed by the 3 vertices at
   3161          * the "+" locations.
   3162          *
   3163          *                                     Y     .
   3164          *                                 .   +    .
   3165          *                                  .     ..
   3166          *                                   .   .
   3167          *                                    . .
   3168          *                                     .  G
   3169          *                                     *
   3170          *                                    * *
   3171          *                                  **   *
   3172          *                                 *      **
   3173          *                                *         *
   3174          *                              **           *
   3175          *                             *              *
   3176          *                            *                *
   3177          *                          **                  *
   3178          *                         *                     *
   3179          *                        *                       **
   3180          *                      **                          *   R    ...
   3181          *                     *                             *  .....
   3182          *                    *                         ***** ..
   3183          *                  **              ************       .   +
   3184          *              B  *    ************                    .   X
   3185          *           ......*****                                 .
   3186          *     ......    .                                        .
   3187          *             ..
   3188          *        +   .
   3189          *      Z    .
   3190          *
   3191          * RGB is contained within XYZ if all the following conditions are true
   3192          * (with "x" the cross product operator):
   3193          *
   3194          *   -->  -->
   3195          *   GR x RX >= 0
   3196          *   -->  -->
   3197          *   RX x BR >= 0
   3198          *   -->  -->
   3199          *   RG x GY >= 0
   3200          *   -->  -->
   3201          *   GY x RG >= 0
   3202          *   -->  -->
   3203          *   RB x BZ >= 0
   3204          *   -->  -->
   3205          *   BZ x GB >= 0
   3206          *
   3207          * @param p1 The enclosing triangle
   3208          * @param p2 The enclosed triangle
   3209          * @return True if the triangle p1 contains the triangle p2
   3210          *
   3211          * @see #isWideGamut(float[], float, float)
   3212          */
   3213         @SuppressWarnings("RedundantIfStatement")
   3214         private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
   3215             // Translate the vertices p1 in the coordinates system
   3216             // with the vertices p2 as the origin
   3217             float[] p0 = new float[] {
   3218                     p1[0] - p2[0], p1[1] - p2[1],
   3219                     p1[2] - p2[2], p1[3] - p2[3],
   3220                     p1[4] - p2[4], p1[5] - p2[5],
   3221             };
   3222             // Check the first vertex of p1
   3223             if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
   3224                     cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
   3225                 return false;
   3226             }
   3227             // Check the second vertex of p1
   3228             if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
   3229                     cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
   3230                 return false;
   3231             }
   3232             // Check the third vertex of p1
   3233             if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
   3234                     cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
   3235                 return false;
   3236             }
   3237             return true;
   3238         }
   3239 
   3240         /**
   3241          * Computes the primaries  of a color space identified only by
   3242          * its RGB->XYZ transform matrix. This method assumes that the
   3243          * range of the color space is [0..1].
   3244          *
   3245          * @param toXYZ The color space's 3x3 transform matrix to XYZ
   3246          * @return A new array of 6 floats containing the color space's
   3247          *         primaries in CIE xyY
   3248          */
   3249         @NonNull
   3250         @Size(6)
   3251         private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) {
   3252             float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f });
   3253             float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f });
   3254             float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f });
   3255 
   3256             float rSum = r[0] + r[1] + r[2];
   3257             float gSum = g[0] + g[1] + g[2];
   3258             float bSum = b[0] + b[1] + b[2];
   3259 
   3260             return new float[] {
   3261                     r[0] / rSum, r[1] / rSum,
   3262                     g[0] / gSum, g[1] / gSum,
   3263                     b[0] / bSum, b[1] / bSum,
   3264             };
   3265         }
   3266 
   3267         /**
   3268          * Computes the white point of a color space identified only by
   3269          * its RGB->XYZ transform matrix. This method assumes that the
   3270          * range of the color space is [0..1].
   3271          *
   3272          * @param toXYZ The color space's 3x3 transform matrix to XYZ
   3273          * @return A new array of 2 floats containing the color space's
   3274          *         white point in CIE xyY
   3275          */
   3276         @NonNull
   3277         @Size(2)
   3278         private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) {
   3279             float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f });
   3280             float sum = w[0] + w[1] + w[2];
   3281             return new float[] { w[0] / sum, w[1] / sum };
   3282         }
   3283 
   3284         /**
   3285          * Converts the specified RGB primaries point to xyY if needed. The primaries
   3286          * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
   3287          * (in CIE XYZ). If no conversion is needed, the input array is copied.
   3288          *
   3289          * @param primaries The primaries in xyY or XYZ
   3290          * @return A new array of 6 floats containing the primaries in xyY
   3291          */
   3292         @NonNull
   3293         @Size(6)
   3294         private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
   3295             float[] xyPrimaries = new float[6];
   3296 
   3297             // XYZ to xyY
   3298             if (primaries.length == 9) {
   3299                 float sum;
   3300 
   3301                 sum = primaries[0] + primaries[1] + primaries[2];
   3302                 xyPrimaries[0] = primaries[0] / sum;
   3303                 xyPrimaries[1] = primaries[1] / sum;
   3304 
   3305                 sum = primaries[3] + primaries[4] + primaries[5];
   3306                 xyPrimaries[2] = primaries[3] / sum;
   3307                 xyPrimaries[3] = primaries[4] / sum;
   3308 
   3309                 sum = primaries[6] + primaries[7] + primaries[8];
   3310                 xyPrimaries[4] = primaries[6] / sum;
   3311                 xyPrimaries[5] = primaries[7] / sum;
   3312             } else {
   3313                 System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
   3314             }
   3315 
   3316             return xyPrimaries;
   3317         }
   3318 
   3319         /**
   3320          * Converts the specified white point to xyY if needed. The white point
   3321          * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
   3322          * (in CIE XYZ). If no conversion is needed, the input array is copied.
   3323          *
   3324          * @param whitePoint The white point in xyY or XYZ
   3325          * @return A new array of 2 floats containing the white point in xyY
   3326          */
   3327         @NonNull
   3328         @Size(2)
   3329         private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
   3330             float[] xyWhitePoint = new float[2];
   3331 
   3332             // XYZ to xyY
   3333             if (whitePoint.length == 3) {
   3334                 float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
   3335                 xyWhitePoint[0] = whitePoint[0] / sum;
   3336                 xyWhitePoint[1] = whitePoint[1] / sum;
   3337             } else {
   3338                 System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
   3339             }
   3340 
   3341             return xyWhitePoint;
   3342         }
   3343 
   3344         /**
   3345          * Computes the matrix that converts from RGB to XYZ based on RGB
   3346          * primaries and a white point, both specified in the CIE xyY space.
   3347          * The Y component of the primaries and white point is implied to be 1.
   3348          *
   3349          * @param primaries The RGB primaries in xyY, as an array of 6 floats
   3350          * @param whitePoint The white point in xyY, as an array of 2 floats
   3351          * @return A 3x3 matrix as a new array of 9 floats
   3352          */
   3353         @NonNull
   3354         @Size(9)
   3355         private static float[] computeXYZMatrix(
   3356                 @NonNull @Size(6) float[] primaries,
   3357                 @NonNull @Size(2) float[] whitePoint) {
   3358             float Rx = primaries[0];
   3359             float Ry = primaries[1];
   3360             float Gx = primaries[2];
   3361             float Gy = primaries[3];
   3362             float Bx = primaries[4];
   3363             float By = primaries[5];
   3364             float Wx = whitePoint[0];
   3365             float Wy = whitePoint[1];
   3366 
   3367             float oneRxRy = (1 - Rx) / Ry;
   3368             float oneGxGy = (1 - Gx) / Gy;
   3369             float oneBxBy = (1 - Bx) / By;
   3370             float oneWxWy = (1 - Wx) / Wy;
   3371 
   3372             float RxRy = Rx / Ry;
   3373             float GxGy = Gx / Gy;
   3374             float BxBy = Bx / By;
   3375             float WxWy = Wx / Wy;
   3376 
   3377             float BY =
   3378                     ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
   3379                     ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
   3380             float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
   3381             float RY = 1 - GY - BY;
   3382 
   3383             float RYRy = RY / Ry;
   3384             float GYGy = GY / Gy;
   3385             float BYBy = BY / By;
   3386 
   3387             return new float[] {
   3388                     RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
   3389                     GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
   3390                     BYBy * Bx, BY, BYBy * (1 - Bx - By)
   3391             };
   3392         }
   3393     }
   3394 
   3395     /**
   3396      * {@usesMathJax}
   3397      *
   3398      * <p>A connector transforms colors from a source color space to a destination
   3399      * color space.</p>
   3400      *
   3401      * <p>A source color space is connected to a destination color space using the
   3402      * color transform \(C\) computed from their respective transforms noted
   3403      * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
   3404      *
   3405      * $$C = T^{-1}_{dst} . T_{src}$$
   3406      *
   3407      * <p>The transform \(C\) shown above is only valid when the source and
   3408      * destination color spaces have the same profile connection space (PCS).
   3409      * We know that instances of {@link ColorSpace} always use CIE XYZ as their
   3410      * PCS but their white points might differ. When they do, we must perform
   3411      * a chromatic adaptation of the color spaces' transforms. To do so, we
   3412      * use the von Kries method described in the documentation of {@link Adaptation},
   3413      * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
   3414      * as the target white point.</p>
   3415      *
   3416      * <p>Example of conversion from {@link Named#SRGB sRGB} to
   3417      * {@link Named#DCI_P3 DCI-P3}:</p>
   3418      *
   3419      * <pre class="prettyprint">
   3420      * ColorSpace.Connector connector = ColorSpace.connect(
   3421      *         ColorSpace.get(ColorSpace.Named.SRGB),
   3422      *         ColorSpace.get(ColorSpace.Named.DCI_P3));
   3423      * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
   3424      * // p3 contains { 0.9473, 0.2740, 0.2076 }
   3425      * </pre>
   3426      *
   3427      * @see Adaptation
   3428      * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
   3429      * @see ColorSpace#adapt(ColorSpace, float[])
   3430      * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
   3431      * @see ColorSpace#connect(ColorSpace, ColorSpace)
   3432      * @see ColorSpace#connect(ColorSpace, RenderIntent)
   3433      * @see ColorSpace#connect(ColorSpace)
   3434      */
   3435     @AnyThread
   3436     public static class Connector {
   3437         @NonNull private final ColorSpace mSource;
   3438         @NonNull private final ColorSpace mDestination;
   3439         @NonNull private final ColorSpace mTransformSource;
   3440         @NonNull private final ColorSpace mTransformDestination;
   3441         @NonNull private final RenderIntent mIntent;
   3442         @NonNull @Size(3) private final float[] mTransform;
   3443 
   3444         /**
   3445          * Creates a new connector between a source and a destination color space.
   3446          *
   3447          * @param source The source color space, cannot be null
   3448          * @param destination The destination color space, cannot be null
   3449          * @param intent The render intent to use when compressing gamuts
   3450          */
   3451         Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
   3452                 @NonNull RenderIntent intent) {
   3453             this(source, destination,
   3454                     source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
   3455                     destination.getModel() == Model.RGB ?
   3456                             adapt(destination, ILLUMINANT_D50_XYZ) : destination,
   3457                     intent, computeTransform(source, destination, intent));
   3458         }
   3459 
   3460         /**
   3461          * To connect between color spaces, we might need to use adapted transforms.
   3462          * This should be transparent to the user so this constructor takes the
   3463          * original source and destinations (returned by the getters), as well as
   3464          * possibly adapted color spaces used by transform().
   3465          */
   3466         private Connector(
   3467                 @NonNull ColorSpace source, @NonNull ColorSpace destination,
   3468                 @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
   3469                 @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
   3470             mSource = source;
   3471             mDestination = destination;
   3472             mTransformSource = transformSource;
   3473             mTransformDestination = transformDestination;
   3474             mIntent = intent;
   3475             mTransform = transform;
   3476         }
   3477 
   3478         /**
   3479          * Computes an extra transform to apply in XYZ space depending on the
   3480          * selected rendering intent.
   3481          */
   3482         @Nullable
   3483         private static float[] computeTransform(@NonNull ColorSpace source,
   3484                 @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
   3485             if (intent != RenderIntent.ABSOLUTE) return null;
   3486 
   3487             boolean srcRGB = source.getModel() == Model.RGB;
   3488             boolean dstRGB = destination.getModel() == Model.RGB;
   3489 
   3490             if (srcRGB && dstRGB) return null;
   3491 
   3492             if (srcRGB || dstRGB) {
   3493                 ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
   3494                 float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
   3495                 float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
   3496                 return new float[] {
   3497                         srcXYZ[0] / dstXYZ[0],
   3498                         srcXYZ[1] / dstXYZ[1],
   3499                         srcXYZ[2] / dstXYZ[2],
   3500                 };
   3501             }
   3502 
   3503             return null;
   3504         }
   3505 
   3506         /**
   3507          * Returns the source color space this connector will convert from.
   3508          *
   3509          * @return A non-null instance of {@link ColorSpace}
   3510          *
   3511          * @see #getDestination()
   3512          */
   3513         @NonNull
   3514         public ColorSpace getSource() {
   3515             return mSource;
   3516         }
   3517 
   3518         /**
   3519          * Returns the destination color space this connector will convert to.
   3520          *
   3521          * @return A non-null instance of {@link ColorSpace}
   3522          *
   3523          * @see #getSource()
   3524          */
   3525         @NonNull
   3526         public ColorSpace getDestination() {
   3527             return mDestination;
   3528         }
   3529 
   3530         /**
   3531          * Returns the render intent this connector will use when mapping the
   3532          * source color space to the destination color space.
   3533          *
   3534          * @return A non-null {@link RenderIntent}
   3535          *
   3536          * @see RenderIntent
   3537          */
   3538         public RenderIntent getRenderIntent() {
   3539             return mIntent;
   3540         }
   3541 
   3542         /**
   3543          * <p>Transforms the specified color from the source color space
   3544          * to a color in the destination color space. This convenience
   3545          * method assumes a source color model with 3 components
   3546          * (typically RGB). To transform from color models with more than
   3547          * 3 components, such as {@link Model#CMYK CMYK}, use
   3548          * {@link #transform(float[])} instead.</p>
   3549          *
   3550          * @param r The red component of the color to transform
   3551          * @param g The green component of the color to transform
   3552          * @param b The blue component of the color to transform
   3553          * @return A new array of 3 floats containing the specified color
   3554          *         transformed from the source space to the destination space
   3555          *
   3556          * @see #transform(float[])
   3557          */
   3558         @NonNull
   3559         @Size(3)
   3560         public float[] transform(float r, float g, float b) {
   3561             return transform(new float[] { r, g, b });
   3562         }
   3563 
   3564         /**
   3565          * <p>Transforms the specified color from the source color space
   3566          * to a color in the destination color space.</p>
   3567          *
   3568          * @param v A non-null array of 3 floats containing the value to transform
   3569          *            and that will hold the result of the transform
   3570          * @return The v array passed as a parameter, containing the specified color
   3571          *         transformed from the source space to the destination space
   3572          *
   3573          * @see #transform(float, float, float)
   3574          */
   3575         @NonNull
   3576         @Size(min = 3)
   3577         public float[] transform(@NonNull @Size(min = 3) float[] v) {
   3578             float[] xyz = mTransformSource.toXyz(v);
   3579             if (mTransform != null) {
   3580                 xyz[0] *= mTransform[0];
   3581                 xyz[1] *= mTransform[1];
   3582                 xyz[2] *= mTransform[2];
   3583             }
   3584             return mTransformDestination.fromXyz(xyz);
   3585         }
   3586 
   3587         /**
   3588          * Optimized connector for RGB->RGB conversions.
   3589          */
   3590         private static class Rgb extends Connector {
   3591             @NonNull private final ColorSpace.Rgb mSource;
   3592             @NonNull private final ColorSpace.Rgb mDestination;
   3593             @NonNull private final float[] mTransform;
   3594 
   3595             Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
   3596                     @NonNull RenderIntent intent) {
   3597                 super(source, destination, source, destination, intent, null);
   3598                 mSource = source;
   3599                 mDestination = destination;
   3600                 mTransform = computeTransform(source, destination, intent);
   3601             }
   3602 
   3603             @Override
   3604             public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
   3605                 rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
   3606                 rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
   3607                 rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
   3608                 mul3x3Float3(mTransform, rgb);
   3609                 rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
   3610                 rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
   3611                 rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
   3612                 return rgb;
   3613             }
   3614 
   3615             /**
   3616              * <p>Computes the color transform that connects two RGB color spaces.</p>
   3617              *
   3618              * <p>We can only connect color spaces if they use the same profile
   3619              * connection space. We assume the connection space is always
   3620              * CIE XYZ but we maye need to perform a chromatic adaptation to
   3621              * match the white points. If an adaptation is needed, we use the
   3622              * CIE standard illuminant D50. The unmatched color space is adapted
   3623              * using the von Kries transform and the {@link Adaptation#BRADFORD}
   3624              * matrix.</p>
   3625              *
   3626              * @param source The source color space, cannot be null
   3627              * @param destination The destination color space, cannot be null
   3628              * @param intent The render intent to use when compressing gamuts
   3629              * @return An array of 9 floats containing the 3x3 matrix transform
   3630              */
   3631             @NonNull
   3632             @Size(9)
   3633             private static float[] computeTransform(
   3634                     @NonNull ColorSpace.Rgb source,
   3635                     @NonNull ColorSpace.Rgb destination,
   3636                     @NonNull RenderIntent intent) {
   3637                 if (compare(source.mWhitePoint, destination.mWhitePoint)) {
   3638                     // RGB->RGB using the PCS of both color spaces since they have the same
   3639                     return mul3x3(destination.mInverseTransform, source.mTransform);
   3640                 } else {
   3641                     // RGB->RGB using CIE XYZ D50 as the PCS
   3642                     float[] transform = source.mTransform;
   3643                     float[] inverseTransform = destination.mInverseTransform;
   3644 
   3645                     float[] srcXYZ = xyYToXyz(source.mWhitePoint);
   3646                     float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
   3647 
   3648                     if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
   3649                         float[] srcAdaptation = chromaticAdaptation(
   3650                                 Adaptation.BRADFORD.mTransform, srcXYZ,
   3651                                 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
   3652                         transform = mul3x3(srcAdaptation, source.mTransform);
   3653                     }
   3654 
   3655                     if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
   3656                         float[] dstAdaptation = chromaticAdaptation(
   3657                                 Adaptation.BRADFORD.mTransform, dstXYZ,
   3658                                 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
   3659                         inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
   3660                     }
   3661 
   3662                     if (intent == RenderIntent.ABSOLUTE) {
   3663                         transform = mul3x3Diag(
   3664                                 new float[] {
   3665                                         srcXYZ[0] / dstXYZ[0],
   3666                                         srcXYZ[1] / dstXYZ[1],
   3667                                         srcXYZ[2] / dstXYZ[2],
   3668                                 }, transform);
   3669                     }
   3670 
   3671                     return mul3x3(inverseTransform, transform);
   3672                 }
   3673             }
   3674         }
   3675 
   3676         /**
   3677          * Returns the identity connector for a given color space.
   3678          *
   3679          * @param source The source and destination color space
   3680          * @return A non-null connector that does not perform any transform
   3681          *
   3682          * @see ColorSpace#connect(ColorSpace, ColorSpace)
   3683          */
   3684         static Connector identity(ColorSpace source) {
   3685             return new Connector(source, source, RenderIntent.RELATIVE) {
   3686                 @Override
   3687                 public float[] transform(@NonNull @Size(min = 3) float[] v) {
   3688                     return v;
   3689                 }
   3690             };
   3691         }
   3692     }
   3693 
   3694     /**
   3695      * <p>A color space renderer can be used to visualize and compare the gamut and
   3696      * white point of one or more color spaces. The output is an sRGB {@link Bitmap}
   3697      * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p>
   3698      *
   3699      * <p>The following code snippet shows how to compare the {@link Named#SRGB}
   3700      * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p>
   3701      *
   3702      * <pre class="prettyprint">
   3703      * Bitmap bitmap = ColorSpace.createRenderer()
   3704      *     .size(768)
   3705      *     .clip(true)
   3706      *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3707      *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3708      *     .render();
   3709      * </pre>
   3710      * <p>
   3711      *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
   3712      *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
   3713      * </p>
   3714      *
   3715      * <p>A renderer can also be used to show the location of specific colors,
   3716      * associated with a color space, in the CIE 1931 xyY chromaticity diagram.
   3717      * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p>
   3718      *
   3719      * @see ColorSpace#createRenderer()
   3720      *
   3721      * @hide
   3722      */
   3723     public static class Renderer {
   3724         private static final int NATIVE_SIZE = 1440;
   3725         private static final float UCS_SCALE = 9.0f / 6.0f;
   3726 
   3727         // Number of subdivision of the inside of the spectral locus
   3728         private static final int CHROMATICITY_RESOLUTION = 32;
   3729         private static final double ONE_THIRD = 1.0 / 3.0;
   3730 
   3731         @IntRange(from = 128, to = Integer.MAX_VALUE)
   3732         private int mSize = 1024;
   3733 
   3734         private boolean mShowWhitePoint = true;
   3735         private boolean mClip = false;
   3736         private boolean mUcs = false;
   3737 
   3738         private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2);
   3739         private final List<Point> mPoints = new ArrayList<>(0);
   3740 
   3741         private Renderer() {
   3742         }
   3743 
   3744         /**
   3745          * <p>Defines whether the chromaticity diagram should be clipped by the first
   3746          * registered color space. The default value is false.</p>
   3747          *
   3748          * <p>The following code snippet and image show the default behavior:</p>
   3749          * <pre class="prettyprint">
   3750          * Bitmap bitmap = ColorSpace.createRenderer()
   3751          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3752          *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3753          *     .render();
   3754          * </pre>
   3755          * <p>
   3756          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
   3757          *     <figcaption style="text-align: center;">Clipping disabled</figcaption>
   3758          * </p>
   3759          *
   3760          * <p>Here is the same example with clipping enabled:</p>
   3761          * <pre class="prettyprint">
   3762          * Bitmap bitmap = ColorSpace.createRenderer()
   3763          *     .clip(true)
   3764          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3765          *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3766          *     .render();
   3767          * </pre>
   3768          * <p>
   3769          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
   3770          *     <figcaption style="text-align: center;">Clipping enabled</figcaption>
   3771          * </p>
   3772          *
   3773          * @param clip True to clip the chromaticity diagram to the first registered color space,
   3774          *             false otherwise
   3775          * @return This instance of {@link Renderer}
   3776          */
   3777         @NonNull
   3778         public Renderer clip(boolean clip) {
   3779             mClip = clip;
   3780             return this;
   3781         }
   3782 
   3783         /**
   3784          * <p>Defines whether the chromaticity diagram should use the uniform
   3785          * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale
   3786          * is used, the distance between two points on the diagram is approximately
   3787          * proportional to the perceived color difference.</p>
   3788          *
   3789          * <p>The following code snippet shows how to enable the uniform chromaticity
   3790          * scale. The image below shows the result:</p>
   3791          * <pre class="prettyprint">
   3792          * Bitmap bitmap = ColorSpace.createRenderer()
   3793          *     .uniformChromaticityScale(true)
   3794          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3795          *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3796          *     .render();
   3797          * </pre>
   3798          * <p>
   3799          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" />
   3800          *     <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption>
   3801          * </p>
   3802          *
   3803          * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram
   3804          * @return This instance of {@link Renderer}
   3805          */
   3806         @NonNull
   3807         public Renderer uniformChromaticityScale(boolean ucs) {
   3808             mUcs = ucs;
   3809             return this;
   3810         }
   3811 
   3812         /**
   3813          * Sets the dimensions (width and height) in pixels of the output bitmap.
   3814          * The size must be at least 128px and defaults to 1024px.
   3815          *
   3816          * @param size The size in pixels of the output bitmap
   3817          * @return This instance of {@link Renderer}
   3818          */
   3819         @NonNull
   3820         public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) {
   3821             mSize = Math.max(128, size);
   3822             return this;
   3823         }
   3824 
   3825         /**
   3826          * Shows or hides the white point of each color space in the output bitmap.
   3827          * The default is true.
   3828          *
   3829          * @param show True to show the white point of each color space, false
   3830          *             otherwise
   3831          * @return This instance of {@link Renderer}
   3832          */
   3833         @NonNull
   3834         public Renderer showWhitePoint(boolean show) {
   3835             mShowWhitePoint = show;
   3836             return this;
   3837         }
   3838 
   3839         /**
   3840          * <p>Adds a color space to represent on the output CIE 1931 chromaticity
   3841          * diagram. The color space is represented as a triangle showing the
   3842          * footprint of its color gamut and, optionally, the location of its
   3843          * white point.</p>
   3844          *
   3845          * <p class="note">Color spaces with a color model that is not RGB are
   3846          * accepted but ignored.</p>
   3847          *
   3848          * <p>The following code snippet and image show an example of calling this
   3849          * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p>
   3850          * <pre class="prettyprint">
   3851          * Bitmap bitmap = ColorSpace.createRenderer()
   3852          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3853          *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3854          *     .render();
   3855          * </pre>
   3856          * <p>
   3857          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
   3858          *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
   3859          * </p>
   3860          *
   3861          * <p>Adding a color space extending beyond the boundaries of the
   3862          * spectral locus will alter the size of the diagram within the output
   3863          * bitmap as shown in this example:</p>
   3864          * <pre class="prettyprint">
   3865          * Bitmap bitmap = ColorSpace.createRenderer()
   3866          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3867          *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
   3868          *     .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9)
   3869          *     .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000)
   3870          *     .render();
   3871          * </pre>
   3872          * <p>
   3873          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" />
   3874          *     <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption>
   3875          * </p>
   3876          *
   3877          * @param colorSpace The color space whose gamut to render on the diagram
   3878          * @param color The sRGB color to use to render the color space's gamut and white point
   3879          * @return This instance of {@link Renderer}
   3880          *
   3881          * @see #clip(boolean)
   3882          * @see #showWhitePoint(boolean)
   3883          */
   3884         @NonNull
   3885         public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) {
   3886             mColorSpaces.add(new Pair<>(colorSpace, color));
   3887             return this;
   3888         }
   3889 
   3890         /**
   3891          * <p>Adds a color to represent as a point on the chromaticity diagram.
   3892          * The color is associated with a color space which will be used to
   3893          * perform the conversion to CIE XYZ and compute the location of the point
   3894          * on the diagram. The point is rendered as a colored circle.</p>
   3895          *
   3896          * <p>The following code snippet and image show an example of calling this
   3897          * method to render the location of several sRGB colors as white circles:</p>
   3898          * <pre class="prettyprint">
   3899          * Bitmap bitmap = ColorSpace.createRenderer()
   3900          *     .clip(true)
   3901          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
   3902          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff)
   3903          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff)
   3904          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff)
   3905          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff)
   3906          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff)
   3907          *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff)
   3908          *     .render();
   3909          * </pre>
   3910          * <p>
   3911          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_points.png" />
   3912          *     <figcaption style="text-align: center;">
   3913          *         Locating colors on the chromaticity diagram
   3914          *     </figcaption>
   3915          * </p>
   3916          *
   3917          * @param colorSpace The color space of the color to locate on the diagram
   3918          * @param r The first component of the color to locate on the diagram
   3919          * @param g The second component of the color to locate on the diagram
   3920          * @param b The third component of the color to locate on the diagram
   3921          * @param pointColor The sRGB color to use to render the point on the diagram
   3922          * @return This instance of {@link Renderer}
   3923          */
   3924         @NonNull
   3925         public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b,
   3926                 @ColorInt int pointColor) {
   3927             mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor));
   3928             return this;
   3929         }
   3930 
   3931         /**
   3932          * <p>Renders the {@link #add(ColorSpace, int) color spaces} and
   3933          * {@link #add(ColorSpace, float, float, float, int) points} registered
   3934          * with this renderer. The output bitmap is an sRGB image with the
   3935          * dimensions specified by calling {@link #size(int)} (1204x1024px by
   3936          * default).</p>
   3937          *
   3938          * @return A new non-null {@link Bitmap} with the dimensions specified
   3939          *        by {@link #size(int)} (1024x1024 by default)
   3940          */
   3941         @NonNull
   3942         public Bitmap render() {
   3943             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
   3944             Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
   3945             Canvas canvas = new Canvas(bitmap);
   3946 
   3947             float[] primaries = new float[6];
   3948             float[] whitePoint = new float[2];
   3949 
   3950             int width = NATIVE_SIZE;
   3951             int height = NATIVE_SIZE;
   3952 
   3953             Path path = new Path();
   3954 
   3955             setTransform(canvas, width, height, primaries);
   3956             drawBox(canvas, width, height, paint, path);
   3957             setUcsTransform(canvas, height);
   3958             drawLocus(canvas, width, height, paint, path, primaries);
   3959             drawGamuts(canvas, width, height, paint, path, primaries, whitePoint);
   3960             drawPoints(canvas, width, height, paint);
   3961 
   3962             return bitmap;
   3963         }
   3964 
   3965         /**
   3966          * Draws registered points at their correct position in the xyY coordinates.
   3967          * Each point is positioned according to its associated color space.
   3968          *
   3969          * @param canvas The canvas to transform
   3970          * @param width Width in pixel of the final image
   3971          * @param height Height in pixel of the final image
   3972          * @param paint A pre-allocated paint used to avoid temporary allocations
   3973          */
   3974         private void drawPoints(@NonNull Canvas canvas, int width, int height,
   3975                 @NonNull Paint paint) {
   3976 
   3977             paint.setStyle(Paint.Style.FILL);
   3978 
   3979             float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
   3980 
   3981             float[] v = new float[3];
   3982             float[] xy = new float[2];
   3983 
   3984             for (final Point point : mPoints) {
   3985                 v[0] = point.mRgb[0];
   3986                 v[1] = point.mRgb[1];
   3987                 v[2] = point.mRgb[2];
   3988                 point.mColorSpace.toXyz(v);
   3989 
   3990                 paint.setColor(point.mColor);
   3991 
   3992                 // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed
   3993                 float sum = v[0] + v[1] + v[2];
   3994                 xy[0] = v[0] / sum;
   3995                 xy[1] = v[1] / sum;
   3996                 if (mUcs) xyYToUv(xy);
   3997 
   3998                 canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint);
   3999             }
   4000         }
   4001 
   4002         /**
   4003          * Draws the color gamuts and white points of all the registered color
   4004          * spaces. Only color spaces with an RGB color model are rendered, the
   4005          * others are ignored.
   4006          *
   4007          * @param canvas The canvas to transform
   4008          * @param width Width in pixel of the final image
   4009          * @param height Height in pixel of the final image
   4010          * @param paint A pre-allocated paint used to avoid temporary allocations
   4011          * @param path A pre-allocated path used to avoid temporary allocations
   4012          * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
   4013          * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations
   4014          */
   4015         private void drawGamuts(
   4016                 @NonNull Canvas canvas, int width, int height,
   4017                 @NonNull Paint paint, @NonNull Path path,
   4018                 @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) {
   4019 
   4020             float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
   4021 
   4022             for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
   4023                 ColorSpace colorSpace = item.first;
   4024                 int color = item.second;
   4025 
   4026                 if (colorSpace.getModel() != Model.RGB) continue;
   4027 
   4028                 Rgb rgb = (Rgb) colorSpace;
   4029                 getPrimaries(rgb, primaries, mUcs);
   4030 
   4031                 path.rewind();
   4032                 path.moveTo(width * primaries[0], height - height * primaries[1]);
   4033                 path.lineTo(width * primaries[2], height - height * primaries[3]);
   4034                 path.lineTo(width * primaries[4], height - height * primaries[5]);
   4035                 path.close();
   4036 
   4037                 paint.setStyle(Paint.Style.STROKE);
   4038                 paint.setColor(color);
   4039                 canvas.drawPath(path, paint);
   4040 
   4041                 // Draw the white point
   4042                 if (mShowWhitePoint) {
   4043                     rgb.getWhitePoint(whitePoint);
   4044                     if (mUcs) xyYToUv(whitePoint);
   4045 
   4046                     paint.setStyle(Paint.Style.FILL);
   4047                     paint.setColor(color);
   4048                     canvas.drawCircle(
   4049                             width * whitePoint[0], height - height * whitePoint[1], radius, paint);
   4050                 }
   4051             }
   4052         }
   4053 
   4054         /**
   4055          * Returns the primaries of the specified RGB color space. This method handles
   4056          * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces.
   4057          *
   4058          * @param rgb The color space whose primaries to extract
   4059          * @param primaries A pre-allocated array of 6 floats that will hold the result
   4060          * @param asUcs True if the primaries should be returned in Luv, false for xyY
   4061          */
   4062         @NonNull
   4063         @Size(6)
   4064         private static void getPrimaries(@NonNull Rgb rgb,
   4065                 @NonNull @Size(6) float[] primaries, boolean asUcs) {
   4066             // TODO: We should find a better way to handle these cases
   4067             if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) ||
   4068                     rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) {
   4069                 primaries[0] = 1.41f;
   4070                 primaries[1] = 0.33f;
   4071                 primaries[2] = 0.27f;
   4072                 primaries[3] = 1.24f;
   4073                 primaries[4] = -0.23f;
   4074                 primaries[5] = -0.57f;
   4075             } else {
   4076                 rgb.getPrimaries(primaries);
   4077             }
   4078             if (asUcs) xyYToUv(primaries);
   4079         }
   4080 
   4081         /**
   4082          * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside.
   4083          * This method respect the clip parameter.
   4084          *
   4085          * @param canvas The canvas to transform
   4086          * @param width Width in pixel of the final image
   4087          * @param height Height in pixel of the final image
   4088          * @param paint A pre-allocated paint used to avoid temporary allocations
   4089          * @param path A pre-allocated path used to avoid temporary allocations
   4090          * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
   4091          */
   4092         private void drawLocus(
   4093                 @NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
   4094                 @NonNull Path path, @NonNull @Size(6) float[] primaries) {
   4095 
   4096             int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6;
   4097             float[] vertices = new float[vertexCount * 2];
   4098             int[] colors = new int[vertices.length];
   4099             computeChromaticityMesh(vertices, colors);
   4100 
   4101             if (mUcs) xyYToUv(vertices);
   4102             for (int i = 0; i < vertices.length; i += 2) {
   4103                 vertices[i] *= width;
   4104                 vertices[i + 1] = height - vertices[i + 1] * height;
   4105             }
   4106 
   4107             // Draw the spectral locus
   4108             if (mClip && mColorSpaces.size() > 0) {
   4109                 for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
   4110                     ColorSpace colorSpace = item.first;
   4111                     if (colorSpace.getModel() != Model.RGB) continue;
   4112 
   4113                     Rgb rgb = (Rgb) colorSpace;
   4114                     getPrimaries(rgb, primaries, mUcs);
   4115 
   4116                     break;
   4117                 }
   4118 
   4119                 path.rewind();
   4120                 path.moveTo(width * primaries[0], height - height * primaries[1]);
   4121                 path.lineTo(width * primaries[2], height - height * primaries[3]);
   4122                 path.lineTo(width * primaries[4], height - height * primaries[5]);
   4123                 path.close();
   4124 
   4125                 int[] solid = new int[colors.length];
   4126                 Arrays.fill(solid, 0xff6c6c6c);
   4127                 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
   4128                         null, 0, solid, 0, null, 0, 0, paint);
   4129 
   4130                 canvas.save();
   4131                 canvas.clipPath(path);
   4132 
   4133                 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
   4134                         null, 0, colors, 0, null, 0, 0, paint);
   4135 
   4136                 canvas.restore();
   4137             } else {
   4138                 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
   4139                         null, 0, colors, 0, null, 0, 0, paint);
   4140             }
   4141 
   4142             // Draw the non-spectral locus
   4143             int index = (CHROMATICITY_RESOLUTION - 1) * 12;
   4144             path.reset();
   4145             path.moveTo(vertices[index], vertices[index + 1]);
   4146             for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) {
   4147                 index += CHROMATICITY_RESOLUTION * 12;
   4148                 path.lineTo(vertices[index], vertices[index + 1]);
   4149             }
   4150             path.close();
   4151 
   4152             paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f));
   4153             paint.setStyle(Paint.Style.STROKE);
   4154             paint.setColor(0xff000000);
   4155             canvas.drawPath(path, paint);
   4156         }
   4157 
   4158         /**
   4159          * Draws the diagram box, including borders, tick marks, grid lines
   4160          * and axis labels.
   4161          *
   4162          * @param canvas The canvas to transform
   4163          * @param width Width in pixel of the final image
   4164          * @param height Height in pixel of the final image
   4165          * @param paint A pre-allocated paint used to avoid temporary allocations
   4166          * @param path A pre-allocated path used to avoid temporary allocations
   4167          */
   4168         private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
   4169                 @NonNull Path path) {
   4170 
   4171             int lineCount = 10;
   4172             float scale = 1.0f;
   4173             if (mUcs) {
   4174                 lineCount = 7;
   4175                 scale = UCS_SCALE;
   4176             }
   4177 
   4178             // Draw the unit grid
   4179             paint.setStyle(Paint.Style.STROKE);
   4180             paint.setStrokeWidth(2.0f);
   4181             paint.setColor(0xffc0c0c0);
   4182 
   4183             for (int i = 1; i < lineCount - 1; i++) {
   4184                 float v = i / 10.0f;
   4185                 float x = (width * v) * scale;
   4186                 float y = height - (height * v) * scale;
   4187 
   4188                 canvas.drawLine(0.0f, y, 0.9f * width, y, paint);
   4189                 canvas.drawLine(x, height, x, 0.1f * height, paint);
   4190             }
   4191 
   4192             // Draw tick marks
   4193             paint.setStrokeWidth(4.0f);
   4194             paint.setColor(0xff000000);
   4195             for (int i = 1; i < lineCount - 1; i++) {
   4196                 float v = i / 10.0f;
   4197                 float x = (width * v) * scale;
   4198                 float y = height - (height * v) * scale;
   4199 
   4200                 canvas.drawLine(0.0f, y, width / 100.0f, y, paint);
   4201                 canvas.drawLine(x, height, x, height - (height / 100.0f), paint);
   4202             }
   4203 
   4204             // Draw the axis labels
   4205             paint.setStyle(Paint.Style.FILL);
   4206             paint.setTextSize(36.0f);
   4207             paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
   4208 
   4209             Rect bounds = new Rect();
   4210             for (int i = 1; i < lineCount - 1; i++) {
   4211                 String text = "0." + i;
   4212                 paint.getTextBounds(text, 0, text.length(), bounds);
   4213 
   4214                 float v = i / 10.0f;
   4215                 float x = (width * v) * scale;
   4216                 float y = height - (height * v) * scale;
   4217 
   4218                 canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
   4219                 canvas.drawText(text, x - bounds.width() / 2.0f,
   4220                         height + bounds.height() + 16, paint);
   4221             }
   4222             paint.setStyle(Paint.Style.STROKE);
   4223 
   4224             // Draw the diagram box
   4225             path.moveTo(0.0f, height);
   4226             path.lineTo(0.9f * width, height);
   4227             path.lineTo(0.9f * width, 0.1f * height);
   4228             path.lineTo(0.0f, 0.1f * height);
   4229             path.close();
   4230             canvas.drawPath(path, paint);
   4231         }
   4232 
   4233         /**
   4234          * Computes and applies the Canvas transforms required to make the color
   4235          * gamut of each color space visible in the final image.
   4236          *
   4237          * @param canvas The canvas to transform
   4238          * @param width Width in pixel of the final image
   4239          * @param height Height in pixel of the final image
   4240          * @param primaries Array of 6 floats used to avoid temporary allocations
   4241          */
   4242         private void setTransform(@NonNull Canvas canvas, int width, int height,
   4243                 @NonNull @Size(6) float[] primaries) {
   4244 
   4245             RectF primariesBounds = new RectF();
   4246             for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
   4247                 ColorSpace colorSpace = item.first;
   4248                 if (colorSpace.getModel() != Model.RGB) continue;
   4249 
   4250                 Rgb rgb = (Rgb) colorSpace;
   4251                 getPrimaries(rgb, primaries, mUcs);
   4252 
   4253                 primariesBounds.left = Math.min(primariesBounds.left, primaries[4]);
   4254                 primariesBounds.top = Math.min(primariesBounds.top, primaries[5]);
   4255                 primariesBounds.right = Math.max(primariesBounds.right, primaries[0]);
   4256                 primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]);
   4257             }
   4258 
   4259             float max = mUcs ? 0.6f : 0.9f;
   4260 
   4261             primariesBounds.left = Math.min(0.0f, primariesBounds.left);
   4262             primariesBounds.top = Math.min(0.0f, primariesBounds.top);
   4263             primariesBounds.right = Math.max(max, primariesBounds.right);
   4264             primariesBounds.bottom = Math.max(max, primariesBounds.bottom);
   4265 
   4266             float scaleX = max / primariesBounds.width();
   4267             float scaleY = max / primariesBounds.height();
   4268             float scale = Math.min(scaleX, scaleY);
   4269 
   4270             canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE);
   4271             canvas.scale(scale, scale);
   4272             canvas.translate(
   4273                     (primariesBounds.width() - max) * width / 2.0f,
   4274                     (primariesBounds.height() - max) * height / 2.0f);
   4275 
   4276             // The spectrum extends ~0.85 vertically and ~0.65 horizontally
   4277             // We shift the canvas a little bit to get nicer margins
   4278             canvas.translate(0.05f * width, -0.05f * height);
   4279         }
   4280 
   4281         /**
   4282          * Computes and applies the Canvas transforms required to render the CIE
   4283          * 197 UCS chromaticity diagram.
   4284          *
   4285          * @param canvas The canvas to transform
   4286          * @param height Height in pixel of the final image
   4287          */
   4288         private void setUcsTransform(@NonNull Canvas canvas, int height) {
   4289             if (mUcs) {
   4290                 canvas.translate(0.0f, (height - height * UCS_SCALE));
   4291                 canvas.scale(UCS_SCALE, UCS_SCALE);
   4292             }
   4293         }
   4294 
   4295         // X coordinates of the spectral locus in CIE 1931
   4296         private static final float[] SPECTRUM_LOCUS_X = {
   4297                 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f,
   4298                 0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f,
   4299                 0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f,
   4300                 0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f,
   4301                 0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f,
   4302                 0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f,
   4303                 0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f,
   4304                 0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f,
   4305                 0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f,
   4306                 0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f,
   4307                 0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f,
   4308                 0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f,
   4309                 0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f,
   4310                 0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f,
   4311                 0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f,
   4312                 0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f,
   4313                 0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f,
   4314                 0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f,
   4315                 0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f
   4316         };
   4317         // Y coordinates of the spectral locus in CIE 1931
   4318         private static final float[] SPECTRUM_LOCUS_Y = {
   4319                 0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f,
   4320                 0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f,
   4321                 0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f,
   4322                 0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f,
   4323                 0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f,
   4324                 0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f,
   4325                 0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f,
   4326                 0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f,
   4327                 0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f,
   4328                 0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f,
   4329                 0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f,
   4330                 0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f,
   4331                 0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f,
   4332                 0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f,
   4333                 0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f,
   4334                 0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f,
   4335                 0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f,
   4336                 0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f,
   4337                 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f
   4338         };
   4339 
   4340         /**
   4341          * Computes a 2D mesh representation of the CIE 1931 chromaticity
   4342          * diagram.
   4343          *
   4344          * @param vertices Array of floats that will hold the mesh vertices
   4345          * @param colors Array of floats that will hold the mesh colors
   4346          */
   4347         private static void computeChromaticityMesh(@NonNull float[] vertices,
   4348                 @NonNull int[] colors) {
   4349 
   4350             ColorSpace colorSpace = get(Named.SRGB);
   4351 
   4352             float[] color = new float[3];
   4353 
   4354             int vertexIndex = 0;
   4355             int colorIndex = 0;
   4356 
   4357             for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) {
   4358                 int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1;
   4359 
   4360                 float a1 = (float) Math.atan2(
   4361                         SPECTRUM_LOCUS_Y[x] - ONE_THIRD,
   4362                         SPECTRUM_LOCUS_X[x] - ONE_THIRD);
   4363                 float a2 = (float) Math.atan2(
   4364                         SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD,
   4365                         SPECTRUM_LOCUS_X[nextX] - ONE_THIRD);
   4366 
   4367                 float radius1 = (float) Math.pow(
   4368                         sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) +
   4369                                 sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD),
   4370                         0.5);
   4371                 float radius2 = (float) Math.pow(
   4372                         sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) +
   4373                                 sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD),
   4374                         0.5);
   4375 
   4376                 // Compute patches; each patch is a quad with a different
   4377                 // color associated with each vertex
   4378                 for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) {
   4379                     float f1 = c / (float) CHROMATICITY_RESOLUTION;
   4380                     float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION;
   4381 
   4382                     double cr1 = radius1 * Math.cos(a1);
   4383                     double sr1 = radius1 * Math.sin(a1);
   4384                     double cr2 = radius2 * Math.cos(a2);
   4385                     double sr2 = radius2 * Math.sin(a2);
   4386 
   4387                     // Compute the XYZ coordinates of the 4 vertices of the patch
   4388                     float v1x = (float) (ONE_THIRD + cr1 * f1);
   4389                     float v1y = (float) (ONE_THIRD + sr1 * f1);
   4390                     float v1z = 1 - v1x - v1y;
   4391 
   4392                     float v2x = (float) (ONE_THIRD + cr1 * f2);
   4393                     float v2y = (float) (ONE_THIRD + sr1 * f2);
   4394                     float v2z = 1 - v2x - v2y;
   4395 
   4396                     float v3x = (float) (ONE_THIRD + cr2 * f2);
   4397                     float v3y = (float) (ONE_THIRD + sr2 * f2);
   4398                     float v3z = 1 - v3x - v3y;
   4399 
   4400                     float v4x = (float) (ONE_THIRD + cr2 * f1);
   4401                     float v4y = (float) (ONE_THIRD + sr2 * f1);
   4402                     float v4z = 1 - v4x - v4y;
   4403 
   4404                     // Compute the sRGB representation of each XYZ coordinate of the patch
   4405                     colors[colorIndex    ] = computeColor(color, v1x, v1y, v1z, colorSpace);
   4406                     colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace);
   4407                     colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace);
   4408                     colors[colorIndex + 3] = colors[colorIndex];
   4409                     colors[colorIndex + 4] = colors[colorIndex + 2];
   4410                     colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace);
   4411                     colorIndex += 6;
   4412 
   4413                     // Flip the mesh upside down to match Canvas' coordinates system
   4414                     vertices[vertexIndex++] = v1x;
   4415                     vertices[vertexIndex++] = v1y;
   4416                     vertices[vertexIndex++] = v2x;
   4417                     vertices[vertexIndex++] = v2y;
   4418                     vertices[vertexIndex++] = v3x;
   4419                     vertices[vertexIndex++] = v3y;
   4420                     vertices[vertexIndex++] = v1x;
   4421                     vertices[vertexIndex++] = v1y;
   4422                     vertices[vertexIndex++] = v3x;
   4423                     vertices[vertexIndex++] = v3y;
   4424                     vertices[vertexIndex++] = v4x;
   4425                     vertices[vertexIndex++] = v4y;
   4426                 }
   4427             }
   4428         }
   4429 
   4430         @ColorInt
   4431         private static int computeColor(@NonNull @Size(3) float[] color,
   4432                 float x, float y, float z, @NonNull ColorSpace cs) {
   4433             color[0] = x;
   4434             color[1] = y;
   4435             color[2] = z;
   4436             cs.fromXyz(color);
   4437             return 0xff000000 |
   4438                     (((int) (color[0] * 255.0f) & 0xff) << 16) |
   4439                     (((int) (color[1] * 255.0f) & 0xff) <<  8) |
   4440                     (((int) (color[2] * 255.0f) & 0xff)      );
   4441         }
   4442 
   4443         private static double sqr(double v) {
   4444             return v * v;
   4445         }
   4446 
   4447         private static class Point {
   4448             @NonNull final ColorSpace mColorSpace;
   4449             @NonNull final float[] mRgb;
   4450             final int mColor;
   4451 
   4452             Point(@NonNull ColorSpace colorSpace,
   4453                     @NonNull @Size(3) float[] rgb, @ColorInt int color) {
   4454                 mColorSpace = colorSpace;
   4455                 mRgb = rgb;
   4456                 mColor = color;
   4457             }
   4458         }
   4459     }
   4460 }
   4461