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