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