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