ColorSpace.java revision 199e5a98ddc4402ba4b4cdafaa3d8deb58ef3c7d
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.IntRange;
20import android.annotation.NonNull;
21import android.annotation.Size;
22import android.annotation.Nullable;
23
24import java.util.Arrays;
25import java.util.function.DoubleUnaryOperator;
26
27/**
28 * {@usesMathJax}
29 *
30 * <p>A {@link ColorSpace} is used to identify a specific organization of colors.
31 * Each color space is characterized by a {@link Model color model} that defines
32 * how a color value is represented (for instance the {@link Model#RGB RGB} color
33 * model defines a color value as a triplet of numbers).</p>
34 *
35 * <p>Each component of a color must fall within a valid range, specific to each
36 * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)}
37 * This range is commonly \([0..1]\). While it is recommended to use values in the
38 * valid range, a color space always clamps input and output values when performing
39 * operations such as converting to a different color space.</p>
40 *
41 * <h3>Using color spaces</h3>
42 *
43 * <p>This implementation provides a pre-defined set of common color spaces
44 * described in the {@link Named} enum. To obtain an instance of one of the
45 * pre-defined color spaces, simply invoke {@link #get(Named)}:</p>
46 *
47 * <pre class="prettyprint">
48 * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB);
49 * </pre>
50 *
51 * <p>The {@link #get(Named)} method always returns the same instance for a given
52 * name. Color spaces with an {@link Model#RGB RGB} color model can be safely
53 * cast to {@link Rgb}. Doing so gives you access to more APIs to query various
54 * properties of RGB color models: color gamut primaries, transfer functions,
55 * conversions to and from linear space, etc. Please refer to {@link Rgb} for
56 * more information.</p>
57 *
58 * <p>The documentation of {@link Named} provides a detailed description of the
59 * various characteristics of each available color space.</p>
60 *
61 * <h3>Color space conversions</h3>
62
63 * <p>To allow conversion between color spaces, this implementation uses the CIE
64 * XYZ profile connection space (PCS). Color values can be converted to and from
65 * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p>
66 *
67 * <p>For color space with a non-RGB color model, the white point of the PCS
68 * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their
69 * native white point (D65 for {@link Named#SRGB sRGB} for instance and must
70 * undergo {@link Adaptation chromatic adaptation} as necessary.</p>
71 *
72 * <p>Since the white point of the PCS is not defined for RGB color space, it is
73 * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)}
74 * method to perform conversions between color spaces. A color space can be
75 * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}.
76 * Please refer to the documentation of {@link Rgb RGB color spaces} for more
77 * information. Several common CIE standard illuminants are provided in this
78 * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50}
79 * for instance).</p>
80 *
81 * <p>Here is an example of how to convert from a color space to another:</p>
82 *
83 * <pre class="prettyprint">
84 * // Convert from DCI-P3 to Rec.2020
85 * ColorSpace.Connector connector = ColorSpace.connect(
86 *         ColorSpace.get(ColorSpace.Named.DCI_P3),
87 *         ColorSpace.get(ColorSpace.Named.BT2020));
88 *
89 * float[] bt2020 = connector.transform(p3r, p3g, p3b);
90 * </pre>
91 *
92 * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second
93 * parameter:</p>
94 *
95 * <pre class="prettyprint">
96 * // Convert from DCI-P3 to sRGB
97 * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
98 *
99 * float[] sRGB = connector.transform(p3r, p3g, p3b);
100 * </pre>
101 *
102 * <p>Conversions also work between color spaces with different color models:</p>
103 *
104 * <pre class="prettyprint">
105 * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB)
106 * ColorSpace.Connector connector = ColorSpace.connect(
107 *         ColorSpace.get(ColorSpace.Named.CIE_LAB),
108 *         ColorSpace.get(ColorSpace.Named.BT709));
109 * </pre>
110 *
111 * <h3>Color spaces and multi-threading</h3>
112 *
113 * <p>Color spaces and other related classes ({@link Connector} for instance)
114 * are immutable and stateless. They can be safely used from multiple concurrent
115 * threads.</p>
116 *
117 * <p>Public static methods provided by this class, such as {@link #get(Named)}
118 * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be
119 * thread-safe.</p>
120 *
121 * @see #get(Named)
122 * @see Named
123 * @see Model
124 * @see Connector
125 * @see Adaptation
126 */
127@SuppressWarnings("StaticInitializerReferencesSubClass")
128public abstract class ColorSpace {
129    /**
130     * Standard CIE 1931 2° illuminant A, encoded in xyY.
131     * This illuminant has a color temperature of 2856K.
132     */
133    public static final float[] ILLUMINANT_A   = { 0.44757f, 0.40745f };
134    /**
135     * Standard CIE 1931 2° illuminant B, encoded in xyY.
136     * This illuminant has a color temperature of 4874K.
137     */
138    public static final float[] ILLUMINANT_B   = { 0.34842f, 0.35161f };
139    /**
140     * Standard CIE 1931 2° illuminant C, encoded in xyY.
141     * This illuminant has a color temperature of 6774K.
142     */
143    public static final float[] ILLUMINANT_C   = { 0.31006f, 0.31616f };
144    /**
145     * Standard CIE 1931 2° illuminant D50, encoded in xyY.
146     * This illuminant has a color temperature of 5003K. This illuminant
147     * is used by the profile connection space in ICC profiles.
148     */
149    public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f };
150    /**
151     * Standard CIE 1931 2° illuminant D55, encoded in xyY.
152     * This illuminant has a color temperature of 5503K.
153     */
154    public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f };
155    /**
156     * Standard CIE 1931 2° illuminant D60, encoded in xyY.
157     * This illuminant has a color temperature of 6004K.
158     */
159    public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f };
160    /**
161     * Standard CIE 1931 2° illuminant D65, encoded in xyY.
162     * This illuminant has a color temperature of 6504K. This illuminant
163     * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
164     */
165    public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
166    /**
167     * Standard CIE 1931 2° illuminant D75, encoded in xyY.
168     * This illuminant has a color temperature of 7504K.
169     */
170    public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f };
171    /**
172     * Standard CIE 1931 2° illuminant E, encoded in xyY.
173     * This illuminant has a color temperature of 5454K.
174     */
175    public static final float[] ILLUMINANT_E   = { 0.33333f, 0.33333f };
176
177    /**
178     * The minimum ID value a color space can have.
179     *
180     * @see #getId()
181     */
182    public static final int MIN_ID = -1; // Do not change
183    /**
184     * The maximum ID value a color space can have.
185     *
186     * @see #getId()
187     */
188    public static final int MAX_ID = 64; // Do not change, used to encode in longs
189
190    private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
191    private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
192    private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
193
194    // See static initialization block next to #get(Named)
195    private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
196
197    @NonNull private final String mName;
198    @NonNull private final Model mModel;
199    @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
200
201    /**
202     * {@usesMathJax}
203     *
204     * <p>List of common, named color spaces. A corresponding instance of
205     * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p>
206     *
207     * <pre class="prettyprint">
208     * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);
209     * </pre>
210     *
211     * <p>The properties of each color space are described below (see {@link #SRGB sRGB}
212     * for instance). When applicable, the color gamut of each color space is compared
213     * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram
214     * shows the location of the color space's primaries and white point.</p>
215     *
216     * @see ColorSpace#get(Named)
217     */
218    public enum Named {
219        // NOTE: Do NOT change the order of the enum
220        /**
221         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
222         * <table summary="Color space definition">
223         *     <tr>
224         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
225         *     </tr>
226         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
227         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
228         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
229         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr>
230         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
231         *     <tr>
232         *         <td>Opto-electronic transfer function</td>
233         *         <td colspan="4">\(\begin{equation}
234         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
235         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
236         *             \end{equation}\)
237         *         </td>
238         *     </tr>
239         *     <tr>
240         *         <td>Electro-optical transfer function</td>
241         *         <td colspan="4">\(\begin{equation}
242         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
243         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases}
244         *             \end{equation}\)
245         *         </td>
246         *     </tr>
247         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
248         * </table>
249         * <p>
250         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
251         *     <figcaption style="text-align: center;">sRGB</figcaption>
252         * </p>
253         */
254        SRGB,
255        /**
256         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
257         * <table summary="Color space definition">
258         *     <tr>
259         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
260         *     </tr>
261         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
262         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
263         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
264         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr>
265         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
266         *     <tr>
267         *         <td>Opto-electronic transfer function</td>
268         *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
269         *     </tr>
270         *     <tr>
271         *         <td>Electro-optical transfer function</td>
272         *         <td colspan="4">\(C_{linear} = C_{sRGB}\)</td>
273         *     </tr>
274         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
275         * </table>
276         * <p>
277         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
278         *     <figcaption style="text-align: center;">sRGB</figcaption>
279         * </p>
280         */
281        LINEAR_SRGB,
282        /**
283         * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p>
284         * <table summary="Color space definition">
285         *     <tr>
286         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
287         *     </tr>
288         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
289         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
290         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
291         *     <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr>
292         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
293         *     <tr>
294         *         <td>Opto-electronic transfer function</td>
295         *         <td colspan="4">\(\begin{equation}
296         *             C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
297         *                      \left| C_{linear} \right| \le 0.0031308 \\
298         *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
299         *                      \left| C_{linear} \right| \gt 0.0031308 \end{cases}
300         *             \end{equation}\)
301         *         </td>
302         *     </tr>
303         *     <tr>
304         *         <td>Electro-optical transfer function</td>
305         *         <td colspan="4">\(\begin{equation}
306         *             C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
307         *                  \left| C_{scRGB} \right| \le 0.04045 \\
308         *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
309         *                  \left| C_{scRGB} \right| \gt 0.04045 \end{cases}
310         *             \end{equation}\)
311         *         </td>
312         *     </tr>
313         *     <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
314         * </table>
315         * <p>
316         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
317         *     <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
318         * </p>
319         */
320        EXTENDED_SRGB,
321        /**
322         * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p>
323         * <table summary="Color space definition">
324         *     <tr>
325         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
326         *     </tr>
327         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
328         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
329         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
330         *     <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr>
331         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
332         *     <tr>
333         *         <td>Opto-electronic transfer function</td>
334         *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
335         *     </tr>
336         *     <tr>
337         *         <td>Electro-optical transfer function</td>
338         *         <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
339         *     </tr>
340         *     <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
341         * </table>
342         * <p>
343         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
344         *     <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
345         * </p>
346         */
347        LINEAR_EXTENDED_SRGB,
348        /**
349         * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p>
350         * <table summary="Color space definition">
351         *     <tr>
352         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
353         *     </tr>
354         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
355         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
356         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
357         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr>
358         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
359         *     <tr>
360         *         <td>Opto-electronic transfer function</td>
361         *         <td colspan="4">\(\begin{equation}
362         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
363         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
364         *             \end{equation}\)
365         *         </td>
366         *     </tr>
367         *     <tr>
368         *         <td>Electro-optical transfer function</td>
369         *         <td colspan="4">\(\begin{equation}
370         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
371         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
372         *             \end{equation}\)
373         *         </td>
374         *     </tr>
375         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
376         * </table>
377         * <p>
378         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" />
379         *     <figcaption style="text-align: center;">BT.709</figcaption>
380         * </p>
381         */
382        BT709,
383        /**
384         * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p>
385         * <table summary="Color space definition">
386         *     <tr>
387         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
388         *     </tr>
389         *     <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr>
390         *     <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr>
391         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
392         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr>
393         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
394         *     <tr>
395         *         <td>Opto-electronic transfer function</td>
396         *         <td colspan="4">\(\begin{equation}
397         *             C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\
398         *             1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
399         *             \end{equation}\)
400         *         </td>
401         *     </tr>
402         *     <tr>
403         *         <td>Electro-optical transfer function</td>
404         *         <td colspan="4">\(\begin{equation}
405         *             C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\
406         *             \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
407         *             \end{equation}\)
408         *         </td>
409         *     </tr>
410         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
411         * </table>
412         * <p>
413         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" />
414         *     <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption>
415         * </p>
416         */
417        BT2020,
418        /**
419         * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p>
420         * <table summary="Color space definition">
421         *     <tr>
422         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
423         *     </tr>
424         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr>
425         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr>
426         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
427         *     <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr>
428         *     <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr>
429         *     <tr>
430         *         <td>Opto-electronic transfer function</td>
431         *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
432         *     </tr>
433         *     <tr>
434         *         <td>Electro-optical transfer function</td>
435         *         <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td>
436         *     </tr>
437         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
438         * </table>
439         * <p>
440         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" />
441         *     <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption>
442         * </p>
443         */
444        DCI_P3,
445        /**
446         * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p>
447         * <table summary="Color space definition">
448         *     <tr>
449         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
450         *     </tr>
451         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr>
452         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr>
453         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
454         *     <tr><td>Name</td><td colspan="4">Display P3</td></tr>
455         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
456         *     <tr>
457         *         <td>Opto-electronic transfer function</td>
458         *         <td colspan="4">\(\begin{equation}
459         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
460         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
461         *             \end{equation}\)
462         *         </td>
463         *     </tr>
464         *     <tr>
465         *         <td>Electro-optical transfer function</td>
466         *         <td colspan="4">\(\begin{equation}
467         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
468         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases}
469         *             \end{equation}\)
470         *         </td>
471         *     </tr>
472         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
473         * </table>
474         * <p>
475         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" />
476         *     <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption>
477         * </p>
478         */
479        DISPLAY_P3,
480        /**
481         * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p>
482         * <table summary="Color space definition">
483         *     <tr>
484         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
485         *     </tr>
486         *     <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr>
487         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr>
488         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
489         *     <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr>
490         *     <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr>
491         *     <tr>
492         *         <td>Opto-electronic transfer function</td>
493         *         <td colspan="4">\(\begin{equation}
494         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
495         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
496         *             \end{equation}\)
497         *         </td>
498         *     </tr>
499         *     <tr>
500         *         <td>Electro-optical transfer function</td>
501         *         <td colspan="4">\(\begin{equation}
502         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
503         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
504         *             \end{equation}\)
505         *         </td>
506         *     </tr>
507         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
508         * </table>
509         * <p>
510         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" />
511         *     <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption>
512         * </p>
513         */
514        NTSC_1953,
515        /**
516         * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p>
517         * <table summary="Color space definition">
518         *     <tr>
519         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
520         *     </tr>
521         *     <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr>
522         *     <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr>
523         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
524         *     <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr>
525         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
526         *     <tr>
527         *         <td>Opto-electronic transfer function</td>
528         *         <td colspan="4">\(\begin{equation}
529         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
530         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
531         *             \end{equation}\)
532         *         </td>
533         *     </tr>
534         *     <tr>
535         *         <td>Electro-optical transfer function</td>
536         *         <td colspan="4">\(\begin{equation}
537         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
538         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
539         *             \end{equation}\)
540         *         </td>
541         *     </tr>
542         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
543         * </table>
544         * <p>
545         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" />
546         *     <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption>
547         * </p>
548         */
549        SMPTE_C,
550        /**
551         * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p>
552         * <table summary="Color space definition">
553         *     <tr>
554         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
555         *     </tr>
556         *     <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr>
557         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr>
558         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
559         *     <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr>
560         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
561         *     <tr>
562         *         <td>Opto-electronic transfer function</td>
563         *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
564         *     </tr>
565         *     <tr>
566         *         <td>Electro-optical transfer function</td>
567         *         <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td>
568         *     </tr>
569         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
570         * </table>
571         * <p>
572         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" />
573         *     <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption>
574         * </p>
575         */
576        ADOBE_RGB,
577        /**
578         * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p>
579         * <table summary="Color space definition">
580         *     <tr>
581         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
582         *     </tr>
583         *     <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr>
584         *     <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr>
585         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
586         *     <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr>
587         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
588         *     <tr>
589         *         <td>Opto-electronic transfer function</td>
590         *         <td colspan="4">\(\begin{equation}
591         *             C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\
592         *             C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
593         *             \end{equation}\)
594         *         </td>
595         *     </tr>
596         *     <tr>
597         *         <td>Electro-optical transfer function</td>
598         *         <td colspan="4">\(\begin{equation}
599         *             C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\
600         *             C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
601         *             \end{equation}\)
602         *         </td>
603         *     </tr>
604         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
605         * </table>
606         * <p>
607         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" />
608         *     <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption>
609         * </p>
610         */
611        PRO_PHOTO_RGB,
612        /**
613         * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p>
614         * <table summary="Color space definition">
615         *     <tr>
616         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
617         *     </tr>
618         *     <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr>
619         *     <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr>
620         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
621         *     <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr>
622         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
623         *     <tr>
624         *         <td>Opto-electronic transfer function</td>
625         *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
626         *     </tr>
627         *     <tr>
628         *         <td>Electro-optical transfer function</td>
629         *         <td colspan="4">\(C_{linear} = C_{ACES}\)</td>
630         *     </tr>
631         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
632         * </table>
633         * <p>
634         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" />
635         *     <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption>
636         * </p>
637         */
638        ACES,
639        /**
640         * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p>
641         * <table summary="Color space definition">
642         *     <tr>
643         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
644         *     </tr>
645         *     <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr>
646         *     <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr>
647         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
648         *     <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr>
649         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
650         *     <tr>
651         *         <td>Opto-electronic transfer function</td>
652         *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
653         *     </tr>
654         *     <tr>
655         *         <td>Electro-optical transfer function</td>
656         *         <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td>
657         *     </tr>
658         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
659         * </table>
660         * <p>
661         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" />
662         *     <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption>
663         * </p>
664         */
665        ACESCG,
666        /**
667         * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard
668         * illuminant D50 as its white point.</p>
669         * <table summary="Color space definition">
670         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
671         *     <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr>
672         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
673         *     <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr>
674         * </table>
675         */
676        CIE_XYZ,
677        /**
678         * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50
679         * as a profile conversion space.</p>
680         * <table summary="Color space definition">
681         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
682         *     <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr>
683         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
684         *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
685         * </table>
686         */
687        CIE_LAB
688        // Update the initialization block next to #get(Named) when adding new values
689    }
690
691    /**
692     * <p>A render intent determines how a {@link ColorSpace.Connector connector}
693     * maps colors from one color space to another. The choice of mapping is
694     * important when the source color space has a larger color gamut than the
695     * destination color space.</p>
696     *
697     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
698     */
699    public enum RenderIntent {
700        /**
701         * <p>Compresses the source gamut into the destination gamut.
702         * This render intent affects all colors, inside and outside
703         * of destination gamut. The goal of this render intent is
704         * to preserve the visual relationship between colors.</p>
705         *
706         * <p class="note">This render intent is currently not
707         * implemented and behaves like {@link #RELATIVE}.</p>
708         */
709        PERCEPTUAL,
710        /**
711         * Similar to the {@link #ABSOLUTE} render intent, this render
712         * intent matches the closest color in the destination gamut
713         * but makes adjustments for the destination white point.
714         */
715        RELATIVE,
716        /**
717         * <p>Attempts to maintain the relative saturation of colors
718         * from the source gamut to the destination gamut, to keep
719         * highly saturated colors as saturated as possible.</p>
720         *
721         * <p class="note">This render intent is currently not
722         * implemented and behaves like {@link #RELATIVE}.</p>
723         */
724        SATURATION,
725        /**
726         * Colors that are in the destination gamut are left unchanged.
727         * Colors that fall outside of the destination gamut are mapped
728         * to the closest possible color within the gamut of the destination
729         * color space (they are clipped).
730         */
731        ABSOLUTE
732    }
733
734    /**
735     * {@usesMathJax}
736     *
737     * <p>List of adaptation matrices that can be used for chromatic adaptation
738     * using the von Kries transform. These matrices are used to convert values
739     * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p>
740     *
741     * <p>Given an adaptation matrix \(A\), the conversion from XYZ to
742     * LMS is straightforward:</p>
743     *
744     * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] =
745     * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$
746     *
747     * <p>The complete von Kries transform \(T\) uses a diagonal matrix
748     * noted \(D\) to perform the adaptation in LMS space. In addition
749     * to \(A\) and \(D\), the source white point \(W1\) and the destination
750     * white point \(W2\) must be specified:</p>
751     *
752     * $$\begin{align*}
753     * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
754     *      A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\
755     * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
756     *      A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\
757     * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\
758     *      0 & \frac{M_2}{M_1} & 0 \\
759     *      0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\
760     * T &= A^{-1}.D.A
761     * \end{align*}$$
762     *
763     * <p>As an example, the resulting matrix \(T\) can then be used to
764     * perform the chromatic adaptation of sRGB XYZ transform from D65
765     * to D50:</p>
766     *
767     * $$sRGB_{D50} = T.sRGB_{D65}$$
768     *
769     * @see ColorSpace.Connector
770     * @see ColorSpace#connect(ColorSpace, ColorSpace)
771     */
772    public enum Adaptation {
773        /**
774         * Bradford matrix for the von Kries chromatic adaptation transform.
775         */
776        BRADFORD(new float[] {
777                 0.8951f, -0.7502f,  0.0389f,
778                 0.2664f,  1.7135f, -0.0685f,
779                -0.1614f,  0.0367f,  1.0296f
780        }),
781        /**
782         * von Kries matrix for the von Kries chromatic adaptation transform.
783         */
784        VON_KRIES(new float[] {
785                 0.40024f, -0.22630f, 0.00000f,
786                 0.70760f,  1.16532f, 0.00000f,
787                -0.08081f,  0.04570f, 0.91822f
788        });
789
790        final float[] mTransform;
791
792        Adaptation(@NonNull @Size(9) float[] transform) {
793            mTransform = transform;
794        }
795    }
796
797    /**
798     * A color model is required by a {@link ColorSpace} to describe the
799     * way colors can be represented as tuples of numbers. A common color
800     * model is the {@link #RGB RGB} color model which defines a color
801     * as represented by a tuple of 3 numbers (red, green and blue).
802     */
803    public enum Model {
804        /**
805         * The RGB model is a color model with 3 components that
806         * refer to the three additive primiaries: red, green
807         * andd blue.
808         */
809        RGB(3),
810        /**
811         * The XYZ model is a color model with 3 components that
812         * are used to model human color vision on a basic sensory
813         * level.
814         */
815        XYZ(3),
816        /**
817         * The Lab model is a color model with 3 components used
818         * to describe a color space that is more perceptually
819         * uniform than XYZ.
820         */
821        LAB(3),
822        /**
823         * The CMYK model is a color model with 4 components that
824         * refer to four inks used in color printing: cyan, magenta,
825         * yellow and black (or key). CMYK is a subtractive color
826         * model.
827         */
828        CMYK(4);
829
830        private final int mComponentCount;
831
832        Model(@IntRange(from = 1, to = 4) int componentCount) {
833            mComponentCount = componentCount;
834        }
835
836        /**
837         * Returns the number of components for this color model.
838         *
839         * @return An integer between 1 and 4
840         */
841        @IntRange(from = 1, to = 4)
842        public int getComponentCount() {
843            return mComponentCount;
844        }
845    }
846
847    private ColorSpace(
848            @NonNull String name,
849            @NonNull Model model,
850            @IntRange(from = MIN_ID, to = MAX_ID) int id) {
851
852        if (name == null || name.length() < 1) {
853            throw new IllegalArgumentException("The name of a color space cannot be null and " +
854                    "must contain at least 1 character");
855        }
856
857        if (model == null) {
858            throw new IllegalArgumentException("A color space must have a model");
859        }
860
861        if (id < MIN_ID || id > MAX_ID) {
862            throw new IllegalArgumentException("The id must be between " +
863                    MIN_ID + " and " + MAX_ID);
864        }
865
866        mName = name;
867        mModel = model;
868        mId = id;
869    }
870
871    /**
872     * Returns the name of this color space. The name is never null
873     * and contains always at least 1 character.
874     *
875     * @return A non-null String of length >= 1
876     */
877    @NonNull
878    public String getName() {
879        return mName;
880    }
881
882    /**
883     * Returns the ID of this color space. Positive IDs match the color
884     * spaces enumerated in {@link Named}. A negative ID indicates a
885     * color space created by calling one of the public constructors.
886     *
887     * @return An integer between {@link #MIN_ID} and {@link #MAX_ID}
888     */
889    @IntRange(from = MIN_ID, to = MAX_ID)
890    public int getId() {
891        return mId;
892    }
893
894    /**
895     * Return the color model of this color space.
896     *
897     * @return A non-null {@link Model}
898     *
899     * @see Model
900     * @see #getComponentCount()
901     */
902    @NonNull
903    public Model getModel() {
904        return mModel;
905    }
906
907    /**
908     * Returns the number of components that form a color value according
909     * to this color space's color model.
910     *
911     * @return An integer between 1 and 4
912     *
913     * @see Model
914     * @see #getModel()
915     */
916    @IntRange(from = 1, to = 4)
917    public int getComponentCount() {
918        return mModel.getComponentCount();
919    }
920
921    /**
922     * Returns whether this color space is a wide-gamut color space.
923     * An RGB color space is wide-gamut if its gamut entirely contains
924     * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is
925     * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC}
926     * gamut.
927     *
928     * @return True if this color space is a wide-gamut color space,
929     *         false otherwise
930     */
931    public abstract boolean isWideGamut();
932
933    /**
934     * <p>Indicates whether this color space is the sRGB color space or
935     * equivalent to the sRGB color space.</p>
936     * <p>A color space is considered sRGB if it meets all the following
937     * conditions:</p>
938     * <ul>
939     *     <li>Its color model is {@link Model#RGB}.</li>
940     *     <li>
941     *         Its primaries are within 1e-3 of the true
942     *         {@link Named#SRGB sRGB} primaries.
943     *     </li>
944     *     <li>
945     *         Its white point is withing 1e-3 of the CIE standard
946     *         illuminant {@link #ILLUMINANT_D65 D65}.
947     *     </li>
948     *     <li>Its opto-electronic transfer function is not linear.</li>
949     *     <li>Its electro-optical transfer function is not linear.</li>
950     *     <li>Its range is \([0..1]\).</li>
951     * </ul>
952     * <p>This method always returns true for {@link Named#SRGB}.</p>
953     *
954     * @return True if this color space is the sRGB color space (or a
955     *         close approximation), false otherwise
956     */
957    public boolean isSrgb() {
958        return false;
959    }
960
961    /**
962     * Returns the minimum valid value for the specified component of this
963     * color space's color model.
964     *
965     * @param component The index of the component
966     * @return A floating point value less than {@link #getMaxValue(int)}
967     *
968     * @see #getMaxValue(int)
969     * @see Model#getComponentCount()
970     */
971    public abstract float getMinValue(@IntRange(from = 0, to = 3) int component);
972
973    /**
974     * Returns the maximum valid value for the specified component of this
975     * color space's color model.
976     *
977     * @param component The index of the component
978     * @return A floating point value greater than {@link #getMinValue(int)}
979     *
980     * @see #getMinValue(int)
981     * @see Model#getComponentCount()
982     */
983    public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component);
984
985    /**
986     * <p>Converts a color value from this color space's model to
987     * tristimulus CIE XYZ values. If the color model of this color
988     * space is not {@link Model#RGB RGB}, it is assumed that the
989     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
990     * standard illuminant.</p>
991     *
992     * <p>This method is a convenience for color spaces with a model
993     * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB}
994     * for instance). With color spaces using fewer or more components,
995     * use {@link #toXyz(float[])} instead</p>.
996     *
997     * @param r The first component of the value to convert from (typically R in RGB)
998     * @param g The second component of the value to convert from (typically G in RGB)
999     * @param b The third component of the value to convert from (typically B in RGB)
1000     * @return A new array of 3 floats, containing tristimulus XYZ values
1001     *
1002     * @see #toXyz(float[])
1003     * @see #fromXyz(float, float, float)
1004     */
1005    @NonNull
1006    @Size(3)
1007    public float[] toXyz(float r, float g, float b) {
1008        return toXyz(new float[] { r, g, b });
1009    }
1010
1011    /**
1012     * <p>Converts a color value from this color space's model to
1013     * tristimulus CIE XYZ values. If the color model of this color
1014     * space is not {@link Model#RGB RGB}, it is assumed that the
1015     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
1016     * standard illuminant.</p>
1017     *
1018     * <p class="note">The specified array's length  must be at least
1019     * equal to to the number of color components as returned by
1020     * {@link Model#getComponentCount()}.</p>
1021     *
1022     * @param v An array of color components containing the color space's
1023     *          color value to convert to XYZ, and large enough to hold
1024     *          the resulting tristimulus XYZ values
1025     * @return The array passed in parameter
1026     *
1027     * @see #toXyz(float, float, float)
1028     * @see #fromXyz(float[])
1029     */
1030    @NonNull
1031    @Size(min = 3)
1032    public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v);
1033
1034    /**
1035     * <p>Converts tristimulus values from the CIE XYZ space to this
1036     * color space's color model.</p>
1037     *
1038     * @param x The X component of the color value
1039     * @param y The Y component of the color value
1040     * @param z The Z component of the color value
1041     * @return A new array whose size is equal to the number of color
1042     *         components as returned by {@link Model#getComponentCount()}
1043     *
1044     * @see #fromXyz(float[])
1045     * @see #toXyz(float, float, float)
1046     */
1047    @NonNull
1048    @Size(min = 3)
1049    public float[] fromXyz(float x, float y, float z) {
1050        float[] xyz = new float[mModel.getComponentCount()];
1051        xyz[0] = x;
1052        xyz[1] = y;
1053        xyz[2] = z;
1054        return fromXyz(xyz);
1055    }
1056
1057    /**
1058     * <p>Converts tristimulus values from the CIE XYZ space to this color
1059     * space's color model. The resulting value is passed back in the specified
1060     * array.</p>
1061     *
1062     * <p class="note>The specified array's length  must be at least equal to
1063     * to the number of color components as returned by
1064     * {@link Model#getComponentCount()}, and its first 3 values must
1065     * be the XYZ components to convert from.</p>
1066     *
1067     * @param v An array of color components containing the XYZ values
1068     *          to convert from, and large enough to hold the number
1069     *          of components of this color space's model
1070     * @return The array passed in parameter
1071     *
1072     * @see #fromXyz(float, float, float)
1073     * @see #toXyz(float[])
1074     */
1075    @NonNull
1076    @Size(min = 3)
1077    public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v);
1078
1079    /**
1080     * <p>Returns a string representation of the object. This method returns
1081     * a string equal to the value of:</p>
1082     *
1083     * <pre class="prettyprint">
1084     * getName() + "(id=" + getId() + ", model=" + getModel() + ")"
1085     * </pre>
1086     *
1087     * <p>For instance, the string representation of the {@link Named#SRGB sRGB}
1088     * color space is equal to the following value:</p>
1089     *
1090     * <pre>
1091     * sRGB IEC61966-2.1 (id=0, model=RGB)
1092     * </pre>
1093     *
1094     * @return A string representation of the object
1095     */
1096    @Override
1097    public String toString() {
1098        return mName + " (id=" + mId + ", model=" + mModel + ")";
1099    }
1100
1101    @Override
1102    public boolean equals(Object o) {
1103        if (this == o) return true;
1104        if (o == null || getClass() != o.getClass()) return false;
1105
1106        ColorSpace that = (ColorSpace) o;
1107
1108        if (mId != that.mId) return false;
1109        //noinspection SimplifiableIfStatement
1110        if (!mName.equals(that.mName)) return false;
1111        return mModel == that.mModel;
1112
1113    }
1114
1115    @Override
1116    public int hashCode() {
1117        int result = mName.hashCode();
1118        result = 31 * result + mModel.hashCode();
1119        result = 31 * result + mId;
1120        return result;
1121    }
1122
1123    /**
1124     * <p>Connects two color spaces to allow conversion from the source color
1125     * space to the destination color space. If the source and destination
1126     * color spaces do not have the same profile connection space (CIE XYZ
1127     * with the same white point), they are chromatically adapted to use the
1128     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
1129     *
1130     * <p>If the source and destination are the same, an optimized connector
1131     * is returned to avoid unnecessary computations and loss of precision.</p>
1132     *
1133     * <p>Colors are mapped from the source color space to the destination color
1134     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
1135     *
1136     * @param source The color space to convert colors from
1137     * @param destination The color space to convert colors to
1138     * @return A non-null connector between the two specified color spaces
1139     *
1140     * @see #connect(ColorSpace)
1141     * @see #connect(ColorSpace, RenderIntent)
1142     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
1143     */
1144    @NonNull
1145    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) {
1146        return connect(source, destination, RenderIntent.PERCEPTUAL);
1147    }
1148
1149    /**
1150     * <p>Connects two color spaces to allow conversion from the source color
1151     * space to the destination color space. If the source and destination
1152     * color spaces do not have the same profile connection space (CIE XYZ
1153     * with the same white point), they are chromatically adapted to use the
1154     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
1155     *
1156     * <p>If the source and destination are the same, an optimized connector
1157     * is returned to avoid unnecessary computations and loss of precision.</p>
1158     *
1159     * @param source The color space to convert colors from
1160     * @param destination The color space to convert colors to
1161     * @param intent The render intent to map colors from the source to the destination
1162     * @return A non-null connector between the two specified color spaces
1163     *
1164     * @see #connect(ColorSpace)
1165     * @see #connect(ColorSpace, RenderIntent)
1166     * @see #connect(ColorSpace, ColorSpace)
1167     */
1168    @NonNull
1169    @SuppressWarnings("ConstantConditions")
1170    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination,
1171            @NonNull RenderIntent intent) {
1172        if (source.equals(destination)) return Connector.identity(source);
1173
1174        if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) {
1175            return new Connector.Rgb((Rgb) source, (Rgb) destination, intent);
1176        }
1177
1178        return new Connector(source, destination, intent);
1179    }
1180
1181    /**
1182     * <p>Connects the specified color spaces to sRGB.
1183     * If the source color space does not use CIE XYZ D65 as its profile
1184     * connection space, the two spaces are chromatically adapted to use the
1185     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
1186     *
1187     * <p>If the source is the sRGB color space, an optimized connector
1188     * is returned to avoid unnecessary computations and loss of precision.</p>
1189     *
1190     * <p>Colors are mapped from the source color space to the destination color
1191     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
1192     *
1193     * @param source The color space to convert colors from
1194     * @return A non-null connector between the specified color space and sRGB
1195     *
1196     * @see #connect(ColorSpace, RenderIntent)
1197     * @see #connect(ColorSpace, ColorSpace)
1198     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
1199     */
1200    @NonNull
1201    public static Connector connect(@NonNull ColorSpace source) {
1202        return connect(source, RenderIntent.PERCEPTUAL);
1203    }
1204
1205    /**
1206     * <p>Connects the specified color spaces to sRGB.
1207     * If the source color space does not use CIE XYZ D65 as its profile
1208     * connection space, the two spaces are chromatically adapted to use the
1209     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
1210     *
1211     * <p>If the source is the sRGB color space, an optimized connector
1212     * is returned to avoid unnecessary computations and loss of precision.</p>
1213     *
1214     * @param source The color space to convert colors from
1215     * @param intent The render intent to map colors from the source to the destination
1216     * @return A non-null connector between the specified color space and sRGB
1217     *
1218     * @see #connect(ColorSpace)
1219     * @see #connect(ColorSpace, ColorSpace)
1220     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
1221     */
1222    @NonNull
1223    public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) {
1224        if (source.isSrgb()) return Connector.identity(source);
1225
1226        if (source.getModel() == Model.RGB) {
1227            return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent);
1228        }
1229
1230        return new Connector(source, get(Named.SRGB), intent);
1231    }
1232
1233    /**
1234     * <p>Performs the chromatic adaptation of a color space from its native
1235     * white point to the specified white point.</p>
1236     *
1237     * <p>The chromatic adaptation is performed using the
1238     * {@link Adaptation#BRADFORD} matrix.</p>
1239     *
1240     * <p class="note">The color space returned by this method always has
1241     * an ID of {@link #MIN_ID}.</p>
1242     *
1243     * @param colorSpace The color space to chromatically adapt
1244     * @param whitePoint The new white point
1245     * @return A {@link ColorSpace} instance with the same name, primaries,
1246     *         transfer functions and range as the specified color space
1247     *
1248     * @see Adaptation
1249     * @see #adapt(ColorSpace, float[], Adaptation)
1250     */
1251    @NonNull
1252    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
1253            @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
1254        return adapt(colorSpace, whitePoint, Adaptation.BRADFORD);
1255    }
1256
1257    /**
1258     * <p>Performs the chromatic adaptation of a color space from its native
1259     * white point to the specified white point. If the specified color space
1260     * does not have an {@link Model#RGB RGB} color model, or if the color
1261     * space already has the target white point, the color space is returned
1262     * unmodified.</p>
1263     *
1264     * <p>The chromatic adaptation is performed using the von Kries method
1265     * described in the documentation of {@link Adaptation}.</p>
1266     *
1267     * <p class="note">The color space returned by this method always has
1268     * an ID of {@link #MIN_ID}.</p>
1269     *
1270     * @param colorSpace The color space to chromatically adapt
1271     * @param whitePoint The new white point
1272     * @param adaptation The adaptation matrix
1273     * @return A new color space if the specified color space has an RGB
1274     *         model and a white point different from the specified white
1275     *         point; the specified color space otherwise
1276     *
1277     * @see Adaptation
1278     * @see #adapt(ColorSpace, float[])
1279     */
1280    @NonNull
1281    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
1282            @NonNull @Size(min = 2, max = 3) float[] whitePoint,
1283            @NonNull Adaptation adaptation) {
1284        if (colorSpace.getModel() == Model.RGB) {
1285            ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
1286            if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace;
1287
1288            float[] xyz = whitePoint.length == 3 ?
1289                    Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint);
1290            float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform,
1291                    xyYToXyz(rgb.getWhitePoint()), xyz);
1292            float[] transform = mul3x3(adaptationTransform, rgb.mTransform);
1293
1294            return new ColorSpace.Rgb(rgb, transform, whitePoint);
1295        }
1296        return colorSpace;
1297    }
1298
1299    /**
1300     * <p>Returns an instance of {@link ColorSpace} whose ID matches the specified
1301     * ID. If the ID is < 0 or &gt; {@link #MAX_ID}, calling this method is equivalent
1302     * to calling <code>get(Named.SRGB)</code>.</p>
1303     *
1304     * <p>This method always returns the same instance for a given ID.</p>
1305     *
1306     * <p>This method is thread-safe.</p>
1307     *
1308     * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID}
1309     * @return A non-null {@link ColorSpace} instance
1310     */
1311    @NonNull
1312    static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
1313        if (index < 0 || index > Named.values().length) {
1314            return get(Named.SRGB);
1315        }
1316        return sNamedColorSpaces[index];
1317    }
1318
1319    /**
1320     * <p>Returns an instance of {@link ColorSpace} identified by the specified
1321     * name. The list of names provided in the {@link Named} enum gives access
1322     * to a variety of common RGB color spaces.</p>
1323     *
1324     * <p>This method always returns the same instance for a given name.</p>
1325     *
1326     * <p>This method is thread-safe.</p>
1327     *
1328     * @param name The name of the color space to get an instance of
1329     * @return A non-null {@link ColorSpace} instance
1330     */
1331    @NonNull
1332    public static ColorSpace get(@NonNull Named name) {
1333        return sNamedColorSpaces[name.ordinal()];
1334    }
1335
1336    static {
1337        sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
1338                "sRGB IEC61966-2.1",
1339                SRGB_PRIMARIES,
1340                ILLUMINANT_D65,
1341                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1342                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1343                0.0f, 1.0f,
1344                Named.SRGB.ordinal()
1345        );
1346        sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
1347                "sRGB IEC61966-2.1 (Linear)",
1348                SRGB_PRIMARIES,
1349                ILLUMINANT_D65,
1350                DoubleUnaryOperator.identity(),
1351                DoubleUnaryOperator.identity(),
1352                0.0f, 1.0f,
1353                Named.LINEAR_SRGB.ordinal()
1354        );
1355        sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1356                "scRGB-nl IEC 61966-2-2:2003",
1357                SRGB_PRIMARIES,
1358                ILLUMINANT_D65,
1359                x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1360                x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1361                -0.5f, 7.5f,
1362                Named.EXTENDED_SRGB.ordinal()
1363        );
1364        sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1365                "scRGB- IEC 61966-2-2:2003",
1366                SRGB_PRIMARIES,
1367                ILLUMINANT_D65,
1368                DoubleUnaryOperator.identity(),
1369                DoubleUnaryOperator.identity(),
1370                -0.5f, 7.5f,
1371                Named.LINEAR_EXTENDED_SRGB.ordinal()
1372        );
1373        sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
1374                "Rec. ITU-R BT.709-5",
1375                new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
1376                ILLUMINANT_D65,
1377                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1378                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1379                0.0f, 1.0f,
1380                Named.BT709.ordinal()
1381        );
1382        sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
1383                "Rec. ITU-R BT.2020-1",
1384                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
1385                ILLUMINANT_D65,
1386                x -> rcpResponse(x, 1 / 0.45, 1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145),
1387                x -> response(x, 1 / 0.45, 1 / 1.0993, 0.099 / 1.0993, 1 / 4.5, 0.08145),
1388                0.0f, 1.0f,
1389                Named.BT2020.ordinal()
1390        );
1391        sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
1392                "SMPTE RP 431-2-2007 DCI (P3)",
1393                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1394                new float[] { 0.314f, 0.351f },
1395                x -> Math.pow(x, 1 / 2.6),
1396                x -> Math.pow(x, 2.6),
1397                0.0f, 1.0f,
1398                Named.DCI_P3.ordinal()
1399        );
1400        sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
1401                "Display P3",
1402                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1403                ILLUMINANT_D65,
1404                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1405                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
1406                0.0f, 1.0f,
1407                Named.DISPLAY_P3.ordinal()
1408        );
1409        sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
1410                "NTSC (1953)",
1411                NTSC_1953_PRIMARIES,
1412                ILLUMINANT_C,
1413                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1414                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
1415                0.0f, 1.0f,
1416                Named.NTSC_1953.ordinal()
1417        );
1418        sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
1419                "SMPTE-C RGB",
1420                new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
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.SMPTE_C.ordinal()
1426        );
1427        sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
1428                "Adobe RGB (1998)",
1429                new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
1430                ILLUMINANT_D65,
1431                x -> Math.pow(x, 1 / 2.2),
1432                x -> Math.pow(x, 2.2),
1433                0.0f, 1.0f,
1434                Named.ADOBE_RGB.ordinal()
1435        );
1436        sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
1437                "ROMM RGB ISO 22028-2:2013",
1438                new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
1439                ILLUMINANT_D50,
1440                x -> rcpResponse(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
1441                x -> response(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
1442                0.0f, 1.0f,
1443                Named.PRO_PHOTO_RGB.ordinal()
1444        );
1445        sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
1446                "SMPTE ST 2065-1:2012 ACES",
1447                new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
1448                ILLUMINANT_D60,
1449                DoubleUnaryOperator.identity(),
1450                DoubleUnaryOperator.identity(),
1451                -65504.0f, 65504.0f,
1452                Named.ACES.ordinal()
1453        );
1454        sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
1455                "Academy S-2014-004 ACEScg",
1456                new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
1457                ILLUMINANT_D60,
1458                DoubleUnaryOperator.identity(),
1459                DoubleUnaryOperator.identity(),
1460                -65504.0f, 65504.0f,
1461                Named.ACESCG.ordinal()
1462        );
1463        sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
1464                "Generic XYZ",
1465                Named.CIE_XYZ.ordinal()
1466        );
1467        sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
1468                "Generic L*a*b*",
1469                Named.CIE_LAB.ordinal()
1470        );
1471    }
1472
1473    // Reciprocal piecewise gamma response
1474    private static double rcpResponse(double x, double g,double a, double b, double c, double d) {
1475        return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
1476    }
1477
1478    // Piecewise gamma response
1479    private static double response(double x, double g, double a, double b, double c, double d) {
1480        return x >= d ? Math.pow(a * x + b, g) : c * x;
1481    }
1482
1483    // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
1484    // spaces that allow negative values
1485    @SuppressWarnings("SameParameterValue")
1486    private static double absRcpResponse(double x, double g, double a, double b, double c, double d) {
1487        return Math.copySign(rcpResponse(x < 0.0 ? -x : x, g, a, b, c, d), x);
1488    }
1489
1490    // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
1491    // allow negative values
1492    @SuppressWarnings("SameParameterValue")
1493    private static double absResponse(double x, double g, double a, double b, double c, double d) {
1494        return Math.copySign(response(x < 0.0 ? -x : x, g, a, b, c, d), x);
1495    }
1496
1497    /**
1498     * Compares two arrays of float with a precision of 1e-3.
1499     *
1500     * @param a The first array to compare
1501     * @param b The second array to compare
1502     * @return True if the two arrays are equal, false otherwise
1503     */
1504    private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
1505        if (a == b) return true;
1506        for (int i = 0; i < a.length; i++) {
1507            if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
1508        }
1509        return true;
1510    }
1511
1512    /**
1513     * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
1514     *
1515     * @param m A 3x3 matrix as a non-null array of 9 floats
1516     * @return A new array of 9 floats containing the inverse of the input matrix
1517     */
1518    @NonNull
1519    @Size(9)
1520    private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
1521        float a = m[0];
1522        float b = m[3];
1523        float c = m[6];
1524        float d = m[1];
1525        float e = m[4];
1526        float f = m[7];
1527        float g = m[2];
1528        float h = m[5];
1529        float i = m[8];
1530
1531        float A = e * i - f * h;
1532        float B = f * g - d * i;
1533        float C = d * h - e * g;
1534
1535        float det = a * A + b * B + c * C;
1536
1537        float inverted[] = new float[m.length];
1538        inverted[0] = A / det;
1539        inverted[1] = B / det;
1540        inverted[2] = C / det;
1541        inverted[3] = (c * h - b * i) / det;
1542        inverted[4] = (a * i - c * g) / det;
1543        inverted[5] = (b * g - a * h) / det;
1544        inverted[6] = (b * f - c * e) / det;
1545        inverted[7] = (c * d - a * f) / det;
1546        inverted[8] = (a * e - b * d) / det;
1547        return inverted;
1548    }
1549
1550    /**
1551     * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
1552     *
1553     * @param lhs 3x3 matrix, as a non-null array of 9 floats
1554     * @param rhs 3x3 matrix, as a non-null array of 9 floats
1555     * @return A new array of 9 floats containing the result of the multiplication
1556     *         of rhs by lhs
1557     */
1558    @NonNull
1559    @Size(9)
1560    private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
1561        float[] r = new float[9];
1562        r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
1563        r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
1564        r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
1565        r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
1566        r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
1567        r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
1568        r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
1569        r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
1570        r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
1571        return r;
1572    }
1573
1574    /**
1575     * Multiplies a vector of 3 components by a 3x3 matrix and stores the
1576     * result in the input vector.
1577     *
1578     * @param lhs 3x3 matrix, as a non-null array of 9 floats
1579     * @param rhs Vector of 3 components, as a non-null array of 3 floats
1580     * @return The array of 3 passed as the rhs parameter
1581     */
1582    @NonNull
1583    @Size(min = 3)
1584    private static float[] mul3x3Float3(
1585            @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
1586        float r0 = rhs[0];
1587        float r1 = rhs[1];
1588        float r2 = rhs[2];
1589        rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
1590        rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
1591        rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
1592        return rhs;
1593    }
1594
1595    /**
1596     * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
1597     * by a 3x3 matrix represented as an array of 9 floats.
1598     *
1599     * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
1600     * @param rhs 3x3 matrix, as a non-null array of 9 floats
1601     * @return A new array of 9 floats containing the result of the multiplication
1602     *         of rhs by lhs
1603     */
1604    @NonNull
1605    @Size(9)
1606    private static float[] mul3x3Diag(
1607            @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
1608        return new float[] {
1609                lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
1610                lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
1611                lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
1612        };
1613    }
1614
1615    /**
1616     * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
1617     * input xyY array only contains the x and y components.
1618     *
1619     * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
1620     * @return A new float array of length 3 containing XYZ values
1621     */
1622    @NonNull
1623    @Size(3)
1624    private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
1625        return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
1626    }
1627
1628    /**
1629     * <p>Computes the chromatic adaptation transform from the specified
1630     * source white point to the specified destination white point.</p>
1631     *
1632     * <p>The transform is computed using the von Kris method, described
1633     * in more details in the documentation of {@link Adaptation}. The
1634     * {@link Adaptation} enum provides different matrices that can be
1635     * used to perform the adaptation.</p>
1636     *
1637     * @param matrix The adaptation matrix
1638     * @param srcWhitePoint The white point to adapt from, *will be modified*
1639     * @param dstWhitePoint The white point to adapt to, *will be modified*
1640     * @return A 3x3 matrix as a non-null array of 9 floats
1641     */
1642    @NonNull
1643    @Size(9)
1644    private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
1645            @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
1646        float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
1647        float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
1648        // LMS is a diagonal matrix stored as a float[3]
1649        float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
1650        return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
1651    }
1652
1653    /**
1654     * Implementation of the CIE XYZ color space. Assumes the white point is D50.
1655     */
1656    private static final class Xyz extends ColorSpace {
1657        private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1658            super(name, Model.XYZ, id);
1659        }
1660
1661        @Override
1662        public boolean isWideGamut() {
1663            return true;
1664        }
1665
1666        @Override
1667        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1668            return -2.0f;
1669        }
1670
1671        @Override
1672        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1673            return 2.0f;
1674        }
1675
1676        @Override
1677        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1678            v[0] = clamp(v[0]);
1679            v[1] = clamp(v[1]);
1680            v[2] = clamp(v[2]);
1681            return v;
1682        }
1683
1684        @Override
1685        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
1686            v[0] = clamp(v[0]);
1687            v[1] = clamp(v[1]);
1688            v[2] = clamp(v[2]);
1689            return v;
1690        }
1691
1692        private static float clamp(float x) {
1693            return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
1694        }
1695    }
1696
1697    /**
1698     * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
1699     * with a white point of D50.
1700     */
1701    private static final class Lab extends ColorSpace {
1702        private static final float A = 216.0f / 24389.0f;
1703        private static final float B = 841.0f / 108.0f;
1704        private static final float C = 4.0f / 29.0f;
1705        private static final float D = 6.0f / 29.0f;
1706
1707        private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1708            super(name, Model.LAB, id);
1709        }
1710
1711        @Override
1712        public boolean isWideGamut() {
1713            return true;
1714        }
1715
1716        @Override
1717        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1718            return component == 0 ? 0.0f : -128.0f;
1719        }
1720
1721        @Override
1722        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1723            return component == 0 ? 100.0f : 128.0f;
1724        }
1725
1726        @Override
1727        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1728            v[0] = clamp(v[0], 0.0f, 100.0f);
1729            v[1] = clamp(v[1], -128.0f, 128.0f);
1730            v[2] = clamp(v[2], -128.0f, 128.0f);
1731
1732            float fy = (v[0] + 16.0f) / 116.0f;
1733            float fx = fy + (v[1] * 0.002f);
1734            float fz = fy - (v[2] * 0.005f);
1735            float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
1736            float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
1737            float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
1738
1739            v[0] = X * ILLUMINANT_D50_XYZ[0];
1740            v[1] = Y * ILLUMINANT_D50_XYZ[1];
1741            v[2] = Z * ILLUMINANT_D50_XYZ[2];
1742
1743            return v;
1744        }
1745
1746        @Override
1747        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
1748            float X = v[0] / ILLUMINANT_D50_XYZ[0];
1749            float Y = v[1] / ILLUMINANT_D50_XYZ[1];
1750            float Z = v[2] / ILLUMINANT_D50_XYZ[2];
1751
1752            float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
1753            float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
1754            float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
1755
1756            float L = 116.0f * fy - 16.0f;
1757            float a = 500.0f * (fx - fy);
1758            float b = 200.0f * (fy - fz);
1759
1760            v[0] = clamp(L, 0.0f, 100.0f);
1761            v[1] = clamp(a, -128.0f, 128.0f);
1762            v[2] = clamp(b, -128.0f, 128.0f);
1763
1764            return v;
1765        }
1766
1767        private static float clamp(float x, float min, float max) {
1768            return x < min ? min : x > max ? max : x;
1769        }
1770    }
1771
1772    /**
1773     * {@usesMathJax}
1774     *
1775     * <p>An RGB color space is an additive color space using the
1776     * {@link Model#RGB RGB} color model (a color is therefore represented
1777     * by a tuple of 3 numbers).</p>
1778     *
1779     * <p>A specific RGB color space is defined by the following properties:</p>
1780     * <ul>
1781     *     <li>Three chromaticities of the red, green and blue primaries, which
1782     *     define the gamut of the color space.</li>
1783     *     <li>A white point chromaticity that defines the stimulus to which
1784     *     color space values are normalized (also just called "white").</li>
1785     *     <li>An opto-electronic transfer function, also called opto-electronic
1786     *     conversion function or often, and approximately, gamma function.</li>
1787     *     <li>An electro-optical transfer function, also called electo-optical
1788     *     conversion function or often, and approximately, gamma function.</li>
1789     *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
1790     * </ul>
1791     *
1792     * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
1793     *
1794     * <h3>Primaries and white point chromaticities</h3>
1795     * <p>In this implementation, the chromaticity of the primaries and the white
1796     * point of an RGB color space is defined in the CIE xyY color space. This
1797     * color space separates the chromaticity of a color, the x and y components,
1798     * and its luminance, the Y component. Since the primaries and the white
1799     * point have full brightness, the Y component is assumed to be 1 and only
1800     * the x and y components are needed to encode them.</p>
1801     * <p>For convenience, this implementation also allows to define the
1802     * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
1803     * are internally converted to xyY.</p>
1804     *
1805     * <p>
1806     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
1807     *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
1808     * </p>
1809     *
1810     * <h3>Transfer functions</h3>
1811     * <p>A transfer function is a color component conversion function, defined as
1812     * a single variable, monotonic mathematical function. It is applied to each
1813     * individual component of a color. They are used to perform the mapping
1814     * between linear tristimulus values and non-linear electronic signal value.</p>
1815     * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
1816     * tristimulus values in a scene to a non-linear electronic signal value.
1817     * An OETF is often expressed as a power function with an exponent between
1818     * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
1819     * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
1820     * a non-linear electronic signal value to a tristimulus value at the display.
1821     * An EOTF is often expressed as a power function with an exponent between
1822     * 1.8 and 2.6.</p>
1823     * <p>Transfer functions are used as a compression scheme. For instance,
1824     * linear sRGB values would normally require 11 to 12 bits of precision to
1825     * store all values that can be perceived by the human eye. When encoding
1826     * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
1827     * an exact mathematical description of that OETF), the values can be
1828     * compressed to only 8 bits precision.</p>
1829     * <p>When manipulating RGB values, particularly sRGB values, it is safe
1830     * to assume that these values have been encoded with the appropriate
1831     * OETF (unless noted otherwise). Encoded values are often said to be in
1832     * "gamma space". They are therefore defined in a non-linear space. This
1833     * in turns means that any linear operation applied to these values is
1834     * going to yield mathematically incorrect results (any linear interpolation
1835     * such as gradient generation for instance, most image processing functions
1836     * such as blurs, etc.).</p>
1837     * <p>To properly process encoded RGB values you must first apply the
1838     * EOTF to decode the value into linear space. After processing, the RGB
1839     * value must be encoded back to non-linear ("gamma") space. Here is a
1840     * formal description of the process, where \(f\) is the processing
1841     * function to apply:</p>
1842     *
1843     * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
1844     *
1845     * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
1846     * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
1847     * their transfer functions are the identity function: \(f(x) = x\).
1848     * If the source and/or destination are known to be linear, it is not
1849     * necessary to invoke the transfer functions.</p>
1850     *
1851     * <h3>Range</h3>
1852     * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
1853     * are however a few RGB color spaces that allow much larger ranges. For
1854     * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
1855     * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
1856     * the range \([-65504, 65504]\).</p>
1857     *
1858     * <p>
1859     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
1860     *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
1861     * </p>
1862     *
1863     * <h3>Converting between RGB color spaces</h3>
1864     * <p>Conversion between two color spaces is achieved by using an intermediate
1865     * color space called the profile connection space (PCS). The PCS used by
1866     * this implementation is CIE XYZ. The conversion operation is defined
1867     * as such:</p>
1868     *
1869     * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
1870     *
1871     * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
1872     * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
1873     * XYZ to RGB transform} of the destination color space.</p>
1874     * <p>Many RGB color spaces commonly used with electronic devices use the
1875     * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
1876     * when converting between two RGB color spaces if their white points do not
1877     * match. This can be achieved by either calling
1878     * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
1879     * a single common white point. This can be achieved automatically by calling
1880     * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
1881     * non-RGB color spaces.</p>
1882     * <p>To learn more about the white point adaptation process, refer to the
1883     * documentation of {@link Adaptation}.</p>
1884     */
1885    public static class Rgb extends ColorSpace {
1886        @NonNull private final float[] mWhitePoint;
1887        @NonNull private final float[] mPrimaries;
1888        @NonNull private final float[] mTransform;
1889        @NonNull private final float[] mInverseTransform;
1890
1891        @NonNull private final boolean mIsWideGamut;
1892        @NonNull private final boolean mIsSrgb;
1893
1894        @NonNull private final DoubleUnaryOperator mOetf;
1895        @NonNull private final DoubleUnaryOperator mEotf;
1896        @NonNull private final DoubleUnaryOperator mClampedOetf;
1897        @NonNull private final DoubleUnaryOperator mClampedEotf;
1898
1899        private final float mMin;
1900        private final float mMax;
1901
1902        /**
1903         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
1904         * The transform matrix must convert from the RGB space to the profile connection
1905         * space CIE XYZ.</p>
1906         *
1907         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
1908         *
1909         * @param name Name of the color space, cannot be null, its length must be >= 1
1910         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
1911         *              connection space CIE XYZ as an array of 9 floats, cannot be null
1912         * @param oetf Opto-electronic transfer function, cannot be null
1913         * @param eotf Electro-optical transfer function, cannot be null
1914         *
1915         * @throws IllegalArgumentException If any of the following conditions is met:
1916         * <ul>
1917         *     <li>The name is null or has a length of 0.</li>
1918         *     <li>The OETF is null or the EOTF is null.</li>
1919         *     <li>The minimum valid value is >= the maximum valid value.</li>
1920         * </ul>
1921         *
1922         * @see #get(Named)
1923         */
1924        public Rgb(
1925                @NonNull @Size(min = 1) String name,
1926                @NonNull @Size(9) float[] toXYZ,
1927                @NonNull DoubleUnaryOperator oetf,
1928                @NonNull DoubleUnaryOperator eotf) {
1929            this(name, computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf),
1930                    oetf, eotf, 0.0f, 1.0f, MIN_ID);
1931        }
1932
1933        /**
1934         * <p>Creates a new RGB color space using a specified set of primaries
1935         * and a specified white point.</p>
1936         *
1937         * <p>The primaries and white point can be specified in the CIE xyY space
1938         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
1939         *
1940         * <table summary="Parameters length">
1941         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
1942         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
1943         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
1944         * </table>
1945         *
1946         * <p>When the primaries and/or white point are specified in xyY, the Y component
1947         * does not need to be specified and is assumed to be 1.0. Only the xy components
1948         * are required.</p>
1949         *
1950         * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
1951         * this constructor is always {@link #MIN_ID}.</p>
1952         *
1953         * @param name Name of the color space, cannot be null, its length must be >= 1
1954         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
1955         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
1956         * @param oetf Opto-electronic transfer function, cannot be null
1957         * @param eotf Electro-optical transfer function, cannot be null
1958         * @param min The minimum valid value in this color space's RGB range
1959         * @param max The maximum valid value in this color space's RGB range
1960         *
1961         * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
1962         * <ul>
1963         *     <li>The name is null or has a length of 0.</li>
1964         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
1965         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
1966         *     <li>The OETF is null or the EOTF is null.</li>
1967         *     <li>The minimum valid value is >= the maximum valid value.</li>
1968         * </ul>
1969         *
1970         * @see #get(Named)
1971         */
1972        public Rgb(
1973                @NonNull @Size(min = 1) String name,
1974                @NonNull @Size(min = 6, max = 9) float[] primaries,
1975                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
1976                @NonNull DoubleUnaryOperator oetf,
1977                @NonNull DoubleUnaryOperator eotf,
1978                float min,
1979                float max) {
1980            this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
1981        }
1982
1983        /**
1984         * <p>Creates a new RGB color space using a specified set of primaries
1985         * and a specified white point.</p>
1986         *
1987         * <p>The primaries and white point can be specified in the CIE xyY space
1988         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
1989         *
1990         * <table summary="Parameters length">
1991         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
1992         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
1993         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
1994         * </table>
1995         *
1996         * <p>When the primaries and/or white point are specified in xyY, the Y component
1997         * does not need to be specified and is assumed to be 1.0. Only the xy components
1998         * are required.</p>
1999         *
2000         * @param name Name of the color space, cannot be null, its length must be >= 1
2001         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2002         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2003         * @param oetf Opto-electronic transfer function, cannot be null
2004         * @param eotf Electro-optical transfer function, cannot be null
2005         * @param min The minimum valid value in this color space's RGB range
2006         * @param max The maximum valid value in this color space's RGB range
2007         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
2008         *
2009         * @throws IllegalArgumentException If any of the following conditions is met:
2010         * <ul>
2011         *     <li>The name is null or has a length of 0.</li>
2012         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2013         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2014         *     <li>The OETF is null or the EOTF is null.</li>
2015         *     <li>The minimum valid value is >= the maximum valid value.</li>
2016         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
2017         * </ul>
2018         *
2019         * @see #get(Named)
2020         */
2021        private Rgb(
2022                @NonNull @Size(min = 1) String name,
2023                @NonNull @Size(min = 6, max = 9) float[] primaries,
2024                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2025                @NonNull DoubleUnaryOperator oetf,
2026                @NonNull DoubleUnaryOperator eotf,
2027                float min,
2028                float max,
2029                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2030
2031            super(name, Model.RGB, id);
2032
2033            if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
2034                throw new IllegalArgumentException("The color space's primaries must be " +
2035                        "defined as an array of 6 floats in xyY or 9 floats in XYZ");
2036            }
2037
2038            if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
2039                throw new IllegalArgumentException("The color space's white point must be " +
2040                        "defined as an array of 2 floats in xyY or 3 float in XYZ");
2041            }
2042
2043            if (oetf == null || eotf == null) {
2044                throw new IllegalArgumentException("The transfer functions of a color space " +
2045                        "cannot be null");
2046            }
2047
2048            if (min >= max) {
2049                throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
2050                        "; min must be strictly < max");
2051            }
2052
2053            mWhitePoint = xyWhitePoint(whitePoint);
2054            mPrimaries =  xyPrimaries(primaries);
2055
2056            mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
2057            mInverseTransform = inverse3x3(mTransform);
2058
2059            mOetf = oetf;
2060            mEotf = eotf;
2061
2062            mMin = min;
2063            mMax = max;
2064
2065            DoubleUnaryOperator clamp = this::clamp;
2066            mClampedOetf = oetf.andThen(clamp);
2067            mClampedEotf = clamp.andThen(eotf);
2068
2069            // A color space is wide-gamut if its area is >90% of NTSC 1953 and
2070            // if it entirely contains the Color space definition in xyY
2071            mIsWideGamut = isWideGamut(mPrimaries, min, max);
2072            mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
2073        }
2074
2075        /**
2076         * Creates a copy of the specified color space with a new transform.
2077         *
2078         * @param colorSpace The color space to create a copy of
2079         */
2080        private Rgb(Rgb colorSpace,
2081                @NonNull @Size(9) float[] transform,
2082                @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
2083            super(colorSpace.getName(), Model.RGB, -1);
2084
2085            mWhitePoint = xyWhitePoint(whitePoint);
2086            mPrimaries = colorSpace.mPrimaries;
2087
2088            mTransform = transform;
2089            mInverseTransform = inverse3x3(transform);
2090
2091            mMin = colorSpace.mMin;
2092            mMax = colorSpace.mMax;
2093
2094            mOetf = colorSpace.mOetf;
2095            mEotf = colorSpace.mEotf;
2096
2097            mClampedOetf = colorSpace.mClampedOetf;
2098            mClampedEotf = colorSpace.mClampedEotf;
2099
2100            mIsWideGamut = colorSpace.mIsWideGamut;
2101            mIsSrgb = colorSpace.mIsSrgb;
2102        }
2103
2104        /**
2105         * Copies the non-adapted CIE xyY white point of this color space in
2106         * specified array. The Y component is assumed to be 1 and is therefore
2107         * not copied into the destination. The x and y components are written
2108         * in the array at positions 0 and 1 respectively.
2109         *
2110         * @param whitePoint The destination array, cannot be null, its length
2111         *                   must be >= 2
2112         *
2113         * @return The destination array passed as a parameter
2114         *
2115         * @see #getWhitePoint(float[])
2116         */
2117        @NonNull
2118        @Size(min = 2)
2119        public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
2120            whitePoint[0] = mWhitePoint[0];
2121            whitePoint[1] = mWhitePoint[1];
2122            return whitePoint;
2123        }
2124
2125        /**
2126         * Returns the non-adapted CIE xyY white point of this color space as
2127         * a new array of 2 floats. The Y component is assumed to be 1 and is
2128         * therefore not copied into the destination. The x and y components
2129         * are written in the array at positions 0 and 1 respectively.
2130         *
2131         * @return A new non-null array of 2 floats
2132         *
2133         * @see #getWhitePoint()
2134         */
2135        @NonNull
2136        @Size(2)
2137        public float[] getWhitePoint() {
2138            return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
2139        }
2140
2141        /**
2142         * Copies the primaries of this color space in specified array. The Y
2143         * component is assumed to be 1 and is therefore not copied into the
2144         * destination. The x and y components of the first primary are written
2145         * in the array at positions 0 and 1 respectively.
2146         *
2147         * @param primaries The destination array, cannot be null, its length
2148         *                  must be >= 6
2149         *
2150         * @return The destination array passed as a parameter
2151         *
2152         * @see #getPrimaries(float[])
2153         */
2154        @NonNull
2155        @Size(min = 6)
2156        public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
2157            System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
2158            return primaries;
2159        }
2160
2161        /**
2162         * Returns the primaries of this color space as a new array of 6 floats.
2163         * The Y component is assumed to be 1 and is therefore not copied into
2164         * the destination. The x and y components of the first primary are
2165         * written in the array at positions 0 and 1 respectively.
2166         *
2167         * @return A new non-null array of 2 floats
2168         *
2169         * @see #getWhitePoint()
2170         */
2171        @NonNull
2172        @Size(6)
2173        public float[] getPrimaries() {
2174            return Arrays.copyOf(mPrimaries, mPrimaries.length);
2175        }
2176
2177        /**
2178         * <p>Copies the transform of this color space in specified array. The
2179         * transform is used to convert from RGB to XYZ (with the same white
2180         * point as this color space). To connect color spaces, you must first
2181         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2182         * same white point.</p>
2183         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2184         * to convert between color spaces.</p>
2185         *
2186         * @param transform The destination array, cannot be null, its length
2187         *                  must be >= 9
2188         *
2189         * @return The destination array passed as a parameter
2190         *
2191         * @see #getInverseTransform()
2192         */
2193        @NonNull
2194        @Size(min = 9)
2195        public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
2196            System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
2197            return transform;
2198        }
2199
2200        /**
2201         * <p>Returns the transform of this color space as a new array. The
2202         * transform is used to convert from RGB to XYZ (with the same white
2203         * point as this color space). To connect color spaces, you must first
2204         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2205         * same white point.</p>
2206         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2207         * to convert between color spaces.</p>
2208         *
2209         * @return A new array of 9 floats
2210         *
2211         * @see #getInverseTransform(float[])
2212         */
2213        @NonNull
2214        @Size(9)
2215        public float[] getTransform() {
2216            return Arrays.copyOf(mTransform, mTransform.length);
2217        }
2218
2219        /**
2220         * <p>Copies the inverse transform of this color space in specified array.
2221         * The inverse transform is used to convert from XYZ to RGB (with the
2222         * same white point as this color space). To connect color spaces, you
2223         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2224         * to the same white point.</p>
2225         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2226         * to convert between color spaces.</p>
2227         *
2228         * @param inverseTransform The destination array, cannot be null, its length
2229         *                  must be >= 9
2230         *
2231         * @return The destination array passed as a parameter
2232         *
2233         * @see #getTransform()
2234         */
2235        @NonNull
2236        @Size(min = 9)
2237        public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
2238            System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
2239            return inverseTransform;
2240        }
2241
2242        /**
2243         * <p>Returns the inverse transform of this color space as a new array.
2244         * The inverse transform is used to convert from XYZ to RGB (with the
2245         * same white point as this color space). To connect color spaces, you
2246         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2247         * to the same white point.</p>
2248         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2249         * to convert between color spaces.</p>
2250         *
2251         * @return A new array of 9 floats
2252         *
2253         * @see #getTransform(float[])
2254         */
2255        @NonNull
2256        @Size(9)
2257        public float[] getInverseTransform() {
2258            return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
2259        }
2260
2261        /**
2262         * <p>Returns the opto-electronic transfer function (OETF) of this color space.
2263         * The inverse function is the electro-optical transfer function (EOTF) returned
2264         * by {@link #getEotf()}. These functions are defined to satisfy the following
2265         * equality for \(x \in [0..1]\):</p>
2266         *
2267         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
2268         *
2269         * <p>For RGB colors, this function can be used to convert from linear space
2270         * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
2271         * are frequently used because many OETFs can be closely approximated using
2272         * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
2273         * approximation of the {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\)
2274         * for instance).</p>
2275         *
2276         * @return A transfer function that converts from linear space to "gamma space"
2277         *
2278         * @see #getEotf()
2279         */
2280        @NonNull
2281        public DoubleUnaryOperator getOetf() {
2282            return mOetf;
2283        }
2284
2285        /**
2286         * <p>Returns the electro-optical transfer function (EOTF) of this color space.
2287         * The inverse function is the opto-electronic transfer function (OETF)
2288         * returned by {@link #getOetf()}. These functions are defined to satisfy the
2289         * following equality for \(x \in [0..1]\):</p>
2290         *
2291         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
2292         *
2293         * <p>For RGB colors, this function can be used to convert from "gamma space"
2294         * (gamma encoded) to linear space. The terms gamma space and gamma encoded
2295         * are frequently used because many EOTFs can be closely approximated using
2296         * a simple power function of the form \(x^\gamma\) (the approximation of the
2297         * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
2298         *
2299         * @return A transfer function that converts from "gamma space" to linear space
2300         *
2301         * @see #getOetf()
2302         */
2303        @NonNull
2304        public DoubleUnaryOperator getEotf() {
2305            return mEotf;
2306        }
2307
2308        @Override
2309        public boolean isSrgb() {
2310            return mIsSrgb;
2311        }
2312
2313        @Override
2314        public boolean isWideGamut() {
2315            return mIsWideGamut;
2316        }
2317
2318        @Override
2319        public float getMinValue(int component) {
2320            return mMin;
2321        }
2322
2323        @Override
2324        public float getMaxValue(int component) {
2325            return mMax;
2326        }
2327
2328        /**
2329         * <p>Decodes an RGB value to linear space. This is achieved by
2330         * applying this color space's electro-optical transfer function
2331         * to the supplied values.</p>
2332         *
2333         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2334         * more information about transfer functions and their use for
2335         * encoding and decoding RGB values.</p>
2336         *
2337         * @param r The red component to decode to linear space
2338         * @param g The green component to decode to linear space
2339         * @param b The blue component to decode to linear space
2340         * @return A new array of 3 floats containing linear RGB values
2341         *
2342         * @see #toLinear(float[])
2343         * @see #fromLinear(float, float, float)
2344         */
2345        @NonNull
2346        @Size(3)
2347        public float[] toLinear(float r, float g, float b) {
2348            return toLinear(new float[] { r, g, b });
2349        }
2350
2351        /**
2352         * <p>Decodes an RGB value to linear space. This is achieved by
2353         * applying this color space's electro-optical transfer function
2354         * to the first 3 values of the supplied array. The result is
2355         * stored back in the input array.</p>
2356         *
2357         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2358         * more information about transfer functions and their use for
2359         * encoding and decoding RGB values.</p>
2360         *
2361         * @param v A non-null array of non-linear RGB values, its length
2362         *          must be at least 3
2363         * @return The specified array
2364         *
2365         * @see #toLinear(float, float, float)
2366         * @see #fromLinear(float[])
2367         */
2368        @NonNull
2369        @Size(min = 3)
2370        public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
2371            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
2372            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
2373            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
2374            return v;
2375        }
2376
2377        /**
2378         * <p>Encodes an RGB value from linear space to this color space's
2379         * "gamma space". This is achieved by applying this color space's
2380         * opto-electronic transfer function to the supplied values.</p>
2381         *
2382         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2383         * more information about transfer functions and their use for
2384         * encoding and decoding RGB values.</p>
2385         *
2386         * @param r The red component to encode from linear space
2387         * @param g The green component to encode from linear space
2388         * @param b The blue component to encode from linear space
2389         * @return A new array of 3 floats containing non-linear RGB values
2390         *
2391         * @see #fromLinear(float[])
2392         * @see #toLinear(float, float, float)
2393         */
2394        @NonNull
2395        @Size(3)
2396        public float[] fromLinear(float r, float g, float b) {
2397            return fromLinear(new float[] { r, g, b });
2398        }
2399
2400        /**
2401         * <p>Encodes an RGB value from linear space to this color space's
2402         * "gamma space". This is achieved by applying this color space's
2403         * opto-electronic transfer function to the first 3 values of the
2404         * supplied array. The result is stored back in the input array.</p>
2405         *
2406         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
2407         * more information about transfer functions and their use for
2408         * encoding and decoding RGB values.</p>
2409         *
2410         * @param v A non-null array of linear RGB values, its length
2411         *          must be at least 3
2412         * @return A new array of 3 floats containing non-linear RGB values
2413         *
2414         * @see #fromLinear(float[])
2415         * @see #toLinear(float, float, float)
2416         */
2417        @NonNull
2418        @Size(min = 3)
2419        public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
2420            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
2421            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
2422            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
2423            return v;
2424        }
2425
2426        @Override
2427        @NonNull
2428        @Size(min = 3)
2429        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
2430            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
2431            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
2432            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
2433            return mul3x3Float3(mTransform, v);
2434        }
2435
2436        @Override
2437        @NonNull
2438        @Size(min = 3)
2439        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
2440            mul3x3Float3(mInverseTransform, v);
2441            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
2442            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
2443            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
2444            return v;
2445        }
2446
2447        private double clamp(double x) {
2448            return x < mMin ? mMin : x > mMax ? mMax : x;
2449        }
2450
2451        @Override
2452        public boolean equals(Object o) {
2453            if (this == o) return true;
2454            if (o == null || getClass() != o.getClass()) return false;
2455            if (!super.equals(o)) return false;
2456
2457            Rgb rgb = (Rgb) o;
2458
2459            if (Float.compare(rgb.mMin, mMin) != 0) return false;
2460            if (Float.compare(rgb.mMax, mMax) != 0) return false;
2461            if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
2462            if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
2463            //noinspection SimplifiableIfStatement
2464            if (!mOetf.equals(rgb.mOetf)) return false;
2465            return mEotf.equals(rgb.mEotf);
2466        }
2467
2468        @Override
2469        public int hashCode() {
2470            int result = super.hashCode();
2471            result = 31 * result + Arrays.hashCode(mWhitePoint);
2472            result = 31 * result + Arrays.hashCode(mPrimaries);
2473            result = 31 * result + mOetf.hashCode();
2474            result = 31 * result + mEotf.hashCode();
2475            result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
2476            result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
2477            return result;
2478        }
2479
2480        /**
2481         * Computes whether a color space is the sRGB color space or at least
2482         * a close approximation.
2483         *
2484         * @param primaries The set of RGB primaries in xyY as an array of 6 floats
2485         * @param whitePoint The white point in xyY as an array of 2 floats
2486         * @param OETF The opto-electronic transfer function
2487         * @param EOTF The electro-optical transfer function
2488         * @param min The minimum value of the color space's range
2489         * @param max The minimum value of the color space's range
2490         * @param id The ID of the color space
2491         * @return True if the color space can be considered as the sRGB color space
2492         *
2493         * @see #isSrgb()
2494         */
2495        @SuppressWarnings("RedundantIfStatement")
2496        private static boolean isSrgb(
2497                @NonNull @Size(6) float[] primaries,
2498                @NonNull @Size(2) float[] whitePoint,
2499                @NonNull DoubleUnaryOperator OETF,
2500                @NonNull DoubleUnaryOperator EOTF,
2501                float min,
2502                float max,
2503                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2504            if (id == 0) return true;
2505            if (!compare(primaries, SRGB_PRIMARIES)) {
2506                return false;
2507            }
2508            if (!compare(whitePoint, ILLUMINANT_D65)) {
2509                return false;
2510            }
2511            if (OETF.applyAsDouble(0.5) < 0.5001) return false;
2512            if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
2513            if (min != 0.0f) return false;
2514            if (max != 1.0f) return false;
2515            return true;
2516        }
2517
2518        /**
2519         * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
2520         * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
2521         * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
2522         * If the conditions above are not met, the color space is considered as having
2523         * a wide color gamut if its range is larger than [0..1].
2524         *
2525         * @param primaries RGB primaries in CIE xyY as an array of 6 floats
2526         * @param min The minimum value of the color space's range
2527         * @param max The minimum value of the color space's range
2528         * @return True if the color space has a wide gamut, false otherwise
2529         *
2530         * @see #isWideGamut()
2531         * @see #area(float[])
2532         */
2533        private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
2534                float min, float max) {
2535            return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
2536                            contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
2537        }
2538
2539        /**
2540         * Computes the area of the triangle represented by a set of RGB primaries
2541         * in the CIE xyY space.
2542         *
2543         * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
2544         * @return The area of the triangle
2545         *
2546         * @see #isWideGamut(float[], float, float)
2547         */
2548        private static float area(@NonNull @Size(6) float[] primaries) {
2549            float Rx = primaries[0];
2550            float Ry = primaries[1];
2551            float Gx = primaries[2];
2552            float Gy = primaries[3];
2553            float Bx = primaries[4];
2554            float By = primaries[5];
2555            float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
2556            float r = 0.5f * det;
2557            return r < 0.0f ? -r : r;
2558        }
2559
2560        /**
2561         * Computes the cross product of two 2D vectors.
2562         *
2563         * @param ax The x coordinate of the first vector
2564         * @param ay The y coordinate of the first vector
2565         * @param bx The x coordinate of the second vector
2566         * @param by The y coordinate of the second vector
2567         * @return The result of a x b
2568         */
2569        private static float cross(float ax, float ay, float bx, float by) {
2570            return ax * by - ay * bx;
2571        }
2572
2573        /**
2574         * Decides whether a 2D triangle, identified by the 6 coordinates of its
2575         * 3 vertices, is contained within another 2D triangle, also identified
2576         * by the 6 coordinates of its 3 vertices.
2577         *
2578         * In the illustration below, we want to test whether the RGB triangle
2579         * is contained within the triangle XYZ formed by the 3 vertices at
2580         * the "+" locations.
2581         *
2582         *                                     Y     .
2583         *                                 .   +    .
2584         *                                  .     ..
2585         *                                   .   .
2586         *                                    . .
2587         *                                     .  G
2588         *                                     *
2589         *                                    * *
2590         *                                  **   *
2591         *                                 *      **
2592         *                                *         *
2593         *                              **           *
2594         *                             *              *
2595         *                            *                *
2596         *                          **                  *
2597         *                         *                     *
2598         *                        *                       **
2599         *                      **                          *   R    ...
2600         *                     *                             *  .....
2601         *                    *                         ***** ..
2602         *                  **              ************       .   +
2603         *              B  *    ************                    .   X
2604         *           ......*****                                 .
2605         *     ......    .                                        .
2606         *             ..
2607         *        +   .
2608         *      Z    .
2609         *
2610         * RGB is contained within XYZ if all the following conditions are true
2611         * (with "x" the cross product operator):
2612         *
2613         *   -->  -->
2614         *   GR x RX >= 0
2615         *   -->  -->
2616         *   RX x BR >= 0
2617         *   -->  -->
2618         *   RG x GY >= 0
2619         *   -->  -->
2620         *   GY x RG >= 0
2621         *   -->  -->
2622         *   RB x BZ >= 0
2623         *   -->  -->
2624         *   BZ x GB >= 0
2625         *
2626         * @param p1 The enclosing triangle
2627         * @param p2 The enclosed triangle
2628         * @return True if the triangle p1 contains the triangle p2
2629         *
2630         * @see #isWideGamut(float[], float, float)
2631         */
2632        @SuppressWarnings("RedundantIfStatement")
2633        private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
2634            // Translate the vertices p1 in the coordinates system
2635            // with the vertices p2 as the origin
2636            float[] p0 = new float[] {
2637                    p1[0] - p2[0], p1[1] - p2[1],
2638                    p1[2] - p2[2], p1[3] - p2[3],
2639                    p1[4] - p2[4], p1[5] - p2[5],
2640            };
2641            // Check the first vertex of p1
2642            if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
2643                    cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
2644                return false;
2645            }
2646            // Check the second vertex of p1
2647            if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
2648                    cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
2649                return false;
2650            }
2651            // Check the third vertex of p1
2652            if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
2653                    cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
2654                return false;
2655            }
2656            return true;
2657        }
2658
2659        /**
2660         * Computes the primaries  of a color space identified only by
2661         * its RGB->XYZ transform matrix. This method assumes that the
2662         * range of the color space is [0..1].
2663         *
2664         * @param toXYZ The color space's 3x3 transform matrix to XYZ
2665         * @param EOTF The color space's electro-optical transfer function
2666         * @return A new array of 6 floats containing the color space's
2667         *         primaries in CIE xyY
2668         */
2669        @NonNull
2670        @Size(6)
2671        private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ,
2672                DoubleUnaryOperator EOTF) {
2673            float one = (float) EOTF.applyAsDouble(1.0);
2674            float[] r = mul3x3Float3(toXYZ, new float[] { one, 0.0f, 0.0f });
2675            float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, one, 0.0f });
2676            float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, one });
2677
2678            float rSum = r[0] + r[1] + r[2];
2679            float gSum = g[0] + g[1] + g[2];
2680            float bSum = b[0] + b[1] + b[2];
2681
2682            return new float[] {
2683                    r[0] / rSum, r[1] / rSum,
2684                    g[0] / gSum, g[1] / gSum,
2685                    b[0] / bSum, b[1] / bSum,
2686            };
2687        }
2688
2689        /**
2690         * Computes the white point of a color space identified only by
2691         * its RGB->XYZ transform matrix. This method assumes that the
2692         * range of the color space is [0..1].
2693         *
2694         * @param toXYZ The color space's 3x3 transform matrix to XYZ
2695         * @param EOTF The color space's electro-optical transfer function
2696         * @return A new array of 2 floats containing the color space's
2697         *         white point in CIE xyY
2698         */
2699        @NonNull
2700        @Size(2)
2701        private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ,
2702                @NonNull DoubleUnaryOperator EOTF) {
2703            float one = (float) EOTF.applyAsDouble(1.0);
2704            float[] w = mul3x3Float3(toXYZ, new float[] { one, one, one });
2705            float sum = w[0] + w[1] + w[2];
2706            return new float[] { w[0] / sum, w[1] / sum };
2707        }
2708
2709        /**
2710         * Converts the specified RGB primaries point to xyY if needed. The primaries
2711         * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
2712         * (in CIE XYZ). If no conversion is needed, the input array is copied.
2713         *
2714         * @param primaries The primaries in xyY or XYZ
2715         * @return A new array of 6 floats containing the primaries in xyY
2716         */
2717        @NonNull
2718        @Size(6)
2719        private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
2720            float[] xyPrimaries = new float[6];
2721
2722            // XYZ to xyY
2723            if (primaries.length == 9) {
2724                float sum;
2725
2726                sum = primaries[0] + primaries[1] + primaries[2];
2727                xyPrimaries[0] = primaries[0] / sum;
2728                xyPrimaries[1] = primaries[1] / sum;
2729
2730                sum = primaries[3] + primaries[4] + primaries[5];
2731                xyPrimaries[2] = primaries[3] / sum;
2732                xyPrimaries[3] = primaries[4] / sum;
2733
2734                sum = primaries[6] + primaries[7] + primaries[8];
2735                xyPrimaries[4] = primaries[6] / sum;
2736                xyPrimaries[5] = primaries[7] / sum;
2737            } else {
2738                System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
2739            }
2740
2741            return xyPrimaries;
2742        }
2743
2744        /**
2745         * Converts the specified white point to xyY if needed. The white point
2746         * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
2747         * (in CIE XYZ). If no conversion is needed, the input array is copied.
2748         *
2749         * @param whitePoint The white point in xyY or XYZ
2750         * @return A new array of 2 floats containing the white point in xyY
2751         */
2752        @NonNull
2753        @Size(2)
2754        private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
2755            float[] xyWhitePoint = new float[2];
2756
2757            // XYZ to xyY
2758            if (whitePoint.length == 3) {
2759                float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
2760                xyWhitePoint[0] = whitePoint[0] / sum;
2761                xyWhitePoint[1] = whitePoint[1] / sum;
2762            } else {
2763                System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
2764            }
2765
2766            return xyWhitePoint;
2767        }
2768
2769        /**
2770         * Computes the matrix that converts from RGB to XYZ based on RGB
2771         * primaries and a white point, both specified in the CIE xyY space.
2772         * The Y component of the primaries and white point is implied to be 1.
2773         *
2774         * @param primaries The RGB primaries in xyY, as an array of 6 floats
2775         * @param whitePoint The white point in xyY, as an array of 2 floats
2776         * @return A 3x3 matrix as a new array of 9 floats
2777         */
2778        @NonNull
2779        @Size(9)
2780        private static float[] computeXYZMatrix(
2781                @NonNull @Size(6) float[] primaries,
2782                @NonNull @Size(2) float[] whitePoint) {
2783            float Rx = primaries[0];
2784            float Ry = primaries[1];
2785            float Gx = primaries[2];
2786            float Gy = primaries[3];
2787            float Bx = primaries[4];
2788            float By = primaries[5];
2789            float Wx = whitePoint[0];
2790            float Wy = whitePoint[1];
2791
2792            float oneRxRy = (1 - Rx) / Ry;
2793            float oneGxGy = (1 - Gx) / Gy;
2794            float oneBxBy = (1 - Bx) / By;
2795            float oneWxWy = (1 - Wx) / Wy;
2796
2797            float RxRy = Rx / Ry;
2798            float GxGy = Gx / Gy;
2799            float BxBy = Bx / By;
2800            float WxWy = Wx / Wy;
2801
2802            float BY =
2803                    ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
2804                    ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
2805            float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
2806            float RY = 1 - GY - BY;
2807
2808            float RYRy = RY / Ry;
2809            float GYGy = GY / Gy;
2810            float BYBy = BY / By;
2811
2812            return new float[] {
2813                    RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
2814                    GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
2815                    BYBy * Bx, BY, BYBy * (1 - Bx - By)
2816            };
2817        }
2818    }
2819
2820    /**
2821     * {@usesMathJax}
2822     *
2823     * <p>A connector transforms colors from a source color space to a destination
2824     * color space.</p>
2825     *
2826     * <p>A source color space is connected to a destination color space using the
2827     * color transform \(C\) computed from their respective transforms noted
2828     * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
2829     *
2830     * $$C = T^{-1}_{dst} . T_{src}$$
2831     *
2832     * <p>The transform \(C\) shown above is only valid when the source and
2833     * destination color spaces have the same profile connection space (PCS).
2834     * We know that instances of {@link ColorSpace} always use CIE XYZ as their
2835     * PCS but their white points might differ. When they do, we must perform
2836     * a chromatic adaptation of the color spaces' transforms. To do so, we
2837     * use the von Kries method described in the documentation of {@link Adaptation},
2838     * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
2839     * as the target white point.</p>
2840     *
2841     * <p>Example of conversion from {@link Named#SRGB sRGB} to
2842     * {@link Named#DCI_P3 DCI-P3}:</p>
2843     *
2844     * <pre class="prettyprint">
2845     * ColorSpace.Connector connector = ColorSpace.connect(
2846     *         ColorSpace.get(ColorSpace.Named.SRGB),
2847     *         ColorSpace.get(ColorSpace.Named.DCI_P3));
2848     * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
2849     * // p3 contains { 0.9473, 0.2740, 0.2076 }
2850     * </pre>
2851     *
2852     * @see Adaptation
2853     * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
2854     * @see ColorSpace#adapt(ColorSpace, float[])
2855     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
2856     * @see ColorSpace#connect(ColorSpace, ColorSpace)
2857     * @see ColorSpace#connect(ColorSpace, RenderIntent)
2858     * @see ColorSpace#connect(ColorSpace)
2859     */
2860    public static class Connector {
2861        @NonNull private final ColorSpace mSource;
2862        @NonNull private final ColorSpace mDestination;
2863        @NonNull private final ColorSpace mTransformSource;
2864        @NonNull private final ColorSpace mTransformDestination;
2865        @NonNull private final RenderIntent mIntent;
2866        @NonNull @Size(3) private final float[] mTransform;
2867
2868        /**
2869         * Creates a new connector between a source and a destination color space.
2870         *
2871         * @param source The source color space, cannot be null
2872         * @param destination The destination color space, cannot be null
2873         * @param intent The render intent to use when compressing gamuts
2874         */
2875        Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
2876                @NonNull RenderIntent intent) {
2877            this(source, destination,
2878                    source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
2879                    destination.getModel() == Model.RGB ?
2880                            adapt(destination, ILLUMINANT_D50_XYZ) : destination,
2881                    intent, computeTransform(source, destination, intent));
2882        }
2883
2884        /**
2885         * To connect between color spaces, we might need to use adapted transforms.
2886         * This should be transparent to the user so this constructor takes the
2887         * original source and destinations (returned by the getters), as well as
2888         * possibly adapted color spaces used by transform().
2889         */
2890        private Connector(
2891                @NonNull ColorSpace source, @NonNull ColorSpace destination,
2892                @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
2893                @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
2894            mSource = source;
2895            mDestination = destination;
2896            mTransformSource = transformSource;
2897            mTransformDestination = transformDestination;
2898            mIntent = intent;
2899            mTransform = transform;
2900        }
2901
2902        /**
2903         * Computes an extra transform to apply in XYZ space depending on the
2904         * selected rendering intent.
2905         */
2906        private static float[] computeTransform(@NonNull ColorSpace source,
2907                @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
2908            if (intent != RenderIntent.ABSOLUTE) return null;
2909
2910            boolean srcRGB = source.getModel() == Model.RGB;
2911            boolean dstRGB = destination.getModel() == Model.RGB;
2912
2913            if (srcRGB && dstRGB) return null;
2914
2915            if (srcRGB || dstRGB) {
2916                ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
2917                float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
2918                float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
2919                return new float[] {
2920                        srcXYZ[0] / dstXYZ[0],
2921                        srcXYZ[1] / dstXYZ[1],
2922                        srcXYZ[2] / dstXYZ[2],
2923                };
2924            }
2925
2926            return null;
2927        }
2928
2929        /**
2930         * Returns the source color space this connector will convert from.
2931         *
2932         * @return A non-null instance of {@link ColorSpace}
2933         *
2934         * @see #getDestination()
2935         */
2936        @NonNull
2937        public ColorSpace getSource() {
2938            return mSource;
2939        }
2940
2941        /**
2942         * Returns the destination color space this connector will convert to.
2943         *
2944         * @return A non-null instance of {@link ColorSpace}
2945         *
2946         * @see #getSource()
2947         */
2948        @NonNull
2949        public ColorSpace getDestination() {
2950            return mDestination;
2951        }
2952
2953        /**
2954         * Returns the render intent this connector will use when mapping the
2955         * source color space to the destination color space.
2956         *
2957         * @return A non-null {@link RenderIntent}
2958         *
2959         * @see RenderIntent
2960         */
2961        public RenderIntent getIntent() {
2962            return mIntent;
2963        }
2964
2965        /**
2966         * <p>Transforms the specified color from the source color space
2967         * to a color in the destination color space. This convenience
2968         * method assumes a source color model with 3 components
2969         * (typically RGB). To transform from color models with more than
2970         * 3 components, such as {@link Model#CMYK CMYK}, use
2971         * {@link #transform(float[])} instead.</p>
2972         *
2973         * @param r The red component of the color to transform
2974         * @param g The green component of the color to transform
2975         * @param b The blue component of the color to transform
2976         * @return A new array of 3 floats containing the specified color
2977         *         transformed from the source space to the destination space
2978         *
2979         * @see #transform(float[])
2980         */
2981        @NonNull
2982        @Size(3)
2983        public float[] transform(float r, float g, float b) {
2984            return transform(new float[] { r, g, b });
2985        }
2986
2987        /**
2988         * <p>Transforms the specified color from the source color space
2989         * to a color in the destination color space.</p>
2990         *
2991         * @param v A non-null array of 3 floats containing the value to transform
2992         *            and that will hold the result of the transform
2993         * @return The v array passed as a parameter, containing the specified color
2994         *         transformed from the source space to the destination space
2995         *
2996         * @see #transform(float, float, float)
2997         */
2998        @NonNull
2999        @Size(min = 3)
3000        public float[] transform(@NonNull @Size(min = 3) float[] v) {
3001            float[] xyz = mTransformSource.toXyz(v);
3002            if (mTransform != null) {
3003                xyz[0] *= mTransform[0];
3004                xyz[1] *= mTransform[1];
3005                xyz[2] *= mTransform[2];
3006            }
3007            return mTransformDestination.fromXyz(xyz);
3008        }
3009
3010        /**
3011         * Optimized connector for RGB->RGB conversions.
3012         */
3013        private static class Rgb extends Connector {
3014            @NonNull private final ColorSpace.Rgb mSource;
3015            @NonNull private final ColorSpace.Rgb mDestination;
3016            @NonNull private final float[] mTransform;
3017
3018            Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
3019                    @NonNull RenderIntent intent) {
3020                super(source, destination, source, destination, intent, null);
3021                mSource = source;
3022                mDestination = destination;
3023                mTransform = computeTransform(source, destination, intent);
3024            }
3025
3026            @Override
3027            public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
3028                rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
3029                rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
3030                rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
3031                mul3x3Float3(mTransform, rgb);
3032                rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
3033                rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
3034                rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
3035                return rgb;
3036            }
3037
3038            /**
3039             * <p>Computes the color transform that connects two RGB color spaces.</p>
3040             *
3041             * <p>We can only connect color spaces if they use the same profile
3042             * connection space. We assume the connection space is always
3043             * CIE XYZ but we maye need to perform a chromatic adaptation to
3044             * match the white points. If an adaptation is needed, we use the
3045             * CIE standard illuminant D50. The unmatched color space is adapted
3046             * using the von Kries transform and the {@link Adaptation#BRADFORD}
3047             * matrix.</p>
3048             *
3049             * @param source The source color space, cannot be null
3050             * @param destination The destination color space, cannot be null
3051             * @param intent The render intent to use when compressing gamuts
3052             * @return An array of 9 floats containing the 3x3 matrix transform
3053             */
3054            @NonNull
3055            @Size(9)
3056            private static float[] computeTransform(
3057                    @NonNull ColorSpace.Rgb source,
3058                    @NonNull ColorSpace.Rgb destination,
3059                    @NonNull RenderIntent intent) {
3060                if (compare(source.mWhitePoint, destination.mWhitePoint)) {
3061                    // RGB->RGB using the PCS of both color spaces since they have the same
3062                    return mul3x3(destination.mInverseTransform, source.mTransform);
3063                } else {
3064                    // RGB->RGB using CIE XYZ D50 as the PCS
3065                    float[] transform = source.mTransform;
3066                    float[] inverseTransform = destination.mInverseTransform;
3067
3068                    float[] srcXYZ = xyYToXyz(source.mWhitePoint);
3069                    float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
3070
3071                    if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
3072                        float[] srcAdaptation = chromaticAdaptation(
3073                                Adaptation.BRADFORD.mTransform, srcXYZ,
3074                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3075                        transform = mul3x3(srcAdaptation, source.mTransform);
3076                    }
3077
3078                    if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
3079                        float[] dstAdaptation = chromaticAdaptation(
3080                                Adaptation.BRADFORD.mTransform, dstXYZ,
3081                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3082                        inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
3083                    }
3084
3085                    if (intent == RenderIntent.ABSOLUTE) {
3086                        transform = mul3x3Diag(
3087                                new float[] {
3088                                        srcXYZ[0] / dstXYZ[0],
3089                                        srcXYZ[1] / dstXYZ[1],
3090                                        srcXYZ[2] / dstXYZ[2],
3091                                }, transform);
3092                    }
3093
3094                    return mul3x3(inverseTransform, transform);
3095                }
3096            }
3097        }
3098
3099        /**
3100         * Returns the identity connector for a given color space.
3101         *
3102         * @param source The source and destination color space
3103         * @return A non-null connector that does not perform any transform
3104         *
3105         * @see ColorSpace#connect(ColorSpace, ColorSpace)
3106         */
3107        static Connector identity(ColorSpace source) {
3108            return new Connector(source, source, RenderIntent.RELATIVE) {
3109                @Override
3110                public float[] transform(@NonNull @Size(min = 3) float[] v) {
3111                    return v;
3112                }
3113            };
3114        }
3115    }
3116}
3117