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 package android.graphics.cts;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertSame;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.graphics.ColorSpace;
27 import android.support.test.filters.SmallTest;
28 import android.support.test.runner.AndroidJUnit4;
29 
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 
33 import java.util.Arrays;
34 import java.util.function.DoubleUnaryOperator;
35 
36 @SmallTest
37 @RunWith(AndroidJUnit4.class)
38 public class ColorSpaceTest {
39     // Column-major RGB->XYZ transform matrix for the sRGB color space
40     private static final float[] SRGB_TO_XYZ = {
41             0.412391f, 0.212639f, 0.019331f,
42             0.357584f, 0.715169f, 0.119195f,
43             0.180481f, 0.072192f, 0.950532f
44     };
45     // Column-major XYZ->RGB transform matrix for the sRGB color space
46     private static final float[] XYZ_TO_SRGB = {
47             3.240970f, -0.969244f,  0.055630f,
48            -1.537383f,  1.875968f, -0.203977f,
49            -0.498611f,  0.041555f,  1.056971f
50     };
51 
52     // Column-major RGB->XYZ transform matrix for the sRGB color space and a D50 white point
53     private static final float[] SRGB_TO_XYZ_D50 = {
54             0.4360747f, 0.2225045f, 0.0139322f,
55             0.3850649f, 0.7168786f, 0.0971045f,
56             0.1430804f, 0.0606169f, 0.7141733f
57     };
58 
59     private static final float[] SRGB_PRIMARIES_xyY =
60             { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
61     private static final float[] SRGB_WHITE_POINT_xyY = { 0.3127f, 0.3290f };
62 
63     private static final float[] SRGB_PRIMARIES_XYZ = {
64             1.939394f, 1.000000f, 0.090909f,
65             0.500000f, 1.000000f, 0.166667f,
66             2.500000f, 1.000000f, 13.166667f
67     };
68     private static final float[] SRGB_WHITE_POINT_XYZ = { 0.950456f, 1.000f, 1.089058f };
69 
70     private static final DoubleUnaryOperator sIdentity = DoubleUnaryOperator.identity();
71 
72     @Test
testNamedColorSpaces()73     public void testNamedColorSpaces() {
74         for (ColorSpace.Named named : ColorSpace.Named.values()) {
75             ColorSpace colorSpace = ColorSpace.get(named);
76             assertNotNull(colorSpace.getName());
77             assertNotNull(colorSpace);
78             assertEquals(named.ordinal(), colorSpace.getId());
79             assertTrue(colorSpace.getComponentCount() >= 1);
80             assertTrue(colorSpace.getComponentCount() <= 4);
81         }
82     }
83 
84     @Test(expected = IllegalArgumentException.class)
testNullName()85     public void testNullName() {
86         new ColorSpace.Rgb(null, new float[6], new float[2], sIdentity, sIdentity, 0.0f, 1.0f);
87     }
88 
89     @Test(expected = IllegalArgumentException.class)
testEmptyName()90     public void testEmptyName() {
91         new ColorSpace.Rgb("", new float[6], new float[2], sIdentity, sIdentity, 0.0f, 1.0f);
92     }
93 
94     @Test
testName()95     public void testName() {
96         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", new float[6], new float[2],
97                 sIdentity, sIdentity, 0.0f, 1.0f);
98         assertEquals("Test", cs.getName());
99     }
100 
101     @Test(expected = IllegalArgumentException.class)
testPrimariesLength()102     public void testPrimariesLength() {
103         new ColorSpace.Rgb("Test", new float[7], new float[2], sIdentity, sIdentity, 0.0f, 1.0f);
104     }
105 
106     @Test(expected = IllegalArgumentException.class)
testWhitePointLength()107     public void testWhitePointLength() {
108         new ColorSpace.Rgb("Test", new float[6], new float[1], sIdentity, sIdentity, 0.0f, 1.0f);
109     }
110 
111     @Test(expected = IllegalArgumentException.class)
testNullOETF()112     public void testNullOETF() {
113         new ColorSpace.Rgb("Test", new float[6], new float[2], null, sIdentity, 0.0f, 1.0f);
114     }
115 
116     @Test
testOETF()117     public void testOETF() {
118         DoubleUnaryOperator op = Math::sqrt;
119         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", new float[6], new float[2],
120                 op, sIdentity, 0.0f, 1.0f);
121         assertEquals(0.5, cs.getOetf().applyAsDouble(0.25), 1e-5);
122     }
123 
124     @Test(expected = IllegalArgumentException.class)
testNullEOTF()125     public void testNullEOTF() {
126         new ColorSpace.Rgb("Test", new float[6], new float[2], sIdentity, null, 0.0f, 1.0f);
127     }
128 
129     @Test
testEOTF()130     public void testEOTF() {
131         DoubleUnaryOperator op = x -> x * x;
132         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", new float[6], new float[2],
133                 sIdentity, op, 0.0f, 1.0f);
134         assertEquals(0.0625, cs.getEotf().applyAsDouble(0.25), 1e-5);
135     }
136 
137     @Test(expected = IllegalArgumentException.class)
testInvalidRange()138     public void testInvalidRange() {
139         new ColorSpace.Rgb("Test", new float[6], new float[2], sIdentity, sIdentity, 2.0f, 1.0f);
140     }
141 
142     @Test
testRanges()143     public void testRanges() {
144         ColorSpace cs = ColorSpace.get(ColorSpace.Named.SRGB);
145 
146         float m1 = cs.getMinValue(0);
147         float m2 = cs.getMinValue(1);
148         float m3 = cs.getMinValue(2);
149 
150         assertEquals(0.0f, m1, 1e-9f);
151         assertEquals(0.0f, m2, 1e-9f);
152         assertEquals(0.0f, m3, 1e-9f);
153 
154         m1 = cs.getMaxValue(0);
155         m2 = cs.getMaxValue(1);
156         m3 = cs.getMaxValue(2);
157 
158         assertEquals(1.0f, m1, 1e-9f);
159         assertEquals(1.0f, m2, 1e-9f);
160         assertEquals(1.0f, m3, 1e-9f);
161 
162         cs = ColorSpace.get(ColorSpace.Named.CIE_LAB);
163 
164         m1 = cs.getMinValue(0);
165         m2 = cs.getMinValue(1);
166         m3 = cs.getMinValue(2);
167 
168         assertEquals(0.0f, m1, 1e-9f);
169         assertEquals(-128.0f, m2, 1e-9f);
170         assertEquals(-128.0f, m3, 1e-9f);
171 
172         m1 = cs.getMaxValue(0);
173         m2 = cs.getMaxValue(1);
174         m3 = cs.getMaxValue(2);
175 
176         assertEquals(100.0f, m1, 1e-9f);
177         assertEquals(128.0f, m2, 1e-9f);
178         assertEquals(128.0f, m3, 1e-9f);
179 
180         cs = ColorSpace.get(ColorSpace.Named.CIE_XYZ);
181 
182         m1 = cs.getMinValue(0);
183         m2 = cs.getMinValue(1);
184         m3 = cs.getMinValue(2);
185 
186         assertEquals(-2.0f, m1, 1e-9f);
187         assertEquals(-2.0f, m2, 1e-9f);
188         assertEquals(-2.0f, m3, 1e-9f);
189 
190         m1 = cs.getMaxValue(0);
191         m2 = cs.getMaxValue(1);
192         m3 = cs.getMaxValue(2);
193 
194         assertEquals(2.0f, m1, 1e-9f);
195         assertEquals(2.0f, m2, 1e-9f);
196         assertEquals(2.0f, m3, 1e-9f);
197     }
198 
199     @Test
testMat3x3()200     public void testMat3x3() {
201         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_TO_XYZ, sIdentity, sIdentity);
202 
203         float[] rgbToXYZ = cs.getTransform();
204         for (int i = 0; i < 9; i++) {
205             assertEquals(SRGB_TO_XYZ[i], rgbToXYZ[i], 1e-5f);
206         }
207     }
208 
209     @Test
testMat3x3Inverse()210     public void testMat3x3Inverse() {
211         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_TO_XYZ, sIdentity, sIdentity);
212 
213         float[] xyzToRGB = cs.getInverseTransform();
214         for (int i = 0; i < 9; i++) {
215             assertEquals(XYZ_TO_SRGB[i], xyzToRGB[i], 1e-5f);
216         }
217     }
218 
219     @Test
testMat3x3Primaries()220     public void testMat3x3Primaries() {
221         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_TO_XYZ, sIdentity, sIdentity);
222 
223         float[] primaries = cs.getPrimaries();
224 
225         assertNotNull(primaries);
226         assertEquals(6, primaries.length);
227 
228         assertEquals(SRGB_PRIMARIES_xyY[0], primaries[0], 1e-5f);
229         assertEquals(SRGB_PRIMARIES_xyY[1], primaries[1], 1e-5f);
230         assertEquals(SRGB_PRIMARIES_xyY[2], primaries[2], 1e-5f);
231         assertEquals(SRGB_PRIMARIES_xyY[3], primaries[3], 1e-5f);
232         assertEquals(SRGB_PRIMARIES_xyY[4], primaries[4], 1e-5f);
233         assertEquals(SRGB_PRIMARIES_xyY[5], primaries[5], 1e-5f);
234     }
235 
236     @Test
testMat3x3WhitePoint()237     public void testMat3x3WhitePoint() {
238         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_TO_XYZ, sIdentity, sIdentity);
239 
240         float[] whitePoint = cs.getWhitePoint();
241 
242         assertNotNull(whitePoint);
243         assertEquals(2, whitePoint.length);
244 
245         assertEquals(SRGB_WHITE_POINT_xyY[0], whitePoint[0], 1e-5f);
246         assertEquals(SRGB_WHITE_POINT_xyY[1], whitePoint[1], 1e-5f);
247     }
248 
249     @Test
testXYZFromPrimaries_xyY()250     public void testXYZFromPrimaries_xyY() {
251         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_PRIMARIES_xyY, SRGB_WHITE_POINT_xyY,
252                 sIdentity, sIdentity, 0.0f, 1.0f);
253 
254         float[] rgbToXYZ = cs.getTransform();
255         for (int i = 0; i < 9; i++) {
256             assertEquals(SRGB_TO_XYZ[i], rgbToXYZ[i], 1e-5f);
257         }
258 
259         float[] xyzToRGB = cs.getInverseTransform();
260         for (int i = 0; i < 9; i++) {
261             assertEquals(XYZ_TO_SRGB[i], xyzToRGB[i], 1e-5f);
262         }
263     }
264 
265     @Test
testXYZFromPrimaries_XYZ()266     public void testXYZFromPrimaries_XYZ() {
267         ColorSpace.Rgb cs = new ColorSpace.Rgb("Test", SRGB_PRIMARIES_XYZ, SRGB_WHITE_POINT_XYZ,
268                 sIdentity, sIdentity, 0.0f, 1.0f);
269 
270         float[] primaries = cs.getPrimaries();
271 
272         assertNotNull(primaries);
273         assertEquals(6, primaries.length);
274 
275         // SRGB_PRIMARIES_xyY only has 1e-3 of precision, match it
276         assertEquals(SRGB_PRIMARIES_xyY[0], primaries[0], 1e-3f);
277         assertEquals(SRGB_PRIMARIES_xyY[1], primaries[1], 1e-3f);
278         assertEquals(SRGB_PRIMARIES_xyY[2], primaries[2], 1e-3f);
279         assertEquals(SRGB_PRIMARIES_xyY[3], primaries[3], 1e-3f);
280         assertEquals(SRGB_PRIMARIES_xyY[4], primaries[4], 1e-3f);
281         assertEquals(SRGB_PRIMARIES_xyY[5], primaries[5], 1e-3f);
282 
283         float[] whitePoint = cs.getWhitePoint();
284 
285         assertNotNull(whitePoint);
286         assertEquals(2, whitePoint.length);
287 
288         // SRGB_WHITE_POINT_xyY only has 1e-3 of precision, match it
289         assertEquals(SRGB_WHITE_POINT_xyY[0], whitePoint[0], 1e-3f);
290         assertEquals(SRGB_WHITE_POINT_xyY[1], whitePoint[1], 1e-3f);
291 
292         float[] rgbToXYZ = cs.getTransform();
293         for (int i = 0; i < 9; i++) {
294             assertEquals(SRGB_TO_XYZ[i], rgbToXYZ[i], 1e-5f);
295         }
296 
297         float[] xyzToRGB = cs.getInverseTransform();
298         for (int i = 0; i < 9; i++) {
299             assertEquals(XYZ_TO_SRGB[i], xyzToRGB[i], 1e-5f);
300         }
301     }
302 
303     @Test
testGetComponentCount()304     public void testGetComponentCount() {
305         assertEquals(3, ColorSpace.get(ColorSpace.Named.SRGB).getComponentCount());
306         assertEquals(3, ColorSpace.get(ColorSpace.Named.LINEAR_SRGB).getComponentCount());
307         assertEquals(3, ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB).getComponentCount());
308         assertEquals(3, ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB).getComponentCount());
309         assertEquals(3, ColorSpace.get(ColorSpace.Named.DISPLAY_P3).getComponentCount());
310         assertEquals(3, ColorSpace.get(ColorSpace.Named.CIE_LAB).getComponentCount());
311         assertEquals(3, ColorSpace.get(ColorSpace.Named.CIE_XYZ).getComponentCount());
312     }
313 
314     @Test
testIsSRGB()315     public void testIsSRGB() {
316         assertTrue(ColorSpace.get(ColorSpace.Named.SRGB).isSrgb());
317         assertFalse(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB).isSrgb());
318         assertFalse(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB).isSrgb());
319         assertFalse(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB).isSrgb());
320         assertFalse(ColorSpace.get(ColorSpace.Named.DISPLAY_P3).isSrgb());
321         assertFalse(ColorSpace.get(ColorSpace.Named.CIE_LAB).isSrgb());
322         assertFalse(ColorSpace.get(ColorSpace.Named.CIE_XYZ).isSrgb());
323 
324         ColorSpace.Rgb cs = new ColorSpace.Rgb("My sRGB", SRGB_TO_XYZ,
325                 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f));
326         assertTrue(cs.isSrgb());
327     }
328 
329     @Test
testIsWideGamut()330     public void testIsWideGamut() {
331         assertFalse(ColorSpace.get(ColorSpace.Named.SRGB).isWideGamut());
332         assertFalse(ColorSpace.get(ColorSpace.Named.BT709).isWideGamut());
333         assertTrue(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB).isWideGamut());
334         assertTrue(ColorSpace.get(ColorSpace.Named.DCI_P3).isWideGamut());
335         assertTrue(ColorSpace.get(ColorSpace.Named.BT2020).isWideGamut());
336         assertTrue(ColorSpace.get(ColorSpace.Named.ACES).isWideGamut());
337         assertTrue(ColorSpace.get(ColorSpace.Named.CIE_LAB).isWideGamut());
338         assertTrue(ColorSpace.get(ColorSpace.Named.CIE_XYZ).isWideGamut());
339     }
340 
341     @Test
testWhitePoint()342     public void testWhitePoint() {
343         ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
344 
345         float[] whitePoint = cs.getWhitePoint();
346 
347         assertNotNull(whitePoint);
348         assertEquals(2, whitePoint.length);
349 
350         // Make sure a copy is returned
351         Arrays.fill(whitePoint, Float.NaN);
352         assertArrayNotEquals(whitePoint, cs.getWhitePoint(), 1e-5f);
353         assertSame(whitePoint, cs.getWhitePoint(whitePoint));
354         assertArrayEquals(whitePoint, cs.getWhitePoint(), 1e-5f);
355     }
356 
357     @Test
testPrimaries()358     public void testPrimaries() {
359         ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
360 
361         float[] primaries = cs.getPrimaries();
362 
363         assertNotNull(primaries);
364         assertEquals(6, primaries.length);
365 
366         // Make sure a copy is returned
367         Arrays.fill(primaries, Float.NaN);
368         assertArrayNotEquals(primaries, cs.getPrimaries(), 1e-5f);
369         assertSame(primaries, cs.getPrimaries(primaries));
370         assertArrayEquals(primaries, cs.getPrimaries(), 1e-5f);
371     }
372 
373     @Test
testRGBtoXYZMatrix()374     public void testRGBtoXYZMatrix() {
375         ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
376 
377         float[] rgbToXYZ = cs.getTransform();
378 
379         assertNotNull(rgbToXYZ);
380         assertEquals(9, rgbToXYZ.length);
381 
382         // Make sure a copy is returned
383         Arrays.fill(rgbToXYZ, Float.NaN);
384         assertArrayNotEquals(rgbToXYZ, cs.getTransform(), 1e-5f);
385         assertSame(rgbToXYZ, cs.getTransform(rgbToXYZ));
386         assertArrayEquals(rgbToXYZ, cs.getTransform(), 1e-5f);
387     }
388 
389     @Test
testXYZtoRGBMatrix()390     public void testXYZtoRGBMatrix() {
391         ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
392 
393         float[] xyzToRGB = cs.getInverseTransform();
394 
395         assertNotNull(xyzToRGB);
396         assertEquals(9, xyzToRGB.length);
397 
398         // Make sure a copy is returned
399         Arrays.fill(xyzToRGB, Float.NaN);
400         assertArrayNotEquals(xyzToRGB, cs.getInverseTransform(), 1e-5f);
401         assertSame(xyzToRGB, cs.getInverseTransform(xyzToRGB));
402         assertArrayEquals(xyzToRGB, cs.getInverseTransform(), 1e-5f);
403     }
404 
405     @Test
testRGBtoXYZ()406     public void testRGBtoXYZ() {
407         ColorSpace cs = ColorSpace.get(ColorSpace.Named.SRGB);
408 
409         float[] source = { 0.75f, 0.5f, 0.25f };
410         float[] expected = { 0.3012f, 0.2679f, 0.0840f };
411 
412         float[] r1 = cs.toXyz(source[0], source[1], source[2]);
413         assertNotNull(r1);
414         assertEquals(3, r1.length);
415         assertArrayNotEquals(source, r1, 1e-5f);
416         assertArrayEquals(expected, r1, 1e-3f);
417 
418         float[] r3 = { source[0], source[1], source[2] };
419         assertSame(r3, cs.toXyz(r3));
420         assertEquals(3, r3.length);
421         assertArrayEquals(r1, r3, 1e-5f);
422     }
423 
424     @Test
testXYZtoRGB()425     public void testXYZtoRGB() {
426         ColorSpace cs = ColorSpace.get(ColorSpace.Named.SRGB);
427 
428         float[] source = { 0.3012f, 0.2679f, 0.0840f };
429         float[] expected = { 0.75f, 0.5f, 0.25f };
430 
431         float[] r1 = cs.fromXyz(source[0], source[1], source[2]);
432         assertNotNull(r1);
433         assertEquals(3, r1.length);
434         assertArrayNotEquals(source, r1, 1e-5f);
435         assertArrayEquals(expected, r1, 1e-3f);
436 
437         float[] r3 = { source[0], source[1], source[2] };
438         assertSame(r3, cs.fromXyz(r3));
439         assertEquals(3, r3.length);
440         assertArrayEquals(r1, r3, 1e-5f);
441     }
442 
443     @Test
testConnect()444     public void testConnect() {
445         ColorSpace.Connector connector = ColorSpace.connect(
446                 ColorSpace.get(ColorSpace.Named.SRGB),
447                 ColorSpace.get(ColorSpace.Named.DCI_P3));
448 
449         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), connector.getSource());
450         assertSame(ColorSpace.get(ColorSpace.Named.DCI_P3), connector.getDestination());
451         assertSame(ColorSpace.RenderIntent.PERCEPTUAL, connector.getRenderIntent());
452 
453         connector = ColorSpace.connect(
454                 ColorSpace.get(ColorSpace.Named.SRGB),
455                 ColorSpace.get(ColorSpace.Named.SRGB));
456 
457         assertSame(connector.getDestination(), connector.getSource());
458         assertSame(ColorSpace.RenderIntent.RELATIVE, connector.getRenderIntent());
459 
460         connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
461         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), connector.getDestination());
462 
463         connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.SRGB));
464         assertSame(connector.getSource(), connector.getDestination());
465     }
466 
467     @Test
testConnector()468     public void testConnector() {
469         // Connect color spaces with same white points
470         ColorSpace.Connector connector = ColorSpace.connect(
471                 ColorSpace.get(ColorSpace.Named.SRGB),
472                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
473 
474         float[] source = { 1.0f, 0.5f, 0.0f };
475         float[] expected = { 0.8912f, 0.4962f, 0.1164f };
476 
477         float[] r1 = connector.transform(source[0], source[1], source[2]);
478         assertNotNull(r1);
479         assertEquals(3, r1.length);
480         assertArrayNotEquals(source, r1, 1e-5f);
481         assertArrayEquals(expected, r1, 1e-3f);
482 
483         float[] r3 = { source[0], source[1], source[2] };
484         assertSame(r3, connector.transform(r3));
485         assertEquals(3, r3.length);
486         assertArrayEquals(r1, r3, 1e-5f);
487 
488         connector = ColorSpace.connect(
489                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB),
490                 ColorSpace.get(ColorSpace.Named.SRGB));
491 
492         float[] tmp = source;
493         source = expected;
494         expected = tmp;
495 
496         r1 = connector.transform(source[0], source[1], source[2]);
497         assertNotNull(r1);
498         assertEquals(3, r1.length);
499         assertArrayNotEquals(source, r1, 1e-5f);
500         assertArrayEquals(expected, r1, 1e-3f);
501 
502         r3 = new float[] { source[0], source[1], source[2] };
503         assertSame(r3, connector.transform(r3));
504         assertEquals(3, r3.length);
505         assertArrayEquals(r1, r3, 1e-5f);
506     }
507 
508     @Test
testAdaptedConnector()509     public void testAdaptedConnector() {
510         // Connect color spaces with different white points
511         ColorSpace.Connector connector = ColorSpace.connect(
512                 ColorSpace.get(ColorSpace.Named.SRGB),
513                 ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB));
514 
515         float[] source = new float[] { 1.0f, 0.0f, 0.0f };
516         float[] expected = new float[] { 0.70226f, 0.2757f, 0.1036f };
517 
518         float[] r = connector.transform(source[0], source[1], source[2]);
519         assertNotNull(r);
520         assertEquals(3, r.length);
521         assertArrayNotEquals(source, r, 1e-5f);
522         assertArrayEquals(expected, r, 1e-4f);
523     }
524 
525     @Test
testAdaptedConnectorWithRenderIntent()526     public void testAdaptedConnectorWithRenderIntent() {
527         // Connect a wider color space to a narrow color space
528         ColorSpace.Connector connector = ColorSpace.connect(
529                 ColorSpace.get(ColorSpace.Named.DCI_P3),
530                 ColorSpace.get(ColorSpace.Named.SRGB),
531                 ColorSpace.RenderIntent.RELATIVE);
532 
533         float[] source = { 0.9f, 0.9f, 0.9f };
534 
535         float[] relative = connector.transform(source[0], source[1], source[2]);
536         assertNotNull(relative);
537         assertEquals(3, relative.length);
538         assertArrayNotEquals(source, relative, 1e-5f);
539         assertArrayEquals(new float[] { 0.8862f, 0.8862f, 0.8862f }, relative, 1e-4f);
540 
541         connector = ColorSpace.connect(
542                 ColorSpace.get(ColorSpace.Named.DCI_P3),
543                 ColorSpace.get(ColorSpace.Named.SRGB),
544                 ColorSpace.RenderIntent.ABSOLUTE);
545 
546         float[] absolute = connector.transform(source[0], source[1], source[2]);
547         assertNotNull(absolute);
548         assertEquals(3, absolute.length);
549         assertArrayNotEquals(source, absolute, 1e-5f);
550         assertArrayNotEquals(relative, absolute, 1e-5f);
551         assertArrayEquals(new float[] { 0.8475f, 0.9217f, 0.8203f }, absolute, 1e-4f);
552     }
553 
554     @Test
testIdentityConnector()555     public void testIdentityConnector() {
556         ColorSpace.Connector connector = ColorSpace.connect(
557                 ColorSpace.get(ColorSpace.Named.SRGB),
558                 ColorSpace.get(ColorSpace.Named.SRGB));
559 
560         assertSame(connector.getSource(), connector.getDestination());
561         assertSame(ColorSpace.RenderIntent.RELATIVE, connector.getRenderIntent());
562 
563         float[] source = new float[] { 0.11112f, 0.22227f, 0.444448f };
564 
565         float[] r = connector.transform(source[0], source[1], source[2]);
566         assertNotNull(r);
567         assertEquals(3, r.length);
568         assertArrayEquals(source, r, 1e-5f);
569     }
570 
571     @Test
testConnectorTransformIdentity()572     public void testConnectorTransformIdentity() {
573         ColorSpace.Connector connector = ColorSpace.connect(
574                 ColorSpace.get(ColorSpace.Named.DCI_P3),
575                 ColorSpace.get(ColorSpace.Named.DCI_P3));
576 
577         float[] source = { 1.0f, 0.0f, 0.0f };
578         float[] expected = { 1.0f, 0.0f, 0.0f };
579 
580         float[] r1 = connector.transform(source[0], source[1], source[2]);
581         assertNotNull(r1);
582         assertEquals(3, r1.length);
583         assertArrayEquals(expected, r1, 1e-3f);
584 
585         float[] r3 = { source[0], source[1], source[2] };
586         assertSame(r3, connector.transform(r3));
587         assertEquals(3, r3.length);
588         assertArrayEquals(r1, r3, 1e-5f);
589     }
590 
591     @Test
testAdaptation()592     public void testAdaptation() {
593         ColorSpace adapted = ColorSpace.adapt(
594                 ColorSpace.get(ColorSpace.Named.SRGB),
595                 ColorSpace.ILLUMINANT_D50);
596 
597         float[] sRGBD50 = {
598                 0.43602175f, 0.22247513f, 0.01392813f,
599                 0.38510883f, 0.71690667f, 0.09710153f,
600                 0.14308129f, 0.06061824f, 0.71415880f
601         };
602 
603         assertArrayEquals(sRGBD50, ((ColorSpace.Rgb) adapted).getTransform(), 1e-7f);
604 
605         adapted = ColorSpace.adapt(
606                 ColorSpace.get(ColorSpace.Named.SRGB),
607                 ColorSpace.ILLUMINANT_D50,
608                 ColorSpace.Adaptation.BRADFORD);
609         assertArrayEquals(sRGBD50, ((ColorSpace.Rgb) adapted).getTransform(), 1e-7f);
610     }
611 
612     @Test
testImplicitSRGBConnector()613     public void testImplicitSRGBConnector() {
614         ColorSpace.Connector connector1 = ColorSpace.connect(
615                 ColorSpace.get(ColorSpace.Named.DCI_P3));
616 
617         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), connector1.getDestination());
618 
619         ColorSpace.Connector connector2 = ColorSpace.connect(
620                 ColorSpace.get(ColorSpace.Named.DCI_P3),
621                 ColorSpace.get(ColorSpace.Named.SRGB));
622 
623         float[] source = { 0.6f, 0.9f, 0.7f };
624         assertArrayEquals(
625                 connector1.transform(source[0], source[1], source[2]),
626                 connector2.transform(source[0], source[1], source[2]), 1e-7f);
627     }
628 
629     @Test
testLab()630     public void testLab() {
631         ColorSpace.Connector connector = ColorSpace.connect(
632                 ColorSpace.get(ColorSpace.Named.CIE_LAB));
633 
634         float[] source = { 100.0f, 0.0f, 0.0f };
635         float[] expected = { 1.0f, 1.0f, 1.0f };
636 
637         float[] r1 = connector.transform(source[0], source[1], source[2]);
638         assertNotNull(r1);
639         assertEquals(3, r1.length);
640         assertArrayEquals(expected, r1, 1e-3f);
641 
642         source = new float[] { 100.0f, 0.0f, 54.0f };
643         expected = new float[] { 1.0f, 0.9925f, 0.5762f };
644 
645         float[] r2 = connector.transform(source[0], source[1], source[2]);
646         assertNotNull(r2);
647         assertEquals(3, r2.length);
648         assertArrayEquals(expected, r2, 1e-3f);
649 
650         connector = ColorSpace.connect(
651                 ColorSpace.get(ColorSpace.Named.CIE_LAB), ColorSpace.RenderIntent.ABSOLUTE);
652 
653         source = new float[] { 100.0f, 0.0f, 0.0f };
654         expected = new float[] { 1.0f, 0.9910f, 0.8651f };
655 
656         r1 = connector.transform(source[0], source[1], source[2]);
657         assertNotNull(r1);
658         assertEquals(3, r1.length);
659         assertArrayEquals(expected, r1, 1e-3f);
660 
661         source = new float[] { 100.0f, 0.0f, 54.0f };
662         expected = new float[] { 1.0f, 0.9853f, 0.4652f };
663 
664         r2 = connector.transform(source[0], source[1], source[2]);
665         assertNotNull(r2);
666         assertEquals(3, r2.length);
667         assertArrayEquals(expected, r2, 1e-3f);
668     }
669 
670     @Test
testXYZ()671     public void testXYZ() {
672         ColorSpace xyz = ColorSpace.get(ColorSpace.Named.CIE_XYZ);
673 
674         float[] source = { 0.32f, 0.43f, 0.54f };
675 
676         float[] r1 = xyz.toXyz(source[0], source[1], source[2]);
677         assertNotNull(r1);
678         assertEquals(3, r1.length);
679         assertArrayEquals(source, r1, 1e-7f);
680 
681         float[] r2 = xyz.fromXyz(source[0], source[1], source[2]);
682         assertNotNull(r2);
683         assertEquals(3, r2.length);
684         assertArrayEquals(source, r2, 1e-7f);
685 
686         ColorSpace.Connector connector =
687                 ColorSpace.connect(ColorSpace.get(ColorSpace.Named.CIE_XYZ));
688 
689         float[] expected = { 0.2280f, 0.7541f, 0.8453f };
690 
691         float[] r3 = connector.transform(source[0], source[1], source[2]);
692         assertNotNull(r3);
693         assertEquals(3, r3.length);
694         assertArrayEquals(expected, r3, 1e-3f);
695     }
696 
697     @Test
testIDs()698     public void testIDs() {
699         // These cannot change
700         assertEquals(0, ColorSpace.get(ColorSpace.Named.SRGB).getId());
701         assertEquals(-1, ColorSpace.MIN_ID);
702         assertEquals(63, ColorSpace.MAX_ID);
703     }
704 
705     @Test
testFromLinear()706     public void testFromLinear() {
707         ColorSpace.Rgb colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
708 
709         float[] source = { 0.0f, 0.5f, 1.0f };
710         float[] expected = { 0.0f, 0.7354f, 1.0f };
711 
712         float[] r1 = colorSpace.fromLinear(source[0], source[1], source[2]);
713         assertNotNull(r1);
714         assertEquals(3, r1.length);
715         assertArrayEquals(expected, r1, 1e-3f);
716 
717         float[] r2 = { source[0], source[1], source[2] };
718         assertSame(r2, colorSpace.fromLinear(r2));
719         assertEquals(3, r2.length);
720         assertArrayEquals(r1, r2, 1e-5f);
721     }
722 
723     @Test
testToLinear()724     public void testToLinear() {
725         ColorSpace.Rgb colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
726 
727         float[] source = { 0.0f, 0.5f, 1.0f };
728         float[] expected = new float[] { 0.0f, 0.2140f, 1.0f };
729 
730         float[] r1 = colorSpace.toLinear(source[0], source[1], source[2]);
731         assertNotNull(r1);
732         assertEquals(3, r1.length);
733         assertArrayEquals(expected, r1, 1e-3f);
734 
735         float[] r2 = new float[] { source[0], source[1], source[2] };
736         assertSame(r2, colorSpace.toLinear(r2));
737         assertEquals(3, r2.length);
738         assertArrayEquals(r1, r2, 1e-5f);
739     }
740 
741     @Test
testTransferParameters()742     public void testTransferParameters() {
743         ColorSpace.Rgb colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
744         assertNotNull(colorSpace.getTransferParameters());
745 
746         colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
747         assertNull(colorSpace.getTransferParameters());
748     }
749 
750     @Test
testIdempotentTransferFunctions()751     public void testIdempotentTransferFunctions() {
752         Arrays.stream(ColorSpace.Named.values())
753                 .map(ColorSpace::get)
754                 .filter(cs -> cs.getModel() == ColorSpace.Model.RGB)
755                 .map(cs -> (ColorSpace.Rgb) cs)
756                 .forEach(cs -> {
757                         float[] source = { 0.0f, 0.5f, 1.0f };
758                         float[] r = cs.fromLinear(cs.toLinear(source[0], source[1], source[2]));
759                         assertArrayEquals(source, r, 1e-3f);
760                 });
761     }
762 
763     @Test
testMatch()764     public void testMatch() {
765         for (ColorSpace.Named named : ColorSpace.Named.values()) {
766             ColorSpace cs = ColorSpace.get(named);
767             if (cs.getModel() == ColorSpace.Model.RGB) {
768                 ColorSpace.Rgb rgb = (ColorSpace.Rgb) cs;
769                 // match() cannot match extended sRGB
770                 if (rgb != ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) &&
771                         rgb != ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) {
772 
773                     // match() uses CIE XYZ D50
774                     rgb = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
775                     assertSame(cs,
776                             ColorSpace.match(rgb.getTransform(), rgb.getTransferParameters()));
777                 }
778             }
779         }
780 
781         assertSame(ColorSpace.get(ColorSpace.Named.SRGB),
782                 ColorSpace.match(SRGB_TO_XYZ_D50, new ColorSpace.Rgb.TransferParameters(
783                         1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4)));
784     }
785 
786 
787     @SuppressWarnings("SameParameterValue")
assertArrayNotEquals(float[] a, float[] b, float eps)788     private static void assertArrayNotEquals(float[] a, float[] b, float eps) {
789         for (int i = 0; i < a.length; i++) {
790             if (Float.compare(a[i], b[i]) == 0 || Math.abs(a[i] - b[i]) < eps) {
791                 fail("Expected " + a[i] + ", received " + b[i]);
792             }
793         }
794     }
795 
assertArrayEquals(float[] a, float[] b, float eps)796     private static void assertArrayEquals(float[] a, float[] b, float eps) {
797         for (int i = 0; i < a.length; i++) {
798             if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > eps) {
799                 fail("Expected " + a[i] + ", received " + b[i]);
800             }
801         }
802     }
803 }
804