• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.Size;
24 import android.annotation.SuppressAutoDoc;
25 import android.annotation.SuppressLint;
26 import android.hardware.DataSpace;
27 import android.hardware.DataSpace.NamedDataSpace;
28 import android.util.SparseIntArray;
29 
30 import libcore.util.NativeAllocationRegistry;
31 
32 import java.util.Arrays;
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     private static final SparseIntArray sDataToColorSpaces = new SparseIntArray();
215 
216     @NonNull private final String mName;
217     @NonNull private final Model mModel;
218     @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
219 
220     /**
221      * {@usesMathJax}
222      *
223      * <p>List of common, named color spaces. A corresponding instance of
224      * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p>
225      *
226      * <pre class="prettyprint">
227      * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);
228      * </pre>
229      *
230      * <p>The properties of each color space are described below (see {@link #SRGB sRGB}
231      * for instance). When applicable, the color gamut of each color space is compared
232      * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram
233      * shows the location of the color space's primaries and white point.</p>
234      *
235      * @see ColorSpace#get(Named)
236      */
237     public enum Named {
238         // NOTE: Do NOT change the order of the enum
239         /**
240          * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
241          * <table summary="Color space definition">
242          *     <tr>
243          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
244          *     </tr>
245          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
246          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
247          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
248          *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr>
249          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
250          *     <tr>
251          *         <td>Opto-electronic transfer function (OETF)</td>
252          *         <td colspan="4">\(\begin{equation}
253          *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\\
254          *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases}
255          *             \end{equation}\)
256          *         </td>
257          *     </tr>
258          *     <tr>
259          *         <td>Electro-optical transfer function (EOTF)</td>
260          *         <td colspan="4">\(\begin{equation}
261          *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\\
262          *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
263          *             \end{equation}\)
264          *         </td>
265          *     </tr>
266          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
267          * </table>
268          * <p>
269          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
270          *     <figcaption style="text-align: center;">sRGB</figcaption>
271          * </p>
272          */
273         SRGB,
274         /**
275          * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
276          * <table summary="Color space definition">
277          *     <tr>
278          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
279          *     </tr>
280          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
281          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
282          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
283          *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr>
284          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
285          *     <tr>
286          *         <td>Opto-electronic transfer function (OETF)</td>
287          *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
288          *     </tr>
289          *     <tr>
290          *         <td>Electro-optical transfer function (EOTF)</td>
291          *         <td colspan="4">\(C_{linear} = C_{sRGB}\)</td>
292          *     </tr>
293          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
294          * </table>
295          * <p>
296          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
297          *     <figcaption style="text-align: center;">sRGB</figcaption>
298          * </p>
299          */
300         LINEAR_SRGB,
301         /**
302          * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p>
303          * <table summary="Color space definition">
304          *     <tr>
305          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
306          *     </tr>
307          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
308          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
309          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
310          *     <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr>
311          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
312          *     <tr>
313          *         <td>Opto-electronic transfer function (OETF)</td>
314          *         <td colspan="4">\(\begin{equation}
315          *             C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
316          *                      \left| C_{linear} \right| \lt 0.0031308 \\\
317          *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
318          *                      \left| C_{linear} \right| \ge 0.0031308 \end{cases}
319          *             \end{equation}\)
320          *         </td>
321          *     </tr>
322          *     <tr>
323          *         <td>Electro-optical transfer function (EOTF)</td>
324          *         <td colspan="4">\(\begin{equation}
325          *             C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
326          *                  \left| C_{scRGB} \right| \lt 0.04045 \\\
327          *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
328          *                  \left| C_{scRGB} \right| \ge 0.04045 \end{cases}
329          *             \end{equation}\)
330          *         </td>
331          *     </tr>
332          *     <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr>
333          * </table>
334          * <p>
335          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
336          *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
337          * </p>
338          */
339         EXTENDED_SRGB,
340         /**
341          * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p>
342          * <table summary="Color space definition">
343          *     <tr>
344          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
345          *     </tr>
346          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
347          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
348          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
349          *     <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr>
350          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
351          *     <tr>
352          *         <td>Opto-electronic transfer function (OETF)</td>
353          *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
354          *     </tr>
355          *     <tr>
356          *         <td>Electro-optical transfer function (EOTF)</td>
357          *         <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
358          *     </tr>
359          *     <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr>
360          * </table>
361          * <p>
362          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
363          *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
364          * </p>
365          */
366         LINEAR_EXTENDED_SRGB,
367         /**
368          * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p>
369          * <table summary="Color space definition">
370          *     <tr>
371          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
372          *     </tr>
373          *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
374          *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
375          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
376          *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr>
377          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
378          *     <tr>
379          *         <td>Opto-electronic transfer function (OETF)</td>
380          *         <td colspan="4">\(\begin{equation}
381          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
382          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
383          *             \end{equation}\)
384          *         </td>
385          *     </tr>
386          *     <tr>
387          *         <td>Electro-optical transfer function (EOTF)</td>
388          *         <td colspan="4">\(\begin{equation}
389          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
390          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
391          *             \end{equation}\)
392          *         </td>
393          *     </tr>
394          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
395          * </table>
396          * <p>
397          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" />
398          *     <figcaption style="text-align: center;">BT.709</figcaption>
399          * </p>
400          */
401         BT709,
402         /**
403          * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p>
404          * <table summary="Color space definition">
405          *     <tr>
406          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
407          *     </tr>
408          *     <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr>
409          *     <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr>
410          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
411          *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr>
412          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
413          *     <tr>
414          *         <td>Opto-electronic transfer function (OETF)</td>
415          *         <td colspan="4">\(\begin{equation}
416          *             C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\\
417          *             1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
418          *             \end{equation}\)
419          *         </td>
420          *     </tr>
421          *     <tr>
422          *         <td>Electro-optical transfer function (EOTF)</td>
423          *         <td colspan="4">\(\begin{equation}
424          *             C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\\
425          *             \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
426          *             \end{equation}\)
427          *         </td>
428          *     </tr>
429          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
430          * </table>
431          * <p>
432          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" />
433          *     <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption>
434          * </p>
435          */
436         BT2020,
437         /**
438          * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p>
439          * <table summary="Color space definition">
440          *     <tr>
441          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
442          *     </tr>
443          *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr>
444          *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr>
445          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
446          *     <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr>
447          *     <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr>
448          *     <tr>
449          *         <td>Opto-electronic transfer function (OETF)</td>
450          *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
451          *     </tr>
452          *     <tr>
453          *         <td>Electro-optical transfer function (EOTF)</td>
454          *         <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td>
455          *     </tr>
456          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
457          * </table>
458          * <p>
459          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" />
460          *     <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption>
461          * </p>
462          */
463         DCI_P3,
464         /**
465          * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p>
466          * <table summary="Color space definition">
467          *     <tr>
468          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
469          *     </tr>
470          *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr>
471          *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr>
472          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
473          *     <tr><td>Name</td><td colspan="4">Display P3</td></tr>
474          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
475          *     <tr>
476          *         <td>Opto-electronic transfer function (OETF)</td>
477          *         <td colspan="4">\(\begin{equation}
478          *             C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\\
479          *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases}
480          *             \end{equation}\)
481          *         </td>
482          *     </tr>
483          *     <tr>
484          *         <td>Electro-optical transfer function (EOTF)</td>
485          *         <td colspan="4">\(\begin{equation}
486          *             C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.04045 \\\
487          *             \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
488          *             \end{equation}\)
489          *         </td>
490          *     </tr>
491          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
492          * </table>
493          * <p>
494          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" />
495          *     <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption>
496          * </p>
497          */
498         DISPLAY_P3,
499         /**
500          * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p>
501          * <table summary="Color space definition">
502          *     <tr>
503          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
504          *     </tr>
505          *     <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr>
506          *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr>
507          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
508          *     <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr>
509          *     <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr>
510          *     <tr>
511          *         <td>Opto-electronic transfer function (OETF)</td>
512          *         <td colspan="4">\(\begin{equation}
513          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
514          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
515          *             \end{equation}\)
516          *         </td>
517          *     </tr>
518          *     <tr>
519          *         <td>Electro-optical transfer function (EOTF)</td>
520          *         <td colspan="4">\(\begin{equation}
521          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
522          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
523          *             \end{equation}\)
524          *         </td>
525          *     </tr>
526          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
527          * </table>
528          * <p>
529          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" />
530          *     <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption>
531          * </p>
532          */
533         NTSC_1953,
534         /**
535          * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p>
536          * <table summary="Color space definition">
537          *     <tr>
538          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
539          *     </tr>
540          *     <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr>
541          *     <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr>
542          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
543          *     <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr>
544          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
545          *     <tr>
546          *         <td>Opto-electronic transfer function (OETF)</td>
547          *         <td colspan="4">\(\begin{equation}
548          *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
549          *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
550          *             \end{equation}\)
551          *         </td>
552          *     </tr>
553          *     <tr>
554          *         <td>Electro-optical transfer function (EOTF)</td>
555          *         <td colspan="4">\(\begin{equation}
556          *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
557          *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
558          *             \end{equation}\)
559          *         </td>
560          *     </tr>
561          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
562          * </table>
563          * <p>
564          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" />
565          *     <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption>
566          * </p>
567          */
568         SMPTE_C,
569         /**
570          * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p>
571          * <table summary="Color space definition">
572          *     <tr>
573          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
574          *     </tr>
575          *     <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr>
576          *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr>
577          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
578          *     <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr>
579          *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
580          *     <tr>
581          *         <td>Opto-electronic transfer function (OETF)</td>
582          *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
583          *     </tr>
584          *     <tr>
585          *         <td>Electro-optical transfer function (EOTF)</td>
586          *         <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td>
587          *     </tr>
588          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
589          * </table>
590          * <p>
591          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" />
592          *     <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption>
593          * </p>
594          */
595         ADOBE_RGB,
596         /**
597          * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p>
598          * <table summary="Color space definition">
599          *     <tr>
600          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
601          *     </tr>
602          *     <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr>
603          *     <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr>
604          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
605          *     <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr>
606          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
607          *     <tr>
608          *         <td>Opto-electronic transfer function (OETF)</td>
609          *         <td colspan="4">\(\begin{equation}
610          *             C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\\
611          *             C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
612          *             \end{equation}\)
613          *         </td>
614          *     </tr>
615          *     <tr>
616          *         <td>Electro-optical transfer function (EOTF)</td>
617          *         <td colspan="4">\(\begin{equation}
618          *             C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\\
619          *             C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
620          *             \end{equation}\)
621          *         </td>
622          *     </tr>
623          *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
624          * </table>
625          * <p>
626          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" />
627          *     <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption>
628          * </p>
629          */
630         PRO_PHOTO_RGB,
631         /**
632          * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p>
633          * <table summary="Color space definition">
634          *     <tr>
635          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
636          *     </tr>
637          *     <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr>
638          *     <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr>
639          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
640          *     <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr>
641          *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
642          *     <tr>
643          *         <td>Opto-electronic transfer function (OETF)</td>
644          *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
645          *     </tr>
646          *     <tr>
647          *         <td>Electro-optical transfer function (EOTF)</td>
648          *         <td colspan="4">\(C_{linear} = C_{ACES}\)</td>
649          *     </tr>
650          *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
651          * </table>
652          * <p>
653          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" />
654          *     <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption>
655          * </p>
656          */
657         ACES,
658         /**
659          * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p>
660          * <table summary="Color space definition">
661          *     <tr>
662          *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
663          *     </tr>
664          *     <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr>
665          *     <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr>
666          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
667          *     <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr>
668          *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
669          *     <tr>
670          *         <td>Opto-electronic transfer function (OETF)</td>
671          *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
672          *     </tr>
673          *     <tr>
674          *         <td>Electro-optical transfer function (EOTF)</td>
675          *         <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td>
676          *     </tr>
677          *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
678          * </table>
679          * <p>
680          *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" />
681          *     <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption>
682          * </p>
683          */
684         ACESCG,
685         /**
686          * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard
687          * illuminant D50 as its white point.</p>
688          * <table summary="Color space definition">
689          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
690          *     <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr>
691          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
692          *     <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr>
693          * </table>
694          */
695         CIE_XYZ,
696         /**
697          * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50
698          * as a profile conversion space.</p>
699          * <table summary="Color space definition">
700          *     <tr><th>Property</th><th colspan="4">Value</th></tr>
701          *     <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr>
702          *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
703          *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
704          * </table>
705          */
706         CIE_LAB
707         // Update the initialization block next to #get(Named) when adding new values
708     }
709 
710     /**
711      * <p>A render intent determines how a {@link ColorSpace.Connector connector}
712      * maps colors from one color space to another. The choice of mapping is
713      * important when the source color space has a larger color gamut than the
714      * destination color space.</p>
715      *
716      * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
717      */
718     public enum RenderIntent {
719         /**
720          * <p>Compresses the source gamut into the destination gamut.
721          * This render intent affects all colors, inside and outside
722          * of destination gamut. The goal of this render intent is
723          * to preserve the visual relationship between colors.</p>
724          *
725          * <p class="note">This render intent is currently not
726          * implemented and behaves like {@link #RELATIVE}.</p>
727          */
728         PERCEPTUAL,
729         /**
730          * Similar to the {@link #ABSOLUTE} render intent, this render
731          * intent matches the closest color in the destination gamut
732          * but makes adjustments for the destination white point.
733          */
734         RELATIVE,
735         /**
736          * <p>Attempts to maintain the relative saturation of colors
737          * from the source gamut to the destination gamut, to keep
738          * highly saturated colors as saturated as possible.</p>
739          *
740          * <p class="note">This render intent is currently not
741          * implemented and behaves like {@link #RELATIVE}.</p>
742          */
743         SATURATION,
744         /**
745          * Colors that are in the destination gamut are left unchanged.
746          * Colors that fall outside of the destination gamut are mapped
747          * to the closest possible color within the gamut of the destination
748          * color space (they are clipped).
749          */
750         ABSOLUTE
751     }
752 
753     /**
754      * {@usesMathJax}
755      *
756      * <p>List of adaptation matrices that can be used for chromatic adaptation
757      * using the von Kries transform. These matrices are used to convert values
758      * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p>
759      *
760      * <p>Given an adaptation matrix \(A\), the conversion from XYZ to
761      * LMS is straightforward:</p>
762      *
763      * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] =
764      * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$
765      *
766      * <p>The complete von Kries transform \(T\) uses a diagonal matrix
767      * noted \(D\) to perform the adaptation in LMS space. In addition
768      * to \(A\) and \(D\), the source white point \(W1\) and the destination
769      * white point \(W2\) must be specified:</p>
770      *
771      * $$\begin{align*}
772      * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
773      *      A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\\
774      * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
775      *      A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\\
776      * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\\
777      *      0 & \frac{M_2}{M_1} & 0 \\\
778      *      0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\\
779      * T &= A^{-1}.D.A
780      * \end{align*}$$
781      *
782      * <p>As an example, the resulting matrix \(T\) can then be used to
783      * perform the chromatic adaptation of sRGB XYZ transform from D65
784      * to D50:</p>
785      *
786      * $$sRGB_{D50} = T.sRGB_{D65}$$
787      *
788      * @see ColorSpace.Connector
789      * @see ColorSpace#connect(ColorSpace, ColorSpace)
790      */
791     public enum Adaptation {
792         /**
793          * Bradford chromatic adaptation transform, as defined in the
794          * CIECAM97s color appearance model.
795          */
796         BRADFORD(new float[] {
797                  0.8951f, -0.7502f,  0.0389f,
798                  0.2664f,  1.7135f, -0.0685f,
799                 -0.1614f,  0.0367f,  1.0296f
800         }),
801         /**
802          * von Kries chromatic adaptation transform.
803          */
804         VON_KRIES(new float[] {
805                  0.40024f, -0.22630f, 0.00000f,
806                  0.70760f,  1.16532f, 0.00000f,
807                 -0.08081f,  0.04570f, 0.91822f
808         }),
809         /**
810          * CIECAT02 chromatic adaption transform, as defined in the
811          * CIECAM02 color appearance model.
812          */
813         CIECAT02(new float[] {
814                  0.7328f, -0.7036f,  0.0030f,
815                  0.4296f,  1.6975f,  0.0136f,
816                 -0.1624f,  0.0061f,  0.9834f
817         });
818 
819         final float[] mTransform;
820 
Adaptation(@onNull @ize9) float[] transform)821         Adaptation(@NonNull @Size(9) float[] transform) {
822             mTransform = transform;
823         }
824     }
825 
826     /**
827      * A color model is required by a {@link ColorSpace} to describe the
828      * way colors can be represented as tuples of numbers. A common color
829      * model is the {@link #RGB RGB} color model which defines a color
830      * as represented by a tuple of 3 numbers (red, green and blue).
831      */
832     public enum Model {
833         /**
834          * The RGB model is a color model with 3 components that
835          * refer to the three additive primiaries: red, green
836          * andd blue.
837          */
838         RGB(3),
839         /**
840          * The XYZ model is a color model with 3 components that
841          * are used to model human color vision on a basic sensory
842          * level.
843          */
844         XYZ(3),
845         /**
846          * The Lab model is a color model with 3 components used
847          * to describe a color space that is more perceptually
848          * uniform than XYZ.
849          */
850         LAB(3),
851         /**
852          * The CMYK model is a color model with 4 components that
853          * refer to four inks used in color printing: cyan, magenta,
854          * yellow and black (or key). CMYK is a subtractive color
855          * model.
856          */
857         CMYK(4);
858 
859         private final int mComponentCount;
860 
Model(@ntRangefrom = 1, to = 4) int componentCount)861         Model(@IntRange(from = 1, to = 4) int componentCount) {
862             mComponentCount = componentCount;
863         }
864 
865         /**
866          * Returns the number of components for this color model.
867          *
868          * @return An integer between 1 and 4
869          */
870         @IntRange(from = 1, to = 4)
getComponentCount()871         public int getComponentCount() {
872             return mComponentCount;
873         }
874     }
875 
ColorSpace( @onNull String name, @NonNull Model model, @IntRange(from = MIN_ID, to = MAX_ID) int id)876     /*package*/ 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      * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace}
1398      * value.
1399      *
1400      * <p>This function maps from a dataspace to a {@link Named} ColorSpace.
1401      * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created,
1402      * {@code null} will return.</p>
1403      *
1404      * @param dataSpace The dataspace value
1405      * @return the ColorSpace object or {@code null} if no matching colorspace can be found.
1406      */
1407     @SuppressLint("MethodNameUnits")
1408     @Nullable
getFromDataSpace(@amedDataSpace int dataSpace)1409     public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
1410         int index = sDataToColorSpaces.get(dataSpace, -1);
1411         if (index != -1) {
1412             return ColorSpace.get(index);
1413         } else {
1414             return null;
1415         }
1416     }
1417 
1418     /**
1419      * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace}
1420      * object.
1421      *
1422      * <p>If this {@link ColorSpace} object has no matching {@code dataSpace} value,
1423      * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.</p>
1424      *
1425      * @return the dataspace value.
1426      */
1427     @SuppressLint("MethodNameUnits")
getDataSpace()1428     public @NamedDataSpace int getDataSpace() {
1429         int index = sDataToColorSpaces.indexOfValue(getId());
1430         if (index != -1) {
1431             return sDataToColorSpaces.keyAt(index);
1432         } else {
1433             return DataSpace.DATASPACE_UNKNOWN;
1434         }
1435     }
1436 
1437     /**
1438      * <p>Returns an instance of {@link ColorSpace} identified by the specified
1439      * name. The list of names provided in the {@link Named} enum gives access
1440      * to a variety of common RGB color spaces.</p>
1441      *
1442      * <p>This method always returns the same instance for a given name.</p>
1443      *
1444      * <p>This method is thread-safe.</p>
1445      *
1446      * @param name The name of the color space to get an instance of
1447      * @return A non-null {@link ColorSpace} instance
1448      */
1449     @NonNull
get(@onNull Named name)1450     public static ColorSpace get(@NonNull Named name) {
1451         return sNamedColorSpaces[name.ordinal()];
1452     }
1453 
1454     /**
1455      * <p>Returns a {@link Named} instance of {@link ColorSpace} that matches
1456      * the specified RGB to CIE XYZ transform and transfer functions. If no
1457      * instance can be found, this method returns null.</p>
1458      *
1459      * <p>The color transform matrix is assumed to target the CIE XYZ space
1460      * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p>
1461      *
1462      * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile
1463      *                 connection space CIE XYZ as an array of 9 floats, cannot be null
1464      * @param function Parameters for the transfer functions
1465      * @return A non-null {@link ColorSpace} if a match is found, null otherwise
1466      */
1467     @Nullable
match( @onNull @ize9) float[] toXYZD50, @NonNull Rgb.TransferParameters function)1468     public static ColorSpace match(
1469             @NonNull @Size(9) float[] toXYZD50,
1470             @NonNull Rgb.TransferParameters function) {
1471 
1472         for (ColorSpace colorSpace : sNamedColorSpaces) {
1473             if (colorSpace.getModel() == Model.RGB) {
1474                 ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ);
1475                 if (compare(toXYZD50, rgb.mTransform) &&
1476                         compare(function, rgb.mTransferParameters)) {
1477                     return colorSpace;
1478                 }
1479             }
1480         }
1481 
1482         return null;
1483     }
1484 
1485     static {
1486         sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
1487                 "sRGB IEC61966-2.1",
1488                 SRGB_PRIMARIES,
1489                 ILLUMINANT_D65,
1490                 null,
1491                 SRGB_TRANSFER_PARAMETERS,
1492                 Named.SRGB.ordinal()
1493         );
sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal())1494         sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal());
1495         sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
1496                 "sRGB IEC61966-2.1 (Linear)",
1497                 SRGB_PRIMARIES,
1498                 ILLUMINANT_D65,
1499                 1.0,
1500                 0.0f, 1.0f,
1501                 Named.LINEAR_SRGB.ordinal()
1502         );
sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal())1503         sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal());
1504         sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1505                 "scRGB-nl IEC 61966-2-2:2003",
1506                 SRGB_PRIMARIES,
1507                 ILLUMINANT_D65,
1508                 null,
1509                 x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
1510                 x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
1511                 -0.799f, 2.399f,
1512                 SRGB_TRANSFER_PARAMETERS,
1513                 Named.EXTENDED_SRGB.ordinal()
1514         );
sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal())1515         sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal());
1516         sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
1517                 "scRGB IEC 61966-2-2:2003",
1518                 SRGB_PRIMARIES,
1519                 ILLUMINANT_D65,
1520                 1.0,
1521                 -0.5f, 7.499f,
1522                 Named.LINEAR_EXTENDED_SRGB.ordinal()
1523         );
sDataToColorSpaces.put( DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal())1524         sDataToColorSpaces.put(
1525                 DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
1526         sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
1527                 "Rec. ITU-R BT.709-5",
1528                 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
1529                 ILLUMINANT_D65,
1530                 null,
1531                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
1532                 Named.BT709.ordinal()
1533         );
sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal())1534         sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
1535         sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
1536                 "Rec. ITU-R BT.2020-1",
1537                 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
1538                 ILLUMINANT_D65,
1539                 null,
1540                 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
1541                 Named.BT2020.ordinal()
1542         );
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal())1543         sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
1544         sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
1545                 "SMPTE RP 431-2-2007 DCI (P3)",
1546                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1547                 new float[] { 0.314f, 0.351f },
1548                 2.6,
1549                 0.0f, 1.0f,
1550                 Named.DCI_P3.ordinal()
1551         );
sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal())1552         sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
1553         sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
1554                 "Display P3",
1555                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
1556                 ILLUMINANT_D65,
1557                 null,
1558                 SRGB_TRANSFER_PARAMETERS,
1559                 Named.DISPLAY_P3.ordinal()
1560         );
sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal())1561         sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal());
1562         sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
1563                 "NTSC (1953)",
1564                 NTSC_1953_PRIMARIES,
1565                 ILLUMINANT_C,
1566                 null,
1567                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
1568                 Named.NTSC_1953.ordinal()
1569         );
1570         sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
1571                 "SMPTE-C RGB",
1572                 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
1573                 ILLUMINANT_D65,
1574                 null,
1575                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
1576                 Named.SMPTE_C.ordinal()
1577         );
1578         sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
1579                 "Adobe RGB (1998)",
1580                 new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
1581                 ILLUMINANT_D65,
1582                 2.2,
1583                 0.0f, 1.0f,
1584                 Named.ADOBE_RGB.ordinal()
1585         );
sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal())1586         sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal());
1587         sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
1588                 "ROMM RGB ISO 22028-2:2013",
1589                 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
1590                 ILLUMINANT_D50,
1591                 null,
1592                 new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8),
1593                 Named.PRO_PHOTO_RGB.ordinal()
1594         );
1595         sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
1596                 "SMPTE ST 2065-1:2012 ACES",
1597                 new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
1598                 ILLUMINANT_D60,
1599                 1.0,
1600                 -65504.0f, 65504.0f,
1601                 Named.ACES.ordinal()
1602         );
1603         sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
1604                 "Academy S-2014-004 ACEScg",
1605                 new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
1606                 ILLUMINANT_D60,
1607                 1.0,
1608                 -65504.0f, 65504.0f,
1609                 Named.ACESCG.ordinal()
1610         );
1611         sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
1612                 "Generic XYZ",
1613                 Named.CIE_XYZ.ordinal()
1614         );
1615         sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
1616                 "Generic L*a*b*",
1617                 Named.CIE_LAB.ordinal()
1618         );
1619     }
1620 
1621     // Reciprocal piecewise gamma response
rcpResponse(double x, double a, double b, double c, double d, double g)1622     private static double rcpResponse(double x, double a, double b, double c, double d, double g) {
1623         return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
1624     }
1625 
1626     // Piecewise gamma response
response(double x, double a, double b, double c, double d, double g)1627     private static double response(double x, double a, double b, double c, double d, double g) {
1628         return x >= d ? Math.pow(a * x + b, g) : c * x;
1629     }
1630 
1631     // Reciprocal piecewise gamma response
rcpResponse(double x, double a, double b, double c, double d, double e, double f, double g)1632     private static double rcpResponse(double x, double a, double b, double c, double d,
1633             double e, double f, double g) {
1634         return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c;
1635     }
1636 
1637     // Piecewise gamma response
response(double x, double a, double b, double c, double d, double e, double f, double g)1638     private static double response(double x, double a, double b, double c, double d,
1639             double e, double f, double g) {
1640         return x >= d ? Math.pow(a * x + b, g) + e : c * x + f;
1641     }
1642 
1643     // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
1644     // spaces that allow negative values
1645     @SuppressWarnings("SameParameterValue")
absRcpResponse(double x, double a, double b, double c, double d, double g)1646     private static double absRcpResponse(double x, double a, double b, double c, double d, double g) {
1647         return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x);
1648     }
1649 
1650     // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
1651     // allow negative values
1652     @SuppressWarnings("SameParameterValue")
absResponse(double x, double a, double b, double c, double d, double g)1653     private static double absResponse(double x, double a, double b, double c, double d, double g) {
1654         return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x);
1655     }
1656 
1657     /**
1658      * Compares two sets of parametric transfer functions parameters with a precision of 1e-3.
1659      *
1660      * @param a The first set of parameters to compare
1661      * @param b The second set of parameters to compare
1662      * @return True if the two sets are equal, false otherwise
1663      */
compare( @ullable Rgb.TransferParameters a, @Nullable Rgb.TransferParameters b)1664     private static boolean compare(
1665             @Nullable Rgb.TransferParameters a,
1666             @Nullable Rgb.TransferParameters b) {
1667         //noinspection SimplifiableIfStatement
1668         if (a == null && b == null) return true;
1669         return a != null && b != null &&
1670                 Math.abs(a.a - b.a) < 1e-3 &&
1671                 Math.abs(a.b - b.b) < 1e-3 &&
1672                 Math.abs(a.c - b.c) < 1e-3 &&
1673                 Math.abs(a.d - b.d) < 2e-3 && // Special case for variations in sRGB OETF/EOTF
1674                 Math.abs(a.e - b.e) < 1e-3 &&
1675                 Math.abs(a.f - b.f) < 1e-3 &&
1676                 Math.abs(a.g - b.g) < 1e-3;
1677     }
1678 
1679     /**
1680      * Compares two arrays of float with a precision of 1e-3.
1681      *
1682      * @param a The first array to compare
1683      * @param b The second array to compare
1684      * @return True if the two arrays are equal, false otherwise
1685      */
compare(@onNull float[] a, @NonNull float[] b)1686     private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
1687         if (a == b) return true;
1688         for (int i = 0; i < a.length; i++) {
1689             if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
1690         }
1691         return true;
1692     }
1693 
1694     /**
1695      * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
1696      *
1697      * @param m A 3x3 matrix as a non-null array of 9 floats
1698      * @return A new array of 9 floats containing the inverse of the input matrix
1699      */
1700     @NonNull
1701     @Size(9)
inverse3x3(@onNull @ize9) float[] m)1702     private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
1703         float a = m[0];
1704         float b = m[3];
1705         float c = m[6];
1706         float d = m[1];
1707         float e = m[4];
1708         float f = m[7];
1709         float g = m[2];
1710         float h = m[5];
1711         float i = m[8];
1712 
1713         float A = e * i - f * h;
1714         float B = f * g - d * i;
1715         float C = d * h - e * g;
1716 
1717         float det = a * A + b * B + c * C;
1718 
1719         float inverted[] = new float[m.length];
1720         inverted[0] = A / det;
1721         inverted[1] = B / det;
1722         inverted[2] = C / det;
1723         inverted[3] = (c * h - b * i) / det;
1724         inverted[4] = (a * i - c * g) / det;
1725         inverted[5] = (b * g - a * h) / det;
1726         inverted[6] = (b * f - c * e) / det;
1727         inverted[7] = (c * d - a * f) / det;
1728         inverted[8] = (a * e - b * d) / det;
1729         return inverted;
1730     }
1731 
1732     /**
1733      * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
1734      *
1735      * @param lhs 3x3 matrix, as a non-null array of 9 floats
1736      * @param rhs 3x3 matrix, as a non-null array of 9 floats
1737      * @return A new array of 9 floats containing the result of the multiplication
1738      *         of rhs by lhs
1739      */
1740     @NonNull
1741     @Size(9)
mul3x3(@onNull @ize9) float[] lhs, @NonNull @Size(9) float[] rhs)1742     private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
1743         float[] r = new float[9];
1744         r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
1745         r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
1746         r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
1747         r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
1748         r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
1749         r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
1750         r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
1751         r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
1752         r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
1753         return r;
1754     }
1755 
1756     /**
1757      * Multiplies a vector of 3 components by a 3x3 matrix and stores the
1758      * result in the input vector.
1759      *
1760      * @param lhs 3x3 matrix, as a non-null array of 9 floats
1761      * @param rhs Vector of 3 components, as a non-null array of 3 floats
1762      * @return The array of 3 passed as the rhs parameter
1763      */
1764     @NonNull
1765     @Size(min = 3)
mul3x3Float3( @onNull @ize9) float[] lhs, @NonNull @Size(min = 3) float[] rhs)1766     private static float[] mul3x3Float3(
1767             @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
1768         float r0 = rhs[0];
1769         float r1 = rhs[1];
1770         float r2 = rhs[2];
1771         rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
1772         rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
1773         rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
1774         return rhs;
1775     }
1776 
1777     /**
1778      * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
1779      * by a 3x3 matrix represented as an array of 9 floats.
1780      *
1781      * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
1782      * @param rhs 3x3 matrix, as a non-null array of 9 floats
1783      * @return A new array of 9 floats containing the result of the multiplication
1784      *         of rhs by lhs
1785      */
1786     @NonNull
1787     @Size(9)
mul3x3Diag( @onNull @ize3) float[] lhs, @NonNull @Size(9) float[] rhs)1788     private static float[] mul3x3Diag(
1789             @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
1790         return new float[] {
1791                 lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
1792                 lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
1793                 lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
1794         };
1795     }
1796 
1797     /**
1798      * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
1799      * input xyY array only contains the x and y components.
1800      *
1801      * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
1802      * @return A new float array of length 3 containing XYZ values
1803      */
1804     @NonNull
1805     @Size(3)
xyYToXyz(@onNull @ize2) float[] xyY)1806     private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
1807         return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
1808     }
1809 
1810     /**
1811      * <p>Computes the chromatic adaptation transform from the specified
1812      * source white point to the specified destination white point.</p>
1813      *
1814      * <p>The transform is computed using the von Kries method, described
1815      * in more details in the documentation of {@link Adaptation}. The
1816      * {@link Adaptation} enum provides different matrices that can be
1817      * used to perform the adaptation.</p>
1818      *
1819      * @param matrix The adaptation matrix
1820      * @param srcWhitePoint The white point to adapt from, *will be modified*
1821      * @param dstWhitePoint The white point to adapt to, *will be modified*
1822      * @return A 3x3 matrix as a non-null array of 9 floats
1823      */
1824     @NonNull
1825     @Size(9)
chromaticAdaptation(@onNull @ize9) float[] matrix, @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint)1826     private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
1827             @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
1828         float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
1829         float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
1830         // LMS is a diagonal matrix stored as a float[3]
1831         float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
1832         return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
1833     }
1834 
1835     /**
1836      * <p>Computes the chromaticity coordinates of a specified correlated color
1837      * temperature (CCT) on the Planckian locus. The specified CCT must be
1838      * greater than 0. A meaningful CCT range is [1667, 25000].</p>
1839      *
1840      * <p>The transform is computed using the methods in Kang et
1841      * al., <i>Design of Advanced Color - Temperature Control System for HDTV
1842      * Applications</i>, Journal of Korean Physical Society 41, 865-871
1843      * (2002).</p>
1844      *
1845      * @param cct The correlated color temperature, in Kelvin
1846      * @return Corresponding XYZ values
1847      * @throws IllegalArgumentException If cct is invalid
1848      */
1849     @NonNull
1850     @Size(3)
cctToXyz(@ntRangefrom = 1) int cct)1851     public static float[] cctToXyz(@IntRange(from = 1) int cct) {
1852         if (cct < 1) {
1853             throw new IllegalArgumentException("Temperature must be greater than 0");
1854         }
1855 
1856         final float icct = 1e3f / cct;
1857         final float icct2 = icct * icct;
1858         final float x = cct <= 4000.0f ?
1859             0.179910f + 0.8776956f * icct - 0.2343589f * icct2 - 0.2661239f * icct2 * icct :
1860             0.240390f + 0.2226347f * icct + 2.1070379f * icct2 - 3.0258469f * icct2 * icct;
1861 
1862         final float x2 = x * x;
1863         final float y = cct <= 2222.0f ?
1864             -0.20219683f + 2.18555832f * x - 1.34811020f * x2 - 1.1063814f * x2 * x :
1865             cct <= 4000.0f ?
1866             -0.16748867f + 2.09137015f * x - 1.37418593f * x2 - 0.9549476f * x2 * x :
1867             -0.37001483f + 3.75112997f * x - 5.8733867f * x2 + 3.0817580f * x2 * x;
1868 
1869         return xyYToXyz(new float[] {x, y});
1870     }
1871 
1872     /**
1873      * <p>Computes the chromatic adaptation transform from the specified
1874      * source white point to the specified destination white point.</p>
1875      *
1876      * <p>The transform is computed using the von Kries method, described
1877      * in more details in the documentation of {@link Adaptation}. The
1878      * {@link Adaptation} enum provides different matrices that can be
1879      * used to perform the adaptation.</p>
1880      *
1881      * @param adaptation The adaptation method
1882      * @param srcWhitePoint The white point to adapt from
1883      * @param dstWhitePoint The white point to adapt to
1884      * @return A 3x3 matrix as a non-null array of 9 floats
1885      */
1886     @NonNull
1887     @Size(9)
chromaticAdaptation(@onNull Adaptation adaptation, @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint, @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint)1888     public static float[] chromaticAdaptation(@NonNull Adaptation adaptation,
1889             @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint,
1890             @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint) {
1891         if ((srcWhitePoint.length != 2 && srcWhitePoint.length != 3)
1892                 || (dstWhitePoint.length != 2 && dstWhitePoint.length != 3)) {
1893             throw new IllegalArgumentException("A white point array must have 2 or 3 floats");
1894         }
1895         float[] srcXyz = srcWhitePoint.length == 3 ?
1896             Arrays.copyOf(srcWhitePoint, 3) : xyYToXyz(srcWhitePoint);
1897         float[] dstXyz = dstWhitePoint.length == 3 ?
1898             Arrays.copyOf(dstWhitePoint, 3) : xyYToXyz(dstWhitePoint);
1899 
1900         if (compare(srcXyz, dstXyz)) {
1901             return new float[] {
1902                 1.0f, 0.0f, 0.0f,
1903                 0.0f, 1.0f, 0.0f,
1904                 0.0f, 0.0f, 1.0f
1905             };
1906         }
1907         return chromaticAdaptation(adaptation.mTransform, srcXyz, dstXyz);
1908     }
1909 
1910     /**
1911      * Implementation of the CIE XYZ color space. Assumes the white point is D50.
1912      */
1913     @AnyThread
1914     private static final class Xyz extends ColorSpace {
Xyz(@onNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id)1915         private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1916             super(name, Model.XYZ, id);
1917         }
1918 
1919         @Override
isWideGamut()1920         public boolean isWideGamut() {
1921             return true;
1922         }
1923 
1924         @Override
getMinValue(@ntRangefrom = 0, to = 3) int component)1925         public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1926             return -2.0f;
1927         }
1928 
1929         @Override
getMaxValue(@ntRangefrom = 0, to = 3) int component)1930         public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1931             return 2.0f;
1932         }
1933 
1934         @Override
toXyz(@onNull @izemin = 3) float[] v)1935         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1936             v[0] = clamp(v[0]);
1937             v[1] = clamp(v[1]);
1938             v[2] = clamp(v[2]);
1939             return v;
1940         }
1941 
1942         @Override
fromXyz(@onNull @izemin = 3) float[] v)1943         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
1944             v[0] = clamp(v[0]);
1945             v[1] = clamp(v[1]);
1946             v[2] = clamp(v[2]);
1947             return v;
1948         }
1949 
clamp(float x)1950         private static float clamp(float x) {
1951             return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
1952         }
1953     }
1954 
1955     /**
1956      * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
1957      * with a white point of D50.
1958      */
1959     @AnyThread
1960     private static final class Lab extends ColorSpace {
1961         private static final float A = 216.0f / 24389.0f;
1962         private static final float B = 841.0f / 108.0f;
1963         private static final float C = 4.0f / 29.0f;
1964         private static final float D = 6.0f / 29.0f;
1965 
Lab(@onNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id)1966         private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
1967             super(name, Model.LAB, id);
1968         }
1969 
1970         @Override
isWideGamut()1971         public boolean isWideGamut() {
1972             return true;
1973         }
1974 
1975         @Override
getMinValue(@ntRangefrom = 0, to = 3) int component)1976         public float getMinValue(@IntRange(from = 0, to = 3) int component) {
1977             return component == 0 ? 0.0f : -128.0f;
1978         }
1979 
1980         @Override
getMaxValue(@ntRangefrom = 0, to = 3) int component)1981         public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
1982             return component == 0 ? 100.0f : 128.0f;
1983         }
1984 
1985         @Override
toXyz(@onNull @izemin = 3) float[] v)1986         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
1987             v[0] = clamp(v[0], 0.0f, 100.0f);
1988             v[1] = clamp(v[1], -128.0f, 128.0f);
1989             v[2] = clamp(v[2], -128.0f, 128.0f);
1990 
1991             float fy = (v[0] + 16.0f) / 116.0f;
1992             float fx = fy + (v[1] * 0.002f);
1993             float fz = fy - (v[2] * 0.005f);
1994             float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
1995             float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
1996             float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
1997 
1998             v[0] = X * ILLUMINANT_D50_XYZ[0];
1999             v[1] = Y * ILLUMINANT_D50_XYZ[1];
2000             v[2] = Z * ILLUMINANT_D50_XYZ[2];
2001 
2002             return v;
2003         }
2004 
2005         @Override
fromXyz(@onNull @izemin = 3) float[] v)2006         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
2007             float X = v[0] / ILLUMINANT_D50_XYZ[0];
2008             float Y = v[1] / ILLUMINANT_D50_XYZ[1];
2009             float Z = v[2] / ILLUMINANT_D50_XYZ[2];
2010 
2011             float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
2012             float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
2013             float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
2014 
2015             float L = 116.0f * fy - 16.0f;
2016             float a = 500.0f * (fx - fy);
2017             float b = 200.0f * (fy - fz);
2018 
2019             v[0] = clamp(L, 0.0f, 100.0f);
2020             v[1] = clamp(a, -128.0f, 128.0f);
2021             v[2] = clamp(b, -128.0f, 128.0f);
2022 
2023             return v;
2024         }
2025 
clamp(float x, float min, float max)2026         private static float clamp(float x, float min, float max) {
2027             return x < min ? min : x > max ? max : x;
2028         }
2029     }
2030 
2031     /**
2032      * Retrieve the native SkColorSpace object for passing to native.
2033      *
2034      * Only valid on ColorSpace.Rgb.
2035      */
getNativeInstance()2036     long getNativeInstance() {
2037         throw new IllegalArgumentException("colorSpace must be an RGB color space");
2038     }
2039 
2040     /**
2041      * {@usesMathJax}
2042      *
2043      * <p>An RGB color space is an additive color space using the
2044      * {@link Model#RGB RGB} color model (a color is therefore represented
2045      * by a tuple of 3 numbers).</p>
2046      *
2047      * <p>A specific RGB color space is defined by the following properties:</p>
2048      * <ul>
2049      *     <li>Three chromaticities of the red, green and blue primaries, which
2050      *     define the gamut of the color space.</li>
2051      *     <li>A white point chromaticity that defines the stimulus to which
2052      *     color space values are normalized (also just called "white").</li>
2053      *     <li>An opto-electronic transfer function, also called opto-electronic
2054      *     conversion function or often, and approximately, gamma function.</li>
2055      *     <li>An electro-optical transfer function, also called electo-optical
2056      *     conversion function or often, and approximately, gamma function.</li>
2057      *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
2058      * </ul>
2059      *
2060      * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
2061      *
2062      * <h3>Primaries and white point chromaticities</h3>
2063      * <p>In this implementation, the chromaticity of the primaries and the white
2064      * point of an RGB color space is defined in the CIE xyY color space. This
2065      * color space separates the chromaticity of a color, the x and y components,
2066      * and its luminance, the Y component. Since the primaries and the white
2067      * point have full brightness, the Y component is assumed to be 1 and only
2068      * the x and y components are needed to encode them.</p>
2069      * <p>For convenience, this implementation also allows to define the
2070      * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
2071      * are internally converted to xyY.</p>
2072      *
2073      * <p>
2074      *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
2075      *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
2076      * </p>
2077      *
2078      * <h3>Transfer functions</h3>
2079      * <p>A transfer function is a color component conversion function, defined as
2080      * a single variable, monotonic mathematical function. It is applied to each
2081      * individual component of a color. They are used to perform the mapping
2082      * between linear tristimulus values and non-linear electronic signal value.</p>
2083      * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
2084      * tristimulus values in a scene to a non-linear electronic signal value.
2085      * An OETF is often expressed as a power function with an exponent between
2086      * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
2087      * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
2088      * a non-linear electronic signal value to a tristimulus value at the display.
2089      * An EOTF is often expressed as a power function with an exponent between
2090      * 1.8 and 2.6.</p>
2091      * <p>Transfer functions are used as a compression scheme. For instance,
2092      * linear sRGB values would normally require 11 to 12 bits of precision to
2093      * store all values that can be perceived by the human eye. When encoding
2094      * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
2095      * an exact mathematical description of that OETF), the values can be
2096      * compressed to only 8 bits precision.</p>
2097      * <p>When manipulating RGB values, particularly sRGB values, it is safe
2098      * to assume that these values have been encoded with the appropriate
2099      * OETF (unless noted otherwise). Encoded values are often said to be in
2100      * "gamma space". They are therefore defined in a non-linear space. This
2101      * in turns means that any linear operation applied to these values is
2102      * going to yield mathematically incorrect results (any linear interpolation
2103      * such as gradient generation for instance, most image processing functions
2104      * such as blurs, etc.).</p>
2105      * <p>To properly process encoded RGB values you must first apply the
2106      * EOTF to decode the value into linear space. After processing, the RGB
2107      * value must be encoded back to non-linear ("gamma") space. Here is a
2108      * formal description of the process, where \(f\) is the processing
2109      * function to apply:</p>
2110      *
2111      * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
2112      *
2113      * <p>If the transfer functions of the color space can be expressed as an
2114      * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters
2115      * can be retrieved by calling {@link #getTransferParameters()}. This can
2116      * be useful to match color spaces for instance.</p>
2117      *
2118      * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
2119      * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
2120      * their transfer functions are the identity function: \(f(x) = x\).
2121      * If the source and/or destination are known to be linear, it is not
2122      * necessary to invoke the transfer functions.</p>
2123      *
2124      * <h3>Range</h3>
2125      * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
2126      * are however a few RGB color spaces that allow much larger ranges. For
2127      * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
2128      * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
2129      * the range \([-65504, 65504]\).</p>
2130      *
2131      * <p>
2132      *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
2133      *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
2134      * </p>
2135      *
2136      * <h3>Converting between RGB color spaces</h3>
2137      * <p>Conversion between two color spaces is achieved by using an intermediate
2138      * color space called the profile connection space (PCS). The PCS used by
2139      * this implementation is CIE XYZ. The conversion operation is defined
2140      * as such:</p>
2141      *
2142      * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
2143      *
2144      * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
2145      * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
2146      * XYZ to RGB transform} of the destination color space.</p>
2147      * <p>Many RGB color spaces commonly used with electronic devices use the
2148      * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
2149      * when converting between two RGB color spaces if their white points do not
2150      * match. This can be achieved by either calling
2151      * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
2152      * a single common white point. This can be achieved automatically by calling
2153      * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
2154      * non-RGB color spaces.</p>
2155      * <p>To learn more about the white point adaptation process, refer to the
2156      * documentation of {@link Adaptation}.</p>
2157      */
2158     @AnyThread
2159     public static class Rgb extends ColorSpace {
2160         /**
2161          * {@usesMathJax}
2162          *
2163          * <p>Defines the parameters for the ICC parametric curve type 4, as
2164          * defined in ICC.1:2004-10, section 10.15.</p>
2165          *
2166          * <p>The EOTF is of the form:</p>
2167          *
2168          * \(\begin{equation}
2169          * Y = \begin{cases}c X + f & X \lt d \\\
2170          * \left( a X + b \right) ^{g} + e & X \ge d \end{cases}
2171          * \end{equation}\)
2172          *
2173          * <p>The corresponding OETF is simply the inverse function.</p>
2174          *
2175          * <p>The parameters defined by this class form a valid transfer
2176          * function only if all the following conditions are met:</p>
2177          * <ul>
2178          *     <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li>
2179          *     <li>\(d\) is in the range \([0..1]\)</li>
2180          *     <li>The function is not constant</li>
2181          *     <li>The function is positive and increasing</li>
2182          * </ul>
2183          */
2184         public static class TransferParameters {
2185             /** Variable \(a\) in the equation of the EOTF described above. */
2186             public final double a;
2187             /** Variable \(b\) in the equation of the EOTF described above. */
2188             public final double b;
2189             /** Variable \(c\) in the equation of the EOTF described above. */
2190             public final double c;
2191             /** Variable \(d\) in the equation of the EOTF described above. */
2192             public final double d;
2193             /** Variable \(e\) in the equation of the EOTF described above. */
2194             public final double e;
2195             /** Variable \(f\) in the equation of the EOTF described above. */
2196             public final double f;
2197             /** Variable \(g\) in the equation of the EOTF described above. */
2198             public final double g;
2199 
2200             /**
2201              * <p>Defines the parameters for the ICC parametric curve type 3, as
2202              * defined in ICC.1:2004-10, section 10.15.</p>
2203              *
2204              * <p>The EOTF is of the form:</p>
2205              *
2206              * \(\begin{equation}
2207              * Y = \begin{cases}c X & X \lt d \\\
2208              * \left( a X + b \right) ^{g} & X \ge d \end{cases}
2209              * \end{equation}\)
2210              *
2211              * <p>This constructor is equivalent to setting  \(e\) and \(f\) to 0.</p>
2212              *
2213              * @param a The value of \(a\) in the equation of the EOTF described above
2214              * @param b The value of \(b\) in the equation of the EOTF described above
2215              * @param c The value of \(c\) in the equation of the EOTF described above
2216              * @param d The value of \(d\) in the equation of the EOTF described above
2217              * @param g The value of \(g\) in the equation of the EOTF described above
2218              *
2219              * @throws IllegalArgumentException If the parameters form an invalid transfer function
2220              */
TransferParameters(double a, double b, double c, double d, double g)2221             public TransferParameters(double a, double b, double c, double d, double g) {
2222                 this(a, b, c, d, 0.0, 0.0, g);
2223             }
2224 
2225             /**
2226              * <p>Defines the parameters for the ICC parametric curve type 4, as
2227              * defined in ICC.1:2004-10, section 10.15.</p>
2228              *
2229              * @param a The value of \(a\) in the equation of the EOTF described above
2230              * @param b The value of \(b\) in the equation of the EOTF described above
2231              * @param c The value of \(c\) in the equation of the EOTF described above
2232              * @param d The value of \(d\) in the equation of the EOTF described above
2233              * @param e The value of \(e\) in the equation of the EOTF described above
2234              * @param f The value of \(f\) in the equation of the EOTF described above
2235              * @param g The value of \(g\) in the equation of the EOTF described above
2236              *
2237              * @throws IllegalArgumentException If the parameters form an invalid transfer function
2238              */
TransferParameters(double a, double b, double c, double d, double e, double f, double g)2239             public TransferParameters(double a, double b, double c, double d, double e,
2240                     double f, double g) {
2241 
2242                 if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
2243                         Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
2244                         Double.isNaN(g)) {
2245                     throw new IllegalArgumentException("Parameters cannot be NaN");
2246                 }
2247 
2248                 // Next representable float after 1.0
2249                 // We use doubles here but the representation inside our native code is often floats
2250                 if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
2251                     throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
2252                             "was " + d);
2253                 }
2254 
2255                 if (d == 0.0 && (a == 0.0 || g == 0.0)) {
2256                     throw new IllegalArgumentException(
2257                             "Parameter a or g is zero, the transfer function is constant");
2258                 }
2259 
2260                 if (d >= 1.0 && c == 0.0) {
2261                     throw new IllegalArgumentException(
2262                             "Parameter c is zero, the transfer function is constant");
2263                 }
2264 
2265                 if ((a == 0.0 || g == 0.0) && c == 0.0) {
2266                     throw new IllegalArgumentException("Parameter a or g is zero," +
2267                             " and c is zero, the transfer function is constant");
2268                 }
2269 
2270                 if (c < 0.0) {
2271                     throw new IllegalArgumentException("The transfer function must be increasing");
2272                 }
2273 
2274                 if (a < 0.0 || g < 0.0) {
2275                     throw new IllegalArgumentException("The transfer function must be " +
2276                             "positive or increasing");
2277                 }
2278 
2279                 this.a = a;
2280                 this.b = b;
2281                 this.c = c;
2282                 this.d = d;
2283                 this.e = e;
2284                 this.f = f;
2285                 this.g = g;
2286             }
2287 
2288             @SuppressWarnings("SimplifiableIfStatement")
2289             @Override
equals(Object o)2290             public boolean equals(Object o) {
2291                 if (this == o) return true;
2292                 if (o == null || getClass() != o.getClass()) return false;
2293 
2294                 TransferParameters that = (TransferParameters) o;
2295 
2296                 if (Double.compare(that.a, a) != 0) return false;
2297                 if (Double.compare(that.b, b) != 0) return false;
2298                 if (Double.compare(that.c, c) != 0) return false;
2299                 if (Double.compare(that.d, d) != 0) return false;
2300                 if (Double.compare(that.e, e) != 0) return false;
2301                 if (Double.compare(that.f, f) != 0) return false;
2302                 return Double.compare(that.g, g) == 0;
2303             }
2304 
2305             @Override
hashCode()2306             public int hashCode() {
2307                 int result;
2308                 long temp;
2309                 temp = Double.doubleToLongBits(a);
2310                 result = (int) (temp ^ (temp >>> 32));
2311                 temp = Double.doubleToLongBits(b);
2312                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2313                 temp = Double.doubleToLongBits(c);
2314                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2315                 temp = Double.doubleToLongBits(d);
2316                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2317                 temp = Double.doubleToLongBits(e);
2318                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2319                 temp = Double.doubleToLongBits(f);
2320                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2321                 temp = Double.doubleToLongBits(g);
2322                 result = 31 * result + (int) (temp ^ (temp >>> 32));
2323                 return result;
2324             }
2325         }
2326 
2327         @NonNull private final float[] mWhitePoint;
2328         @NonNull private final float[] mPrimaries;
2329         @NonNull private final float[] mTransform;
2330         @NonNull private final float[] mInverseTransform;
2331 
2332         @NonNull private final DoubleUnaryOperator mOetf;
2333         @NonNull private final DoubleUnaryOperator mEotf;
2334         @NonNull private final DoubleUnaryOperator mClampedOetf;
2335         @NonNull private final DoubleUnaryOperator mClampedEotf;
2336 
2337         private final float mMin;
2338         private final float mMax;
2339 
2340         private final boolean mIsWideGamut;
2341         private final boolean mIsSrgb;
2342 
2343         @Nullable private final TransferParameters mTransferParameters;
2344         private final long mNativePtr;
2345 
2346         @Override
getNativeInstance()2347         long getNativeInstance() {
2348             if (mNativePtr == 0) {
2349                 // If this object has TransferParameters, it must have a native object.
2350                 throw new IllegalArgumentException("ColorSpace must use an ICC "
2351                         + "parametric transfer function! used " + this);
2352             }
2353             return mNativePtr;
2354         }
2355 
nativeGetNativeFinalizer()2356         private static native long nativeGetNativeFinalizer();
nativeCreate(float a, float b, float c, float d, float e, float f, float g, float[] xyz)2357         private static native long nativeCreate(float a, float b, float c, float d,
2358                 float e, float f, float g, float[] xyz);
2359 
2360         /**
2361          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
2362          * The transform matrix must convert from the RGB space to the profile connection
2363          * space CIE XYZ.</p>
2364          *
2365          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
2366          *
2367          * @param name Name of the color space, cannot be null, its length must be >= 1
2368          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
2369          *              connection space CIE XYZ as an array of 9 floats, cannot be null
2370          * @param oetf Opto-electronic transfer function, cannot be null
2371          * @param eotf Electro-optical transfer function, cannot be null
2372          *
2373          * @throws IllegalArgumentException If any of the following conditions is met:
2374          * <ul>
2375          *     <li>The name is null or has a length of 0.</li>
2376          *     <li>The OETF is null or the EOTF is null.</li>
2377          *     <li>The minimum valid value is >= the maximum valid value.</li>
2378          * </ul>
2379          *
2380          * @see #get(Named)
2381          */
Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf)2382         public Rgb(
2383                 @NonNull @Size(min = 1) String name,
2384                 @NonNull @Size(9) float[] toXYZ,
2385                 @NonNull DoubleUnaryOperator oetf,
2386                 @NonNull DoubleUnaryOperator eotf) {
2387             this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null,
2388                     oetf, eotf, 0.0f, 1.0f, null, MIN_ID);
2389         }
2390 
2391         /**
2392          * <p>Creates a new RGB color space using a specified set of primaries
2393          * and a specified white point.</p>
2394          *
2395          * <p>The primaries and white point can be specified in the CIE xyY space
2396          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2397          *
2398          * <table summary="Parameters length">
2399          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2400          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2401          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2402          * </table>
2403          *
2404          * <p>When the primaries and/or white point are specified in xyY, the Y component
2405          * does not need to be specified and is assumed to be 1.0. Only the xy components
2406          * are required.</p>
2407          *
2408          * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
2409          * this constructor is always {@link #MIN_ID}.</p>
2410          *
2411          * @param name Name of the color space, cannot be null, its length must be >= 1
2412          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2413          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2414          * @param oetf Opto-electronic transfer function, cannot be null
2415          * @param eotf Electro-optical transfer function, cannot be null
2416          * @param min The minimum valid value in this color space's RGB range
2417          * @param max The maximum valid value in this color space's RGB range
2418          *
2419          * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
2420          * <ul>
2421          *     <li>The name is null or has a length of 0.</li>
2422          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2423          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2424          *     <li>The OETF is null or the EOTF is null.</li>
2425          *     <li>The minimum valid value is >= the maximum valid value.</li>
2426          * </ul>
2427          *
2428          * @see #get(Named)
2429          */
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)2430         public Rgb(
2431                 @NonNull @Size(min = 1) String name,
2432                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2433                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2434                 @NonNull DoubleUnaryOperator oetf,
2435                 @NonNull DoubleUnaryOperator eotf,
2436                 float min,
2437                 float max) {
2438             this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID);
2439         }
2440 
2441         /**
2442          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
2443          * The transform matrix must convert from the RGB space to the profile connection
2444          * space CIE XYZ.</p>
2445          *
2446          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
2447          *
2448          * @param name Name of the color space, cannot be null, its length must be >= 1
2449          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
2450          *              connection space CIE XYZ as an array of 9 floats, cannot be null
2451          * @param function Parameters for the transfer functions
2452          *
2453          * @throws IllegalArgumentException If any of the following conditions is met:
2454          * <ul>
2455          *     <li>The name is null or has a length of 0.</li>
2456          *     <li>Gamma is negative.</li>
2457          * </ul>
2458          *
2459          * @see #get(Named)
2460          */
Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull TransferParameters function)2461         public Rgb(
2462                 @NonNull @Size(min = 1) String name,
2463                 @NonNull @Size(9) float[] toXYZ,
2464                 @NonNull TransferParameters function) {
2465             // Note: when isGray() returns false, this passes null for the transform for
2466             // consistency with other constructors, which compute the transform from the primaries
2467             // and white point.
2468             this(name, isGray(toXYZ) ? GRAY_PRIMARIES : computePrimaries(toXYZ),
2469                     computeWhitePoint(toXYZ), isGray(toXYZ) ? toXYZ : null, function, MIN_ID);
2470         }
2471 
2472         /**
2473          * <p>Creates a new RGB color space using a specified set of primaries
2474          * and a specified white point.</p>
2475          *
2476          * <p>The primaries and white point can be specified in the CIE xyY space
2477          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2478          *
2479          * <table summary="Parameters length">
2480          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2481          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2482          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2483          * </table>
2484          *
2485          * <p>When the primaries and/or white point are specified in xyY, the Y component
2486          * does not need to be specified and is assumed to be 1.0. Only the xy components
2487          * are required.</p>
2488          *
2489          * @param name Name of the color space, cannot be null, its length must be >= 1
2490          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2491          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2492          * @param function Parameters for the transfer functions
2493          *
2494          * @throws IllegalArgumentException If any of the following conditions is met:
2495          * <ul>
2496          *     <li>The name is null or has a length of 0.</li>
2497          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2498          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2499          *     <li>The transfer parameters are invalid.</li>
2500          * </ul>
2501          *
2502          * @see #get(Named)
2503          */
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)2504         public Rgb(
2505                 @NonNull @Size(min = 1) String name,
2506                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2507                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2508                 @NonNull TransferParameters function) {
2509             this(name, primaries, whitePoint, null, function, MIN_ID);
2510         }
2511 
2512         /**
2513          * <p>Creates a new RGB color space using a specified set of primaries
2514          * and a specified white point.</p>
2515          *
2516          * <p>The primaries and white point can be specified in the CIE xyY space
2517          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2518          *
2519          * <table summary="Parameters length">
2520          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2521          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2522          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2523          * </table>
2524          *
2525          * <p>When the primaries and/or white point are specified in xyY, the Y component
2526          * does not need to be specified and is assumed to be 1.0. Only the xy components
2527          * are required.</p>
2528          *
2529          * @param name Name of the color space, cannot be null, its length must be >= 1
2530          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2531          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2532          * @param transform Computed transform matrix that converts from RGB to XYZ, or
2533          *      {@code null} to compute it from {@code primaries} and {@code whitePoint}.
2534          * @param function Parameters for the transfer functions
2535          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
2536          *
2537          * @throws IllegalArgumentException If any of the following conditions is met:
2538          * <ul>
2539          *     <li>The name is null or has a length of 0.</li>
2540          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2541          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2542          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
2543          *     <li>The transfer parameters are invalid.</li>
2544          * </ul>
2545          *
2546          * @see #get(Named)
2547          */
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)2548         private Rgb(
2549                 @NonNull @Size(min = 1) String name,
2550                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2551                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2552                 @Nullable @Size(9) float[] transform,
2553                 @NonNull TransferParameters function,
2554                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2555             this(name, primaries, whitePoint, transform,
2556                     function.e == 0.0 && function.f == 0.0 ?
2557                             x -> rcpResponse(x, function.a, function.b,
2558                                     function.c, function.d, function.g) :
2559                             x -> rcpResponse(x, function.a, function.b, function.c,
2560                                     function.d, function.e, function.f, function.g),
2561                     function.e == 0.0 && function.f == 0.0 ?
2562                             x -> response(x, function.a, function.b,
2563                                     function.c, function.d, function.g) :
2564                             x -> response(x, function.a, function.b, function.c,
2565                                     function.d, function.e, function.f, function.g),
2566                     0.0f, 1.0f, function, id);
2567         }
2568 
2569         /**
2570          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
2571          * The transform matrix must convert from the RGB space to the profile connection
2572          * space CIE XYZ.</p>
2573          *
2574          * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
2575          *
2576          * @param name Name of the color space, cannot be null, its length must be >= 1
2577          * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
2578          *              connection space CIE XYZ as an array of 9 floats, cannot be null
2579          * @param gamma Gamma to use as the transfer function
2580          *
2581          * @throws IllegalArgumentException If any of the following conditions is met:
2582          * <ul>
2583          *     <li>The name is null or has a length of 0.</li>
2584          *     <li>Gamma is negative.</li>
2585          * </ul>
2586          *
2587          * @see #get(Named)
2588          */
Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, double gamma)2589         public Rgb(
2590                 @NonNull @Size(min = 1) String name,
2591                 @NonNull @Size(9) float[] toXYZ,
2592                 double gamma) {
2593             this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID);
2594         }
2595 
2596         /**
2597          * <p>Creates a new RGB color space using a specified set of primaries
2598          * and a specified white point.</p>
2599          *
2600          * <p>The primaries and white point can be specified in the CIE xyY space
2601          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2602          *
2603          * <table summary="Parameters length">
2604          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2605          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2606          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2607          * </table>
2608          *
2609          * <p>When the primaries and/or white point are specified in xyY, the Y component
2610          * does not need to be specified and is assumed to be 1.0. Only the xy components
2611          * are required.</p>
2612          *
2613          * @param name Name of the color space, cannot be null, its length must be >= 1
2614          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2615          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2616          * @param gamma Gamma to use as the transfer function
2617          *
2618          * @throws IllegalArgumentException If any of the following conditions is met:
2619          * <ul>
2620          *     <li>The name is null or has a length of 0.</li>
2621          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2622          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2623          *     <li>Gamma is negative.</li>
2624          * </ul>
2625          *
2626          * @see #get(Named)
2627          */
Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, double gamma)2628         public Rgb(
2629                 @NonNull @Size(min = 1) String name,
2630                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2631                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2632                 double gamma) {
2633             this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID);
2634         }
2635 
2636         /**
2637          * <p>Creates a new RGB color space using a specified set of primaries
2638          * and a specified white point.</p>
2639          *
2640          * <p>The primaries and white point can be specified in the CIE xyY space
2641          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2642          *
2643          * <table summary="Parameters length">
2644          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2645          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2646          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2647          * </table>
2648          *
2649          * <p>When the primaries and/or white point are specified in xyY, the Y component
2650          * does not need to be specified and is assumed to be 1.0. Only the xy components
2651          * are required.</p>
2652          *
2653          * @param name Name of the color space, cannot be null, its length must be >= 1
2654          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2655          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2656          * @param gamma Gamma to use as the transfer function
2657          * @param min The minimum valid value in this color space's RGB range
2658          * @param max The maximum valid value in this color space's RGB range
2659          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
2660          *
2661          * @throws IllegalArgumentException If any of the following conditions is met:
2662          * <ul>
2663          *     <li>The name is null or has a length of 0.</li>
2664          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2665          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2666          *     <li>The minimum valid value is >= the maximum valid value.</li>
2667          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
2668          *     <li>Gamma is negative.</li>
2669          * </ul>
2670          *
2671          * @see #get(Named)
2672          */
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)2673         private Rgb(
2674                 @NonNull @Size(min = 1) String name,
2675                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2676                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2677                 double gamma,
2678                 float min,
2679                 float max,
2680                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2681             this(name, primaries, whitePoint, null,
2682                     gamma == 1.0 ? DoubleUnaryOperator.identity() :
2683                             x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
2684                     gamma == 1.0 ? DoubleUnaryOperator.identity() :
2685                             x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
2686                     min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id);
2687         }
2688 
2689         /**
2690          * <p>Creates a new RGB color space using a specified set of primaries
2691          * and a specified white point.</p>
2692          *
2693          * <p>The primaries and white point can be specified in the CIE xyY space
2694          * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
2695          *
2696          * <table summary="Parameters length">
2697          *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
2698          *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
2699          *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
2700          * </table>
2701          *
2702          * <p>When the primaries and/or white point are specified in xyY, the Y component
2703          * does not need to be specified and is assumed to be 1.0. Only the xy components
2704          * are required.</p>
2705          *
2706          * @param name Name of the color space, cannot be null, its length must be >= 1
2707          * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
2708          * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
2709          * @param transform Computed transform matrix that converts from RGB to XYZ, or
2710          *      {@code null} to compute it from {@code primaries} and {@code whitePoint}.
2711          * @param oetf Opto-electronic transfer function, cannot be null
2712          * @param eotf Electro-optical transfer function, cannot be null
2713          * @param min The minimum valid value in this color space's RGB range
2714          * @param max The maximum valid value in this color space's RGB range
2715          * @param transferParameters Parameters for the transfer functions
2716          * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
2717          *
2718          * @throws IllegalArgumentException If any of the following conditions is met:
2719          * <ul>
2720          *     <li>The name is null or has a length of 0.</li>
2721          *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
2722          *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
2723          *     <li>The OETF is null or the EOTF is null.</li>
2724          *     <li>The minimum valid value is >= the maximum valid value.</li>
2725          *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
2726          * </ul>
2727          *
2728          * @see #get(Named)
2729          */
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)2730         private Rgb(
2731                 @NonNull @Size(min = 1) String name,
2732                 @NonNull @Size(min = 6, max = 9) float[] primaries,
2733                 @NonNull @Size(min = 2, max = 3) float[] whitePoint,
2734                 @Nullable @Size(9) float[] transform,
2735                 @NonNull DoubleUnaryOperator oetf,
2736                 @NonNull DoubleUnaryOperator eotf,
2737                 float min,
2738                 float max,
2739                 @Nullable TransferParameters transferParameters,
2740                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
2741 
2742             super(name, Model.RGB, id);
2743 
2744             if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
2745                 throw new IllegalArgumentException("The color space's primaries must be " +
2746                         "defined as an array of 6 floats in xyY or 9 floats in XYZ");
2747             }
2748 
2749             if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
2750                 throw new IllegalArgumentException("The color space's white point must be " +
2751                         "defined as an array of 2 floats in xyY or 3 float in XYZ");
2752             }
2753 
2754             if (oetf == null || eotf == null) {
2755                 throw new IllegalArgumentException("The transfer functions of a color space " +
2756                         "cannot be null");
2757             }
2758 
2759             if (min >= max) {
2760                 throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
2761                         "; min must be strictly < max");
2762             }
2763 
2764             mWhitePoint = xyWhitePoint(whitePoint);
2765             mPrimaries =  xyPrimaries(primaries);
2766 
2767             if (transform == null) {
2768                 mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
2769             } else {
2770                 if (transform.length != 9) {
2771                     throw new IllegalArgumentException("Transform must have 9 entries! Has "
2772                             + transform.length);
2773                 }
2774                 mTransform = transform;
2775             }
2776             mInverseTransform = inverse3x3(mTransform);
2777 
2778             mOetf = oetf;
2779             mEotf = eotf;
2780 
2781             mMin = min;
2782             mMax = max;
2783 
2784             DoubleUnaryOperator clamp = this::clamp;
2785             mClampedOetf = oetf.andThen(clamp);
2786             mClampedEotf = clamp.andThen(eotf);
2787 
2788             mTransferParameters = transferParameters;
2789 
2790             // A color space is wide-gamut if its area is >90% of NTSC 1953 and
2791             // if it entirely contains the Color space definition in xyY
2792             mIsWideGamut = isWideGamut(mPrimaries, min, max);
2793             mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
2794 
2795             if (mTransferParameters != null) {
2796                 if (mWhitePoint == null || mTransform == null) {
2797                     throw new IllegalStateException(
2798                             "ColorSpace (" + this + ") cannot create native object! mWhitePoint: "
2799                             + mWhitePoint + " mTransform: " + mTransform);
2800                 }
2801 
2802                 // This mimics the old code that was in native.
2803                 float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
2804                 mNativePtr = nativeCreate((float) mTransferParameters.a,
2805                                           (float) mTransferParameters.b,
2806                                           (float) mTransferParameters.c,
2807                                           (float) mTransferParameters.d,
2808                                           (float) mTransferParameters.e,
2809                                           (float) mTransferParameters.f,
2810                                           (float) mTransferParameters.g,
2811                                           nativeTransform);
2812                 NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
2813             } else {
2814                 mNativePtr = 0;
2815             }
2816         }
2817 
2818         private static class NoImagePreloadHolder {
2819             public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
2820                 ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
2821         }
2822 
2823         /**
2824          * Creates a copy of the specified color space with a new transform.
2825          *
2826          * @param colorSpace The color space to create a copy of
2827          */
Rgb(Rgb colorSpace, @NonNull @Size(9) float[] transform, @NonNull @Size(min = 2, max = 3) float[] whitePoint)2828         private Rgb(Rgb colorSpace,
2829                 @NonNull @Size(9) float[] transform,
2830                 @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
2831             this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform,
2832                     colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax,
2833                     colorSpace.mTransferParameters, MIN_ID);
2834         }
2835 
2836         /**
2837          * Copies the non-adapted CIE xyY white point of this color space in
2838          * specified array. The Y component is assumed to be 1 and is therefore
2839          * not copied into the destination. The x and y components are written
2840          * in the array at positions 0 and 1 respectively.
2841          *
2842          * @param whitePoint The destination array, cannot be null, its length
2843          *                   must be >= 2
2844          *
2845          * @return The destination array passed as a parameter
2846          *
2847          * @see #getWhitePoint()
2848          */
2849         @NonNull
2850         @Size(min = 2)
getWhitePoint(@onNull @izemin = 2) float[] whitePoint)2851         public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
2852             whitePoint[0] = mWhitePoint[0];
2853             whitePoint[1] = mWhitePoint[1];
2854             return whitePoint;
2855         }
2856 
2857         /**
2858          * Returns the non-adapted CIE xyY white point of this color space as
2859          * a new array of 2 floats. The Y component is assumed to be 1 and is
2860          * therefore not copied into the destination. The x and y components
2861          * are written in the array at positions 0 and 1 respectively.
2862          *
2863          * @return A new non-null array of 2 floats
2864          *
2865          * @see #getWhitePoint(float[])
2866          */
2867         @NonNull
2868         @Size(2)
getWhitePoint()2869         public float[] getWhitePoint() {
2870             return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
2871         }
2872 
2873         /**
2874          * Copies the primaries of this color space in specified array. The Y
2875          * component is assumed to be 1 and is therefore not copied into the
2876          * destination. The x and y components of the first primary are written
2877          * in the array at positions 0 and 1 respectively.
2878          *
2879          * <p>Note: Some ColorSpaces represent gray profiles. The concept of
2880          * primaries for such a ColorSpace does not make sense, so we use a special
2881          * set of primaries that are all 1s.</p>
2882          *
2883          * @param primaries The destination array, cannot be null, its length
2884          *                  must be >= 6
2885          *
2886          * @return The destination array passed as a parameter
2887          *
2888          * @see #getPrimaries()
2889          */
2890         @NonNull
2891         @Size(min = 6)
getPrimaries(@onNull @izemin = 6) float[] primaries)2892         public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
2893             System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
2894             return primaries;
2895         }
2896 
2897         /**
2898          * Returns the primaries of this color space as a new array of 6 floats.
2899          * The Y component is assumed to be 1 and is therefore not copied into
2900          * the destination. The x and y components of the first primary are
2901          * written in the array at positions 0 and 1 respectively.
2902          *
2903          * <p>Note: Some ColorSpaces represent gray profiles. The concept of
2904          * primaries for such a ColorSpace does not make sense, so we use a special
2905          * set of primaries that are all 1s.</p>
2906          *
2907          * @return A new non-null array of 2 floats
2908          *
2909          * @see #getPrimaries(float[])
2910          */
2911         @NonNull
2912         @Size(6)
getPrimaries()2913         public float[] getPrimaries() {
2914             return Arrays.copyOf(mPrimaries, mPrimaries.length);
2915         }
2916 
2917         /**
2918          * <p>Copies the transform of this color space in specified array. The
2919          * transform is used to convert from RGB to XYZ (with the same white
2920          * point as this color space). To connect color spaces, you must first
2921          * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2922          * same white point.</p>
2923          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2924          * to convert between color spaces.</p>
2925          *
2926          * @param transform The destination array, cannot be null, its length
2927          *                  must be >= 9
2928          *
2929          * @return The destination array passed as a parameter
2930          *
2931          * @see #getTransform()
2932          */
2933         @NonNull
2934         @Size(min = 9)
getTransform(@onNull @izemin = 9) float[] transform)2935         public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
2936             System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
2937             return transform;
2938         }
2939 
2940         /**
2941          * <p>Returns the transform of this color space as a new array. The
2942          * transform is used to convert from RGB to XYZ (with the same white
2943          * point as this color space). To connect color spaces, you must first
2944          * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
2945          * same white point.</p>
2946          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2947          * to convert between color spaces.</p>
2948          *
2949          * @return A new array of 9 floats
2950          *
2951          * @see #getTransform(float[])
2952          */
2953         @NonNull
2954         @Size(9)
getTransform()2955         public float[] getTransform() {
2956             return Arrays.copyOf(mTransform, mTransform.length);
2957         }
2958 
2959         /**
2960          * <p>Copies the inverse transform of this color space in specified array.
2961          * The inverse transform is used to convert from XYZ to RGB (with the
2962          * same white point as this color space). To connect color spaces, you
2963          * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2964          * to the same white point.</p>
2965          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2966          * to convert between color spaces.</p>
2967          *
2968          * @param inverseTransform The destination array, cannot be null, its length
2969          *                  must be >= 9
2970          *
2971          * @return The destination array passed as a parameter
2972          *
2973          * @see #getInverseTransform()
2974          */
2975         @NonNull
2976         @Size(min = 9)
getInverseTransform(@onNull @izemin = 9) float[] inverseTransform)2977         public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
2978             System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
2979             return inverseTransform;
2980         }
2981 
2982         /**
2983          * <p>Returns the inverse transform of this color space as a new array.
2984          * The inverse transform is used to convert from XYZ to RGB (with the
2985          * same white point as this color space). To connect color spaces, you
2986          * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
2987          * to the same white point.</p>
2988          * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
2989          * to convert between color spaces.</p>
2990          *
2991          * @return A new array of 9 floats
2992          *
2993          * @see #getInverseTransform(float[])
2994          */
2995         @NonNull
2996         @Size(9)
getInverseTransform()2997         public float[] getInverseTransform() {
2998             return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
2999         }
3000 
3001         /**
3002          * <p>Returns the opto-electronic transfer function (OETF) of this color space.
3003          * The inverse function is the electro-optical transfer function (EOTF) returned
3004          * by {@link #getEotf()}. These functions are defined to satisfy the following
3005          * equality for \(x \in [0..1]\):</p>
3006          *
3007          * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
3008          *
3009          * <p>For RGB colors, this function can be used to convert from linear space
3010          * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
3011          * are frequently used because many OETFs can be closely approximated using
3012          * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
3013          * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
3014          * for instance).</p>
3015          *
3016          * @return A transfer function that converts from linear space to "gamma space"
3017          *
3018          * @see #getEotf()
3019          * @see #getTransferParameters()
3020          */
3021         @NonNull
getOetf()3022         public DoubleUnaryOperator getOetf() {
3023             return mClampedOetf;
3024         }
3025 
3026         /**
3027          * <p>Returns the electro-optical transfer function (EOTF) of this color space.
3028          * The inverse function is the opto-electronic transfer function (OETF)
3029          * returned by {@link #getOetf()}. These functions are defined to satisfy the
3030          * following equality for \(x \in [0..1]\):</p>
3031          *
3032          * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
3033          *
3034          * <p>For RGB colors, this function can be used to convert from "gamma space"
3035          * (gamma encoded) to linear space. The terms gamma space and gamma encoded
3036          * are frequently used because many EOTFs can be closely approximated using
3037          * a simple power function of the form \(x^\gamma\) (the approximation of the
3038          * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
3039          *
3040          * @return A transfer function that converts from "gamma space" to linear space
3041          *
3042          * @see #getOetf()
3043          * @see #getTransferParameters()
3044          */
3045         @NonNull
getEotf()3046         public DoubleUnaryOperator getEotf() {
3047             return mClampedEotf;
3048         }
3049 
3050         /**
3051          * <p>Returns the parameters used by the {@link #getEotf() electro-optical}
3052          * and {@link #getOetf() opto-electronic} transfer functions. If the transfer
3053          * functions do not match the ICC parametric curves defined in ICC.1:2004-10
3054          * (section 10.15), this method returns null.</p>
3055          *
3056          * <p>See {@link TransferParameters} for a full description of the transfer
3057          * functions.</p>
3058          *
3059          * @return An instance of {@link TransferParameters} or null if this color
3060          *         space's transfer functions do not match the equation defined in
3061          *         {@link TransferParameters}
3062          */
3063         @Nullable
getTransferParameters()3064         public TransferParameters getTransferParameters() {
3065             return mTransferParameters;
3066         }
3067 
3068         @Override
isSrgb()3069         public boolean isSrgb() {
3070             return mIsSrgb;
3071         }
3072 
3073         @Override
isWideGamut()3074         public boolean isWideGamut() {
3075             return mIsWideGamut;
3076         }
3077 
3078         @Override
getMinValue(int component)3079         public float getMinValue(int component) {
3080             return mMin;
3081         }
3082 
3083         @Override
getMaxValue(int component)3084         public float getMaxValue(int component) {
3085             return mMax;
3086         }
3087 
3088         /**
3089          * <p>Decodes an RGB value to linear space. This is achieved by
3090          * applying this color space's electro-optical transfer function
3091          * to the supplied values.</p>
3092          *
3093          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
3094          * more information about transfer functions and their use for
3095          * encoding and decoding RGB values.</p>
3096          *
3097          * @param r The red component to decode to linear space
3098          * @param g The green component to decode to linear space
3099          * @param b The blue component to decode to linear space
3100          * @return A new array of 3 floats containing linear RGB values
3101          *
3102          * @see #toLinear(float[])
3103          * @see #fromLinear(float, float, float)
3104          */
3105         @NonNull
3106         @Size(3)
toLinear(float r, float g, float b)3107         public float[] toLinear(float r, float g, float b) {
3108             return toLinear(new float[] { r, g, b });
3109         }
3110 
3111         /**
3112          * <p>Decodes an RGB value to linear space. This is achieved by
3113          * applying this color space's electro-optical transfer function
3114          * to the first 3 values of the supplied array. The result is
3115          * stored back in the input array.</p>
3116          *
3117          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
3118          * more information about transfer functions and their use for
3119          * encoding and decoding RGB values.</p>
3120          *
3121          * @param v A non-null array of non-linear RGB values, its length
3122          *          must be at least 3
3123          * @return The specified array
3124          *
3125          * @see #toLinear(float, float, float)
3126          * @see #fromLinear(float[])
3127          */
3128         @NonNull
3129         @Size(min = 3)
toLinear(@onNull @izemin = 3) float[] v)3130         public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
3131             v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
3132             v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
3133             v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
3134             return v;
3135         }
3136 
3137         /**
3138          * <p>Encodes an RGB value from linear space to this color space's
3139          * "gamma space". This is achieved by applying this color space's
3140          * opto-electronic transfer function to the supplied values.</p>
3141          *
3142          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
3143          * more information about transfer functions and their use for
3144          * encoding and decoding RGB values.</p>
3145          *
3146          * @param r The red component to encode from linear space
3147          * @param g The green component to encode from linear space
3148          * @param b The blue component to encode from linear space
3149          * @return A new array of 3 floats containing non-linear RGB values
3150          *
3151          * @see #fromLinear(float[])
3152          * @see #toLinear(float, float, float)
3153          */
3154         @NonNull
3155         @Size(3)
fromLinear(float r, float g, float b)3156         public float[] fromLinear(float r, float g, float b) {
3157             return fromLinear(new float[] { r, g, b });
3158         }
3159 
3160         /**
3161          * <p>Encodes an RGB value from linear space to this color space's
3162          * "gamma space". This is achieved by applying this color space's
3163          * opto-electronic transfer function to the first 3 values of the
3164          * supplied array. The result is stored back in the input array.</p>
3165          *
3166          * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
3167          * more information about transfer functions and their use for
3168          * encoding and decoding RGB values.</p>
3169          *
3170          * @param v A non-null array of linear RGB values, its length
3171          *          must be at least 3
3172          * @return A new array of 3 floats containing non-linear RGB values
3173          *
3174          * @see #fromLinear(float[])
3175          * @see #toLinear(float, float, float)
3176          */
3177         @NonNull
3178         @Size(min = 3)
fromLinear(@onNull @izemin = 3) float[] v)3179         public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
3180             v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
3181             v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
3182             v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
3183             return v;
3184         }
3185 
3186         @Override
3187         @NonNull
3188         @Size(min = 3)
toXyz(@onNull @izemin = 3) float[] v)3189         public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
3190             v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
3191             v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
3192             v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
3193             return mul3x3Float3(mTransform, v);
3194         }
3195 
3196         @Override
3197         @NonNull
3198         @Size(min = 3)
fromXyz(@onNull @izemin = 3) float[] v)3199         public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
3200             mul3x3Float3(mInverseTransform, 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 
clamp(double x)3207         private double clamp(double x) {
3208             return x < mMin ? mMin : x > mMax ? mMax : x;
3209         }
3210 
3211         @Override
equals(Object o)3212         public boolean equals(Object o) {
3213             if (this == o) return true;
3214             if (o == null || getClass() != o.getClass()) return false;
3215             if (!super.equals(o)) return false;
3216 
3217             Rgb rgb = (Rgb) o;
3218 
3219             if (Float.compare(rgb.mMin, mMin) != 0) return false;
3220             if (Float.compare(rgb.mMax, mMax) != 0) return false;
3221             if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
3222             if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
3223             if (mTransferParameters != null) {
3224                 return mTransferParameters.equals(rgb.mTransferParameters);
3225             } else if (rgb.mTransferParameters == null) {
3226                 return true;
3227             }
3228             //noinspection SimplifiableIfStatement
3229             if (!mOetf.equals(rgb.mOetf)) return false;
3230             return mEotf.equals(rgb.mEotf);
3231         }
3232 
3233         @Override
hashCode()3234         public int hashCode() {
3235             int result = super.hashCode();
3236             result = 31 * result + Arrays.hashCode(mWhitePoint);
3237             result = 31 * result + Arrays.hashCode(mPrimaries);
3238             result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
3239             result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
3240             result = 31 * result +
3241                     (mTransferParameters != null ? mTransferParameters.hashCode() : 0);
3242             if (mTransferParameters == null) {
3243                 result = 31 * result + mOetf.hashCode();
3244                 result = 31 * result + mEotf.hashCode();
3245             }
3246             return result;
3247         }
3248 
3249         /**
3250          * Computes whether a color space is the sRGB color space or at least
3251          * a close approximation.
3252          *
3253          * @param primaries The set of RGB primaries in xyY as an array of 6 floats
3254          * @param whitePoint The white point in xyY as an array of 2 floats
3255          * @param OETF The opto-electronic transfer function
3256          * @param EOTF The electro-optical transfer function
3257          * @param min The minimum value of the color space's range
3258          * @param max The minimum value of the color space's range
3259          * @param id The ID of the color space
3260          * @return True if the color space can be considered as the sRGB color space
3261          *
3262          * @see #isSrgb()
3263          */
3264         @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)3265         private static boolean isSrgb(
3266                 @NonNull @Size(6) float[] primaries,
3267                 @NonNull @Size(2) float[] whitePoint,
3268                 @NonNull DoubleUnaryOperator OETF,
3269                 @NonNull DoubleUnaryOperator EOTF,
3270                 float min,
3271                 float max,
3272                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
3273             if (id == 0) return true;
3274             if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) {
3275                 return false;
3276             }
3277             if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) {
3278                 return false;
3279             }
3280 
3281             if (min != 0.0f) return false;
3282             if (max != 1.0f) return false;
3283 
3284             // We would have already returned true if this was SRGB itself, so
3285             // it is safe to reference it here.
3286             ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB);
3287 
3288             for (double x = 0.0; x <= 1.0; x += 1 / 255.0) {
3289                 if (!compare(x, OETF, srgb.mOetf)) return false;
3290                 if (!compare(x, EOTF, srgb.mEotf)) return false;
3291             }
3292 
3293             return true;
3294         }
3295 
3296         /**
3297          * Report whether this matrix is a special gray matrix.
3298          * @param toXYZ A XYZD50 matrix. Skia uses a special form for a gray profile.
3299          * @return true if this is a special gray matrix.
3300          */
isGray(@onNull @ize9) float[] toXYZ)3301         private static boolean isGray(@NonNull @Size(9) float[] toXYZ) {
3302             return toXYZ.length == 9 && toXYZ[1] == 0 && toXYZ[2] == 0 && toXYZ[3] == 0
3303                     && toXYZ[5] == 0 && toXYZ[6] == 0 && toXYZ[7] == 0;
3304         }
3305 
compare(double point, @NonNull DoubleUnaryOperator a, @NonNull DoubleUnaryOperator b)3306         private static boolean compare(double point, @NonNull DoubleUnaryOperator a,
3307                 @NonNull DoubleUnaryOperator b) {
3308             double rA = a.applyAsDouble(point);
3309             double rB = b.applyAsDouble(point);
3310             return Math.abs(rA - rB) <= 1e-3;
3311         }
3312 
3313         /**
3314          * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
3315          * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
3316          * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
3317          * If the conditions above are not met, the color space is considered as having
3318          * a wide color gamut if its range is larger than [0..1].
3319          *
3320          * @param primaries RGB primaries in CIE xyY as an array of 6 floats
3321          * @param min The minimum value of the color space's range
3322          * @param max The minimum value of the color space's range
3323          * @return True if the color space has a wide gamut, false otherwise
3324          *
3325          * @see #isWideGamut()
3326          * @see #area(float[])
3327          */
isWideGamut(@onNull @ize6) float[] primaries, float min, float max)3328         private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
3329                 float min, float max) {
3330             return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
3331                             contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
3332         }
3333 
3334         /**
3335          * Computes the area of the triangle represented by a set of RGB primaries
3336          * in the CIE xyY space.
3337          *
3338          * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
3339          * @return The area of the triangle
3340          *
3341          * @see #isWideGamut(float[], float, float)
3342          */
area(@onNull @ize6) float[] primaries)3343         private static float area(@NonNull @Size(6) float[] primaries) {
3344             float Rx = primaries[0];
3345             float Ry = primaries[1];
3346             float Gx = primaries[2];
3347             float Gy = primaries[3];
3348             float Bx = primaries[4];
3349             float By = primaries[5];
3350             float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
3351             float r = 0.5f * det;
3352             return r < 0.0f ? -r : r;
3353         }
3354 
3355         /**
3356          * Computes the cross product of two 2D vectors.
3357          *
3358          * @param ax The x coordinate of the first vector
3359          * @param ay The y coordinate of the first vector
3360          * @param bx The x coordinate of the second vector
3361          * @param by The y coordinate of the second vector
3362          * @return The result of a x b
3363          */
cross(float ax, float ay, float bx, float by)3364         private static float cross(float ax, float ay, float bx, float by) {
3365             return ax * by - ay * bx;
3366         }
3367 
3368         /**
3369          * Decides whether a 2D triangle, identified by the 6 coordinates of its
3370          * 3 vertices, is contained within another 2D triangle, also identified
3371          * by the 6 coordinates of its 3 vertices.
3372          *
3373          * In the illustration below, we want to test whether the RGB triangle
3374          * is contained within the triangle XYZ formed by the 3 vertices at
3375          * the "+" locations.
3376          *
3377          *                                     Y     .
3378          *                                 .   +    .
3379          *                                  .     ..
3380          *                                   .   .
3381          *                                    . .
3382          *                                     .  G
3383          *                                     *
3384          *                                    * *
3385          *                                  **   *
3386          *                                 *      **
3387          *                                *         *
3388          *                              **           *
3389          *                             *              *
3390          *                            *                *
3391          *                          **                  *
3392          *                         *                     *
3393          *                        *                       **
3394          *                      **                          *   R    ...
3395          *                     *                             *  .....
3396          *                    *                         ***** ..
3397          *                  **              ************       .   +
3398          *              B  *    ************                    .   X
3399          *           ......*****                                 .
3400          *     ......    .                                        .
3401          *             ..
3402          *        +   .
3403          *      Z    .
3404          *
3405          * RGB is contained within XYZ if all the following conditions are true
3406          * (with "x" the cross product operator):
3407          *
3408          *   -->  -->
3409          *   GR x RX >= 0
3410          *   -->  -->
3411          *   RX x BR >= 0
3412          *   -->  -->
3413          *   RG x GY >= 0
3414          *   -->  -->
3415          *   GY x RG >= 0
3416          *   -->  -->
3417          *   RB x BZ >= 0
3418          *   -->  -->
3419          *   BZ x GB >= 0
3420          *
3421          * @param p1 The enclosing triangle
3422          * @param p2 The enclosed triangle
3423          * @return True if the triangle p1 contains the triangle p2
3424          *
3425          * @see #isWideGamut(float[], float, float)
3426          */
3427         @SuppressWarnings("RedundantIfStatement")
contains(@onNull @ize6) float[] p1, @NonNull @Size(6) float[] p2)3428         private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
3429             // Translate the vertices p1 in the coordinates system
3430             // with the vertices p2 as the origin
3431             float[] p0 = new float[] {
3432                     p1[0] - p2[0], p1[1] - p2[1],
3433                     p1[2] - p2[2], p1[3] - p2[3],
3434                     p1[4] - p2[4], p1[5] - p2[5],
3435             };
3436             // Check the first vertex of p1
3437             if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
3438                     cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
3439                 return false;
3440             }
3441             // Check the second vertex of p1
3442             if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
3443                     cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
3444                 return false;
3445             }
3446             // Check the third vertex of p1
3447             if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
3448                     cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
3449                 return false;
3450             }
3451             return true;
3452         }
3453 
3454         /**
3455          * Computes the primaries  of a color space identified only by
3456          * its RGB->XYZ transform matrix. This method assumes that the
3457          * range of the color space is [0..1].
3458          *
3459          * @param toXYZ The color space's 3x3 transform matrix to XYZ
3460          * @return A new array of 6 floats containing the color space's
3461          *         primaries in CIE xyY
3462          */
3463         @NonNull
3464         @Size(6)
computePrimaries(@onNull @ize9) float[] toXYZ)3465         private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) {
3466             float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f });
3467             float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f });
3468             float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f });
3469 
3470             float rSum = r[0] + r[1] + r[2];
3471             float gSum = g[0] + g[1] + g[2];
3472             float bSum = b[0] + b[1] + b[2];
3473 
3474             return new float[] {
3475                     r[0] / rSum, r[1] / rSum,
3476                     g[0] / gSum, g[1] / gSum,
3477                     b[0] / bSum, b[1] / bSum,
3478             };
3479         }
3480 
3481         /**
3482          * Computes the white point of a color space identified only by
3483          * its RGB->XYZ transform matrix. This method assumes that the
3484          * range of the color space is [0..1].
3485          *
3486          * @param toXYZ The color space's 3x3 transform matrix to XYZ
3487          * @return A new array of 2 floats containing the color space's
3488          *         white point in CIE xyY
3489          */
3490         @NonNull
3491         @Size(2)
computeWhitePoint(@onNull @ize9) float[] toXYZ)3492         private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) {
3493             float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f });
3494             float sum = w[0] + w[1] + w[2];
3495             return new float[] { w[0] / sum, w[1] / sum };
3496         }
3497 
3498         /**
3499          * Converts the specified RGB primaries point to xyY if needed. The primaries
3500          * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
3501          * (in CIE XYZ). If no conversion is needed, the input array is copied.
3502          *
3503          * @param primaries The primaries in xyY or XYZ
3504          * @return A new array of 6 floats containing the primaries in xyY
3505          */
3506         @NonNull
3507         @Size(6)
xyPrimaries(@onNull @izemin = 6, max = 9) float[] primaries)3508         private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
3509             float[] xyPrimaries = new float[6];
3510 
3511             // XYZ to xyY
3512             if (primaries.length == 9) {
3513                 float sum;
3514 
3515                 sum = primaries[0] + primaries[1] + primaries[2];
3516                 xyPrimaries[0] = primaries[0] / sum;
3517                 xyPrimaries[1] = primaries[1] / sum;
3518 
3519                 sum = primaries[3] + primaries[4] + primaries[5];
3520                 xyPrimaries[2] = primaries[3] / sum;
3521                 xyPrimaries[3] = primaries[4] / sum;
3522 
3523                 sum = primaries[6] + primaries[7] + primaries[8];
3524                 xyPrimaries[4] = primaries[6] / sum;
3525                 xyPrimaries[5] = primaries[7] / sum;
3526             } else {
3527                 System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
3528             }
3529 
3530             return xyPrimaries;
3531         }
3532 
3533         /**
3534          * Converts the specified white point to xyY if needed. The white point
3535          * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
3536          * (in CIE XYZ). If no conversion is needed, the input array is copied.
3537          *
3538          * @param whitePoint The white point in xyY or XYZ
3539          * @return A new array of 2 floats containing the white point in xyY
3540          */
3541         @NonNull
3542         @Size(2)
xyWhitePoint(@izemin = 2, max = 3) float[] whitePoint)3543         private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
3544             float[] xyWhitePoint = new float[2];
3545 
3546             // XYZ to xyY
3547             if (whitePoint.length == 3) {
3548                 float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
3549                 xyWhitePoint[0] = whitePoint[0] / sum;
3550                 xyWhitePoint[1] = whitePoint[1] / sum;
3551             } else {
3552                 System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
3553             }
3554 
3555             return xyWhitePoint;
3556         }
3557 
3558         /**
3559          * Computes the matrix that converts from RGB to XYZ based on RGB
3560          * primaries and a white point, both specified in the CIE xyY space.
3561          * The Y component of the primaries and white point is implied to be 1.
3562          *
3563          * @param primaries The RGB primaries in xyY, as an array of 6 floats
3564          * @param whitePoint The white point in xyY, as an array of 2 floats
3565          * @return A 3x3 matrix as a new array of 9 floats
3566          */
3567         @NonNull
3568         @Size(9)
computeXYZMatrix( @onNull @ize6) float[] primaries, @NonNull @Size(2) float[] whitePoint)3569         private static float[] computeXYZMatrix(
3570                 @NonNull @Size(6) float[] primaries,
3571                 @NonNull @Size(2) float[] whitePoint) {
3572             float Rx = primaries[0];
3573             float Ry = primaries[1];
3574             float Gx = primaries[2];
3575             float Gy = primaries[3];
3576             float Bx = primaries[4];
3577             float By = primaries[5];
3578             float Wx = whitePoint[0];
3579             float Wy = whitePoint[1];
3580 
3581             float oneRxRy = (1 - Rx) / Ry;
3582             float oneGxGy = (1 - Gx) / Gy;
3583             float oneBxBy = (1 - Bx) / By;
3584             float oneWxWy = (1 - Wx) / Wy;
3585 
3586             float RxRy = Rx / Ry;
3587             float GxGy = Gx / Gy;
3588             float BxBy = Bx / By;
3589             float WxWy = Wx / Wy;
3590 
3591             float BY =
3592                     ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
3593                     ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
3594             float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
3595             float RY = 1 - GY - BY;
3596 
3597             float RYRy = RY / Ry;
3598             float GYGy = GY / Gy;
3599             float BYBy = BY / By;
3600 
3601             return new float[] {
3602                     RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
3603                     GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
3604                     BYBy * Bx, BY, BYBy * (1 - Bx - By)
3605             };
3606         }
3607     }
3608 
3609     /**
3610      * {@usesMathJax}
3611      *
3612      * <p>A connector transforms colors from a source color space to a destination
3613      * color space.</p>
3614      *
3615      * <p>A source color space is connected to a destination color space using the
3616      * color transform \(C\) computed from their respective transforms noted
3617      * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
3618      *
3619      * $$C = T^{-1}_{dst} . T_{src}$$
3620      *
3621      * <p>The transform \(C\) shown above is only valid when the source and
3622      * destination color spaces have the same profile connection space (PCS).
3623      * We know that instances of {@link ColorSpace} always use CIE XYZ as their
3624      * PCS but their white points might differ. When they do, we must perform
3625      * a chromatic adaptation of the color spaces' transforms. To do so, we
3626      * use the von Kries method described in the documentation of {@link Adaptation},
3627      * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
3628      * as the target white point.</p>
3629      *
3630      * <p>Example of conversion from {@link Named#SRGB sRGB} to
3631      * {@link Named#DCI_P3 DCI-P3}:</p>
3632      *
3633      * <pre class="prettyprint">
3634      * ColorSpace.Connector connector = ColorSpace.connect(
3635      *         ColorSpace.get(ColorSpace.Named.SRGB),
3636      *         ColorSpace.get(ColorSpace.Named.DCI_P3));
3637      * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
3638      * // p3 contains { 0.9473, 0.2740, 0.2076 }
3639      * </pre>
3640      *
3641      * @see Adaptation
3642      * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
3643      * @see ColorSpace#adapt(ColorSpace, float[])
3644      * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
3645      * @see ColorSpace#connect(ColorSpace, ColorSpace)
3646      * @see ColorSpace#connect(ColorSpace, RenderIntent)
3647      * @see ColorSpace#connect(ColorSpace)
3648      */
3649     @AnyThread
3650     public static class Connector {
3651         @NonNull private final ColorSpace mSource;
3652         @NonNull private final ColorSpace mDestination;
3653         @NonNull private final ColorSpace mTransformSource;
3654         @NonNull private final ColorSpace mTransformDestination;
3655         @NonNull private final RenderIntent mIntent;
3656         @NonNull @Size(3) private final float[] mTransform;
3657 
3658         /**
3659          * Creates a new connector between a source and a destination color space.
3660          *
3661          * @param source The source color space, cannot be null
3662          * @param destination The destination color space, cannot be null
3663          * @param intent The render intent to use when compressing gamuts
3664          */
Connector(@onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent)3665         Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
3666                 @NonNull RenderIntent intent) {
3667             this(source, destination,
3668                     source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
3669                     destination.getModel() == Model.RGB ?
3670                             adapt(destination, ILLUMINANT_D50_XYZ) : destination,
3671                     intent, computeTransform(source, destination, intent));
3672         }
3673 
3674         /**
3675          * To connect between color spaces, we might need to use adapted transforms.
3676          * This should be transparent to the user so this constructor takes the
3677          * original source and destinations (returned by the getters), as well as
3678          * possibly adapted color spaces used by transform().
3679          */
Connector( @onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform)3680         private Connector(
3681                 @NonNull ColorSpace source, @NonNull ColorSpace destination,
3682                 @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
3683                 @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
3684             mSource = source;
3685             mDestination = destination;
3686             mTransformSource = transformSource;
3687             mTransformDestination = transformDestination;
3688             mIntent = intent;
3689             mTransform = transform;
3690         }
3691 
3692         /**
3693          * Computes an extra transform to apply in XYZ space depending on the
3694          * selected rendering intent.
3695          */
3696         @Nullable
computeTransform(@onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent)3697         private static float[] computeTransform(@NonNull ColorSpace source,
3698                 @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
3699             if (intent != RenderIntent.ABSOLUTE) return null;
3700 
3701             boolean srcRGB = source.getModel() == Model.RGB;
3702             boolean dstRGB = destination.getModel() == Model.RGB;
3703 
3704             if (srcRGB && dstRGB) return null;
3705 
3706             if (srcRGB || dstRGB) {
3707                 ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
3708                 float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
3709                 float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
3710                 return new float[] {
3711                         srcXYZ[0] / dstXYZ[0],
3712                         srcXYZ[1] / dstXYZ[1],
3713                         srcXYZ[2] / dstXYZ[2],
3714                 };
3715             }
3716 
3717             return null;
3718         }
3719 
3720         /**
3721          * Returns the source color space this connector will convert from.
3722          *
3723          * @return A non-null instance of {@link ColorSpace}
3724          *
3725          * @see #getDestination()
3726          */
3727         @NonNull
getSource()3728         public ColorSpace getSource() {
3729             return mSource;
3730         }
3731 
3732         /**
3733          * Returns the destination color space this connector will convert to.
3734          *
3735          * @return A non-null instance of {@link ColorSpace}
3736          *
3737          * @see #getSource()
3738          */
3739         @NonNull
getDestination()3740         public ColorSpace getDestination() {
3741             return mDestination;
3742         }
3743 
3744         /**
3745          * Returns the render intent this connector will use when mapping the
3746          * source color space to the destination color space.
3747          *
3748          * @return A non-null {@link RenderIntent}
3749          *
3750          * @see RenderIntent
3751          */
getRenderIntent()3752         public RenderIntent getRenderIntent() {
3753             return mIntent;
3754         }
3755 
3756         /**
3757          * <p>Transforms the specified color from the source color space
3758          * to a color in the destination color space. This convenience
3759          * method assumes a source color model with 3 components
3760          * (typically RGB). To transform from color models with more than
3761          * 3 components, such as {@link Model#CMYK CMYK}, use
3762          * {@link #transform(float[])} instead.</p>
3763          *
3764          * @param r The red component of the color to transform
3765          * @param g The green component of the color to transform
3766          * @param b The blue component of the color to transform
3767          * @return A new array of 3 floats containing the specified color
3768          *         transformed from the source space to the destination space
3769          *
3770          * @see #transform(float[])
3771          */
3772         @NonNull
3773         @Size(3)
transform(float r, float g, float b)3774         public float[] transform(float r, float g, float b) {
3775             return transform(new float[] { r, g, b });
3776         }
3777 
3778         /**
3779          * <p>Transforms the specified color from the source color space
3780          * to a color in the destination color space.</p>
3781          *
3782          * @param v A non-null array of 3 floats containing the value to transform
3783          *            and that will hold the result of the transform
3784          * @return The v array passed as a parameter, containing the specified color
3785          *         transformed from the source space to the destination space
3786          *
3787          * @see #transform(float, float, float)
3788          */
3789         @NonNull
3790         @Size(min = 3)
transform(@onNull @izemin = 3) float[] v)3791         public float[] transform(@NonNull @Size(min = 3) float[] v) {
3792             float[] xyz = mTransformSource.toXyz(v);
3793             if (mTransform != null) {
3794                 xyz[0] *= mTransform[0];
3795                 xyz[1] *= mTransform[1];
3796                 xyz[2] *= mTransform[2];
3797             }
3798             return mTransformDestination.fromXyz(xyz);
3799         }
3800 
3801         /**
3802          * Optimized connector for RGB->RGB conversions.
3803          */
3804         private static class Rgb extends Connector {
3805             @NonNull private final ColorSpace.Rgb mSource;
3806             @NonNull private final ColorSpace.Rgb mDestination;
3807             @NonNull private final float[] mTransform;
3808 
Rgb(@onNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent)3809             Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
3810                     @NonNull RenderIntent intent) {
3811                 super(source, destination, source, destination, intent, null);
3812                 mSource = source;
3813                 mDestination = destination;
3814                 mTransform = computeTransform(source, destination, intent);
3815             }
3816 
3817             @Override
transform(@onNull @izemin = 3) float[] rgb)3818             public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
3819                 rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
3820                 rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
3821                 rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
3822                 mul3x3Float3(mTransform, rgb);
3823                 rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
3824                 rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
3825                 rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
3826                 return rgb;
3827             }
3828 
3829             /**
3830              * <p>Computes the color transform that connects two RGB color spaces.</p>
3831              *
3832              * <p>We can only connect color spaces if they use the same profile
3833              * connection space. We assume the connection space is always
3834              * CIE XYZ but we maye need to perform a chromatic adaptation to
3835              * match the white points. If an adaptation is needed, we use the
3836              * CIE standard illuminant D50. The unmatched color space is adapted
3837              * using the von Kries transform and the {@link Adaptation#BRADFORD}
3838              * matrix.</p>
3839              *
3840              * @param source The source color space, cannot be null
3841              * @param destination The destination color space, cannot be null
3842              * @param intent The render intent to use when compressing gamuts
3843              * @return An array of 9 floats containing the 3x3 matrix transform
3844              */
3845             @NonNull
3846             @Size(9)
computeTransform( @onNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent)3847             private static float[] computeTransform(
3848                     @NonNull ColorSpace.Rgb source,
3849                     @NonNull ColorSpace.Rgb destination,
3850                     @NonNull RenderIntent intent) {
3851                 if (compare(source.mWhitePoint, destination.mWhitePoint)) {
3852                     // RGB->RGB using the PCS of both color spaces since they have the same
3853                     return mul3x3(destination.mInverseTransform, source.mTransform);
3854                 } else {
3855                     // RGB->RGB using CIE XYZ D50 as the PCS
3856                     float[] transform = source.mTransform;
3857                     float[] inverseTransform = destination.mInverseTransform;
3858 
3859                     float[] srcXYZ = xyYToXyz(source.mWhitePoint);
3860                     float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
3861 
3862                     if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
3863                         float[] srcAdaptation = chromaticAdaptation(
3864                                 Adaptation.BRADFORD.mTransform, srcXYZ,
3865                                 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3866                         transform = mul3x3(srcAdaptation, source.mTransform);
3867                     }
3868 
3869                     if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
3870                         float[] dstAdaptation = chromaticAdaptation(
3871                                 Adaptation.BRADFORD.mTransform, dstXYZ,
3872                                 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
3873                         inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
3874                     }
3875 
3876                     if (intent == RenderIntent.ABSOLUTE) {
3877                         transform = mul3x3Diag(
3878                                 new float[] {
3879                                         srcXYZ[0] / dstXYZ[0],
3880                                         srcXYZ[1] / dstXYZ[1],
3881                                         srcXYZ[2] / dstXYZ[2],
3882                                 }, transform);
3883                     }
3884 
3885                     return mul3x3(inverseTransform, transform);
3886                 }
3887             }
3888         }
3889 
3890         /**
3891          * Returns the identity connector for a given color space.
3892          *
3893          * @param source The source and destination color space
3894          * @return A non-null connector that does not perform any transform
3895          *
3896          * @see ColorSpace#connect(ColorSpace, ColorSpace)
3897          */
identity(ColorSpace source)3898         static Connector identity(ColorSpace source) {
3899             return new Connector(source, source, RenderIntent.RELATIVE) {
3900                 @Override
3901                 public float[] transform(@NonNull @Size(min = 3) float[] v) {
3902                     return v;
3903                 }
3904             };
3905         }
3906     }
3907 }
3908