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