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 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) 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) 91 public void testEmptyName() { 92 new ColorSpace.Rgb("", new float[6], new float[2], sIdentity, sIdentity, 0.0f, 1.0f); 93 } 94 95 @Test 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) 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) 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) 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 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) 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 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) 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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") 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 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