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