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