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