ColorSpace.java revision 4db2c229be8e4d243ca19fac4080cda1eeb22710
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.Size;
24import android.annotation.Nullable;
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 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</td>
267         *         <td colspan="4">\(\begin{equation}
268         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
269         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
270         *             \end{equation}\)
271         *         </td>
272         *     </tr>
273         *     <tr>
274         *         <td>Electro-optical transfer function</td>
275         *         <td colspan="4">\(\begin{equation}
276         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
277         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 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 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</td>
302         *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
303         *     </tr>
304         *     <tr>
305         *         <td>Electro-optical transfer function</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 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</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| \le 0.0031308 \\
332         *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
333         *                      \left| C_{linear} \right| \gt 0.0031308 \end{cases}
334         *             \end{equation}\)
335         *         </td>
336         *     </tr>
337         *     <tr>
338         *         <td>Electro-optical transfer function</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| \le 0.04045 \\
342         *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
343         *                  \left| C_{scRGB} \right| \gt 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 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</td>
368         *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
369         *     </tr>
370         *     <tr>
371         *         <td>Electro-optical transfer function</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 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</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</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 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</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</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 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</td>
465         *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
466         *     </tr>
467         *     <tr>
468         *         <td>Electro-optical transfer function</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 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</td>
492         *         <td colspan="4">\(\begin{equation}
493         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
494         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
495         *             \end{equation}\)
496         *         </td>
497         *     </tr>
498         *     <tr>
499         *         <td>Electro-optical transfer function</td>
500         *         <td colspan="4">\(\begin{equation}
501         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
502         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \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 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</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</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 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</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</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 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</td>
597         *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
598         *     </tr>
599         *     <tr>
600         *         <td>Electro-optical transfer function</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 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</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</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 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</td>
659         *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
660         *     </tr>
661         *     <tr>
662         *         <td>Electro-optical transfer function</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 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</td>
686         *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
687         *     </tr>
688         *     <tr>
689         *         <td>Electro-optical transfer function</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 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>Creates a new {@link Renderer} that can be used to visualize and
1383     * debug color spaces. See the documentation of {@link Renderer} for
1384     * more information.</p>
1385     *
1386     * @return A new non-null {@link Renderer} instance
1387     *
1388     * @see Renderer
1389     */
1390    @NonNull
1391    public static Renderer createRenderer() {
1392        return new Renderer();
1393    }
1394
1395    static {
1396        sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
1397                "sRGB IEC61966-2.1",
1398                SRGB_PRIMARIES,
1399                ILLUMINANT_D65,
1400                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1401                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1402                0.0f, 1.0f,
1403                Named.SRGB.ordinal()
1404        );
1405        sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
1406                "sRGB IEC61966-2.1 (Linear)",
1407                SRGB_PRIMARIES,
1408                ILLUMINANT_D65,
1409                DoubleUnaryOperator.identity(),
1410                DoubleUnaryOperator.identity(),
1411                0.0f, 1.0f,
1412                Named.LINEAR_SRGB.ordinal()
1413        );
1414        sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1415                "scRGB-nl IEC 61966-2-2:2003",
1416                SRGB_PRIMARIES,
1417                ILLUMINANT_D65,
1418                x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1419                x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1420                -0.799f, 2.399f,
1421                Named.EXTENDED_SRGB.ordinal()
1422        );
1423        sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1424                "scRGB- IEC 61966-2-2:2003",
1425                SRGB_PRIMARIES,
1426                ILLUMINANT_D65,
1427                DoubleUnaryOperator.identity(),
1428                DoubleUnaryOperator.identity(),
1429                -0.5f, 7.499f,
1430                Named.LINEAR_EXTENDED_SRGB.ordinal()
1431        );
1432        sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
1433                "Rec. ITU-R BT.709-5",
1434                new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
1435                ILLUMINANT_D65,
1436                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1437                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1438                0.0f, 1.0f,
1439                Named.BT709.ordinal()
1440        );
1441        sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
1442                "Rec. ITU-R BT.2020-1",
1443                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
1444                ILLUMINANT_D65,
1445                x -> rcpResponse(x, 1 / 0.45, 1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145),
1446                x -> response(x, 1 / 0.45, 1 / 1.0993, 0.099 / 1.0993, 1 / 4.5, 0.08145),
1447                0.0f, 1.0f,
1448                Named.BT2020.ordinal()
1449        );
1450        sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
1451                "SMPTE RP 431-2-2007 DCI (P3)",
1452                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1453                new float[] { 0.314f, 0.351f },
1454                x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.6),
1455                x -> Math.pow(x < 0.0f ? 0.0f : x, 2.6),
1456                0.0f, 1.0f,
1457                Named.DCI_P3.ordinal()
1458        );
1459        sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
1460                "Display P3",
1461                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1462                ILLUMINANT_D65,
1463                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1464                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1465                0.0f, 1.0f,
1466                Named.DISPLAY_P3.ordinal()
1467        );
1468        sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
1469                "NTSC (1953)",
1470                NTSC_1953_PRIMARIES,
1471                ILLUMINANT_C,
1472                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1473                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1474                0.0f, 1.0f,
1475                Named.NTSC_1953.ordinal()
1476        );
1477        sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
1478                "SMPTE-C RGB",
1479                new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
1480                ILLUMINANT_D65,
1481                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1482                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1483                0.0f, 1.0f,
1484                Named.SMPTE_C.ordinal()
1485        );
1486        sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
1487                "Adobe RGB (1998)",
1488                new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
1489                ILLUMINANT_D65,
1490                x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.2),
1491                x -> Math.pow(x < 0.0f ? 0.0f : x, 2.2),
1492                0.0f, 1.0f,
1493                Named.ADOBE_RGB.ordinal()
1494        );
1495        sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
1496                "ROMM RGB ISO 22028-2:2013",
1497                new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
1498                ILLUMINANT_D50,
1499                x -> rcpResponse(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
1500                x -> response(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
1501                0.0f, 1.0f,
1502                Named.PRO_PHOTO_RGB.ordinal()
1503        );
1504        sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
1505                "SMPTE ST 2065-1:2012 ACES",
1506                new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
1507                ILLUMINANT_D60,
1508                DoubleUnaryOperator.identity(),
1509                DoubleUnaryOperator.identity(),
1510                -65504.0f, 65504.0f,
1511                Named.ACES.ordinal()
1512        );
1513        sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
1514                "Academy S-2014-004 ACEScg",
1515                new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
1516                ILLUMINANT_D60,
1517                DoubleUnaryOperator.identity(),
1518                DoubleUnaryOperator.identity(),
1519                -65504.0f, 65504.0f,
1520                Named.ACESCG.ordinal()
1521        );
1522        sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
1523                "Generic XYZ",
1524                Named.CIE_XYZ.ordinal()
1525        );
1526        sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
1527                "Generic L*a*b*",
1528                Named.CIE_LAB.ordinal()
1529        );
1530    }
1531
1532    // Reciprocal piecewise gamma response
1533    private static double rcpResponse(double x, double g,double a, double b, double c, double d) {
1534        return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
1535    }
1536
1537    // Piecewise gamma response
1538    private static double response(double x, double g, double a, double b, double c, double d) {
1539        return x >= d ? Math.pow(a * x + b, g) : c * x;
1540    }
1541
1542    // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
1543    // spaces that allow negative values
1544    @SuppressWarnings("SameParameterValue")
1545    private static double absRcpResponse(double x, double g, double a, double b, double c, double d) {
1546        return Math.copySign(rcpResponse(x < 0.0 ? -x : x, g, a, b, c, d), x);
1547    }
1548
1549    // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
1550    // allow negative values
1551    @SuppressWarnings("SameParameterValue")
1552    private static double absResponse(double x, double g, double a, double b, double c, double d) {
1553        return Math.copySign(response(x < 0.0 ? -x : x, g, a, b, c, d), x);
1554    }
1555
1556    /**
1557     * Compares two arrays of float with a precision of 1e-3.
1558     *
1559     * @param a The first array to compare
1560     * @param b The second array to compare
1561     * @return True if the two arrays are equal, false otherwise
1562     */
1563    private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
1564        if (a == b) return true;
1565        for (int i = 0; i < a.length; i++) {
1566            if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
1567        }
1568        return true;
1569    }
1570
1571    /**
1572     * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
1573     *
1574     * @param m A 3x3 matrix as a non-null array of 9 floats
1575     * @return A new array of 9 floats containing the inverse of the input matrix
1576     */
1577    @NonNull
1578    @Size(9)
1579    private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
1580        float a = m[0];
1581        float b = m[3];
1582        float c = m[6];
1583        float d = m[1];
1584        float e = m[4];
1585        float f = m[7];
1586        float g = m[2];
1587        float h = m[5];
1588        float i = m[8];
1589
1590        float A = e * i - f * h;
1591        float B = f * g - d * i;
1592        float C = d * h - e * g;
1593
1594        float det = a * A + b * B + c * C;
1595
1596        float inverted[] = new float[m.length];
1597        inverted[0] = A / det;
1598        inverted[1] = B / det;
1599        inverted[2] = C / det;
1600        inverted[3] = (c * h - b * i) / det;
1601        inverted[4] = (a * i - c * g) / det;
1602        inverted[5] = (b * g - a * h) / det;
1603        inverted[6] = (b * f - c * e) / det;
1604        inverted[7] = (c * d - a * f) / det;
1605        inverted[8] = (a * e - b * d) / det;
1606        return inverted;
1607    }
1608
1609    /**
1610     * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
1611     *
1612     * @param lhs 3x3 matrix, as a non-null array of 9 floats
1613     * @param rhs 3x3 matrix, as a non-null array of 9 floats
1614     * @return A new array of 9 floats containing the result of the multiplication
1615     *         of rhs by lhs
1616     */
1617    @NonNull
1618    @Size(9)
1619    private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
1620        float[] r = new float[9];
1621        r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
1622        r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
1623        r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
1624        r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
1625        r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
1626        r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
1627        r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
1628        r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
1629        r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
1630        return r;
1631    }
1632
1633    /**
1634     * Multiplies a vector of 3 components by a 3x3 matrix and stores the
1635     * result in the input vector.
1636     *
1637     * @param lhs 3x3 matrix, as a non-null array of 9 floats
1638     * @param rhs Vector of 3 components, as a non-null array of 3 floats
1639     * @return The array of 3 passed as the rhs parameter
1640     */
1641    @NonNull
1642    @Size(min = 3)
1643    private static float[] mul3x3Float3(
1644            @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
1645        float r0 = rhs[0];
1646        float r1 = rhs[1];
1647        float r2 = rhs[2];
1648        rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
1649        rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
1650        rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
1651        return rhs;
1652    }
1653
1654    /**
1655     * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
1656     * by a 3x3 matrix represented as an array of 9 floats.
1657     *
1658     * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
1659     * @param rhs 3x3 matrix, as a non-null array of 9 floats
1660     * @return A new array of 9 floats containing the result of the multiplication
1661     *         of rhs by lhs
1662     */
1663    @NonNull
1664    @Size(9)
1665    private static float[] mul3x3Diag(
1666            @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
1667        return new float[] {
1668                lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
1669                lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
1670                lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
1671        };
1672    }
1673
1674    /**
1675     * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
1676     * input xyY array only contains the x and y components.
1677     *
1678     * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
1679     * @return A new float array of length 3 containing XYZ values
1680     */
1681    @NonNull
1682    @Size(3)
1683    private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
1684        return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
1685    }
1686
1687    /**
1688     * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the
1689     * input xyY array only contains the x and y components. After this method
1690     * returns, the xyY array contains the converted u and v components.
1691     *
1692     * @param xyY The xyY value to convert to XYZ, cannot be null,
1693     *            length must be a multiple of 2
1694     */
1695    private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) {
1696        for (int i = 0; i < xyY.length; i += 2) {
1697            float x = xyY[i];
1698            float y = xyY[i + 1];
1699
1700            float d = -2.0f * x + 12.0f * y + 3;
1701            float u = (4.0f * x) / d;
1702            float v = (9.0f * y) / d;
1703
1704            xyY[i] = u;
1705            xyY[i + 1] = v;
1706        }
1707    }
1708
1709    /**
1710     * <p>Computes the chromatic adaptation transform from the specified
1711     * source white point to the specified destination white point.</p>
1712     *
1713     * <p>The transform is computed using the von Kris method, described
1714     * in more details in the documentation of {@link Adaptation}. The
1715     * {@link Adaptation} enum provides different matrices that can be
1716     * used to perform the adaptation.</p>
1717     *
1718     * @param matrix The adaptation matrix
1719     * @param srcWhitePoint The white point to adapt from, *will be modified*
1720     * @param dstWhitePoint The white point to adapt to, *will be modified*
1721     * @return A 3x3 matrix as a non-null array of 9 floats
1722     */
1723    @NonNull
1724    @Size(9)
1725    private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
1726            @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
1727        float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
1728        float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
1729        // LMS is a diagonal matrix stored as a float[3]
1730        float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
1731        return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
1732    }
1733
1734    /**
1735     * Implementation of the CIE XYZ color space. Assumes the white point is D50.
1736     */
1737    @AnyThread
1738    private static final class Xyz extends ColorSpace {
1739        private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1740            super(name, Model.XYZ, id);
1741        }
1742
1743        @Override
1744        public boolean isWideGamut() {
1745            return true;
1746        }
1747
1748        @Override
1749        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1750            return -2.0f;
1751        }
1752
1753        @Override
1754        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1755            return 2.0f;
1756        }
1757
1758        @Override
1759        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1760            v[0] = clamp(v[0]);
1761            v[1] = clamp(v[1]);
1762            v[2] = clamp(v[2]);
1763            return v;
1764        }
1765
1766        @Override
1767        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
1768            v[0] = clamp(v[0]);
1769            v[1] = clamp(v[1]);
1770            v[2] = clamp(v[2]);
1771            return v;
1772        }
1773
1774        private static float clamp(float x) {
1775            return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
1776        }
1777    }
1778
1779    /**
1780     * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
1781     * with a white point of D50.
1782     */
1783    @AnyThread
1784    private static final class Lab extends ColorSpace {
1785        private static final float A = 216.0f / 24389.0f;
1786        private static final float B = 841.0f / 108.0f;
1787        private static final float C = 4.0f / 29.0f;
1788        private static final float D = 6.0f / 29.0f;
1789
1790        private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1791            super(name, Model.LAB, id);
1792        }
1793
1794        @Override
1795        public boolean isWideGamut() {
1796            return true;
1797        }
1798
1799        @Override
1800        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1801            return component == 0 ? 0.0f : -128.0f;
1802        }
1803
1804        @Override
1805        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1806            return component == 0 ? 100.0f : 128.0f;
1807        }
1808
1809        @Override
1810        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1811            v[0] = clamp(v[0], 0.0f, 100.0f);
1812            v[1] = clamp(v[1], -128.0f, 128.0f);
1813            v[2] = clamp(v[2], -128.0f, 128.0f);
1814
1815            float fy = (v[0] + 16.0f) / 116.0f;
1816            float fx = fy + (v[1] * 0.002f);
1817            float fz = fy - (v[2] * 0.005f);
1818            float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
1819            float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
1820            float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
1821
1822            v[0] = X * ILLUMINANT_D50_XYZ[0];
1823            v[1] = Y * ILLUMINANT_D50_XYZ[1];
1824            v[2] = Z * ILLUMINANT_D50_XYZ[2];
1825
1826            return v;
1827        }
1828
1829        @Override
1830        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
1831            float X = v[0] / ILLUMINANT_D50_XYZ[0];
1832            float Y = v[1] / ILLUMINANT_D50_XYZ[1];
1833            float Z = v[2] / ILLUMINANT_D50_XYZ[2];
1834
1835            float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
1836            float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
1837            float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
1838
1839            float L = 116.0f * fy - 16.0f;
1840            float a = 500.0f * (fx - fy);
1841            float b = 200.0f * (fy - fz);
1842
1843            v[0] = clamp(L, 0.0f, 100.0f);
1844            v[1] = clamp(a, -128.0f, 128.0f);
1845            v[2] = clamp(b, -128.0f, 128.0f);
1846
1847            return v;
1848        }
1849
1850        private static float clamp(float x, float min, float max) {
1851            return x < min ? min : x > max ? max : x;
1852        }
1853    }
1854
1855    /**
1856     * {@usesMathJax}
1857     *
1858     * <p>An RGB color space is an additive color space using the
1859     * {@link Model#RGB RGB} color model (a color is therefore represented
1860     * by a tuple of 3 numbers).</p>
1861     *
1862     * <p>A specific RGB color space is defined by the following properties:</p>
1863     * <ul>
1864     *     <li>Three chromaticities of the red, green and blue primaries, which
1865     *     define the gamut of the color space.</li>
1866     *     <li>A white point chromaticity that defines the stimulus to which
1867     *     color space values are normalized (also just called "white").</li>
1868     *     <li>An opto-electronic transfer function, also called opto-electronic
1869     *     conversion function or often, and approximately, gamma function.</li>
1870     *     <li>An electro-optical transfer function, also called electo-optical
1871     *     conversion function or often, and approximately, gamma function.</li>
1872     *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
1873     * </ul>
1874     *
1875     * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
1876     *
1877     * <h3>Primaries and white point chromaticities</h3>
1878     * <p>In this implementation, the chromaticity of the primaries and the white
1879     * point of an RGB color space is defined in the CIE xyY color space. This
1880     * color space separates the chromaticity of a color, the x and y components,
1881     * and its luminance, the Y component. Since the primaries and the white
1882     * point have full brightness, the Y component is assumed to be 1 and only
1883     * the x and y components are needed to encode them.</p>
1884     * <p>For convenience, this implementation also allows to define the
1885     * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
1886     * are internally converted to xyY.</p>
1887     *
1888     * <p>
1889     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
1890     *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
1891     * </p>
1892     *
1893     * <h3>Transfer functions</h3>
1894     * <p>A transfer function is a color component conversion function, defined as
1895     * a single variable, monotonic mathematical function. It is applied to each
1896     * individual component of a color. They are used to perform the mapping
1897     * between linear tristimulus values and non-linear electronic signal value.</p>
1898     * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
1899     * tristimulus values in a scene to a non-linear electronic signal value.
1900     * An OETF is often expressed as a power function with an exponent between
1901     * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
1902     * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
1903     * a non-linear electronic signal value to a tristimulus value at the display.
1904     * An EOTF is often expressed as a power function with an exponent between
1905     * 1.8 and 2.6.</p>
1906     * <p>Transfer functions are used as a compression scheme. For instance,
1907     * linear sRGB values would normally require 11 to 12 bits of precision to
1908     * store all values that can be perceived by the human eye. When encoding
1909     * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
1910     * an exact mathematical description of that OETF), the values can be
1911     * compressed to only 8 bits precision.</p>
1912     * <p>When manipulating RGB values, particularly sRGB values, it is safe
1913     * to assume that these values have been encoded with the appropriate
1914     * OETF (unless noted otherwise). Encoded values are often said to be in
1915     * "gamma space". They are therefore defined in a non-linear space. This
1916     * in turns means that any linear operation applied to these values is
1917     * going to yield mathematically incorrect results (any linear interpolation
1918     * such as gradient generation for instance, most image processing functions
1919     * such as blurs, etc.).</p>
1920     * <p>To properly process encoded RGB values you must first apply the
1921     * EOTF to decode the value into linear space. After processing, the RGB
1922     * value must be encoded back to non-linear ("gamma") space. Here is a
1923     * formal description of the process, where \(f\) is the processing
1924     * function to apply:</p>
1925     *
1926     * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
1927     *
1928     * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
1929     * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
1930     * their transfer functions are the identity function: \(f(x) = x\).
1931     * If the source and/or destination are known to be linear, it is not
1932     * necessary to invoke the transfer functions.</p>
1933     *
1934     * <h3>Range</h3>
1935     * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
1936     * are however a few RGB color spaces that allow much larger ranges. For
1937     * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
1938     * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
1939     * the range \([-65504, 65504]\).</p>
1940     *
1941     * <p>
1942     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
1943     *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
1944     * </p>
1945     *
1946     * <h3>Converting between RGB color spaces</h3>
1947     * <p>Conversion between two color spaces is achieved by using an intermediate
1948     * color space called the profile connection space (PCS). The PCS used by
1949     * this implementation is CIE XYZ. The conversion operation is defined
1950     * as such:</p>
1951     *
1952     * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
1953     *
1954     * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
1955     * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
1956     * XYZ to RGB transform} of the destination color space.</p>
1957     * <p>Many RGB color spaces commonly used with electronic devices use the
1958     * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
1959     * when converting between two RGB color spaces if their white points do not
1960     * match. This can be achieved by either calling
1961     * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
1962     * a single common white point. This can be achieved automatically by calling
1963     * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
1964     * non-RGB color spaces.</p>
1965     * <p>To learn more about the white point adaptation process, refer to the
1966     * documentation of {@link Adaptation}.</p>
1967     */
1968    @AnyThread
1969    public static class Rgb extends ColorSpace {
1970        @NonNull private final float[] mWhitePoint;
1971        @NonNull private final float[] mPrimaries;
1972        @NonNull private final float[] mTransform;
1973        @NonNull private final float[] mInverseTransform;
1974
1975        @NonNull private final boolean mIsWideGamut;
1976        @NonNull private final boolean mIsSrgb;
1977
1978        @NonNull private final DoubleUnaryOperator mOetf;
1979        @NonNull private final DoubleUnaryOperator mEotf;
1980        @NonNull private final DoubleUnaryOperator mClampedOetf;
1981        @NonNull private final DoubleUnaryOperator mClampedEotf;
1982
1983        private final float mMin;
1984        private final float mMax;
1985
1986        /**
1987         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
1988         * The transform matrix must convert from the RGB space to the profile connection
1989         * space CIE XYZ.</p>
1990         *
1991         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
1992         *
1993         * @param name Name of the color space, cannot be null, its length must be >= 1
1994         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
1995         *              connection space CIE XYZ as an array of 9 floats, cannot be null
1996         * @param oetf Opto-electronic transfer function, cannot be null
1997         * @param eotf Electro-optical transfer function, cannot be null
1998         *
1999         * @throws IllegalArgumentException If any of the following conditions is met:
2000         * <ul>
2001         *     <li>The name is null or has a length of 0.</li>
2002         *     <li>The OETF is null or the EOTF is null.</li>
2003         *     <li>The minimum valid value is >= the maximum valid value.</li>
2004         * </ul>
2005         *
2006         * @see #get(Named)
2007         */
2008        public Rgb(
2009                @NonNull @Size(min = 1) String name,
2010                @NonNull @Size(9) float[] toXYZ,
2011                @NonNull DoubleUnaryOperator oetf,
2012                @NonNull DoubleUnaryOperator eotf) {
2013            this(name, computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf),
2014                    oetf, eotf, 0.0f, 1.0f, MIN_ID);
2015        }
2016
2017        /**
2018         * <p>Creates a new RGB color space using a specified set of primaries
2019         * and a specified white point.</p>
2020         *
2021         * <p>The primaries and white point can be specified in the CIE xyY space
2022         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2023         *
2024         * <table summary="Parameters length">
2025         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2026         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2027         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2028         * </table>
2029         *
2030         * <p>When the primaries and/or white point are specified in xyY, the Y component
2031         * does not need to be specified and is assumed to be 1.0. Only the xy components
2032         * are required.</p>
2033         *
2034         * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
2035         * this constructor is always {@link #MIN_ID}.</p>
2036         *
2037         * @param name Name of the color space, cannot be null, its length must be >= 1
2038         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2039         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2040         * @param oetf Opto-electronic transfer function, cannot be null
2041         * @param eotf Electro-optical transfer function, cannot be null
2042         * @param min The minimum valid value in this color space's RGB range
2043         * @param max The maximum valid value in this color space's RGB range
2044         *
2045         * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
2046         * <ul>
2047         *     <li>The name is null or has a length of 0.</li>
2048         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2049         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2050         *     <li>The OETF is null or the EOTF is null.</li>
2051         *     <li>The minimum valid value is >= the maximum valid value.</li>
2052         * </ul>
2053         *
2054         * @see #get(Named)
2055         */
2056        public Rgb(
2057                @NonNull @Size(min = 1) String name,
2058                @NonNull @Size(min = 6, max = 9) float[] primaries,
2059                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2060                @NonNull DoubleUnaryOperator oetf,
2061                @NonNull DoubleUnaryOperator eotf,
2062                float min,
2063                float max) {
2064            this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
2065        }
2066
2067        /**
2068         * <p>Creates a new RGB color space using a specified set of primaries
2069         * and a specified white point.</p>
2070         *
2071         * <p>The primaries and white point can be specified in the CIE xyY space
2072         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2073         *
2074         * <table summary="Parameters length">
2075         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2076         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2077         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2078         * </table>
2079         *
2080         * <p>When the primaries and/or white point are specified in xyY, the Y component
2081         * does not need to be specified and is assumed to be 1.0. Only the xy components
2082         * are required.</p>
2083         *
2084         * @param name Name of the color space, cannot be null, its length must be >= 1
2085         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2086         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2087         * @param oetf Opto-electronic transfer function, cannot be null
2088         * @param eotf Electro-optical transfer function, cannot be null
2089         * @param min The minimum valid value in this color space's RGB range
2090         * @param max The maximum valid value in this color space's RGB range
2091         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
2092         *
2093         * @throws IllegalArgumentException If any of the following conditions is met:
2094         * <ul>
2095         *     <li>The name is null or has a length of 0.</li>
2096         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2097         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2098         *     <li>The OETF is null or the EOTF is null.</li>
2099         *     <li>The minimum valid value is >= the maximum valid value.</li>
2100         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
2101         * </ul>
2102         *
2103         * @see #get(Named)
2104         */
2105        private Rgb(
2106                @NonNull @Size(min = 1) String name,
2107                @NonNull @Size(min = 6, max = 9) float[] primaries,
2108                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2109                @NonNull DoubleUnaryOperator oetf,
2110                @NonNull DoubleUnaryOperator eotf,
2111                float min,
2112                float max,
2113                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2114
2115            super(name, Model.RGB, id);
2116
2117            if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
2118                throw new IllegalArgumentException("The color space's primaries must be " +
2119                        "defined as an array of 6 floats in xyY or 9 floats in XYZ");
2120            }
2121
2122            if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
2123                throw new IllegalArgumentException("The color space's white point must be " +
2124                        "defined as an array of 2 floats in xyY or 3 float in XYZ");
2125            }
2126
2127            if (oetf == null || eotf == null) {
2128                throw new IllegalArgumentException("The transfer functions of a color space " +
2129                        "cannot be null");
2130            }
2131
2132            if (min >= max) {
2133                throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
2134                        "; min must be strictly < max");
2135            }
2136
2137            mWhitePoint = xyWhitePoint(whitePoint);
2138            mPrimaries =  xyPrimaries(primaries);
2139
2140            mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
2141            mInverseTransform = inverse3x3(mTransform);
2142
2143            mOetf = oetf;
2144            mEotf = eotf;
2145
2146            mMin = min;
2147            mMax = max;
2148
2149            DoubleUnaryOperator clamp = this::clamp;
2150            mClampedOetf = oetf.andThen(clamp);
2151            mClampedEotf = clamp.andThen(eotf);
2152
2153            // A color space is wide-gamut if its area is >90% of NTSC 1953 and
2154            // if it entirely contains the Color space definition in xyY
2155            mIsWideGamut = isWideGamut(mPrimaries, min, max);
2156            mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
2157        }
2158
2159        /**
2160         * Creates a copy of the specified color space with a new transform.
2161         *
2162         * @param colorSpace The color space to create a copy of
2163         */
2164        private Rgb(Rgb colorSpace,
2165                @NonNull @Size(9) float[] transform,
2166                @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
2167            super(colorSpace.getName(), Model.RGB, -1);
2168
2169            mWhitePoint = xyWhitePoint(whitePoint);
2170            mPrimaries = colorSpace.mPrimaries;
2171
2172            mTransform = transform;
2173            mInverseTransform = inverse3x3(transform);
2174
2175            mMin = colorSpace.mMin;
2176            mMax = colorSpace.mMax;
2177
2178            mOetf = colorSpace.mOetf;
2179            mEotf = colorSpace.mEotf;
2180
2181            mClampedOetf = colorSpace.mClampedOetf;
2182            mClampedEotf = colorSpace.mClampedEotf;
2183
2184            mIsWideGamut = colorSpace.mIsWideGamut;
2185            mIsSrgb = colorSpace.mIsSrgb;
2186        }
2187
2188        /**
2189         * Copies the non-adapted CIE xyY white point of this color space in
2190         * specified array. The Y component is assumed to be 1 and is therefore
2191         * not copied into the destination. The x and y components are written
2192         * in the array at positions 0 and 1 respectively.
2193         *
2194         * @param whitePoint The destination array, cannot be null, its length
2195         *                   must be >= 2
2196         *
2197         * @return The destination array passed as a parameter
2198         *
2199         * @see #getWhitePoint(float[])
2200         */
2201        @NonNull
2202        @Size(min = 2)
2203        public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
2204            whitePoint[0] = mWhitePoint[0];
2205            whitePoint[1] = mWhitePoint[1];
2206            return whitePoint;
2207        }
2208
2209        /**
2210         * Returns the non-adapted CIE xyY white point of this color space as
2211         * a new array of 2 floats. The Y component is assumed to be 1 and is
2212         * therefore not copied into the destination. The x and y components
2213         * are written in the array at positions 0 and 1 respectively.
2214         *
2215         * @return A new non-null array of 2 floats
2216         *
2217         * @see #getWhitePoint()
2218         */
2219        @NonNull
2220        @Size(2)
2221        public float[] getWhitePoint() {
2222            return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
2223        }
2224
2225        /**
2226         * Copies the primaries of this color space in specified array. The Y
2227         * component is assumed to be 1 and is therefore not copied into the
2228         * destination. The x and y components of the first primary are written
2229         * in the array at positions 0 and 1 respectively.
2230         *
2231         * @param primaries The destination array, cannot be null, its length
2232         *                  must be >= 6
2233         *
2234         * @return The destination array passed as a parameter
2235         *
2236         * @see #getPrimaries(float[])
2237         */
2238        @NonNull
2239        @Size(min = 6)
2240        public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
2241            System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
2242            return primaries;
2243        }
2244
2245        /**
2246         * Returns the primaries of this color space as a new array of 6 floats.
2247         * The Y component is assumed to be 1 and is therefore not copied into
2248         * the destination. The x and y components of the first primary are
2249         * written in the array at positions 0 and 1 respectively.
2250         *
2251         * @return A new non-null array of 2 floats
2252         *
2253         * @see #getWhitePoint()
2254         */
2255        @NonNull
2256        @Size(6)
2257        public float[] getPrimaries() {
2258            return Arrays.copyOf(mPrimaries, mPrimaries.length);
2259        }
2260
2261        /**
2262         * <p>Copies the transform of this color space in specified array. The
2263         * transform is used to convert from RGB to XYZ (with the same white
2264         * point as this color space). To connect color spaces, you must first
2265         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2266         * same white point.</p>
2267         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2268         * to convert between color spaces.</p>
2269         *
2270         * @param transform The destination array, cannot be null, its length
2271         *                  must be >= 9
2272         *
2273         * @return The destination array passed as a parameter
2274         *
2275         * @see #getInverseTransform()
2276         */
2277        @NonNull
2278        @Size(min = 9)
2279        public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
2280            System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
2281            return transform;
2282        }
2283
2284        /**
2285         * <p>Returns the transform of this color space as a new array. The
2286         * transform is used to convert from RGB to XYZ (with the same white
2287         * point as this color space). To connect color spaces, you must first
2288         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2289         * same white point.</p>
2290         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2291         * to convert between color spaces.</p>
2292         *
2293         * @return A new array of 9 floats
2294         *
2295         * @see #getInverseTransform(float[])
2296         */
2297        @NonNull
2298        @Size(9)
2299        public float[] getTransform() {
2300            return Arrays.copyOf(mTransform, mTransform.length);
2301        }
2302
2303        /**
2304         * <p>Copies the inverse transform of this color space in specified array.
2305         * The inverse transform is used to convert from XYZ to RGB (with the
2306         * same white point as this color space). To connect color spaces, you
2307         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2308         * to the same white point.</p>
2309         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2310         * to convert between color spaces.</p>
2311         *
2312         * @param inverseTransform The destination array, cannot be null, its length
2313         *                  must be >= 9
2314         *
2315         * @return The destination array passed as a parameter
2316         *
2317         * @see #getTransform()
2318         */
2319        @NonNull
2320        @Size(min = 9)
2321        public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
2322            System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
2323            return inverseTransform;
2324        }
2325
2326        /**
2327         * <p>Returns the inverse transform of this color space as a new array.
2328         * The inverse transform is used to convert from XYZ to RGB (with the
2329         * same white point as this color space). To connect color spaces, you
2330         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2331         * to the same white point.</p>
2332         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2333         * to convert between color spaces.</p>
2334         *
2335         * @return A new array of 9 floats
2336         *
2337         * @see #getTransform(float[])
2338         */
2339        @NonNull
2340        @Size(9)
2341        public float[] getInverseTransform() {
2342            return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
2343        }
2344
2345        /**
2346         * <p>Returns the opto-electronic transfer function (OETF) of this color space.
2347         * The inverse function is the electro-optical transfer function (EOTF) returned
2348         * by {@link #getEotf()}. These functions are defined to satisfy the following
2349         * equality for \(x \in [0..1]\):</p>
2350         *
2351         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
2352         *
2353         * <p>For RGB colors, this function can be used to convert from linear space
2354         * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
2355         * are frequently used because many OETFs can be closely approximated using
2356         * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
2357         * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
2358         * for instance).</p>
2359         *
2360         * @return A transfer function that converts from linear space to "gamma space"
2361         *
2362         * @see #getEotf()
2363         */
2364        @NonNull
2365        public DoubleUnaryOperator getOetf() {
2366            return mClampedOetf;
2367        }
2368
2369        /**
2370         * <p>Returns the electro-optical transfer function (EOTF) of this color space.
2371         * The inverse function is the opto-electronic transfer function (OETF)
2372         * returned by {@link #getOetf()}. These functions are defined to satisfy the
2373         * following equality for \(x \in [0..1]\):</p>
2374         *
2375         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
2376         *
2377         * <p>For RGB colors, this function can be used to convert from "gamma space"
2378         * (gamma encoded) to linear space. The terms gamma space and gamma encoded
2379         * are frequently used because many EOTFs can be closely approximated using
2380         * a simple power function of the form \(x^\gamma\) (the approximation of the
2381         * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
2382         *
2383         * @return A transfer function that converts from "gamma space" to linear space
2384         *
2385         * @see #getOetf()
2386         */
2387        @NonNull
2388        public DoubleUnaryOperator getEotf() {
2389            return mClampedEotf;
2390        }
2391
2392        @Override
2393        public boolean isSrgb() {
2394            return mIsSrgb;
2395        }
2396
2397        @Override
2398        public boolean isWideGamut() {
2399            return mIsWideGamut;
2400        }
2401
2402        @Override
2403        public float getMinValue(int component) {
2404            return mMin;
2405        }
2406
2407        @Override
2408        public float getMaxValue(int component) {
2409            return mMax;
2410        }
2411
2412        /**
2413         * <p>Decodes an RGB value to linear space. This is achieved by
2414         * applying this color space's electro-optical transfer function
2415         * to the supplied values.</p>
2416         *
2417         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2418         * more information about transfer functions and their use for
2419         * encoding and decoding RGB values.</p>
2420         *
2421         * @param r The red component to decode to linear space
2422         * @param g The green component to decode to linear space
2423         * @param b The blue component to decode to linear space
2424         * @return A new array of 3 floats containing linear RGB values
2425         *
2426         * @see #toLinear(float[])
2427         * @see #fromLinear(float, float, float)
2428         */
2429        @NonNull
2430        @Size(3)
2431        public float[] toLinear(float r, float g, float b) {
2432            return toLinear(new float[] { r, g, b });
2433        }
2434
2435        /**
2436         * <p>Decodes an RGB value to linear space. This is achieved by
2437         * applying this color space's electro-optical transfer function
2438         * to the first 3 values of the supplied array. The result is
2439         * stored back in the input array.</p>
2440         *
2441         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2442         * more information about transfer functions and their use for
2443         * encoding and decoding RGB values.</p>
2444         *
2445         * @param v A non-null array of non-linear RGB values, its length
2446         *          must be at least 3
2447         * @return The specified array
2448         *
2449         * @see #toLinear(float, float, float)
2450         * @see #fromLinear(float[])
2451         */
2452        @NonNull
2453        @Size(min = 3)
2454        public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
2455            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
2456            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
2457            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
2458            return v;
2459        }
2460
2461        /**
2462         * <p>Encodes an RGB value from linear space to this color space's
2463         * "gamma space". This is achieved by applying this color space's
2464         * opto-electronic transfer function to the supplied values.</p>
2465         *
2466         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2467         * more information about transfer functions and their use for
2468         * encoding and decoding RGB values.</p>
2469         *
2470         * @param r The red component to encode from linear space
2471         * @param g The green component to encode from linear space
2472         * @param b The blue component to encode from linear space
2473         * @return A new array of 3 floats containing non-linear RGB values
2474         *
2475         * @see #fromLinear(float[])
2476         * @see #toLinear(float, float, float)
2477         */
2478        @NonNull
2479        @Size(3)
2480        public float[] fromLinear(float r, float g, float b) {
2481            return fromLinear(new float[] { r, g, b });
2482        }
2483
2484        /**
2485         * <p>Encodes an RGB value from linear space to this color space's
2486         * "gamma space". This is achieved by applying this color space's
2487         * opto-electronic transfer function to the first 3 values of the
2488         * supplied array. The result is stored back in the input array.</p>
2489         *
2490         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2491         * more information about transfer functions and their use for
2492         * encoding and decoding RGB values.</p>
2493         *
2494         * @param v A non-null array of linear RGB values, its length
2495         *          must be at least 3
2496         * @return A new array of 3 floats containing non-linear RGB values
2497         *
2498         * @see #fromLinear(float[])
2499         * @see #toLinear(float, float, float)
2500         */
2501        @NonNull
2502        @Size(min = 3)
2503        public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
2504            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
2505            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
2506            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
2507            return v;
2508        }
2509
2510        @Override
2511        @NonNull
2512        @Size(min = 3)
2513        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
2514            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
2515            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
2516            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
2517            return mul3x3Float3(mTransform, v);
2518        }
2519
2520        @Override
2521        @NonNull
2522        @Size(min = 3)
2523        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
2524            mul3x3Float3(mInverseTransform, v);
2525            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
2526            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
2527            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
2528            return v;
2529        }
2530
2531        private double clamp(double x) {
2532            return x < mMin ? mMin : x > mMax ? mMax : x;
2533        }
2534
2535        @Override
2536        public boolean equals(Object o) {
2537            if (this == o) return true;
2538            if (o == null || getClass() != o.getClass()) return false;
2539            if (!super.equals(o)) return false;
2540
2541            Rgb rgb = (Rgb) o;
2542
2543            if (Float.compare(rgb.mMin, mMin) != 0) return false;
2544            if (Float.compare(rgb.mMax, mMax) != 0) return false;
2545            if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
2546            if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
2547            //noinspection SimplifiableIfStatement
2548            if (!mOetf.equals(rgb.mOetf)) return false;
2549            return mEotf.equals(rgb.mEotf);
2550        }
2551
2552        @Override
2553        public int hashCode() {
2554            int result = super.hashCode();
2555            result = 31 * result + Arrays.hashCode(mWhitePoint);
2556            result = 31 * result + Arrays.hashCode(mPrimaries);
2557            result = 31 * result + mOetf.hashCode();
2558            result = 31 * result + mEotf.hashCode();
2559            result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
2560            result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
2561            return result;
2562        }
2563
2564        /**
2565         * Computes whether a color space is the sRGB color space or at least
2566         * a close approximation.
2567         *
2568         * @param primaries The set of RGB primaries in xyY as an array of 6 floats
2569         * @param whitePoint The white point in xyY as an array of 2 floats
2570         * @param OETF The opto-electronic transfer function
2571         * @param EOTF The electro-optical transfer function
2572         * @param min The minimum value of the color space's range
2573         * @param max The minimum value of the color space's range
2574         * @param id The ID of the color space
2575         * @return True if the color space can be considered as the sRGB color space
2576         *
2577         * @see #isSrgb()
2578         */
2579        @SuppressWarnings("RedundantIfStatement")
2580        private static boolean isSrgb(
2581                @NonNull @Size(6) float[] primaries,
2582                @NonNull @Size(2) float[] whitePoint,
2583                @NonNull DoubleUnaryOperator OETF,
2584                @NonNull DoubleUnaryOperator EOTF,
2585                float min,
2586                float max,
2587                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2588            if (id == 0) return true;
2589            if (!compare(primaries, SRGB_PRIMARIES)) {
2590                return false;
2591            }
2592            if (!compare(whitePoint, ILLUMINANT_D65)) {
2593                return false;
2594            }
2595            if (OETF.applyAsDouble(0.5) < 0.5001) return false;
2596            if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
2597            if (min != 0.0f) return false;
2598            if (max != 1.0f) return false;
2599            return true;
2600        }
2601
2602        /**
2603         * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
2604         * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
2605         * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
2606         * If the conditions above are not met, the color space is considered as having
2607         * a wide color gamut if its range is larger than [0..1].
2608         *
2609         * @param primaries RGB primaries in CIE xyY as an array of 6 floats
2610         * @param min The minimum value of the color space's range
2611         * @param max The minimum value of the color space's range
2612         * @return True if the color space has a wide gamut, false otherwise
2613         *
2614         * @see #isWideGamut()
2615         * @see #area(float[])
2616         */
2617        private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
2618                float min, float max) {
2619            return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
2620                            contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
2621        }
2622
2623        /**
2624         * Computes the area of the triangle represented by a set of RGB primaries
2625         * in the CIE xyY space.
2626         *
2627         * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
2628         * @return The area of the triangle
2629         *
2630         * @see #isWideGamut(float[], float, float)
2631         */
2632        private static float area(@NonNull @Size(6) float[] primaries) {
2633            float Rx = primaries[0];
2634            float Ry = primaries[1];
2635            float Gx = primaries[2];
2636            float Gy = primaries[3];
2637            float Bx = primaries[4];
2638            float By = primaries[5];
2639            float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
2640            float r = 0.5f * det;
2641            return r < 0.0f ? -r : r;
2642        }
2643
2644        /**
2645         * Computes the cross product of two 2D vectors.
2646         *
2647         * @param ax The x coordinate of the first vector
2648         * @param ay The y coordinate of the first vector
2649         * @param bx The x coordinate of the second vector
2650         * @param by The y coordinate of the second vector
2651         * @return The result of a x b
2652         */
2653        private static float cross(float ax, float ay, float bx, float by) {
2654            return ax * by - ay * bx;
2655        }
2656
2657        /**
2658         * Decides whether a 2D triangle, identified by the 6 coordinates of its
2659         * 3 vertices, is contained within another 2D triangle, also identified
2660         * by the 6 coordinates of its 3 vertices.
2661         *
2662         * In the illustration below, we want to test whether the RGB triangle
2663         * is contained within the triangle XYZ formed by the 3 vertices at
2664         * the "+" locations.
2665         *
2666         *                                     Y     .
2667         *                                 .   +    .
2668         *                                  .     ..
2669         *                                   .   .
2670         *                                    . .
2671         *                                     .  G
2672         *                                     *
2673         *                                    * *
2674         *                                  **   *
2675         *                                 *      **
2676         *                                *         *
2677         *                              **           *
2678         *                             *              *
2679         *                            *                *
2680         *                          **                  *
2681         *                         *                     *
2682         *                        *                       **
2683         *                      **                          *   R    ...
2684         *                     *                             *  .....
2685         *                    *                         ***** ..
2686         *                  **              ************       .   +
2687         *              B  *    ************                    .   X
2688         *           ......*****                                 .
2689         *     ......    .                                        .
2690         *             ..
2691         *        +   .
2692         *      Z    .
2693         *
2694         * RGB is contained within XYZ if all the following conditions are true
2695         * (with "x" the cross product operator):
2696         *
2697         *   -->  -->
2698         *   GR x RX >= 0
2699         *   -->  -->
2700         *   RX x BR >= 0
2701         *   -->  -->
2702         *   RG x GY >= 0
2703         *   -->  -->
2704         *   GY x RG >= 0
2705         *   -->  -->
2706         *   RB x BZ >= 0
2707         *   -->  -->
2708         *   BZ x GB >= 0
2709         *
2710         * @param p1 The enclosing triangle
2711         * @param p2 The enclosed triangle
2712         * @return True if the triangle p1 contains the triangle p2
2713         *
2714         * @see #isWideGamut(float[], float, float)
2715         */
2716        @SuppressWarnings("RedundantIfStatement")
2717        private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
2718            // Translate the vertices p1 in the coordinates system
2719            // with the vertices p2 as the origin
2720            float[] p0 = new float[] {
2721                    p1[0] - p2[0], p1[1] - p2[1],
2722                    p1[2] - p2[2], p1[3] - p2[3],
2723                    p1[4] - p2[4], p1[5] - p2[5],
2724            };
2725            // Check the first vertex of p1
2726            if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
2727                    cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
2728                return false;
2729            }
2730            // Check the second vertex of p1
2731            if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
2732                    cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
2733                return false;
2734            }
2735            // Check the third vertex of p1
2736            if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
2737                    cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
2738                return false;
2739            }
2740            return true;
2741        }
2742
2743        /**
2744         * Computes the primaries  of a color space identified only by
2745         * its RGB->XYZ transform matrix. This method assumes that the
2746         * range of the color space is [0..1].
2747         *
2748         * @param toXYZ The color space's 3x3 transform matrix to XYZ
2749         * @param EOTF The color space's electro-optical transfer function
2750         * @return A new array of 6 floats containing the color space's
2751         *         primaries in CIE xyY
2752         */
2753        @NonNull
2754        @Size(6)
2755        private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ,
2756                DoubleUnaryOperator EOTF) {
2757            float one = (float) EOTF.applyAsDouble(1.0);
2758            float[] r = mul3x3Float3(toXYZ, new float[] { one, 0.0f, 0.0f });
2759            float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, one, 0.0f });
2760            float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, one });
2761
2762            float rSum = r[0] + r[1] + r[2];
2763            float gSum = g[0] + g[1] + g[2];
2764            float bSum = b[0] + b[1] + b[2];
2765
2766            return new float[] {
2767                    r[0] / rSum, r[1] / rSum,
2768                    g[0] / gSum, g[1] / gSum,
2769                    b[0] / bSum, b[1] / bSum,
2770            };
2771        }
2772
2773        /**
2774         * Computes the white point of a color space identified only by
2775         * its RGB->XYZ transform matrix. This method assumes that the
2776         * range of the color space is [0..1].
2777         *
2778         * @param toXYZ The color space's 3x3 transform matrix to XYZ
2779         * @param EOTF The color space's electro-optical transfer function
2780         * @return A new array of 2 floats containing the color space's
2781         *         white point in CIE xyY
2782         */
2783        @NonNull
2784        @Size(2)
2785        private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ,
2786                @NonNull DoubleUnaryOperator EOTF) {
2787            float one = (float) EOTF.applyAsDouble(1.0);
2788            float[] w = mul3x3Float3(toXYZ, new float[] { one, one, one });
2789            float sum = w[0] + w[1] + w[2];
2790            return new float[] { w[0] / sum, w[1] / sum };
2791        }
2792
2793        /**
2794         * Converts the specified RGB primaries point to xyY if needed. The primaries
2795         * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
2796         * (in CIE XYZ). If no conversion is needed, the input array is copied.
2797         *
2798         * @param primaries The primaries in xyY or XYZ
2799         * @return A new array of 6 floats containing the primaries in xyY
2800         */
2801        @NonNull
2802        @Size(6)
2803        private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
2804            float[] xyPrimaries = new float[6];
2805
2806            // XYZ to xyY
2807            if (primaries.length == 9) {
2808                float sum;
2809
2810                sum = primaries[0] + primaries[1] + primaries[2];
2811                xyPrimaries[0] = primaries[0] / sum;
2812                xyPrimaries[1] = primaries[1] / sum;
2813
2814                sum = primaries[3] + primaries[4] + primaries[5];
2815                xyPrimaries[2] = primaries[3] / sum;
2816                xyPrimaries[3] = primaries[4] / sum;
2817
2818                sum = primaries[6] + primaries[7] + primaries[8];
2819                xyPrimaries[4] = primaries[6] / sum;
2820                xyPrimaries[5] = primaries[7] / sum;
2821            } else {
2822                System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
2823            }
2824
2825            return xyPrimaries;
2826        }
2827
2828        /**
2829         * Converts the specified white point to xyY if needed. The white point
2830         * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
2831         * (in CIE XYZ). If no conversion is needed, the input array is copied.
2832         *
2833         * @param whitePoint The white point in xyY or XYZ
2834         * @return A new array of 2 floats containing the white point in xyY
2835         */
2836        @NonNull
2837        @Size(2)
2838        private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
2839            float[] xyWhitePoint = new float[2];
2840
2841            // XYZ to xyY
2842            if (whitePoint.length == 3) {
2843                float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
2844                xyWhitePoint[0] = whitePoint[0] / sum;
2845                xyWhitePoint[1] = whitePoint[1] / sum;
2846            } else {
2847                System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
2848            }
2849
2850            return xyWhitePoint;
2851        }
2852
2853        /**
2854         * Computes the matrix that converts from RGB to XYZ based on RGB
2855         * primaries and a white point, both specified in the CIE xyY space.
2856         * The Y component of the primaries and white point is implied to be 1.
2857         *
2858         * @param primaries The RGB primaries in xyY, as an array of 6 floats
2859         * @param whitePoint The white point in xyY, as an array of 2 floats
2860         * @return A 3x3 matrix as a new array of 9 floats
2861         */
2862        @NonNull
2863        @Size(9)
2864        private static float[] computeXYZMatrix(
2865                @NonNull @Size(6) float[] primaries,
2866                @NonNull @Size(2) float[] whitePoint) {
2867            float Rx = primaries[0];
2868            float Ry = primaries[1];
2869            float Gx = primaries[2];
2870            float Gy = primaries[3];
2871            float Bx = primaries[4];
2872            float By = primaries[5];
2873            float Wx = whitePoint[0];
2874            float Wy = whitePoint[1];
2875
2876            float oneRxRy = (1 - Rx) / Ry;
2877            float oneGxGy = (1 - Gx) / Gy;
2878            float oneBxBy = (1 - Bx) / By;
2879            float oneWxWy = (1 - Wx) / Wy;
2880
2881            float RxRy = Rx / Ry;
2882            float GxGy = Gx / Gy;
2883            float BxBy = Bx / By;
2884            float WxWy = Wx / Wy;
2885
2886            float BY =
2887                    ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
2888                    ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
2889            float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
2890            float RY = 1 - GY - BY;
2891
2892            float RYRy = RY / Ry;
2893            float GYGy = GY / Gy;
2894            float BYBy = BY / By;
2895
2896            return new float[] {
2897                    RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
2898                    GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
2899                    BYBy * Bx, BY, BYBy * (1 - Bx - By)
2900            };
2901        }
2902    }
2903
2904    /**
2905     * {@usesMathJax}
2906     *
2907     * <p>A connector transforms colors from a source color space to a destination
2908     * color space.</p>
2909     *
2910     * <p>A source color space is connected to a destination color space using the
2911     * color transform \(C\) computed from their respective transforms noted
2912     * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
2913     *
2914     * $$C = T^{-1}_{dst} . T_{src}$$
2915     *
2916     * <p>The transform \(C\) shown above is only valid when the source and
2917     * destination color spaces have the same profile connection space (PCS).
2918     * We know that instances of {@link ColorSpace} always use CIE XYZ as their
2919     * PCS but their white points might differ. When they do, we must perform
2920     * a chromatic adaptation of the color spaces' transforms. To do so, we
2921     * use the von Kries method described in the documentation of {@link Adaptation},
2922     * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
2923     * as the target white point.</p>
2924     *
2925     * <p>Example of conversion from {@link Named#SRGB sRGB} to
2926     * {@link Named#DCI_P3 DCI-P3}:</p>
2927     *
2928     * <pre class="prettyprint">
2929     * ColorSpace.Connector connector = ColorSpace.connect(
2930     *         ColorSpace.get(ColorSpace.Named.SRGB),
2931     *         ColorSpace.get(ColorSpace.Named.DCI_P3));
2932     * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
2933     * // p3 contains { 0.9473, 0.2740, 0.2076 }
2934     * </pre>
2935     *
2936     * @see Adaptation
2937     * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
2938     * @see ColorSpace#adapt(ColorSpace, float[])
2939     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
2940     * @see ColorSpace#connect(ColorSpace, ColorSpace)
2941     * @see ColorSpace#connect(ColorSpace, RenderIntent)
2942     * @see ColorSpace#connect(ColorSpace)
2943     */
2944    @AnyThread
2945    public static class Connector {
2946        @NonNull private final ColorSpace mSource;
2947        @NonNull private final ColorSpace mDestination;
2948        @NonNull private final ColorSpace mTransformSource;
2949        @NonNull private final ColorSpace mTransformDestination;
2950        @NonNull private final RenderIntent mIntent;
2951        @NonNull @Size(3) private final float[] mTransform;
2952
2953        /**
2954         * Creates a new connector between a source and a destination color space.
2955         *
2956         * @param source The source color space, cannot be null
2957         * @param destination The destination color space, cannot be null
2958         * @param intent The render intent to use when compressing gamuts
2959         */
2960        Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
2961                @NonNull RenderIntent intent) {
2962            this(source, destination,
2963                    source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
2964                    destination.getModel() == Model.RGB ?
2965                            adapt(destination, ILLUMINANT_D50_XYZ) : destination,
2966                    intent, computeTransform(source, destination, intent));
2967        }
2968
2969        /**
2970         * To connect between color spaces, we might need to use adapted transforms.
2971         * This should be transparent to the user so this constructor takes the
2972         * original source and destinations (returned by the getters), as well as
2973         * possibly adapted color spaces used by transform().
2974         */
2975        private Connector(
2976                @NonNull ColorSpace source, @NonNull ColorSpace destination,
2977                @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
2978                @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
2979            mSource = source;
2980            mDestination = destination;
2981            mTransformSource = transformSource;
2982            mTransformDestination = transformDestination;
2983            mIntent = intent;
2984            mTransform = transform;
2985        }
2986
2987        /**
2988         * Computes an extra transform to apply in XYZ space depending on the
2989         * selected rendering intent.
2990         */
2991        private static float[] computeTransform(@NonNull ColorSpace source,
2992                @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
2993            if (intent != RenderIntent.ABSOLUTE) return null;
2994
2995            boolean srcRGB = source.getModel() == Model.RGB;
2996            boolean dstRGB = destination.getModel() == Model.RGB;
2997
2998            if (srcRGB && dstRGB) return null;
2999
3000            if (srcRGB || dstRGB) {
3001                ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
3002                float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
3003                float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
3004                return new float[] {
3005                        srcXYZ[0] / dstXYZ[0],
3006                        srcXYZ[1] / dstXYZ[1],
3007                        srcXYZ[2] / dstXYZ[2],
3008                };
3009            }
3010
3011            return null;
3012        }
3013
3014        /**
3015         * Returns the source color space this connector will convert from.
3016         *
3017         * @return A non-null instance of {@link ColorSpace}
3018         *
3019         * @see #getDestination()
3020         */
3021        @NonNull
3022        public ColorSpace getSource() {
3023            return mSource;
3024        }
3025
3026        /**
3027         * Returns the destination color space this connector will convert to.
3028         *
3029         * @return A non-null instance of {@link ColorSpace}
3030         *
3031         * @see #getSource()
3032         */
3033        @NonNull
3034        public ColorSpace getDestination() {
3035            return mDestination;
3036        }
3037
3038        /**
3039         * Returns the render intent this connector will use when mapping the
3040         * source color space to the destination color space.
3041         *
3042         * @return A non-null {@link RenderIntent}
3043         *
3044         * @see RenderIntent
3045         */
3046        public RenderIntent getIntent() {
3047            return mIntent;
3048        }
3049
3050        /**
3051         * <p>Transforms the specified color from the source color space
3052         * to a color in the destination color space. This convenience
3053         * method assumes a source color model with 3 components
3054         * (typically RGB). To transform from color models with more than
3055         * 3 components, such as {@link Model#CMYK CMYK}, use
3056         * {@link #transform(float[])} instead.</p>
3057         *
3058         * @param r The red component of the color to transform
3059         * @param g The green component of the color to transform
3060         * @param b The blue component of the color to transform
3061         * @return A new array of 3 floats containing the specified color
3062         *         transformed from the source space to the destination space
3063         *
3064         * @see #transform(float[])
3065         */
3066        @NonNull
3067        @Size(3)
3068        public float[] transform(float r, float g, float b) {
3069            return transform(new float[] { r, g, b });
3070        }
3071
3072        /**
3073         * <p>Transforms the specified color from the source color space
3074         * to a color in the destination color space.</p>
3075         *
3076         * @param v A non-null array of 3 floats containing the value to transform
3077         *            and that will hold the result of the transform
3078         * @return The v array passed as a parameter, containing the specified color
3079         *         transformed from the source space to the destination space
3080         *
3081         * @see #transform(float, float, float)
3082         */
3083        @NonNull
3084        @Size(min = 3)
3085        public float[] transform(@NonNull @Size(min = 3) float[] v) {
3086            float[] xyz = mTransformSource.toXyz(v);
3087            if (mTransform != null) {
3088                xyz[0] *= mTransform[0];
3089                xyz[1] *= mTransform[1];
3090                xyz[2] *= mTransform[2];
3091            }
3092            return mTransformDestination.fromXyz(xyz);
3093        }
3094
3095        /**
3096         * Optimized connector for RGB->RGB conversions.
3097         */
3098        private static class Rgb extends Connector {
3099            @NonNull private final ColorSpace.Rgb mSource;
3100            @NonNull private final ColorSpace.Rgb mDestination;
3101            @NonNull private final float[] mTransform;
3102
3103            Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
3104                    @NonNull RenderIntent intent) {
3105                super(source, destination, source, destination, intent, null);
3106                mSource = source;
3107                mDestination = destination;
3108                mTransform = computeTransform(source, destination, intent);
3109            }
3110
3111            @Override
3112            public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
3113                rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
3114                rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
3115                rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
3116                mul3x3Float3(mTransform, rgb);
3117                rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
3118                rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
3119                rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
3120                return rgb;
3121            }
3122
3123            /**
3124             * <p>Computes the color transform that connects two RGB color spaces.</p>
3125             *
3126             * <p>We can only connect color spaces if they use the same profile
3127             * connection space. We assume the connection space is always
3128             * CIE XYZ but we maye need to perform a chromatic adaptation to
3129             * match the white points. If an adaptation is needed, we use the
3130             * CIE standard illuminant D50. The unmatched color space is adapted
3131             * using the von Kries transform and the {@link Adaptation#BRADFORD}
3132             * matrix.</p>
3133             *
3134             * @param source The source color space, cannot be null
3135             * @param destination The destination color space, cannot be null
3136             * @param intent The render intent to use when compressing gamuts
3137             * @return An array of 9 floats containing the 3x3 matrix transform
3138             */
3139            @NonNull
3140            @Size(9)
3141            private static float[] computeTransform(
3142                    @NonNull ColorSpace.Rgb source,
3143                    @NonNull ColorSpace.Rgb destination,
3144                    @NonNull RenderIntent intent) {
3145                if (compare(source.mWhitePoint, destination.mWhitePoint)) {
3146                    // RGB->RGB using the PCS of both color spaces since they have the same
3147                    return mul3x3(destination.mInverseTransform, source.mTransform);
3148                } else {
3149                    // RGB->RGB using CIE XYZ D50 as the PCS
3150                    float[] transform = source.mTransform;
3151                    float[] inverseTransform = destination.mInverseTransform;
3152
3153                    float[] srcXYZ = xyYToXyz(source.mWhitePoint);
3154                    float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
3155
3156                    if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
3157                        float[] srcAdaptation = chromaticAdaptation(
3158                                Adaptation.BRADFORD.mTransform, srcXYZ,
3159                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3160                        transform = mul3x3(srcAdaptation, source.mTransform);
3161                    }
3162
3163                    if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
3164                        float[] dstAdaptation = chromaticAdaptation(
3165                                Adaptation.BRADFORD.mTransform, dstXYZ,
3166                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3167                        inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
3168                    }
3169
3170                    if (intent == RenderIntent.ABSOLUTE) {
3171                        transform = mul3x3Diag(
3172                                new float[] {
3173                                        srcXYZ[0] / dstXYZ[0],
3174                                        srcXYZ[1] / dstXYZ[1],
3175                                        srcXYZ[2] / dstXYZ[2],
3176                                }, transform);
3177                    }
3178
3179                    return mul3x3(inverseTransform, transform);
3180                }
3181            }
3182        }
3183
3184        /**
3185         * Returns the identity connector for a given color space.
3186         *
3187         * @param source The source and destination color space
3188         * @return A non-null connector that does not perform any transform
3189         *
3190         * @see ColorSpace#connect(ColorSpace, ColorSpace)
3191         */
3192        static Connector identity(ColorSpace source) {
3193            return new Connector(source, source, RenderIntent.RELATIVE) {
3194                @Override
3195                public float[] transform(@NonNull @Size(min = 3) float[] v) {
3196                    return v;
3197                }
3198            };
3199        }
3200    }
3201
3202    /**
3203     * <p>A color space renderer can be used to visualize and compare the gamut and
3204     * white point of one or more color spaces. The output is an sRGB {@link Bitmap}
3205     * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p>
3206     *
3207     * <p>The following code snippet shows how to compare the {@link Named#SRGB}
3208     * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p>
3209     *
3210     * <pre class="prettyprint">
3211     * Bitmap bitmap = ColorSpace.createRenderer()
3212     *     .size(768)
3213     *     .clip(true)
3214     *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3215     *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3216     *     .render();
3217     * </pre>
3218     * <p>
3219     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
3220     *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
3221     * </p>
3222     *
3223     * <p>A renderer can also be used to show the location of specific colors,
3224     * associated with a color space, in the CIE 1931 xyY chromaticity diagram.
3225     * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p>
3226     *
3227     * @see ColorSpace#createRenderer()
3228     */
3229    public static class Renderer {
3230        private static final int NATIVE_SIZE = 1440;
3231        private static final float UCS_SCALE = 9.0f / 6.0f;
3232
3233        // Number of subdivision of the inside of the spectral locus
3234        private static final int CHROMATICITY_RESOLUTION = 32;
3235        private static final double ONE_THIRD = 1.0 / 3.0;
3236
3237        @IntRange(from = 128, to = Integer.MAX_VALUE)
3238        private int mSize = 1024;
3239
3240        private boolean mShowWhitePoint = true;
3241        private boolean mClip = false;
3242        private boolean mUcs = false;
3243
3244        private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2);
3245        private final List<Point> mPoints = new ArrayList<>(0);
3246
3247        private Renderer() {
3248        }
3249
3250        /**
3251         * <p>Defines whether the chromaticity diagram should be clipped by the first
3252         * registered color space. The default value is false.</p>
3253         *
3254         * <p>The following code snippet and image show the default behavior:</p>
3255         * <pre class="prettyprint">
3256         * Bitmap bitmap = ColorSpace.createRenderer()
3257         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3258         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3259         *     .render();
3260         * </pre>
3261         * <p>
3262         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
3263         *     <figcaption style="text-align: center;">Clipping disabled</figcaption>
3264         * </p>
3265         *
3266         * <p>Here is the same example with clipping enabled:</p>
3267         * <pre class="prettyprint">
3268         * Bitmap bitmap = ColorSpace.createRenderer()
3269         *     .clip(true)
3270         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3271         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3272         *     .render();
3273         * </pre>
3274         * <p>
3275         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
3276         *     <figcaption style="text-align: center;">Clipping enabled</figcaption>
3277         * </p>
3278         *
3279         * @param clip True to clip the chromaticity diagram to the first registered color space,
3280         *             false otherwise
3281         * @return This instance of {@link Renderer}
3282         */
3283        @NonNull
3284        public Renderer clip(boolean clip) {
3285            mClip = clip;
3286            return this;
3287        }
3288
3289        /**
3290         * <p>Defines whether the chromaticity diagram should use the uniform
3291         * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale
3292         * is used, the distance between two points on the diagram is approximately
3293         * proportional to the perceived color difference.</p>
3294         *
3295         * <p>The following code snippet shows how to enable the uniform chromaticity
3296         * scale. The image below shows the result:</p>
3297         * <pre class="prettyprint">
3298         * Bitmap bitmap = ColorSpace.createRenderer()
3299         *     .uniformChromaticityScale(true)
3300         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3301         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3302         *     .render();
3303         * </pre>
3304         * <p>
3305         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" />
3306         *     <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption>
3307         * </p>
3308         *
3309         * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram
3310         * @return This instance of {@link Renderer}
3311         */
3312        @NonNull
3313        public Renderer uniformChromaticityScale(boolean ucs) {
3314            mUcs = ucs;
3315            return this;
3316        }
3317
3318        /**
3319         * Sets the dimensions (width and height) in pixels of the output bitmap.
3320         * The size must be at least 128px and defaults to 1024px.
3321         *
3322         * @param size The size in pixels of the output bitmap
3323         * @return This instance of {@link Renderer}
3324         */
3325        @NonNull
3326        public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) {
3327            mSize = Math.max(128, size);
3328            return this;
3329        }
3330
3331        /**
3332         * Shows or hides the white point of each color space in the output bitmap.
3333         * The default is true.
3334         *
3335         * @param show True to show the white point of each color space, false
3336         *             otherwise
3337         * @return This instance of {@link Renderer}
3338         */
3339        @NonNull
3340        public Renderer showWhitePoint(boolean show) {
3341            mShowWhitePoint = show;
3342            return this;
3343        }
3344
3345        /**
3346         * <p>Adds a color space to represent on the output CIE 1931 chromaticity
3347         * diagram. The color space is represented as a triangle showing the
3348         * footprint of its color gamut and, optionally, the location of its
3349         * white point.</p>
3350         *
3351         * <p class="note">Color spaces with a color model that is not RGB are
3352         * accepted but ignored.</p>
3353         *
3354         * <p>The following code snippet and image show an example of calling this
3355         * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p>
3356         * <pre class="prettyprint">
3357         * Bitmap bitmap = ColorSpace.createRenderer()
3358         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3359         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3360         *     .render();
3361         * </pre>
3362         * <p>
3363         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
3364         *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
3365         * </p>
3366         *
3367         * <p>Adding a color space extending beyond the boundaries of the
3368         * spectral locus will alter the size of the diagram within the output
3369         * bitmap as shown in this example:</p>
3370         * <pre class="prettyprint">
3371         * Bitmap bitmap = ColorSpace.createRenderer()
3372         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3373         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
3374         *     .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9)
3375         *     .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000)
3376         *     .render();
3377         * </pre>
3378         * <p>
3379         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" />
3380         *     <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption>
3381         * </p>
3382         *
3383         * @param colorSpace The color space whose gamut to render on the diagram
3384         * @param color The sRGB color to use to render the color space's gamut and white point
3385         * @return This instance of {@link Renderer}
3386         *
3387         * @see #clip(boolean)
3388         * @see #showWhitePoint(boolean)
3389         */
3390        @NonNull
3391        public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) {
3392            mColorSpaces.add(new Pair<>(colorSpace, color));
3393            return this;
3394        }
3395
3396        /**
3397         * <p>Adds a color to represent as a point on the chromaticity diagram.
3398         * The color is associated with a color space which will be used to
3399         * perform the conversion to CIE XYZ and compute the location of the point
3400         * on the diagram. The point is rendered as a colored circle.</p>
3401         *
3402         * <p>The following code snippet and image show an example of calling this
3403         * method to render the location of several sRGB colors as white circles:</p>
3404         * <pre class="prettyprint">
3405         * Bitmap bitmap = ColorSpace.createRenderer()
3406         *     .clip(true)
3407         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
3408         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff)
3409         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff)
3410         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff)
3411         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff)
3412         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff)
3413         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff)
3414         *     .render();
3415         * </pre>
3416         * <p>
3417         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_points.png" />
3418         *     <figcaption style="text-align: center;">
3419         *         Locating colors on the chromaticity diagram
3420         *     </figcaption>
3421         * </p>
3422         *
3423         * @param colorSpace The color space of the color to locate on the diagram
3424         * @param r The first component of the color to locate on the diagram
3425         * @param g The second component of the color to locate on the diagram
3426         * @param b The third component of the color to locate on the diagram
3427         * @param pointColor The sRGB color to use to render the point on the diagram
3428         * @return This instance of {@link Renderer}
3429         */
3430        @NonNull
3431        public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b,
3432                @ColorInt int pointColor) {
3433            mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor));
3434            return this;
3435        }
3436
3437        /**
3438         * <p>Renders the {@link #add(ColorSpace, int) color spaces} and
3439         * {@link #add(ColorSpace, float, float, float, int) points} registered
3440         * with this renderer. The output bitmap is an sRGB image with the
3441         * dimensions specified by calling {@link #size(int)} (1204x1024px by
3442         * default).</p>
3443         *
3444         * @return A new non-null {@link Bitmap} with the dimensions specified
3445         *        by {@link #size(int)} (1024x1024 by default)
3446         */
3447        @NonNull
3448        public Bitmap render() {
3449            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
3450            Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
3451            Canvas canvas = new Canvas(bitmap);
3452
3453            float[] primaries = new float[6];
3454            float[] whitePoint = new float[2];
3455
3456            int width = NATIVE_SIZE;
3457            int height = NATIVE_SIZE;
3458
3459            Path path = new Path();
3460
3461            setTransform(canvas, width, height, primaries);
3462            drawBox(canvas, width, height, paint, path);
3463            setUcsTransform(canvas, height);
3464            drawLocus(canvas, width, height, paint, path, primaries);
3465            drawGamuts(canvas, width, height, paint, path, primaries, whitePoint);
3466            drawPoints(canvas, width, height, paint);
3467
3468            return bitmap;
3469        }
3470
3471        /**
3472         * Draws registered points at their correct position in the xyY coordinates.
3473         * Each point is positioned according to its associated color space.
3474         *
3475         * @param canvas The canvas to transform
3476         * @param width Width in pixel of the final image
3477         * @param height Height in pixel of the final image
3478         * @param paint A pre-allocated paint used to avoid temporary allocations
3479         */
3480        private void drawPoints(@NonNull Canvas canvas, int width, int height,
3481                @NonNull Paint paint) {
3482
3483            paint.setStyle(Paint.Style.FILL);
3484
3485            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
3486
3487            float[] v = new float[3];
3488            float[] xy = new float[2];
3489
3490            for (final Point point : mPoints) {
3491                v[0] = point.mRgb[0];
3492                v[1] = point.mRgb[1];
3493                v[2] = point.mRgb[2];
3494                point.mColorSpace.toXyz(v);
3495
3496                paint.setColor(point.mColor);
3497
3498                // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed
3499                float sum = v[0] + v[1] + v[2];
3500                xy[0] = v[0] / sum;
3501                xy[1] = v[1] / sum;
3502                if (mUcs) xyYToUv(xy);
3503
3504                canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint);
3505            }
3506        }
3507
3508        /**
3509         * Draws the color gamuts and white points of all the registered color
3510         * spaces. Only color spaces with an RGB color model are rendered, the
3511         * others are ignored.
3512         *
3513         * @param canvas The canvas to transform
3514         * @param width Width in pixel of the final image
3515         * @param height Height in pixel of the final image
3516         * @param paint A pre-allocated paint used to avoid temporary allocations
3517         * @param path A pre-allocated path used to avoid temporary allocations
3518         * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
3519         * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations
3520         */
3521        private void drawGamuts(
3522                @NonNull Canvas canvas, int width, int height,
3523                @NonNull Paint paint, @NonNull Path path,
3524                @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) {
3525
3526            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
3527
3528            for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
3529                ColorSpace colorSpace = item.first;
3530                int color = item.second;
3531
3532                if (colorSpace.getModel() != Model.RGB) continue;
3533
3534                Rgb rgb = (Rgb) colorSpace;
3535                getPrimaries(rgb, primaries, mUcs);
3536
3537                path.rewind();
3538                path.moveTo(width * primaries[0], height - height * primaries[1]);
3539                path.lineTo(width * primaries[2], height - height * primaries[3]);
3540                path.lineTo(width * primaries[4], height - height * primaries[5]);
3541                path.close();
3542
3543                paint.setStyle(Paint.Style.STROKE);
3544                paint.setColor(color);
3545                canvas.drawPath(path, paint);
3546
3547                // Draw the white point
3548                if (mShowWhitePoint) {
3549                    rgb.getWhitePoint(whitePoint);
3550                    if (mUcs) xyYToUv(whitePoint);
3551
3552                    paint.setStyle(Paint.Style.FILL);
3553                    paint.setColor(color);
3554                    canvas.drawCircle(
3555                            width * whitePoint[0], height - height * whitePoint[1], radius, paint);
3556                }
3557            }
3558        }
3559
3560        /**
3561         * Returns the primaries of the specified RGB color space. This method handles
3562         * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces.
3563         *
3564         * @param rgb The color space whose primaries to extract
3565         * @param primaries A pre-allocated array of 6 floats that will hold the result
3566         * @param asUcs True if the primaries should be returned in Luv, false for xyY
3567         */
3568        @NonNull
3569        @Size(6)
3570        private static float[] getPrimaries(@NonNull Rgb rgb,
3571                @NonNull @Size(6) float[] primaries, boolean asUcs) {
3572            // TODO: We should find a better way to handle these cases
3573            if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) ||
3574                    rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) {
3575                primaries[0] = 1.41f;
3576                primaries[1] = 0.33f;
3577                primaries[2] = 0.27f;
3578                primaries[3] = 1.24f;
3579                primaries[4] = -0.23f;
3580                primaries[5] = -0.57f;
3581            } else {
3582                rgb.getPrimaries(primaries);
3583            }
3584            if (asUcs) xyYToUv(primaries);
3585            return primaries;
3586        }
3587
3588        /**
3589         * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside.
3590         * This method respect the clip parameter.
3591         *
3592         * @param canvas The canvas to transform
3593         * @param width Width in pixel of the final image
3594         * @param height Height in pixel of the final image
3595         * @param paint A pre-allocated paint used to avoid temporary allocations
3596         * @param path A pre-allocated path used to avoid temporary allocations
3597         * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
3598         */
3599        private void drawLocus(
3600                @NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
3601                @NonNull Path path, @NonNull @Size(6) float[] primaries) {
3602
3603            int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6;
3604            float[] vertices = new float[vertexCount * 2];
3605            int[] colors = new int[vertices.length];
3606            computeChromaticityMesh(vertices, colors);
3607
3608            if (mUcs) xyYToUv(vertices);
3609            for (int i = 0; i < vertices.length; i += 2) {
3610                vertices[i] *= width;
3611                vertices[i + 1] = height - vertices[i + 1] * height;
3612            }
3613
3614            // Draw the spectral locus
3615            if (mClip && mColorSpaces.size() > 0) {
3616                for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
3617                    ColorSpace colorSpace = item.first;
3618                    if (colorSpace.getModel() != Model.RGB) continue;
3619
3620                    Rgb rgb = (Rgb) colorSpace;
3621                    getPrimaries(rgb, primaries, mUcs);
3622
3623                    break;
3624                }
3625
3626                path.rewind();
3627                path.moveTo(width * primaries[0], height - height * primaries[1]);
3628                path.lineTo(width * primaries[2], height - height * primaries[3]);
3629                path.lineTo(width * primaries[4], height - height * primaries[5]);
3630                path.close();
3631
3632                int[] solid = new int[colors.length];
3633                Arrays.fill(solid, 0xff6c6c6c);
3634                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
3635                        null, 0, solid, 0, null, 0, 0, paint);
3636
3637                canvas.save();
3638                canvas.clipPath(path);
3639
3640                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
3641                        null, 0, colors, 0, null, 0, 0, paint);
3642
3643                canvas.restore();
3644            } else {
3645                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
3646                        null, 0, colors, 0, null, 0, 0, paint);
3647            }
3648
3649            // Draw the non-spectral locus
3650            int index = (CHROMATICITY_RESOLUTION - 1) * 12;
3651            path.reset();
3652            path.moveTo(vertices[index], vertices[index + 1]);
3653            for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) {
3654                index += CHROMATICITY_RESOLUTION * 12;
3655                path.lineTo(vertices[index], vertices[index + 1]);
3656            }
3657            path.close();
3658
3659            paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f));
3660            paint.setStyle(Paint.Style.STROKE);
3661            paint.setColor(0xff000000);
3662            canvas.drawPath(path, paint);
3663        }
3664
3665        /**
3666         * Draws the diagram box, including borders, tick marks, grid lines
3667         * and axis labels.
3668         *
3669         * @param canvas The canvas to transform
3670         * @param width Width in pixel of the final image
3671         * @param height Height in pixel of the final image
3672         * @param paint A pre-allocated paint used to avoid temporary allocations
3673         * @param path A pre-allocated path used to avoid temporary allocations
3674         */
3675        private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
3676                @NonNull Path path) {
3677
3678            int lineCount = 10;
3679            float scale = 1.0f;
3680            if (mUcs) {
3681                lineCount = 7;
3682                scale = UCS_SCALE;
3683            }
3684
3685            // Draw the unit grid
3686            paint.setStyle(Paint.Style.STROKE);
3687            paint.setStrokeWidth(2.0f);
3688            paint.setColor(0xffc0c0c0);
3689
3690            for (int i = 1; i < lineCount - 1; i++) {
3691                float v = i / 10.0f;
3692                float x = (width * v) * scale;
3693                float y = height - (height * v) * scale;
3694
3695                canvas.drawLine(0.0f, y, 0.9f * width, y, paint);
3696                canvas.drawLine(x, height, x, 0.1f * height, paint);
3697            }
3698
3699            // Draw tick marks
3700            paint.setStrokeWidth(4.0f);
3701            paint.setColor(0xff000000);
3702            for (int i = 1; i < lineCount - 1; i++) {
3703                float v = i / 10.0f;
3704                float x = (width * v) * scale;
3705                float y = height - (height * v) * scale;
3706
3707                canvas.drawLine(0.0f, y, width / 100.0f, y, paint);
3708                canvas.drawLine(x, height, x, height - (height / 100.0f), paint);
3709            }
3710
3711            // Draw the axis labels
3712            paint.setStyle(Paint.Style.FILL);
3713            paint.setTextSize(36.0f);
3714            paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
3715
3716            Rect bounds = new Rect();
3717            for (int i = 1; i < lineCount - 1; i++) {
3718                String text = "0." + i;
3719                paint.getTextBounds(text, 0, text.length(), bounds);
3720
3721                float v = i / 10.0f;
3722                float x = (width * v) * scale;
3723                float y = height - (height * v) * scale;
3724
3725                canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
3726                canvas.drawText(text, x - bounds.width() / 2.0f,
3727                        height + bounds.height() + 16, paint);
3728            }
3729            paint.setStyle(Paint.Style.STROKE);
3730
3731            // Draw the diagram box
3732            path.moveTo(0.0f, height);
3733            path.lineTo(0.9f * width, height);
3734            path.lineTo(0.9f * width, 0.1f * height);
3735            path.lineTo(0.0f, 0.1f * height);
3736            path.close();
3737            canvas.drawPath(path, paint);
3738        }
3739
3740        /**
3741         * Computes and applies the Canvas transforms required to make the color
3742         * gamut of each color space visible in the final image.
3743         *
3744         * @param canvas The canvas to transform
3745         * @param width Width in pixel of the final image
3746         * @param height Height in pixel of the final image
3747         * @param primaries Array of 6 floats used to avoid temporary allocations
3748         */
3749        private void setTransform(@NonNull Canvas canvas, int width, int height,
3750                @NonNull @Size(6) float[] primaries) {
3751
3752            RectF primariesBounds = new RectF();
3753            for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
3754                ColorSpace colorSpace = item.first;
3755                if (colorSpace.getModel() != Model.RGB) continue;
3756
3757                Rgb rgb = (Rgb) colorSpace;
3758                getPrimaries(rgb, primaries, mUcs);
3759
3760                primariesBounds.left = Math.min(primariesBounds.left, primaries[4]);
3761                primariesBounds.top = Math.min(primariesBounds.top, primaries[5]);
3762                primariesBounds.right = Math.max(primariesBounds.right, primaries[0]);
3763                primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]);
3764            }
3765
3766            float max = mUcs ? 0.6f : 0.9f;
3767
3768            primariesBounds.left = Math.min(0.0f, primariesBounds.left);
3769            primariesBounds.top = Math.min(0.0f, primariesBounds.top);
3770            primariesBounds.right = Math.max(max, primariesBounds.right);
3771            primariesBounds.bottom = Math.max(max, primariesBounds.bottom);
3772
3773            float scaleX = max / primariesBounds.width();
3774            float scaleY = max / primariesBounds.height();
3775            float scale = Math.min(scaleX, scaleY);
3776
3777            canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE);
3778            canvas.scale(scale, scale);
3779            canvas.translate(
3780                    (primariesBounds.width() - max) * width / 2.0f,
3781                    (primariesBounds.height() - max) * height / 2.0f);
3782
3783            // The spectrum extends ~0.85 vertically and ~0.65 horizontally
3784            // We shift the canvas a little bit to get nicer margins
3785            canvas.translate(0.05f * width, -0.05f * height);
3786        }
3787
3788        /**
3789         * Computes and applies the Canvas transforms required to render the CIE
3790         * 197 UCS chromaticity diagram.
3791         *
3792         * @param canvas The canvas to transform
3793         * @param height Height in pixel of the final image
3794         */
3795        private void setUcsTransform(@NonNull Canvas canvas, int height) {
3796            if (mUcs) {
3797                canvas.translate(0.0f, (height - height * UCS_SCALE));
3798                canvas.scale(UCS_SCALE, UCS_SCALE);
3799            }
3800        }
3801
3802        // X coordinates of the spectral locus in CIE 1931
3803        private static final float[] SPECTRUM_LOCUS_X = {
3804                0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f,
3805                0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f,
3806                0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f,
3807                0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f,
3808                0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f,
3809                0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f,
3810                0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f,
3811                0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f,
3812                0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f,
3813                0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f,
3814                0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f,
3815                0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f,
3816                0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f,
3817                0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f,
3818                0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f,
3819                0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f,
3820                0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f,
3821                0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f,
3822                0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f
3823        };
3824        // Y coordinates of the spectral locus in CIE 1931
3825        private static final float[] SPECTRUM_LOCUS_Y = {
3826                0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f,
3827                0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f,
3828                0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f,
3829                0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f,
3830                0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f,
3831                0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f,
3832                0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f,
3833                0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f,
3834                0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f,
3835                0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f,
3836                0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f,
3837                0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f,
3838                0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f,
3839                0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f,
3840                0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f,
3841                0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f,
3842                0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f,
3843                0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f,
3844                0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f
3845        };
3846
3847        /**
3848         * Computes a 2D mesh representation of the CIE 1931 chromaticity
3849         * diagram.
3850         *
3851         * @param vertices Array of floats that will hold the mesh vertices
3852         * @param colors Array of floats that will hold the mesh colors
3853         */
3854        private static void computeChromaticityMesh(@NonNull float[] vertices,
3855                @NonNull int[] colors) {
3856
3857            ColorSpace colorSpace = get(Named.SRGB);
3858
3859            float[] color = new float[3];
3860
3861            int vertexIndex = 0;
3862            int colorIndex = 0;
3863
3864            for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) {
3865                int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1;
3866
3867                float a1 = (float) Math.atan2(
3868                        SPECTRUM_LOCUS_Y[x] - ONE_THIRD,
3869                        SPECTRUM_LOCUS_X[x] - ONE_THIRD);
3870                float a2 = (float) Math.atan2(
3871                        SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD,
3872                        SPECTRUM_LOCUS_X[nextX] - ONE_THIRD);
3873
3874                float radius1 = (float) Math.pow(
3875                        sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) +
3876                                sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD),
3877                        0.5);
3878                float radius2 = (float) Math.pow(
3879                        sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) +
3880                                sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD),
3881                        0.5);
3882
3883                // Compute patches; each patch is a quad with a different
3884                // color associated with each vertex
3885                for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) {
3886                    float f1 = c / (float) CHROMATICITY_RESOLUTION;
3887                    float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION;
3888
3889                    double cr1 = radius1 * Math.cos(a1);
3890                    double sr1 = radius1 * Math.sin(a1);
3891                    double cr2 = radius2 * Math.cos(a2);
3892                    double sr2 = radius2 * Math.sin(a2);
3893
3894                    // Compute the XYZ coordinates of the 4 vertices of the patch
3895                    float v1x = (float) (ONE_THIRD + cr1 * f1);
3896                    float v1y = (float) (ONE_THIRD + sr1 * f1);
3897                    float v1z = 1 - v1x - v1y;
3898
3899                    float v2x = (float) (ONE_THIRD + cr1 * f2);
3900                    float v2y = (float) (ONE_THIRD + sr1 * f2);
3901                    float v2z = 1 - v2x - v2y;
3902
3903                    float v3x = (float) (ONE_THIRD + cr2 * f2);
3904                    float v3y = (float) (ONE_THIRD + sr2 * f2);
3905                    float v3z = 1 - v3x - v3y;
3906
3907                    float v4x = (float) (ONE_THIRD + cr2 * f1);
3908                    float v4y = (float) (ONE_THIRD + sr2 * f1);
3909                    float v4z = 1 - v4x - v4y;
3910
3911                    // Compute the sRGB representation of each XYZ coordinate of the patch
3912                    colors[colorIndex    ] = computeColor(color, v1x, v1y, v1z, colorSpace);
3913                    colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace);
3914                    colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace);
3915                    colors[colorIndex + 3] = colors[colorIndex];
3916                    colors[colorIndex + 4] = colors[colorIndex + 2];
3917                    colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace);
3918                    colorIndex += 6;
3919
3920                    // Flip the mesh upside down to match Canvas' coordinates system
3921                    vertices[vertexIndex++] = v1x;
3922                    vertices[vertexIndex++] = v1y;
3923                    vertices[vertexIndex++] = v2x;
3924                    vertices[vertexIndex++] = v2y;
3925                    vertices[vertexIndex++] = v3x;
3926                    vertices[vertexIndex++] = v3y;
3927                    vertices[vertexIndex++] = v1x;
3928                    vertices[vertexIndex++] = v1y;
3929                    vertices[vertexIndex++] = v3x;
3930                    vertices[vertexIndex++] = v3y;
3931                    vertices[vertexIndex++] = v4x;
3932                    vertices[vertexIndex++] = v4y;
3933                }
3934            }
3935        }
3936
3937        @ColorInt
3938        private static int computeColor(@NonNull @Size(3) float[] color,
3939                float x, float y, float z, @NonNull ColorSpace cs) {
3940            color[0] = x;
3941            color[1] = y;
3942            color[2] = z;
3943            cs.fromXyz(color);
3944            return 0xff000000 |
3945                    (((int) (color[0] * 255.0f) & 0xff) << 16) |
3946                    (((int) (color[1] * 255.0f) & 0xff) <<  8) |
3947                    (((int) (color[2] * 255.0f) & 0xff)      );
3948        }
3949
3950        private static double sqr(double v) {
3951            return v * v;
3952        }
3953
3954        private static class Point {
3955            @NonNull final ColorSpace mColorSpace;
3956            @NonNull final float[] mRgb;
3957            final int mColor;
3958
3959            Point(@NonNull ColorSpace colorSpace,
3960                    @NonNull @Size(3) float[] rgb, @ColorInt int color) {
3961                mColorSpace = colorSpace;
3962                mRgb = rgb;
3963                mColor = color;
3964            }
3965        }
3966    }
3967}
3968