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