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