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