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