1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.cts; 18 19 import static org.junit.Assert.assertEquals; 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.content.res.Resources; 27 import android.graphics.Bitmap; 28 import android.graphics.BitmapFactory; 29 import android.graphics.Canvas; 30 import android.graphics.Color; 31 import android.graphics.ColorSpace; 32 import android.graphics.ImageDecoder; 33 import android.graphics.Matrix; 34 import android.os.Parcel; 35 import android.util.Log; 36 37 import androidx.annotation.ColorInt; 38 import androidx.annotation.NonNull; 39 import androidx.test.InstrumentationRegistry; 40 import androidx.test.filters.RequiresDevice; 41 import androidx.test.filters.SmallTest; 42 43 import com.android.compatibility.common.util.ColorUtils; 44 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.io.ByteArrayOutputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.nio.ByteBuffer; 53 import java.nio.IntBuffer; 54 import java.util.Arrays; 55 56 import junitparams.JUnitParamsRunner; 57 import junitparams.Parameters; 58 59 @SmallTest 60 @RunWith(JUnitParamsRunner.class) 61 public class BitmapColorSpaceTest { 62 private static final String LOG_TAG = "BitmapColorSpaceTest"; 63 64 private Resources mResources; 65 66 @Before setup()67 public void setup() { 68 mResources = InstrumentationRegistry.getTargetContext().getResources(); 69 } 70 71 @SuppressWarnings("deprecation") 72 @Test createWithColorSpace()73 public void createWithColorSpace() { 74 // We don't test HARDWARE configs because they are not compatible with mutable bitmaps 75 76 Bitmap.Config[] configs = new Bitmap.Config[] { 77 Bitmap.Config.ARGB_8888, 78 Bitmap.Config.RGB_565, 79 Bitmap.Config.ARGB_4444, 80 Bitmap.Config.RGBA_F16, 81 }; 82 // in most cases, createBitmap respects the ColorSpace 83 for (Bitmap.Config config : configs) { 84 for (ColorSpace.Named e : new ColorSpace.Named[] { 85 ColorSpace.Named.PRO_PHOTO_RGB, 86 ColorSpace.Named.ADOBE_RGB, 87 ColorSpace.Named.DISPLAY_P3, 88 ColorSpace.Named.DCI_P3, 89 ColorSpace.Named.BT709, 90 ColorSpace.Named.BT2020, 91 }) { 92 ColorSpace requested = ColorSpace.get(e); 93 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested); 94 ColorSpace cs = b.getColorSpace(); 95 assertNotNull(cs); 96 assertSame(requested, cs); 97 } 98 99 // SRGB and LINEAR_SRGB are special. 100 ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); 101 ColorSpace extendedSrgb = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB); 102 for (ColorSpace requested : new ColorSpace[] { 103 sRGB, 104 extendedSrgb, 105 }) { 106 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested); 107 ColorSpace cs = b.getColorSpace(); 108 assertNotNull(cs); 109 if (config == Bitmap.Config.RGBA_F16) { 110 assertSame(extendedSrgb, cs); 111 } else { 112 assertSame(sRGB, cs); 113 } 114 } 115 116 ColorSpace linearRgb = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB); 117 ColorSpace linearExtendedSrgb = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); 118 for (ColorSpace requested : new ColorSpace[] { 119 linearRgb, 120 linearExtendedSrgb, 121 }) { 122 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested); 123 ColorSpace cs = b.getColorSpace(); 124 assertNotNull(cs); 125 if (config == Bitmap.Config.RGBA_F16) { 126 assertSame(linearExtendedSrgb, cs); 127 } else { 128 assertSame(linearRgb, cs); 129 } 130 } 131 } 132 } 133 134 @Test createAlpha8ColorSpace()135 public void createAlpha8ColorSpace() { 136 Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8); 137 assertNull(bitmap.getColorSpace()); 138 139 for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) { 140 bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8, true, cs); 141 assertNull(bitmap.getColorSpace()); 142 } 143 } 144 145 @Test createDefaultColorSpace()146 public void createDefaultColorSpace() { 147 ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); 148 Bitmap.Config[] configs = new Bitmap.Config[] { 149 Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 150 }; 151 for (Bitmap.Config config : configs) { 152 Bitmap bitmap = Bitmap.createBitmap(32, 32, config, true); 153 assertSame(sRGB, bitmap.getColorSpace()); 154 } 155 } 156 157 @Test(expected = IllegalArgumentException.class) createWithoutColorSpace()158 public void createWithoutColorSpace() { 159 Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, null); 160 } 161 162 @Test(expected = IllegalArgumentException.class) createWithNonRgbColorSpace()163 public void createWithNonRgbColorSpace() { 164 Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, 165 ColorSpace.get(ColorSpace.Named.CIE_LAB)); 166 } 167 168 @Test(expected = IllegalArgumentException.class) createWithNoTransferParameters()169 public void createWithNoTransferParameters() { 170 Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, 171 new ColorSpace.Rgb("NoTransferParams", 172 new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 173 ColorSpace.ILLUMINANT_D50, 174 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f), 175 0, 1)); 176 } 177 178 @Test createFromSourceWithColorSpace()179 public void createFromSourceWithColorSpace() { 180 for (ColorSpace rgb : BitmapTest.getRgbColorSpaces()) { 181 Bitmap.Config[] configs = new Bitmap.Config[] { 182 Bitmap.Config.ARGB_8888, 183 Bitmap.Config.RGB_565, 184 Bitmap.Config.ALPHA_8, 185 Bitmap.Config.ARGB_4444, 186 Bitmap.Config.RGBA_F16, 187 }; 188 for (Bitmap.Config config : configs) { 189 Bitmap orig = Bitmap.createBitmap(32, 32, config, false, rgb); 190 Bitmap cropped = Bitmap.createBitmap(orig, 0, 0, orig.getWidth() / 2, 191 orig.getHeight() / 2, null, false); 192 assertSame(orig.getColorSpace(), cropped.getColorSpace()); 193 if (config == Bitmap.Config.ALPHA_8) { 194 assertNull(cropped.getColorSpace()); 195 } 196 197 Matrix m = new Matrix(); 198 m.setRotate(45, orig.getWidth() / 2, orig.getHeight() / 2); 199 Bitmap rotated = Bitmap.createBitmap(orig, 0, 0, orig.getWidth(), 200 orig.getHeight(), m, false); 201 switch (config) { 202 case ALPHA_8: 203 assertSame(Bitmap.Config.ARGB_8888, rotated.getConfig()); 204 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), rotated.getColorSpace()); 205 break; 206 case RGB_565: 207 assertSame(Bitmap.Config.ARGB_8888, rotated.getConfig()); 208 // Fallthrough. 209 default: 210 assertSame("Mismatch with Config " + config, 211 orig.getColorSpace(), rotated.getColorSpace()); 212 break; 213 } 214 } 215 } 216 } 217 218 @Test sRGB()219 public void sRGB() { 220 Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot); 221 ColorSpace cs = b.getColorSpace(); 222 assertNotNull(cs); 223 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 224 225 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 226 cs = b.getColorSpace(); 227 assertNotNull(cs); 228 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 229 230 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 231 cs = b.getColorSpace(); 232 assertNotNull(cs); 233 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 234 } 235 236 @Test p3()237 public void p3() { 238 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 239 Bitmap b = BitmapFactory.decodeStream(in); 240 ColorSpace cs = b.getColorSpace(); 241 assertNotNull(cs); 242 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 243 244 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 245 cs = b.getColorSpace(); 246 assertNotNull(cs); 247 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 248 249 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 250 cs = b.getColorSpace(); 251 assertNotNull(cs); 252 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 253 } catch (IOException e) { 254 fail(); 255 } 256 } 257 258 @Test extendedSRGB()259 public void extendedSRGB() { 260 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 261 Bitmap b = BitmapFactory.decodeStream(in); 262 ColorSpace cs = b.getColorSpace(); 263 assertNotNull(cs); 264 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 265 266 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 267 cs = b.getColorSpace(); 268 assertNotNull(cs); 269 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 270 271 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 272 cs = b.getColorSpace(); 273 assertNotNull(cs); 274 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 275 } catch (IOException e) { 276 fail(); 277 } 278 } 279 280 @Test linearSRGB()281 public void linearSRGB() { 282 String assetInLinearSRGB = "grayscale-linearSrgb.png"; 283 try (InputStream in = mResources.getAssets().open(assetInLinearSRGB)) { 284 Bitmap b = BitmapFactory.decodeStream(in); 285 ColorSpace cs = b.getColorSpace(); 286 assertNotNull(cs); 287 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), cs); 288 } catch (IOException e) { 289 fail(); 290 } 291 292 try (InputStream in = mResources.getAssets().open(assetInLinearSRGB)) { 293 BitmapFactory.Options options = new BitmapFactory.Options(); 294 options.inPreferredConfig = Bitmap.Config.RGBA_F16; 295 Bitmap b = BitmapFactory.decodeStream(in, null, options); 296 ColorSpace cs = b.getColorSpace(); 297 assertNotNull(cs); 298 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs); 299 } catch (IOException e) { 300 fail(); 301 } 302 } 303 304 private static class Asset { 305 public final String name; 306 public final ColorSpace colorSpace; Asset(String name, ColorSpace.Named e)307 Asset(String name, ColorSpace.Named e) { 308 this.name = name; 309 this.colorSpace = ColorSpace.get(e); 310 } 311 }; 312 313 @Test reconfigure()314 public void reconfigure() { 315 Asset[] assets = new Asset[] { 316 new Asset("green-p3.png", ColorSpace.Named.DISPLAY_P3), 317 new Asset("red-adobergb.png", ColorSpace.Named.ADOBE_RGB), 318 }; 319 for (Asset asset : assets) { 320 for (Bitmap.Config config : new Bitmap.Config[] { 321 Bitmap.Config.ARGB_8888, 322 Bitmap.Config.RGB_565, 323 }) { 324 try (InputStream in = mResources.getAssets().open(asset.name)) { 325 BitmapFactory.Options opts = new BitmapFactory.Options(); 326 opts.inMutable = true; 327 opts.inPreferredConfig = config; 328 329 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 330 ColorSpace cs = b.getColorSpace(); 331 assertNotNull(cs); 332 assertSame(asset.colorSpace, cs); 333 334 b.reconfigure(b.getWidth() / 4, b.getHeight() / 4, Bitmap.Config.RGBA_F16); 335 cs = b.getColorSpace(); 336 assertNotNull(cs); 337 assertSame(asset.colorSpace, cs); 338 339 b.reconfigure(b.getWidth(), b.getHeight(), config); 340 cs = b.getColorSpace(); 341 assertNotNull(cs); 342 assertSame(asset.colorSpace, cs); 343 } catch (IOException e) { 344 fail(); 345 } 346 } 347 } 348 } 349 350 @Test reuse()351 public void reuse() { 352 BitmapFactory.Options opts = new BitmapFactory.Options(); 353 opts.inMutable = true; 354 355 Bitmap bitmap1 = null; 356 try (InputStream in = mResources.getAssets().open("green-srgb.png")) { 357 bitmap1 = BitmapFactory.decodeStream(in, null, opts); 358 ColorSpace cs = bitmap1.getColorSpace(); 359 assertNotNull(cs); 360 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 361 } catch (IOException e) { 362 fail(); 363 } 364 365 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 366 opts.inBitmap = bitmap1; 367 368 Bitmap bitmap2 = BitmapFactory.decodeStream(in, null, opts); 369 assertSame(bitmap1, bitmap2); 370 ColorSpace cs = bitmap2.getColorSpace(); 371 assertNotNull(cs); 372 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 373 } catch (IOException e) { 374 fail(); 375 } 376 } 377 378 @Test getPixel()379 public void getPixel() { 380 verifyGetPixel("green-p3.png", 0x75fb4cff); 381 verifyGetPixel("translucent-green-p3.png", 0x3a7d267f); // 50% translucent 382 } 383 verifyGetPixel(@onNull String fileName, @ColorInt int rawColor)384 private void verifyGetPixel(@NonNull String fileName, @ColorInt int rawColor) { 385 try (InputStream in = mResources.getAssets().open(fileName)) { 386 Bitmap b = BitmapFactory.decodeStream(in); 387 ColorSpace cs = b.getColorSpace(); 388 assertNotNull(cs); 389 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 390 391 verifyGetPixel(b, rawColor); 392 393 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 394 verifyGetPixel(b, rawColor); 395 396 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 397 verifyGetPixel(b, rawColor); 398 } catch (IOException e) { 399 fail(); 400 } 401 } 402 verifyGetPixel(@onNull Bitmap b, @ColorInt int rawColor)403 private static void verifyGetPixel(@NonNull Bitmap b, @ColorInt int rawColor) { 404 ByteBuffer dst = ByteBuffer.allocate(b.getByteCount()); 405 b.copyPixelsToBuffer(dst); 406 dst.rewind(); 407 408 // Stored as RGBA 409 assertEquals(rawColor, dst.asIntBuffer().get()); 410 411 int srgbColor = convertPremulColorToColorInt(rawColor, b.getColorSpace()); 412 int srgb = b.getPixel(15, 15); 413 almostEqual(srgbColor, srgb, 3, 15 * b.getWidth() + 15); 414 } 415 convertPremulColorToColorInt(int premulColor, ColorSpace premulCS)416 private static int convertPremulColorToColorInt(int premulColor, ColorSpace premulCS) { 417 float alpha = (premulColor & 0xff) / 255.0f; 418 return Color.toArgb(Color.convert((premulColor >>> 24) / 255.0f / alpha, 419 ((premulColor >> 16) & 0xff) / 255.0f / alpha, 420 ((premulColor >> 8) & 0xff) / 255.0f / alpha, 421 alpha, premulCS, ColorSpace.get(ColorSpace.Named.SRGB))); 422 } 423 424 @Test getPixels()425 public void getPixels() { 426 verifyGetPixels("green-p3.png"); 427 verifyGetPixels("translucent-green-p3.png"); // 50% translucent 428 } 429 verifyGetPixels(@onNull String fileName)430 private void verifyGetPixels(@NonNull String fileName) { 431 try (InputStream in = mResources.getAssets().open(fileName)) { 432 Bitmap b = BitmapFactory.decodeStream(in); 433 ColorSpace cs = b.getColorSpace(); 434 assertNotNull(cs); 435 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 436 437 ByteBuffer dst = ByteBuffer.allocate(b.getByteCount()); 438 b.copyPixelsToBuffer(dst); 439 dst.rewind(); 440 441 // Stored as RGBA 442 int expected = convertPremulColorToColorInt(dst.asIntBuffer().get(), b.getColorSpace()); 443 444 verifyGetPixels(b, expected); 445 446 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 447 verifyGetPixels(b, expected); 448 449 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 450 verifyGetPixels(b, expected); 451 } catch (IOException e) { 452 fail(); 453 } 454 } 455 verifyGetPixels(@onNull Bitmap b, @ColorInt int expected)456 private static void verifyGetPixels(@NonNull Bitmap b, @ColorInt int expected) { 457 int[] pixels = new int[b.getWidth() * b.getHeight()]; 458 b.getPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight()); 459 460 for (int i = 0; i < pixels.length; i++) { 461 int pixel = pixels[i]; 462 almostEqual(expected, pixel, 3, i); 463 } 464 } 465 466 @Test setPixel()467 public void setPixel() { 468 verifySetPixel("green-p3.png", 0xffff0000, 0xea3323ff); 469 verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x7519127f); 470 } 471 verifySetPixel(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)472 private void verifySetPixel(@NonNull String fileName, 473 @ColorInt int newColor, @ColorInt int expectedColor) { 474 try (InputStream in = mResources.getAssets().open(fileName)) { 475 BitmapFactory.Options opts = new BitmapFactory.Options(); 476 opts.inMutable = true; 477 478 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 479 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 480 assertTrue(b.isMutable()); 481 verifySetPixel(b, newColor, expectedColor); 482 483 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 484 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 485 assertTrue(b.isMutable()); 486 verifySetPixel(b, newColor, expectedColor); 487 488 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 489 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 490 assertTrue(b.isMutable()); 491 verifySetPixel(b, newColor, expectedColor); 492 } catch (IOException e) { 493 fail(); 494 } 495 } 496 verifySetPixel(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)497 private static void verifySetPixel(@NonNull Bitmap b, 498 @ColorInt int newColor, @ColorInt int expectedColor) { 499 assertTrue(b.isMutable()); 500 b.setPixel(0, 0, newColor); 501 502 ByteBuffer dst = ByteBuffer.allocate(b.getByteCount()); 503 b.copyPixelsToBuffer(dst); 504 dst.rewind(); 505 // Stored as RGBA 506 ColorUtils.verifyColor(expectedColor, dst.asIntBuffer().get(), 1); 507 } 508 509 @Test setPixels()510 public void setPixels() { 511 verifySetPixels("green-p3.png", 0xffff0000, 0xea3323ff); 512 verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x7519127f); 513 } 514 verifySetPixels(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)515 private void verifySetPixels(@NonNull String fileName, 516 @ColorInt int newColor, @ColorInt int expectedColor) { 517 try (InputStream in = mResources.getAssets().open(fileName)) { 518 BitmapFactory.Options opts = new BitmapFactory.Options(); 519 opts.inMutable = true; 520 521 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 522 assertNotNull(b.getColorSpace()); 523 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 524 525 verifySetPixels(b, newColor, expectedColor); 526 527 b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2); 528 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 529 assertTrue(b.isMutable()); 530 verifySetPixels(b, newColor, expectedColor); 531 532 b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true); 533 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 534 assertTrue(b.isMutable()); 535 verifySetPixels(b, newColor, expectedColor); 536 } catch (IOException e) { 537 fail(); 538 } 539 } 540 verifySetPixels(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)541 private static void verifySetPixels(@NonNull Bitmap b, 542 @ColorInt int newColor, @ColorInt int expectedColor) { 543 assertTrue(b.isMutable()); 544 int[] pixels = new int[b.getWidth() * b.getHeight()]; 545 Arrays.fill(pixels, newColor); 546 b.setPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight()); 547 548 ByteBuffer dst = ByteBuffer.allocate(b.getByteCount()); 549 b.copyPixelsToBuffer(dst); 550 dst.rewind(); 551 552 IntBuffer buffer = dst.asIntBuffer(); 553 //noinspection ForLoopReplaceableByForEach 554 for (int i = 0; i < pixels.length; i++) { 555 // Stored as RGBA 556 ColorUtils.verifyColor(expectedColor, buffer.get(), 1); 557 } 558 } 559 560 @Test writeColorSpace()561 public void writeColorSpace() { 562 verifyColorSpaceMarshalling("green-srgb.png", ColorSpace.get(ColorSpace.Named.SRGB)); 563 verifyColorSpaceMarshalling("green-p3.png", ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); 564 verifyColorSpaceMarshalling("blue-16bit-srgb.png", 565 ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)); 566 567 Bitmap bitmapIn = BitmapFactory.decodeResource(mResources, R.drawable.robot); 568 verifyParcelUnparcel(bitmapIn, ColorSpace.get(ColorSpace.Named.SRGB)); 569 } 570 verifyColorSpaceMarshalling( @onNull String fileName, @NonNull ColorSpace colorSpace)571 private void verifyColorSpaceMarshalling( 572 @NonNull String fileName, @NonNull ColorSpace colorSpace) { 573 try (InputStream in = mResources.getAssets().open(fileName)) { 574 Bitmap bitmapIn = BitmapFactory.decodeStream(in); 575 verifyParcelUnparcel(bitmapIn, colorSpace); 576 } catch (IOException e) { 577 fail(); 578 } 579 } 580 verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected)581 private void verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected) { 582 ColorSpace cs = bitmapIn.getColorSpace(); 583 assertNotNull(cs); 584 assertSame(expected, cs); 585 586 Parcel p = Parcel.obtain(); 587 bitmapIn.writeToParcel(p, 0); 588 p.setDataPosition(0); 589 590 Bitmap bitmapOut = Bitmap.CREATOR.createFromParcel(p); 591 cs = bitmapOut.getColorSpace(); 592 assertNotNull(cs); 593 assertSame(expected, cs); 594 595 p.recycle(); 596 } 597 598 @Test p3rgb565()599 public void p3rgb565() { 600 BitmapFactory.Options opts = new BitmapFactory.Options(); 601 opts.inPreferredConfig = Bitmap.Config.RGB_565; 602 603 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 604 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 605 ColorSpace cs = b.getColorSpace(); 606 assertNotNull(cs); 607 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 608 } catch (IOException e) { 609 fail(); 610 } 611 } 612 613 @Test p3hardware()614 public void p3hardware() { 615 BitmapFactory.Options opts = new BitmapFactory.Options(); 616 opts.inPreferredConfig = Bitmap.Config.HARDWARE; 617 618 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 619 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 620 ColorSpace cs = b.getColorSpace(); 621 assertNotNull(cs); 622 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 623 } catch (IOException e) { 624 fail(); 625 } 626 } 627 628 @Test guessSRGB()629 public void guessSRGB() { 630 BitmapFactory.Options opts = new BitmapFactory.Options(); 631 opts.inJustDecodeBounds = true; 632 633 try (InputStream in = mResources.getAssets().open("green-srgb.png")) { 634 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 635 ColorSpace cs = opts.outColorSpace; 636 assertNull(b); 637 assertNotNull(cs); 638 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 639 } catch (IOException e) { 640 fail(); 641 } 642 } 643 644 @Test guess16bitUntagged()645 public void guess16bitUntagged() { 646 BitmapFactory.Options opts = new BitmapFactory.Options(); 647 opts.inJustDecodeBounds = true; 648 649 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 650 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 651 ColorSpace cs = opts.outColorSpace; 652 assertNull(b); 653 assertNotNull(cs); 654 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 655 } catch (IOException e) { 656 fail(); 657 } 658 } 659 660 @Test guessProPhotoRGB()661 public void guessProPhotoRGB() { 662 BitmapFactory.Options opts = new BitmapFactory.Options(); 663 opts.inJustDecodeBounds = true; 664 665 try (InputStream in = mResources.getAssets().open("blue-16bit-prophoto.png")) { 666 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 667 ColorSpace cs = opts.outColorSpace; 668 assertNull(b); 669 assertNotNull(cs); 670 assertSame(ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), cs); 671 } catch (IOException e) { 672 fail(); 673 } 674 } 675 676 @Test guessP3()677 public void guessP3() { 678 BitmapFactory.Options opts = new BitmapFactory.Options(); 679 opts.inJustDecodeBounds = true; 680 681 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 682 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 683 ColorSpace cs = opts.outColorSpace; 684 assertNull(b); 685 assertNotNull(cs); 686 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 687 } catch (IOException e) { 688 fail(); 689 } 690 } 691 692 @Test guessAdobeRGB()693 public void guessAdobeRGB() { 694 BitmapFactory.Options opts = new BitmapFactory.Options(); 695 opts.inJustDecodeBounds = true; 696 697 try (InputStream in = mResources.getAssets().open("red-adobergb.png")) { 698 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 699 ColorSpace cs = opts.outColorSpace; 700 assertNull(b); 701 assertNotNull(cs); 702 assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs); 703 } catch (IOException e) { 704 fail(); 705 } 706 } 707 708 @Test guessUnknown()709 public void guessUnknown() { 710 BitmapFactory.Options opts = new BitmapFactory.Options(); 711 opts.inJustDecodeBounds = true; 712 713 try (InputStream in = mResources.getAssets().open("purple-displayprofile.png")) { 714 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 715 ColorSpace cs = opts.outColorSpace; 716 assertNull(b); 717 assertNotNull(cs); 718 assertEquals("Unknown", cs.getName()); 719 } catch (IOException e) { 720 fail(); 721 } 722 } 723 724 @Test guessCMYK()725 public void guessCMYK() { 726 BitmapFactory.Options opts = new BitmapFactory.Options(); 727 opts.inJustDecodeBounds = true; 728 729 try (InputStream in = mResources.getAssets().open("purple-cmyk.png")) { 730 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 731 ColorSpace cs = opts.outColorSpace; 732 assertNull(b); 733 assertNotNull(cs); 734 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 735 } catch (IOException e) { 736 fail(); 737 } 738 } 739 740 @Test inColorSpaceP3ToSRGB()741 public void inColorSpaceP3ToSRGB() { 742 BitmapFactory.Options opts = new BitmapFactory.Options(); 743 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); 744 745 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 746 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 747 ColorSpace cs = b.getColorSpace(); 748 assertNotNull(cs); 749 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 750 assertEquals(opts.inPreferredColorSpace, opts.outColorSpace); 751 752 verifyGetPixel(b, 0x2ff00ff); 753 } catch (IOException e) { 754 fail(); 755 } 756 } 757 758 @Test inColorSpaceSRGBToP3()759 public void inColorSpaceSRGBToP3() { 760 BitmapFactory.Options opts = new BitmapFactory.Options(); 761 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); 762 763 try (InputStream in = mResources.getAssets().open("green-srgb.png")) { 764 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 765 ColorSpace cs = b.getColorSpace(); 766 assertNotNull(cs); 767 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 768 assertEquals(opts.inPreferredColorSpace, opts.outColorSpace); 769 770 verifyGetPixel(b, 0x75fb4cff); 771 } catch (IOException e) { 772 fail(); 773 } 774 } 775 776 @Test inColorSpaceWith16BitSrc()777 public void inColorSpaceWith16BitSrc() { 778 BitmapFactory.Options opts = new BitmapFactory.Options(); 779 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB); 780 781 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 782 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 783 ColorSpace cs = b.getColorSpace(); 784 assertNotNull(cs); 785 assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs); 786 assertSame(opts.inPreferredColorSpace, opts.outColorSpace); 787 } catch (IOException e) { 788 fail(); 789 } 790 } 791 792 @Test inColorSpaceWith16BitDst()793 public void inColorSpaceWith16BitDst() { 794 BitmapFactory.Options opts = new BitmapFactory.Options(); 795 opts.inPreferredConfig = Bitmap.Config.RGBA_F16; 796 797 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 798 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 799 ColorSpace cs = b.getColorSpace(); 800 assertNotNull(cs); 801 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 802 } catch (IOException e) { 803 fail(); 804 } 805 } 806 807 @Test inColorSpaceWith16BitSrcAndDst()808 public void inColorSpaceWith16BitSrcAndDst() { 809 BitmapFactory.Options opts = new BitmapFactory.Options(); 810 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB); 811 opts.inPreferredConfig = Bitmap.Config.RGBA_F16; 812 813 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 814 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 815 ColorSpace cs = b.getColorSpace(); 816 assertNotNull(cs); 817 assertSame(opts.inPreferredColorSpace, cs); 818 assertSame(opts.inPreferredColorSpace, opts.outColorSpace); 819 } catch (IOException e) { 820 fail(); 821 } 822 } 823 824 @Test inColorSpaceWith16BitWithDecreasedGamut()825 public void inColorSpaceWith16BitWithDecreasedGamut() { 826 final String asset = "blue-16bit-prophoto.png"; 827 BitmapFactory.Options opts = new BitmapFactory.Options(); 828 opts.inJustDecodeBounds = true; 829 try (InputStream in = mResources.getAssets().open(asset)) { 830 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 831 assertNull(b); 832 assertEquals(ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), opts.outColorSpace); 833 assertEquals(Bitmap.Config.RGBA_F16, opts.outConfig); 834 } catch (IOException e) { 835 fail(); 836 } 837 838 opts.inJustDecodeBounds = false; 839 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); 840 841 try (InputStream in = mResources.getAssets().open(asset)) { 842 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 843 ColorSpace cs = b.getColorSpace(); 844 assertNotNull(cs); 845 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 846 } catch (IOException e) { 847 fail(); 848 } 849 } 850 851 @Test inColorSpace565()852 public void inColorSpace565() { 853 BitmapFactory.Options opts = new BitmapFactory.Options(); 854 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB); 855 opts.inPreferredConfig = Bitmap.Config.RGB_565; 856 857 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 858 Bitmap b = BitmapFactory.decodeStream(in, null, opts); 859 ColorSpace cs = b.getColorSpace(); 860 assertNotNull(cs); 861 assertSame(opts.inPreferredColorSpace, cs); 862 assertSame(opts.inPreferredColorSpace, opts.outColorSpace); 863 } catch (IOException e) { 864 fail(); 865 } 866 } 867 868 @Test(expected = IllegalArgumentException.class) inColorSpaceNotRGB()869 public void inColorSpaceNotRGB() { 870 BitmapFactory.Options opts = new BitmapFactory.Options(); 871 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB); 872 873 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 874 BitmapFactory.decodeStream(in, null, opts); 875 } catch (IOException e) { 876 fail(); 877 } 878 } 879 880 @Test(expected = IllegalArgumentException.class) inColorSpaceNoTransferParameters()881 public void inColorSpaceNoTransferParameters() { 882 BitmapFactory.Options opts = new BitmapFactory.Options(); 883 opts.inPreferredColorSpace = new ColorSpace.Rgb("NoTransferParams", 884 new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 885 ColorSpace.ILLUMINANT_D50, 886 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f), 887 0, 1); 888 889 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 890 BitmapFactory.decodeStream(in, null, opts); 891 } catch (IOException e) { 892 fail(); 893 } 894 } 895 896 @Test copyF16()897 public void copyF16() { 898 // Copying from (LINEAR_)SRGB to RGBA_F16 results in (LINEAR_)EXTENDED_SRGB. 899 ColorSpace[] srcCS = new ColorSpace[] { ColorSpace.get(ColorSpace.Named.SRGB), 900 ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) }; 901 ColorSpace[] dstCS = new ColorSpace[] { ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 902 ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB) }; 903 904 for (int i = 0; i < srcCS.length; ++i) { 905 for (Bitmap.Config config : new Bitmap.Config[] { Bitmap.Config.ARGB_8888, 906 Bitmap.Config.RGB_565 }) { 907 Bitmap b = Bitmap.createBitmap(10, 10, config, false, srcCS[i]); 908 assertSame(srcCS[i], b.getColorSpace()); 909 910 for (boolean mutable : new boolean[] { true, false }) { 911 Bitmap copy = b.copy(Bitmap.Config.RGBA_F16, mutable); 912 assertSame(dstCS[i], copy.getColorSpace()); 913 } 914 } 915 } 916 917 // The same is true for the reverse 918 for (int i = 0; i < srcCS.length; ++i) { 919 Bitmap b = Bitmap.createBitmap(10, 10, Bitmap.Config.RGBA_F16, false, dstCS[i]); 920 assertSame(dstCS[i], b.getColorSpace()); 921 for (Bitmap.Config config : new Bitmap.Config[] { Bitmap.Config.ARGB_8888, 922 Bitmap.Config.RGB_565 }) { 923 for (boolean mutable : new boolean[] { true, false }) { 924 Bitmap copy = b.copy(config, mutable); 925 assertSame(srcCS[i], copy.getColorSpace()); 926 } 927 } 928 } 929 } 930 931 @Test copyAlpha8()932 public void copyAlpha8() { 933 for (Bitmap.Config srcConfig : new Bitmap.Config[] { 934 Bitmap.Config.ALPHA_8, 935 Bitmap.Config.RGB_565, 936 Bitmap.Config.ARGB_8888, 937 Bitmap.Config.RGBA_F16, 938 }) { 939 Bitmap b = Bitmap.createBitmap(1, 1, srcConfig); 940 assertNotNull(b); 941 if (srcConfig == Bitmap.Config.ALPHA_8) { 942 assertNull(b.getColorSpace()); 943 } else { 944 assertNotNull(b.getColorSpace()); 945 } 946 947 Bitmap copy = b.copy(Bitmap.Config.ALPHA_8, false); 948 assertNotNull(copy); 949 assertNull(copy.getColorSpace()); 950 951 Bitmap copy2 = copy.copy(srcConfig, false); 952 switch (srcConfig) { 953 case RGBA_F16: 954 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 955 copy2.getColorSpace()); 956 break; 957 case ALPHA_8: 958 assertNull(b.getColorSpace()); 959 break; 960 default: 961 assertSame("Copied from ALPHA_8 to " + srcConfig, 962 ColorSpace.get(ColorSpace.Named.SRGB), copy2.getColorSpace()); 963 } 964 } 965 } 966 967 @Test copyHardwareToAlpha8()968 public void copyHardwareToAlpha8() { 969 BitmapFactory.Options options = new BitmapFactory.Options(); 970 options.inPreferredConfig = Bitmap.Config.HARDWARE; 971 Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot, options); 972 assertSame(Bitmap.Config.HARDWARE, b.getConfig()); 973 assertNotNull(b.getColorSpace()); 974 975 Bitmap copy = b.copy(Bitmap.Config.ALPHA_8, false); 976 assertNull(copy.getColorSpace()); 977 } 978 979 @Test copy()980 public void copy() { 981 Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot); 982 Bitmap c; 983 ColorSpace cs; 984 boolean[] trueFalse = new boolean[] { true, false }; 985 986 for (boolean mutable : trueFalse) { 987 c = b.copy(Bitmap.Config.ARGB_8888, mutable); 988 cs = c.getColorSpace(); 989 assertNotNull(cs); 990 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs); 991 } 992 993 try (InputStream in = mResources.getAssets().open("green-p3.png")) { 994 b = BitmapFactory.decodeStream(in); 995 c = b.copy(Bitmap.Config.ARGB_8888, false); 996 cs = c.getColorSpace(); 997 assertNotNull(cs); 998 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 999 1000 c = b.copy(Bitmap.Config.ARGB_8888, true); 1001 cs = c.getColorSpace(); 1002 assertNotNull(cs); 1003 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs); 1004 } catch (IOException e) { 1005 fail(); 1006 } 1007 1008 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 1009 b = BitmapFactory.decodeStream(in); 1010 c = b.copy(Bitmap.Config.RGBA_F16, false); 1011 cs = c.getColorSpace(); 1012 assertNotNull(cs); 1013 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 1014 1015 c = b.copy(Bitmap.Config.RGBA_F16, true); 1016 cs = c.getColorSpace(); 1017 assertNotNull(cs); 1018 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs); 1019 } catch (IOException e) { 1020 fail(); 1021 } 1022 } 1023 1024 @SuppressWarnings("SameParameterValue") almostEqual(@olorInt int expected, @ColorInt int pixel, int threshold, int index)1025 private static void almostEqual(@ColorInt int expected, 1026 @ColorInt int pixel, int threshold, int index) { 1027 int diffA = Math.abs((expected >>> 24) - (pixel >>> 24)); 1028 int diffR = Math.abs(((expected >> 16) & 0xff) - ((pixel >> 16) & 0xff)); 1029 int diffG = Math.abs(((expected >> 8) & 0xff) - ((pixel >> 8) & 0xff)); 1030 int diffB = Math.abs((expected & 0xff) - (pixel & 0xff)); 1031 1032 boolean pass = diffA + diffR + diffG + diffB <= threshold; 1033 if (!pass) { 1034 Log.d(LOG_TAG, "Expected 0x" + Integer.toHexString(expected) + 1035 " but was 0x" + Integer.toHexString(pixel) + " with index " + index); 1036 } 1037 1038 assertTrue(pass); 1039 } 1040 compressFormatsAndColorSpaces()1041 private Object[] compressFormatsAndColorSpaces() { 1042 return Utils.crossProduct(Bitmap.CompressFormat.values(), 1043 BitmapTest.getRgbColorSpaces().toArray()); 1044 } 1045 1046 @Test 1047 @Parameters(method = "compressFormatsAndColorSpaces") testEncodeColorSpace(Bitmap.CompressFormat format, ColorSpace colorSpace)1048 public void testEncodeColorSpace(Bitmap.CompressFormat format, ColorSpace colorSpace) { 1049 Bitmap b = null; 1050 ColorSpace decodedColorSpace = null; 1051 ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(), 1052 "blue-16bit-srgb.png"); 1053 try { 1054 b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> { 1055 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 1056 decoder.setTargetColorSpace(colorSpace); 1057 }); 1058 assertNotNull(b); 1059 assertEquals(Bitmap.Config.RGBA_F16, b.getConfig()); 1060 decodedColorSpace = b.getColorSpace(); 1061 1062 // Requesting a ColorSpace with an EXTENDED variant will use the EXTENDED one because 1063 // the image is 16-bit. 1064 if (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) { 1065 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), decodedColorSpace); 1066 } else if (colorSpace == ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)) { 1067 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), 1068 decodedColorSpace); 1069 } else { 1070 assertSame(colorSpace, decodedColorSpace); 1071 } 1072 } catch (IOException e) { 1073 fail("Failed with " + e); 1074 } 1075 1076 ByteArrayOutputStream out = new ByteArrayOutputStream(); 1077 assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out)); 1078 1079 byte[] array = out.toByteArray(); 1080 src = ImageDecoder.createSource(ByteBuffer.wrap(array)); 1081 1082 try { 1083 Bitmap b2 = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> { 1084 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 1085 }); 1086 ColorSpace encodedColorSpace = b2.getColorSpace(); 1087 if (format == Bitmap.CompressFormat.PNG) { 1088 assertEquals(Bitmap.Config.RGBA_F16, b2.getConfig()); 1089 assertSame(decodedColorSpace, encodedColorSpace); 1090 } else { 1091 // Compressing to the other formats does not support creating a compressed version 1092 // that we will decode to F16. 1093 assertEquals(Bitmap.Config.ARGB_8888, b2.getConfig()); 1094 1095 // Decoding an EXTENDED variant to 8888 results in the non-extended variant. 1096 if (decodedColorSpace == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) { 1097 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), encodedColorSpace); 1098 } else if (decodedColorSpace 1099 == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) { 1100 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), encodedColorSpace); 1101 } else { 1102 assertSame(decodedColorSpace, encodedColorSpace); 1103 } 1104 } 1105 } catch (IOException e) { 1106 fail("Failed with " + e); 1107 } 1108 } 1109 1110 @Test testEncodeP3hardware()1111 public void testEncodeP3hardware() { 1112 Bitmap b = null; 1113 ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(), 1114 "green-p3.png"); 1115 try { 1116 b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> { 1117 decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE); 1118 }); 1119 assertNotNull(b); 1120 assertEquals(Bitmap.Config.HARDWARE, b.getConfig()); 1121 assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace()); 1122 } catch (IOException e) { 1123 fail("Failed with " + e); 1124 } 1125 1126 for (Bitmap.CompressFormat format : Bitmap.CompressFormat.values()) { 1127 ByteArrayOutputStream out = new ByteArrayOutputStream(); 1128 assertTrue("Failed to encode 8888 to " + format, b.compress(format, 100, out)); 1129 1130 byte[] array = out.toByteArray(); 1131 src = ImageDecoder.createSource(ByteBuffer.wrap(array)); 1132 1133 try { 1134 Bitmap b2 = ImageDecoder.decodeBitmap(src); 1135 assertEquals("Wrong color space for " + format, 1136 ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace()); 1137 } catch (IOException e) { 1138 fail("Failed with " + e); 1139 } 1140 } 1141 } 1142 1143 @Test 1144 @RequiresDevice // SwiftShader does not yet have support for F16 in HARDWARE b/75778024 test16bitHardware()1145 public void test16bitHardware() { 1146 // Decoding to HARDWARE may use EXTENDED_SRGB or SRGB, depending 1147 // on whether F16 is supported in HARDWARE. 1148 try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) { 1149 BitmapFactory.Options options = new BitmapFactory.Options(); 1150 options.inPreferredConfig = Bitmap.Config.HARDWARE; 1151 Bitmap b = BitmapFactory.decodeStream(in, null, options); 1152 assertEquals(Bitmap.Config.HARDWARE, b.getConfig()); 1153 1154 final ColorSpace cs = b.getColorSpace(); 1155 if (cs != ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) 1156 && cs != ColorSpace.get(ColorSpace.Named.SRGB)) { 1157 fail("Unexpected color space " + cs); 1158 } 1159 } catch (Exception e) { 1160 fail("Failed with " + e); 1161 } 1162 } 1163 1164 @Test testProPhoto()1165 public void testProPhoto() throws IOException { 1166 ColorSpace extendedSrgb = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB); 1167 Color blue = Color.valueOf(0, 0, 1, 1, ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB)); 1168 Color expected = blue.convert(extendedSrgb); 1169 try (InputStream in = mResources.getAssets().open("blue-16bit-prophoto.png")) { 1170 Bitmap src = BitmapFactory.decodeStream(in, null, null); 1171 1172 Bitmap dst = Bitmap.createBitmap(src.getWidth(), src.getHeight(), 1173 Bitmap.Config.RGBA_F16, true, extendedSrgb); 1174 Canvas c = new Canvas(dst); 1175 c.drawBitmap(src, 0, 0, null); 1176 ColorUtils.verifyColor("PRO_PHOTO image did not convert properly", expected, 1177 dst.getColor(0, 0), .001f); 1178 } 1179 } 1180 1181 @Test testGrayscaleProfile()1182 public void testGrayscaleProfile() throws IOException { 1183 ImageDecoder.Source source = ImageDecoder.createSource(mResources.getAssets(), 1184 "gimp-d65-grayscale.jpg"); 1185 Bitmap bm = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> { 1186 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 1187 }); 1188 ColorSpace cs = bm.getColorSpace(); 1189 assertNotNull(cs); 1190 assertTrue(cs instanceof ColorSpace.Rgb); 1191 ColorSpace.Rgb rgbCs = (ColorSpace.Rgb) cs; 1192 1193 // A gray color space uses a special primaries array of all 1s. 1194 float[] primaries = rgbCs.getPrimaries(); 1195 assertNotNull(primaries); 1196 assertEquals(6, primaries.length); 1197 for (float primary : primaries) { 1198 assertEquals(0, Float.compare(primary, 1.0f)); 1199 } 1200 1201 // A gray color space will have all zeroes in the transform 1202 // and inverse transform, except for the diagonal. 1203 for (float[] transform : new float[][]{rgbCs.getTransform(), rgbCs.getInverseTransform()}) { 1204 assertNotNull(transform); 1205 assertEquals(9, transform.length); 1206 for (int index : new int[] { 1, 2, 3, 5, 6, 7 }) { 1207 assertEquals(0, Float.compare(0.0f, transform[index])); 1208 } 1209 } 1210 1211 // When creating another Bitmap with the same ColorSpace, the two 1212 // ColorSpaces should be equal. 1213 Bitmap otherBm = Bitmap.createBitmap(null, 100, 100, Bitmap.Config.ARGB_8888, true, cs); 1214 assertEquals(cs, otherBm.getColorSpace()); 1215 1216 // Same for a scaled bitmap. 1217 Bitmap scaledBm = Bitmap.createScaledBitmap(bm, bm.getWidth() / 4, bm.getHeight() / 4, 1218 true); 1219 assertEquals(cs, scaledBm.getColorSpace()); 1220 1221 // A previous ColorSpace bug resulted in a Bitmap created like scaledBm 1222 // having all black pixels. Verify that the Bitmap contains colors other 1223 // than black and white. 1224 boolean foundOtherColor = false; 1225 final int width = scaledBm.getWidth(); 1226 final int height = scaledBm.getHeight(); 1227 int[] pixels = new int[width * height]; 1228 scaledBm.getPixels(pixels, 0, width, 0, 0, width, height); 1229 for (int pixel : pixels) { 1230 if (pixel != Color.BLACK && pixel != Color.WHITE) { 1231 foundOtherColor = true; 1232 break; 1233 } 1234 } 1235 assertTrue(foundOtherColor); 1236 } 1237 } 1238