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