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