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