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