1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.graphics.cts; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNotEquals; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.junit.Assume.assumeNoException; 27 import static org.junit.Assume.assumeNotNull; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.content.res.Resources; 31 import android.graphics.Bitmap; 32 import android.graphics.Bitmap.CompressFormat; 33 import android.graphics.Bitmap.Config; 34 import android.graphics.BitmapFactory; 35 import android.graphics.Canvas; 36 import android.graphics.Color; 37 import android.graphics.ColorSpace; 38 import android.graphics.ColorSpace.Named; 39 import android.graphics.ImageDecoder; 40 import android.graphics.ImageFormat; 41 import android.graphics.LinearGradient; 42 import android.graphics.Matrix; 43 import android.graphics.Paint; 44 import android.graphics.Picture; 45 import android.graphics.Shader; 46 import android.hardware.HardwareBuffer; 47 import android.os.Debug; 48 import android.os.Parcel; 49 import android.os.StrictMode; 50 import android.util.DisplayMetrics; 51 import android.view.Surface; 52 53 import androidx.test.InstrumentationRegistry; 54 import androidx.test.filters.LargeTest; 55 import androidx.test.filters.SmallTest; 56 57 import com.android.compatibility.common.util.BitmapUtils; 58 import com.android.compatibility.common.util.ColorUtils; 59 import com.android.compatibility.common.util.WidgetTestUtils; 60 61 import org.junit.Before; 62 import org.junit.Test; 63 import org.junit.runner.RunWith; 64 65 import java.io.ByteArrayOutputStream; 66 import java.io.File; 67 import java.io.IOException; 68 import java.io.OutputStream; 69 import java.nio.ByteBuffer; 70 import java.nio.CharBuffer; 71 import java.nio.IntBuffer; 72 import java.nio.ShortBuffer; 73 import java.util.ArrayList; 74 import java.util.Arrays; 75 import java.util.HashSet; 76 import java.util.List; 77 import java.util.concurrent.CountDownLatch; 78 import java.util.concurrent.TimeUnit; 79 80 import junitparams.JUnitParamsRunner; 81 import junitparams.Parameters; 82 83 @SmallTest 84 @RunWith(JUnitParamsRunner.class) 85 public class BitmapTest { 86 // small alpha values cause color values to be pre-multiplied down, losing accuracy 87 private static final int PREMUL_COLOR = Color.argb(2, 255, 254, 253); 88 private static final int PREMUL_ROUNDED_COLOR = Color.argb(2, 255, 255, 255); 89 private static final int PREMUL_STORED_COLOR = Color.argb(2, 2, 2, 2); 90 91 private static final BitmapFactory.Options HARDWARE_OPTIONS = createHardwareBitmapOptions(); 92 93 static { 94 System.loadLibrary("ctsgraphics_jni"); 95 } 96 97 private Resources mRes; 98 private Bitmap mBitmap; 99 private BitmapFactory.Options mOptions; 100 getRgbColorSpaces()101 public static List<ColorSpace> getRgbColorSpaces() { 102 List<ColorSpace> rgbColorSpaces; 103 rgbColorSpaces = new ArrayList<ColorSpace>(); 104 for (ColorSpace.Named e : ColorSpace.Named.values()) { 105 ColorSpace cs = ColorSpace.get(e); 106 if (cs.getModel() != ColorSpace.Model.RGB) { 107 continue; 108 } 109 if (((ColorSpace.Rgb) cs).getTransferParameters() == null) { 110 continue; 111 } 112 rgbColorSpaces.add(cs); 113 } 114 return rgbColorSpaces; 115 } 116 117 @Before setup()118 public void setup() { 119 mRes = InstrumentationRegistry.getTargetContext().getResources(); 120 mOptions = new BitmapFactory.Options(); 121 mOptions.inScaled = false; 122 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 123 } 124 125 @Test(expected=IllegalStateException.class) testCompressRecycled()126 public void testCompressRecycled() { 127 mBitmap.recycle(); 128 mBitmap.compress(CompressFormat.JPEG, 0, null); 129 } 130 131 @Test(expected=NullPointerException.class) testCompressNullStream()132 public void testCompressNullStream() { 133 mBitmap.compress(CompressFormat.JPEG, 0, null); 134 } 135 136 @Test(expected=IllegalArgumentException.class) testCompressQualityTooLow()137 public void testCompressQualityTooLow() { 138 mBitmap.compress(CompressFormat.JPEG, -1, new ByteArrayOutputStream()); 139 } 140 141 @Test(expected=IllegalArgumentException.class) testCompressQualityTooHigh()142 public void testCompressQualityTooHigh() { 143 mBitmap.compress(CompressFormat.JPEG, 101, new ByteArrayOutputStream()); 144 } 145 compressFormats()146 private static Object[] compressFormats() { 147 return CompressFormat.values(); 148 } 149 150 @Test 151 @Parameters(method = "compressFormats") testCompress(CompressFormat format)152 public void testCompress(CompressFormat format) { 153 assertTrue(mBitmap.compress(format, 50, new ByteArrayOutputStream())); 154 } 155 decodeBytes(byte[] bytes)156 private Bitmap decodeBytes(byte[] bytes) { 157 ByteBuffer buffer = ByteBuffer.wrap(bytes); 158 ImageDecoder.Source src = ImageDecoder.createSource(buffer); 159 try { 160 return ImageDecoder.decodeBitmap(src, (decoder, info, s) -> { 161 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 162 }); 163 } catch (IOException e) { 164 fail("Failed to decode with " + e); 165 return null; 166 } 167 } 168 169 // There are three color components and 170 // each should be within a square difference of 15 * 15. 171 private static final int MSE_MARGIN = 3 * (15 * 15); 172 173 @Test testCompressWebpLossy()174 public void testCompressWebpLossy() { 175 // For qualities < 100, WEBP performs a lossy decode. 176 byte[] last = null; 177 Bitmap lastBitmap = null; 178 for (int quality : new int[] { 25, 50, 80, 99 }) { 179 ByteArrayOutputStream webp = new ByteArrayOutputStream(); 180 assertTrue(mBitmap.compress(CompressFormat.WEBP, quality, webp)); 181 byte[] webpCompressed = webp.toByteArray(); 182 183 184 ByteArrayOutputStream webpLossy = new ByteArrayOutputStream(); 185 assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSY, quality, webpLossy)); 186 byte[] webpLossyCompressed = webpLossy.toByteArray(); 187 188 assertTrue("Compression did not match at quality " + quality, 189 Arrays.equals(webpCompressed, webpLossyCompressed)); 190 191 Bitmap result = decodeBytes(webpCompressed); 192 if (last != null) { 193 // Higher quality will generally result in a larger file. 194 assertTrue(webpCompressed.length > last.length); 195 if (!BitmapUtils.compareBitmapsMse(lastBitmap, result, MSE_MARGIN, true, false)) { 196 fail("Bad comparison for quality " + quality); 197 } 198 } 199 last = webpCompressed; 200 lastBitmap = result; 201 } 202 } 203 204 @Test 205 @Parameters({ "0", "50", "80", "99", "100" }) testCompressWebpLossless(int quality)206 public void testCompressWebpLossless(int quality) { 207 ByteArrayOutputStream webp = new ByteArrayOutputStream(); 208 assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSLESS, quality, webp)); 209 byte[] webpCompressed = webp.toByteArray(); 210 Bitmap result = decodeBytes(webpCompressed); 211 212 assertTrue("WEBP_LOSSLESS did not losslessly compress at quality " + quality, 213 BitmapUtils.compareBitmaps(mBitmap, result)); 214 } 215 216 @Test testCompressWebp100MeansLossless()217 public void testCompressWebp100MeansLossless() { 218 ByteArrayOutputStream webp = new ByteArrayOutputStream(); 219 assertTrue(mBitmap.compress(CompressFormat.WEBP, 100, webp)); 220 byte[] webpCompressed = webp.toByteArray(); 221 Bitmap result = decodeBytes(webpCompressed); 222 assertTrue("WEBP_LOSSLESS did not losslessly compress at quality 100", 223 BitmapUtils.compareBitmaps(mBitmap, result)); 224 } 225 226 @Test 227 @Parameters(method = "compressFormats") testCompressAlpha8Fails(CompressFormat format)228 public void testCompressAlpha8Fails(CompressFormat format) { 229 Bitmap bitmap = Bitmap.createBitmap(1, 1, Config.ALPHA_8); 230 assertFalse("Incorrectly compressed ALPHA_8 to " + format, 231 bitmap.compress(format, 50, new ByteArrayOutputStream())); 232 233 if (format == CompressFormat.WEBP) { 234 // Skip the native test, since the NDK just has equivalents for 235 // WEBP_LOSSY and WEBP_LOSSLESS. 236 return; 237 } 238 239 byte[] storage = new byte[16 * 1024]; 240 OutputStream stream = new ByteArrayOutputStream(); 241 assertFalse("Incorrectly compressed ALPHA_8 with the NDK to " + format, 242 nCompress(bitmap, nativeCompressFormat(format), 50, stream, storage)); 243 } 244 245 @Test(expected=IllegalStateException.class) testCopyRecycled()246 public void testCopyRecycled() { 247 mBitmap.recycle(); 248 mBitmap.copy(Config.RGB_565, false); 249 } 250 251 @Test testCopy()252 public void testCopy() { 253 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 254 Bitmap bitmap = mBitmap.copy(Config.ARGB_8888, false); 255 WidgetTestUtils.assertEquals(mBitmap, bitmap); 256 } 257 258 @Test testCopyConfigs()259 public void testCopyConfigs() { 260 Config[] supportedConfigs = new Config[] { 261 Config.ALPHA_8, Config.RGB_565, Config.ARGB_8888, Config.RGBA_F16, 262 }; 263 for (Config src : supportedConfigs) { 264 for (Config dst : supportedConfigs) { 265 Bitmap srcBitmap = Bitmap.createBitmap(1, 1, src); 266 srcBitmap.eraseColor(Color.WHITE); 267 Bitmap dstBitmap = srcBitmap.copy(dst, false); 268 assertNotNull("Should support copying from " + src + " to " + dst, 269 dstBitmap); 270 if (Config.ALPHA_8 == dst || Config.ALPHA_8 == src) { 271 // Color will be opaque but color information will be lost. 272 assertEquals("Color should be black when copying from " + src + " to " 273 + dst, Color.BLACK, dstBitmap.getPixel(0, 0)); 274 } else { 275 assertEquals("Color should be preserved when copying from " + src + " to " 276 + dst, Color.WHITE, dstBitmap.getPixel(0, 0)); 277 } 278 } 279 } 280 } 281 282 @Test(expected=IllegalArgumentException.class) testCopyMutableHwBitmap()283 public void testCopyMutableHwBitmap() { 284 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 285 mBitmap.copy(Config.HARDWARE, true); 286 } 287 288 @Test(expected=RuntimeException.class) testCopyPixelsToBufferUnsupportedBufferClass()289 public void testCopyPixelsToBufferUnsupportedBufferClass() { 290 final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight(); 291 292 mBitmap.copyPixelsToBuffer(CharBuffer.allocate(pixSize)); 293 } 294 295 @Test(expected=RuntimeException.class) testCopyPixelsToBufferBufferTooSmall()296 public void testCopyPixelsToBufferBufferTooSmall() { 297 final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight(); 298 final int tooSmall = pixSize / 2; 299 300 mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall)); 301 } 302 303 @Test testCopyPixelsToBuffer()304 public void testCopyPixelsToBuffer() { 305 final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight(); 306 307 ByteBuffer byteBuf = ByteBuffer.allocate(pixSize); 308 assertEquals(0, byteBuf.position()); 309 mBitmap.copyPixelsToBuffer(byteBuf); 310 assertEquals(pixSize, byteBuf.position()); 311 312 ShortBuffer shortBuf = ShortBuffer.allocate(pixSize); 313 assertEquals(0, shortBuf.position()); 314 mBitmap.copyPixelsToBuffer(shortBuf); 315 assertEquals(pixSize >> 1, shortBuf.position()); 316 317 IntBuffer intBuf1 = IntBuffer.allocate(pixSize); 318 assertEquals(0, intBuf1.position()); 319 mBitmap.copyPixelsToBuffer(intBuf1); 320 assertEquals(pixSize >> 2, intBuf1.position()); 321 322 Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), 323 mBitmap.getConfig()); 324 intBuf1.position(0); // copyPixelsToBuffer adjusted the position, so rewind to start 325 bitmap.copyPixelsFromBuffer(intBuf1); 326 IntBuffer intBuf2 = IntBuffer.allocate(pixSize); 327 bitmap.copyPixelsToBuffer(intBuf2); 328 329 assertEquals(pixSize >> 2, intBuf2.position()); 330 assertEquals(intBuf1.position(), intBuf2.position()); 331 int size = intBuf1.position(); 332 intBuf1.position(0); 333 intBuf2.position(0); 334 for (int i = 0; i < size; i++) { 335 assertEquals("mismatching pixels at position " + i, intBuf1.get(), intBuf2.get()); 336 } 337 } 338 339 @Test testCreateBitmap1()340 public void testCreateBitmap1() { 341 int[] colors = createColors(100); 342 Bitmap bitmap = Bitmap.createBitmap(colors, 10, 10, Config.RGB_565); 343 assertFalse(bitmap.isMutable()); 344 Bitmap ret = Bitmap.createBitmap(bitmap); 345 assertNotNull(ret); 346 assertFalse(ret.isMutable()); 347 assertEquals(10, ret.getWidth()); 348 assertEquals(10, ret.getHeight()); 349 assertEquals(Config.RGB_565, ret.getConfig()); 350 assertEquals(ANDROID_BITMAP_FORMAT_RGB_565, nGetFormat(ret)); 351 } 352 353 @Test(expected=IllegalArgumentException.class) testCreateBitmapNegativeX()354 public void testCreateBitmapNegativeX() { 355 Bitmap.createBitmap(mBitmap, -100, 50, 50, 200); 356 } 357 358 @Test testCreateBitmap2()359 public void testCreateBitmap2() { 360 // special case: output bitmap is equal to the input bitmap 361 mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888); 362 assertFalse(mBitmap.isMutable()); // createBitmap w/ colors should be immutable 363 Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100); 364 assertNotNull(ret); 365 assertFalse(ret.isMutable()); // createBitmap from subset should be immutable 366 assertTrue(mBitmap.equals(ret)); 367 368 //normal case 369 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 370 ret = Bitmap.createBitmap(mBitmap, 10, 10, 50, 50); 371 assertNotNull(ret); 372 assertFalse(mBitmap.equals(ret)); 373 assertEquals(ANDROID_BITMAP_FORMAT_RGBA_8888, nGetFormat(mBitmap)); 374 } 375 376 @Test(expected=IllegalArgumentException.class) testCreateBitmapNegativeXY()377 public void testCreateBitmapNegativeXY() { 378 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 379 380 // abnormal case: x and/or y less than 0 381 Bitmap.createBitmap(mBitmap, -1, -1, 10, 10, null, false); 382 } 383 384 @Test(expected=IllegalArgumentException.class) testCreateBitmapNegativeWidthHeight()385 public void testCreateBitmapNegativeWidthHeight() { 386 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 387 388 // abnormal case: width and/or height less than 0 389 Bitmap.createBitmap(mBitmap, 1, 1, -10, -10, null, false); 390 } 391 392 @Test(expected=IllegalArgumentException.class) testCreateBitmapXRegionTooWide()393 public void testCreateBitmapXRegionTooWide() { 394 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 395 396 // abnormal case: (x + width) bigger than source bitmap's width 397 Bitmap.createBitmap(mBitmap, 10, 10, 95, 50, null, false); 398 } 399 400 @Test(expected=IllegalArgumentException.class) testCreateBitmapYRegionTooTall()401 public void testCreateBitmapYRegionTooTall() { 402 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 403 404 // abnormal case: (y + height) bigger than source bitmap's height 405 Bitmap.createBitmap(mBitmap, 10, 10, 50, 95, null, false); 406 } 407 408 @Test(expected=IllegalArgumentException.class) testCreateMutableBitmapWithHardwareConfig()409 public void testCreateMutableBitmapWithHardwareConfig() { 410 Bitmap.createBitmap(100, 100, Config.HARDWARE); 411 } 412 413 @Test testCreateBitmap3()414 public void testCreateBitmap3() { 415 // special case: output bitmap is equal to the input bitmap 416 mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888); 417 Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100, null, false); 418 assertNotNull(ret); 419 assertFalse(ret.isMutable()); // subset should be immutable 420 assertTrue(mBitmap.equals(ret)); 421 422 // normal case 423 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 424 ret = Bitmap.createBitmap(mBitmap, 10, 10, 50, 50, new Matrix(), true); 425 assertTrue(ret.isMutable()); 426 assertNotNull(ret); 427 assertFalse(mBitmap.equals(ret)); 428 } 429 430 @Test testCreateBitmapFromHardwareBitmap()431 public void testCreateBitmapFromHardwareBitmap() { 432 Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, 433 HARDWARE_OPTIONS); 434 assertEquals(Config.HARDWARE, hardwareBitmap.getConfig()); 435 436 Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 96, 96, null, false); 437 assertEquals(Config.HARDWARE, ret.getConfig()); 438 assertFalse(ret.isMutable()); 439 } 440 441 @Test testCreateBitmap4()442 public void testCreateBitmap4() { 443 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 444 assertNotNull(ret); 445 assertTrue(ret.isMutable()); 446 assertEquals(100, ret.getWidth()); 447 assertEquals(200, ret.getHeight()); 448 assertEquals(Config.RGB_565, ret.getConfig()); 449 } 450 verify2x2BitmapContents(int[] expected, Bitmap observed)451 private static void verify2x2BitmapContents(int[] expected, Bitmap observed) { 452 ColorUtils.verifyColor(expected[0], observed.getPixel(0, 0)); 453 ColorUtils.verifyColor(expected[1], observed.getPixel(1, 0)); 454 ColorUtils.verifyColor(expected[2], observed.getPixel(0, 1)); 455 ColorUtils.verifyColor(expected[3], observed.getPixel(1, 1)); 456 } 457 458 @Test testCreateBitmap_matrix()459 public void testCreateBitmap_matrix() { 460 int[] colorArray = new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.BLACK }; 461 Bitmap src = Bitmap.createBitmap(2, 2, Config.ARGB_8888); 462 assertTrue(src.isMutable()); 463 src.setPixels(colorArray,0, 2, 0, 0, 2, 2); 464 465 // baseline 466 verify2x2BitmapContents(colorArray, src); 467 468 // null 469 Bitmap dst = Bitmap.createBitmap(src, 0, 0, 2, 2, null, false); 470 assertTrue(dst.isMutable()); 471 verify2x2BitmapContents(colorArray, dst); 472 473 // identity matrix 474 Matrix matrix = new Matrix(); 475 dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false); 476 assertTrue(dst.isMutable()); 477 verify2x2BitmapContents(colorArray, dst); 478 479 // big scale - only red visible 480 matrix.setScale(10, 10); 481 dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false); 482 assertTrue(dst.isMutable()); 483 verify2x2BitmapContents(new int[] { Color.RED, Color.RED, Color.RED, Color.RED }, dst); 484 485 // rotation 486 matrix.setRotate(90); 487 dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false); 488 assertTrue(dst.isMutable()); 489 verify2x2BitmapContents( 490 new int[] { Color.BLUE, Color.RED, Color.BLACK, Color.GREEN }, dst); 491 } 492 493 @Test(expected=IllegalArgumentException.class) testCreateBitmapFromColorsNegativeWidthHeight()494 public void testCreateBitmapFromColorsNegativeWidthHeight() { 495 int[] colors = createColors(100); 496 497 // abnormal case: width and/or height less than 0 498 Bitmap.createBitmap(colors, 0, 100, -1, 100, Config.RGB_565); 499 } 500 501 @Test(expected=IllegalArgumentException.class) testCreateBitmapFromColorsIllegalStride()502 public void testCreateBitmapFromColorsIllegalStride() { 503 int[] colors = createColors(100); 504 505 // abnormal case: stride less than width and bigger than -width 506 Bitmap.createBitmap(colors, 10, 10, 100, 100, Config.RGB_565); 507 } 508 509 @Test(expected=ArrayIndexOutOfBoundsException.class) testCreateBitmapFromColorsNegativeOffset()510 public void testCreateBitmapFromColorsNegativeOffset() { 511 int[] colors = createColors(100); 512 513 // abnormal case: offset less than 0 514 Bitmap.createBitmap(colors, -10, 100, 100, 100, Config.RGB_565); 515 } 516 517 @Test(expected=ArrayIndexOutOfBoundsException.class) testCreateBitmapFromColorsOffsetTooLarge()518 public void testCreateBitmapFromColorsOffsetTooLarge() { 519 int[] colors = createColors(100); 520 521 // abnormal case: (offset + width) bigger than colors' length 522 Bitmap.createBitmap(colors, 10, 100, 100, 100, Config.RGB_565); 523 } 524 525 @Test(expected=ArrayIndexOutOfBoundsException.class) testCreateBitmapFromColorsScalnlineTooLarge()526 public void testCreateBitmapFromColorsScalnlineTooLarge() { 527 int[] colors = createColors(100); 528 529 // abnormal case: (lastScanline + width) bigger than colors' length 530 Bitmap.createBitmap(colors, 10, 100, 50, 100, Config.RGB_565); 531 } 532 533 @Test testCreateBitmap6()534 public void testCreateBitmap6() { 535 int[] colors = createColors(100); 536 537 // normal case 538 Bitmap ret = Bitmap.createBitmap(colors, 5, 10, 10, 5, Config.RGB_565); 539 assertNotNull(ret); 540 assertFalse(ret.isMutable()); 541 assertEquals(10, ret.getWidth()); 542 assertEquals(5, ret.getHeight()); 543 assertEquals(Config.RGB_565, ret.getConfig()); 544 } 545 546 @Test testCreateBitmap_displayMetrics_mutable()547 public void testCreateBitmap_displayMetrics_mutable() { 548 DisplayMetrics metrics = 549 InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics(); 550 551 Bitmap bitmap; 552 bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888); 553 assertTrue(bitmap.isMutable()); 554 assertEquals(metrics.densityDpi, bitmap.getDensity()); 555 556 bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888); 557 assertTrue(bitmap.isMutable()); 558 assertEquals(metrics.densityDpi, bitmap.getDensity()); 559 560 bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888, true); 561 assertTrue(bitmap.isMutable()); 562 assertEquals(metrics.densityDpi, bitmap.getDensity()); 563 564 bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888, true, ColorSpace.get( 565 ColorSpace.Named.SRGB)); 566 567 assertTrue(bitmap.isMutable()); 568 assertEquals(metrics.densityDpi, bitmap.getDensity()); 569 570 int[] colors = createColors(100); 571 bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888); 572 assertNotNull(bitmap); 573 assertFalse(bitmap.isMutable()); 574 575 bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888); 576 assertNotNull(bitmap); 577 assertFalse(bitmap.isMutable()); 578 } 579 580 @Test testCreateBitmap_noDisplayMetrics_mutable()581 public void testCreateBitmap_noDisplayMetrics_mutable() { 582 Bitmap bitmap; 583 bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 584 assertTrue(bitmap.isMutable()); 585 586 bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true); 587 assertTrue(bitmap.isMutable()); 588 589 bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true, ColorSpace.get(Named.SRGB)); 590 assertTrue(bitmap.isMutable()); 591 } 592 593 @Test testCreateBitmap_displayMetrics_immutable()594 public void testCreateBitmap_displayMetrics_immutable() { 595 DisplayMetrics metrics = 596 InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics(); 597 int[] colors = createColors(100); 598 599 Bitmap bitmap; 600 bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888); 601 assertFalse(bitmap.isMutable()); 602 assertEquals(metrics.densityDpi, bitmap.getDensity()); 603 604 bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888); 605 assertFalse(bitmap.isMutable()); 606 assertEquals(metrics.densityDpi, bitmap.getDensity()); 607 } 608 609 @Test testCreateBitmap_noDisplayMetrics_immutable()610 public void testCreateBitmap_noDisplayMetrics_immutable() { 611 int[] colors = createColors(100); 612 Bitmap bitmap; 613 bitmap = Bitmap.createBitmap(colors, 0, 10, 10, 10, Config.ARGB_8888); 614 assertFalse(bitmap.isMutable()); 615 616 bitmap = Bitmap.createBitmap(colors, 10, 10, Config.ARGB_8888); 617 assertFalse(bitmap.isMutable()); 618 } 619 620 @Test testCreateBitmap_Picture_immutable()621 public void testCreateBitmap_Picture_immutable() { 622 Picture picture = new Picture(); 623 Canvas canvas = picture.beginRecording(200, 100); 624 625 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 626 627 p.setColor(0x88FF0000); 628 canvas.drawCircle(50, 50, 40, p); 629 630 p.setColor(Color.GREEN); 631 p.setTextSize(30); 632 canvas.drawText("Pictures", 60, 60, p); 633 picture.endRecording(); 634 635 Bitmap bitmap; 636 bitmap = Bitmap.createBitmap(picture); 637 assertFalse(bitmap.isMutable()); 638 639 bitmap = Bitmap.createBitmap(picture, 100, 100, Config.HARDWARE); 640 assertFalse(bitmap.isMutable()); 641 assertNotNull(bitmap.getColorSpace()); 642 643 bitmap = Bitmap.createBitmap(picture, 100, 100, Config.ARGB_8888); 644 assertFalse(bitmap.isMutable()); 645 } 646 647 @Test testCreateScaledBitmap()648 public void testCreateScaledBitmap() { 649 mBitmap = Bitmap.createBitmap(100, 200, Config.RGB_565); 650 assertTrue(mBitmap.isMutable()); 651 Bitmap ret = Bitmap.createScaledBitmap(mBitmap, 50, 100, false); 652 assertNotNull(ret); 653 assertEquals(50, ret.getWidth()); 654 assertEquals(100, ret.getHeight()); 655 assertTrue(ret.isMutable()); 656 } 657 658 @Test testWrapHardwareBufferSucceeds()659 public void testWrapHardwareBufferSucceeds() { 660 try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) { 661 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 662 assertNotNull(bitmap); 663 bitmap.recycle(); 664 } 665 } 666 667 @Test(expected = IllegalArgumentException.class) testWrapHardwareBufferWithInvalidUsageFails()668 public void testWrapHardwareBufferWithInvalidUsageFails() { 669 try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1, 670 HardwareBuffer.USAGE_CPU_WRITE_RARELY)) { 671 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 672 } 673 } 674 675 @Test(expected = IllegalArgumentException.class) testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails()676 public void testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails() { 677 try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1, 678 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)) { 679 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.CIE_LAB)); 680 } 681 } 682 683 @Test testWrapHardwareBufferFor1010102BufferSucceeds()684 public void testWrapHardwareBufferFor1010102BufferSucceeds() { 685 HardwareBuffer hwBufferMaybe = null; 686 687 try { 688 hwBufferMaybe = HardwareBuffer.create(128, 128, HardwareBuffer.RGBA_1010102, 1, 689 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); 690 } catch (IllegalArgumentException e) { 691 assumeNoException("Creating a 1010102 HW buffer was not supported", e); 692 } 693 694 assumeNotNull(hwBufferMaybe); 695 696 try (HardwareBuffer buffer = hwBufferMaybe) { 697 Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(Named.SRGB)); 698 assertNotNull(bitmap); 699 bitmap.recycle(); 700 } 701 } 702 assertMatches(HardwareBuffer hwBuffer, HardwareBuffer hwBuffer2)703 private void assertMatches(HardwareBuffer hwBuffer, HardwareBuffer hwBuffer2) { 704 assertEquals(hwBuffer, hwBuffer2); 705 assertEquals(hwBuffer.hashCode(), hwBuffer2.hashCode()); 706 assertEquals(hwBuffer.getWidth(), hwBuffer2.getWidth()); 707 assertEquals(hwBuffer.getHeight(), hwBuffer2.getHeight()); 708 assertEquals(hwBuffer.getFormat(), hwBuffer2.getFormat()); 709 assertEquals(hwBuffer.getLayers(), hwBuffer2.getLayers()); 710 assertEquals(hwBuffer.getUsage(), hwBuffer2.getUsage()); 711 } 712 713 @Test testGetHardwareBufferMatchesWrapped()714 public void testGetHardwareBufferMatchesWrapped() { 715 try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) { 716 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 717 assertNotNull(bitmap); 718 719 try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) { 720 assertNotNull(hwBuffer2); 721 assertMatches(hwBuffer, hwBuffer2); 722 } 723 bitmap.recycle(); 724 } 725 } 726 parametersFor_testGetAllocationSizeWrappedBuffer()727 private static Object[] parametersFor_testGetAllocationSizeWrappedBuffer() { 728 return new Object[] { 729 HardwareBuffer.YCBCR_420_888, 730 HardwareBuffer.YCBCR_P010, 731 ImageFormat.YV12, 732 }; 733 } 734 735 @Test 736 @Parameters(method = "parametersFor_testGetAllocationSizeWrappedBuffer") testGetAllocationSizeWrappedBuffer(int format)737 public void testGetAllocationSizeWrappedBuffer(int format) { 738 final long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE; 739 assumeTrue(HardwareBuffer.isSupported(1, 1, format, 1, usage)); 740 HardwareBuffer buffer = HardwareBuffer.create(100, 100, format, 1, usage); 741 assertNotNull("isSupported = true but allocation failed", buffer); 742 Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, null); 743 buffer.close(); 744 try { 745 // We can probably assert closer to at least 100 * 100 but maybe someone has super 746 // duper good compression rates, so assume a lower bound of 2kb 747 assertTrue(bitmap.getAllocationByteCount() > 2000); 748 } finally { 749 bitmap.recycle(); 750 } 751 } 752 parametersFor_testGetHardwareBufferConfig()753 private static Object[] parametersFor_testGetHardwareBufferConfig() { 754 return new Object[] {Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565}; 755 } 756 757 @Test 758 @Parameters(method = "parametersFor_testGetHardwareBufferConfig") testGetHardwareBufferConfig(Config config)759 public void testGetHardwareBufferConfig(Config config) { 760 Bitmap bitmap = Bitmap.createBitmap(10, 10, config); 761 bitmap = bitmap.copy(Config.HARDWARE, false); 762 if (bitmap == null) { 763 fail("Failed to copy to HARDWARE with Config " + config); 764 } 765 try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) { 766 assertNotNull(hwBuffer); 767 assertEquals(hwBuffer.getWidth(), 10); 768 assertEquals(hwBuffer.getHeight(), 10); 769 } 770 } 771 772 @Test testGetHardwareBufferTwice()773 public void testGetHardwareBufferTwice() { 774 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 775 bitmap = bitmap.copy(Config.HARDWARE, false); 776 try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) { 777 assertNotNull(hwBuffer); 778 try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) { 779 assertNotNull(hwBuffer2); 780 assertMatches(hwBuffer, hwBuffer2); 781 } 782 } 783 } 784 785 @Test testGetHardwareBufferMatches()786 public void testGetHardwareBufferMatches() { 787 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 788 bitmap = bitmap.copy(Config.HARDWARE, false); 789 try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) { 790 HashSet<HardwareBuffer> set = new HashSet<HardwareBuffer>(); 791 set.add(hwBuffer); 792 assertTrue(set.contains(bitmap.getHardwareBuffer())); 793 } 794 } 795 796 @Test(expected = IllegalStateException.class) testGetHardwareBufferNonHardware()797 public void testGetHardwareBufferNonHardware() { 798 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 799 bitmap.getHardwareBuffer(); 800 } 801 802 @Test(expected = IllegalStateException.class) testGetHardwareBufferRecycled()803 public void testGetHardwareBufferRecycled() { 804 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 805 bitmap = bitmap.copy(Config.HARDWARE, false); 806 bitmap.recycle(); 807 bitmap.getHardwareBuffer(); 808 } 809 810 @Test testGetHardwareBufferClosed()811 public void testGetHardwareBufferClosed() { 812 HardwareBuffer hwBuffer = createTestBuffer(128, 128, false); 813 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 814 assertNotNull(bitmap); 815 816 hwBuffer.close(); 817 818 try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) { 819 assertNotNull(hwBuffer2); 820 assertFalse(hwBuffer2.isClosed()); 821 assertNotEquals(hwBuffer, hwBuffer2); 822 } 823 bitmap.recycle(); 824 } 825 826 @Test testGenerationId()827 public void testGenerationId() { 828 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 829 int genId = bitmap.getGenerationId(); 830 assertEquals("not expected to change", genId, bitmap.getGenerationId()); 831 bitmap.setDensity(bitmap.getDensity() + 4); 832 assertEquals("not expected to change", genId, bitmap.getGenerationId()); 833 bitmap.getPixel(0, 0); 834 assertEquals("not expected to change", genId, bitmap.getGenerationId()); 835 836 int beforeGenId = bitmap.getGenerationId(); 837 bitmap.eraseColor(Color.WHITE); 838 int afterGenId = bitmap.getGenerationId(); 839 assertTrue("expected to increase", afterGenId > beforeGenId); 840 841 beforeGenId = bitmap.getGenerationId(); 842 bitmap.setPixel(4, 4, Color.BLUE); 843 afterGenId = bitmap.getGenerationId(); 844 assertTrue("expected to increase again", afterGenId > beforeGenId); 845 } 846 847 @Test testDescribeContents()848 public void testDescribeContents() { 849 assertEquals(0, mBitmap.describeContents()); 850 } 851 852 @Test(expected=IllegalStateException.class) testEraseColorOnRecycled()853 public void testEraseColorOnRecycled() { 854 mBitmap.recycle(); 855 856 mBitmap.eraseColor(0); 857 } 858 859 @Test(expected = IllegalStateException.class) testEraseColorLongOnRecycled()860 public void testEraseColorLongOnRecycled() { 861 mBitmap.recycle(); 862 863 mBitmap.eraseColor(Color.pack(0)); 864 } 865 866 @Test(expected=IllegalStateException.class) testEraseColorOnImmutable()867 public void testEraseColorOnImmutable() { 868 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 869 870 //abnormal case: bitmap is immutable 871 mBitmap.eraseColor(0); 872 } 873 874 @Test(expected = IllegalStateException.class) testEraseColorLongOnImmutable()875 public void testEraseColorLongOnImmutable() { 876 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 877 878 //abnormal case: bitmap is immutable 879 mBitmap.eraseColor(Color.pack(0)); 880 } 881 882 @Test testEraseColor()883 public void testEraseColor() { 884 // normal case 885 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 886 mBitmap.eraseColor(0xffff0000); 887 assertEquals(0xffff0000, mBitmap.getPixel(10, 10)); 888 assertEquals(0xffff0000, mBitmap.getPixel(50, 50)); 889 } 890 891 @Test(expected = IllegalArgumentException.class) testGetColorOOB()892 public void testGetColorOOB() { 893 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 894 mBitmap.getColor(-1, 0); 895 } 896 897 @Test(expected = IllegalArgumentException.class) testGetColorOOB2()898 public void testGetColorOOB2() { 899 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 900 mBitmap.getColor(5, -10); 901 } 902 903 @Test(expected = IllegalArgumentException.class) testGetColorOOB3()904 public void testGetColorOOB3() { 905 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 906 mBitmap.getColor(100, 10); 907 } 908 909 @Test(expected = IllegalArgumentException.class) testGetColorOOB4()910 public void testGetColorOOB4() { 911 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 912 mBitmap.getColor(99, 1000); 913 } 914 915 @Test(expected = IllegalStateException.class) testGetColorRecycled()916 public void testGetColorRecycled() { 917 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 918 mBitmap.recycle(); 919 mBitmap.getColor(0, 0); 920 } 921 922 @Test(expected = IllegalStateException.class) testGetColorHardware()923 public void testGetColorHardware() { 924 BitmapFactory.Options options = new BitmapFactory.Options(); 925 options.inPreferredConfig = Bitmap.Config.HARDWARE; 926 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, options); 927 mBitmap.getColor(50, 50); 928 929 } 930 clamp(float f)931 private static float clamp(float f) { 932 return clamp(f, 0.0f, 1.0f); 933 } 934 clamp(float f, float min, float max)935 private static float clamp(float f, float min, float max) { 936 return Math.min(Math.max(f, min), max); 937 } 938 939 @Test testGetColor()940 public void testGetColor() { 941 final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); 942 List<ColorSpace> rgbColorSpaces = getRgbColorSpaces(); 943 for (Config config : new Config[] { Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565 }) { 944 for (ColorSpace bitmapColorSpace : rgbColorSpaces) { 945 mBitmap = Bitmap.createBitmap(1, 1, config, /*hasAlpha*/ false, 946 bitmapColorSpace); 947 bitmapColorSpace = mBitmap.getColorSpace(); 948 for (ColorSpace eraseColorSpace : rgbColorSpaces) { 949 for (long wideGamutLong : new long[] { 950 Color.pack(1.0f, 0.0f, 0.0f, 1.0f, eraseColorSpace), 951 Color.pack(0.0f, 1.0f, 0.0f, 1.0f, eraseColorSpace), 952 Color.pack(0.0f, 0.0f, 1.0f, 1.0f, eraseColorSpace)}) { 953 mBitmap.eraseColor(wideGamutLong); 954 955 Color result = mBitmap.getColor(0, 0); 956 if (mBitmap.getColorSpace().equals(sRGB)) { 957 assertEquals(mBitmap.getPixel(0, 0), result.toArgb()); 958 } 959 if (eraseColorSpace.equals(bitmapColorSpace)) { 960 final Color wideGamutColor = Color.valueOf(wideGamutLong); 961 ColorUtils.verifyColor("Erasing to Bitmap's ColorSpace " 962 + bitmapColorSpace, wideGamutColor, result, .001f); 963 964 } else { 965 Color convertedColor = Color.valueOf( 966 Color.convert(wideGamutLong, bitmapColorSpace)); 967 if (mBitmap.getConfig() != Config.RGBA_F16) { 968 // It's possible that we have to clip to fit into the Config. 969 convertedColor = Color.valueOf( 970 clamp(convertedColor.red()), 971 clamp(convertedColor.green()), 972 clamp(convertedColor.blue()), 973 convertedColor.alpha(), 974 bitmapColorSpace); 975 } 976 ColorUtils.verifyColor("Bitmap(Config: " + mBitmap.getConfig() 977 + ", ColorSpace: " + bitmapColorSpace 978 + ") erasing to " + Color.valueOf(wideGamutLong), 979 convertedColor, result, .03f); 980 } 981 } 982 } 983 } 984 } 985 } 986 987 private static class ARGB { 988 public float alpha; 989 public float red; 990 public float green; 991 public float blue; ARGB(float alpha, float red, float green, float blue)992 ARGB(float alpha, float red, float green, float blue) { 993 this.alpha = alpha; 994 this.red = red; 995 this.green = green; 996 this.blue = blue; 997 } 998 }; 999 1000 @Test testEraseColorLong()1001 public void testEraseColorLong() { 1002 List<ColorSpace> rgbColorSpaces = getRgbColorSpaces(); 1003 for (Config config : new Config[]{Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16}) { 1004 mBitmap = Bitmap.createBitmap(100, 100, config); 1005 // pack SRGB colors into ColorLongs. 1006 for (int color : new int[]{ Color.RED, Color.BLUE, Color.GREEN, Color.BLACK, 1007 Color.WHITE, Color.TRANSPARENT }) { 1008 if (config.equals(Config.RGB_565) && Float.compare(Color.alpha(color), 1.0f) != 0) { 1009 // 565 doesn't support alpha. 1010 continue; 1011 } 1012 mBitmap.eraseColor(Color.pack(color)); 1013 // The Bitmap is either SRGB or SRGBLinear (F16). getPixel(), which retrieves the 1014 // color in SRGB, should match exactly. 1015 ColorUtils.verifyColor("Config " + config + " mismatch at 10, 10 ", 1016 color, mBitmap.getPixel(10, 10), 0); 1017 ColorUtils.verifyColor("Config " + config + " mismatch at 50, 50 ", 1018 color, mBitmap.getPixel(50, 50), 0); 1019 } 1020 1021 // Use arbitrary colors in various ColorSpaces. getPixel() should approximately match 1022 // the SRGB version of the color. 1023 for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f), 1024 new ARGB(1.0f, .3f, .6f, .9f), 1025 new ARGB(0.5f, .2f, .8f, .7f) }) { 1026 if (config.equals(Config.RGB_565) && Float.compare(color.alpha, 1.0f) != 0) { 1027 continue; 1028 } 1029 int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue); 1030 for (ColorSpace cs : rgbColorSpaces) { 1031 long longColor = Color.convert(srgbColor, cs); 1032 mBitmap.eraseColor(longColor); 1033 // These tolerances were chosen by trial and error. It is expected that 1034 // some conversions do not round-trip perfectly. 1035 int tolerance = 1; 1036 if (config.equals(Config.RGB_565)) { 1037 tolerance = 4; 1038 } else if (cs.equals(ColorSpace.get(ColorSpace.Named.SMPTE_C))) { 1039 tolerance = 3; 1040 } 1041 1042 ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs 1043 + ", mismatch at 10, 10 ", srgbColor, mBitmap.getPixel(10, 10), 1044 tolerance); 1045 ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs 1046 + ", mismatch at 50, 50 ", srgbColor, mBitmap.getPixel(50, 50), 1047 tolerance); 1048 } 1049 } 1050 } 1051 } 1052 1053 @Test testEraseColorOnP3()1054 public void testEraseColorOnP3() { 1055 // Use a ColorLong with a different ColorSpace than the Bitmap. getPixel() should 1056 // approximately match the SRGB version of the color. 1057 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888, true, 1058 ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); 1059 int srgbColor = Color.argb(.5f, .3f, .6f, .7f); 1060 long acesColor = Color.convert(srgbColor, ColorSpace.get(ColorSpace.Named.ACES)); 1061 mBitmap.eraseColor(acesColor); 1062 ColorUtils.verifyColor("Mismatch at 15, 15", srgbColor, mBitmap.getPixel(15, 15), 1); 1063 } 1064 1065 @Test(expected = IllegalArgumentException.class) testEraseColorXYZ()1066 public void testEraseColorXYZ() { 1067 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1068 mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ))); 1069 } 1070 1071 @Test(expected = IllegalArgumentException.class) testEraseColorLAB()1072 public void testEraseColorLAB() { 1073 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1074 mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB))); 1075 } 1076 1077 @Test(expected = IllegalArgumentException.class) testEraseColorUnknown()1078 public void testEraseColorUnknown() { 1079 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1080 mBitmap.eraseColor(-1L); 1081 } 1082 1083 @Test(expected=IllegalStateException.class) testExtractAlphaFromRecycled()1084 public void testExtractAlphaFromRecycled() { 1085 mBitmap.recycle(); 1086 1087 mBitmap.extractAlpha(); 1088 } 1089 1090 @Test testExtractAlpha()1091 public void testExtractAlpha() { 1092 // normal case 1093 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1094 Bitmap ret = mBitmap.extractAlpha(); 1095 assertNotNull(ret); 1096 int source = mBitmap.getPixel(10, 20); 1097 int result = ret.getPixel(10, 20); 1098 assertEquals(Color.alpha(source), Color.alpha(result)); 1099 assertEquals(0xFF, Color.alpha(result)); 1100 } 1101 1102 @Test(expected=IllegalStateException.class) testExtractAlphaWithPaintAndOffsetFromRecycled()1103 public void testExtractAlphaWithPaintAndOffsetFromRecycled() { 1104 mBitmap.recycle(); 1105 1106 mBitmap.extractAlpha(new Paint(), new int[]{0, 1}); 1107 } 1108 1109 @Test testExtractAlphaWithPaintAndOffset()1110 public void testExtractAlphaWithPaintAndOffset() { 1111 // normal case 1112 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1113 Bitmap ret = mBitmap.extractAlpha(new Paint(), new int[]{0, 1}); 1114 assertNotNull(ret); 1115 int source = mBitmap.getPixel(10, 20); 1116 int result = ret.getPixel(10, 20); 1117 assertEquals(Color.alpha(source), Color.alpha(result)); 1118 assertEquals(0xFF, Color.alpha(result)); 1119 } 1120 1121 @Test testGetAllocationByteCount()1122 public void testGetAllocationByteCount() { 1123 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8); 1124 int alloc = mBitmap.getAllocationByteCount(); 1125 assertEquals(mBitmap.getByteCount(), alloc); 1126 1127 // reconfigure same size 1128 mBitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888); 1129 assertEquals(mBitmap.getByteCount(), alloc); 1130 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1131 1132 // reconfigure different size 1133 mBitmap.reconfigure(10, 10, Bitmap.Config.ALPHA_8); 1134 assertEquals(mBitmap.getByteCount(), 100); 1135 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1136 } 1137 1138 @Test testGetConfig()1139 public void testGetConfig() { 1140 Bitmap bm0 = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8); 1141 Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1142 Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1143 Bitmap bm3 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444); 1144 1145 assertEquals(Bitmap.Config.ALPHA_8, bm0.getConfig()); 1146 assertEquals(Bitmap.Config.ARGB_8888, bm1.getConfig()); 1147 assertEquals(Bitmap.Config.RGB_565, bm2.getConfig()); 1148 // Attempting to create a 4444 bitmap actually creates an 8888 bitmap. 1149 assertEquals(Bitmap.Config.ARGB_8888, bm3.getConfig()); 1150 1151 // Can't call Bitmap.createBitmap with Bitmap.Config.HARDWARE, 1152 // because createBitmap creates mutable bitmap and hardware bitmaps are always immutable, 1153 // so such call will throw an exception. 1154 Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, 1155 HARDWARE_OPTIONS); 1156 assertEquals(Bitmap.Config.HARDWARE, hardwareBitmap.getConfig()); 1157 } 1158 1159 @Test testGetHeight()1160 public void testGetHeight() { 1161 assertEquals(31, mBitmap.getHeight()); 1162 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1163 assertEquals(200, mBitmap.getHeight()); 1164 } 1165 1166 @Test testGetNinePatchChunk()1167 public void testGetNinePatchChunk() { 1168 assertNull(mBitmap.getNinePatchChunk()); 1169 } 1170 1171 @Test(expected=IllegalStateException.class) testGetPixelFromRecycled()1172 public void testGetPixelFromRecycled() { 1173 mBitmap.recycle(); 1174 1175 mBitmap.getPixel(10, 16); 1176 } 1177 1178 @Test(expected=IllegalArgumentException.class) testGetPixelXTooLarge()1179 public void testGetPixelXTooLarge() { 1180 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1181 1182 // abnormal case: x bigger than the source bitmap's width 1183 mBitmap.getPixel(200, 16); 1184 } 1185 1186 @Test(expected=IllegalArgumentException.class) testGetPixelYTooLarge()1187 public void testGetPixelYTooLarge() { 1188 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1189 1190 // abnormal case: y bigger than the source bitmap's height 1191 mBitmap.getPixel(10, 300); 1192 } 1193 1194 @Test testGetPixel()1195 public void testGetPixel() { 1196 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1197 1198 // normal case 565 1199 mBitmap.setPixel(10, 16, 0xFF << 24); 1200 assertEquals(0xFF << 24, mBitmap.getPixel(10, 16)); 1201 1202 // normal case A_8 1203 mBitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8); 1204 mBitmap.setPixel(5, 5, 0xFFFFFFFF); 1205 assertEquals(0xFF000000, mBitmap.getPixel(5, 5)); 1206 mBitmap.setPixel(5, 5, 0xA8A8A8A8); 1207 assertEquals(0xA8000000, mBitmap.getPixel(5, 5)); 1208 mBitmap.setPixel(5, 5, 0x00000000); 1209 assertEquals(0x00000000, mBitmap.getPixel(5, 5)); 1210 mBitmap.setPixel(5, 5, 0x1F000000); 1211 assertEquals(0x1F000000, mBitmap.getPixel(5, 5)); 1212 } 1213 1214 @Test testGetRowBytes()1215 public void testGetRowBytes() { 1216 Bitmap bm0 = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8); 1217 Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1218 Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1219 Bitmap bm3 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444); 1220 1221 assertEquals(100, bm0.getRowBytes()); 1222 assertEquals(400, bm1.getRowBytes()); 1223 assertEquals(200, bm2.getRowBytes()); 1224 // Attempting to create a 4444 bitmap actually creates an 8888 bitmap. 1225 assertEquals(400, bm3.getRowBytes()); 1226 } 1227 1228 @Test testGetWidth()1229 public void testGetWidth() { 1230 assertEquals(31, mBitmap.getWidth()); 1231 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1232 assertEquals(100, mBitmap.getWidth()); 1233 } 1234 1235 @Test testHasAlpha()1236 public void testHasAlpha() { 1237 assertFalse(mBitmap.hasAlpha()); 1238 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1239 assertTrue(mBitmap.hasAlpha()); 1240 } 1241 1242 @Test testIsMutable()1243 public void testIsMutable() { 1244 assertFalse(mBitmap.isMutable()); 1245 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1246 assertTrue(mBitmap.isMutable()); 1247 } 1248 1249 @Test testIsRecycled()1250 public void testIsRecycled() { 1251 assertFalse(mBitmap.isRecycled()); 1252 mBitmap.recycle(); 1253 assertTrue(mBitmap.isRecycled()); 1254 } 1255 1256 @Test testReconfigure()1257 public void testReconfigure() { 1258 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1259 int alloc = mBitmap.getAllocationByteCount(); 1260 1261 // test shrinking 1262 mBitmap.reconfigure(50, 100, Bitmap.Config.ALPHA_8); 1263 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1264 assertEquals(mBitmap.getByteCount() * 8, alloc); 1265 } 1266 1267 @Test(expected=IllegalArgumentException.class) testReconfigureExpanding()1268 public void testReconfigureExpanding() { 1269 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1270 mBitmap.reconfigure(101, 201, Bitmap.Config.ARGB_8888); 1271 } 1272 1273 @Test(expected=IllegalStateException.class) testReconfigureMutable()1274 public void testReconfigureMutable() { 1275 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1276 mBitmap.reconfigure(1, 1, Bitmap.Config.ALPHA_8); 1277 } 1278 1279 // Used by testAlphaAndPremul. 1280 private static Config[] CONFIGS = new Config[] { Config.ALPHA_8, Config.ARGB_4444, 1281 Config.ARGB_8888, Config.RGB_565 }; 1282 1283 // test that reconfigure, setHasAlpha, and setPremultiplied behave as expected with 1284 // respect to alpha and premultiplied. 1285 @Test testAlphaAndPremul()1286 public void testAlphaAndPremul() { 1287 boolean falseTrue[] = new boolean[] { false, true }; 1288 for (Config fromConfig : CONFIGS) { 1289 for (Config toConfig : CONFIGS) { 1290 for (boolean hasAlpha : falseTrue) { 1291 for (boolean isPremul : falseTrue) { 1292 Bitmap bitmap = Bitmap.createBitmap(10, 10, fromConfig); 1293 1294 // 4444 is deprecated, and will convert to 8888. No need to 1295 // attempt a reconfigure, which will be tested when fromConfig 1296 // is 8888. 1297 if (fromConfig == Config.ARGB_4444) { 1298 assertEquals(bitmap.getConfig(), Config.ARGB_8888); 1299 break; 1300 } 1301 1302 bitmap.setHasAlpha(hasAlpha); 1303 bitmap.setPremultiplied(isPremul); 1304 1305 verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, false); 1306 1307 // reconfigure to a smaller size so the function will still succeed when 1308 // going to a Config that requires more bits. 1309 bitmap.reconfigure(1, 1, toConfig); 1310 if (toConfig == Config.ARGB_4444) { 1311 assertEquals(bitmap.getConfig(), Config.ARGB_8888); 1312 } else { 1313 assertEquals(bitmap.getConfig(), toConfig); 1314 } 1315 1316 // Check that the alpha and premultiplied state has not changed (unless 1317 // we expected it to). 1318 verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, fromConfig == Config.RGB_565); 1319 } 1320 } 1321 } 1322 } 1323 } 1324 1325 /** 1326 * Assert that bitmap returns the appropriate values for hasAlpha() and isPremultiplied(). 1327 * @param bitmap Bitmap to check. 1328 * @param expectedAlpha Expected return value from bitmap.hasAlpha(). Note that this is based 1329 * on what was set, but may be different from the actual return value depending on the 1330 * Config and convertedFrom565. 1331 * @param expectedPremul Expected return value from bitmap.isPremultiplied(). Similar to 1332 * expectedAlpha, this is based on what was set, but may be different from the actual 1333 * return value depending on the Config. 1334 * @param convertedFrom565 Whether bitmap was converted to its current Config by being 1335 * reconfigured from RGB_565. If true, and bitmap is now a Config that supports alpha, 1336 * hasAlpha() is expected to be true even if expectedAlpha is false. 1337 */ verifyAlphaAndPremul(Bitmap bitmap, boolean expectedAlpha, boolean expectedPremul, boolean convertedFrom565)1338 private void verifyAlphaAndPremul(Bitmap bitmap, boolean expectedAlpha, boolean expectedPremul, 1339 boolean convertedFrom565) { 1340 switch (bitmap.getConfig()) { 1341 case ARGB_4444: 1342 // This shouldn't happen, since we don't allow creating or converting 1343 // to 4444. 1344 assertFalse(true); 1345 break; 1346 case RGB_565: 1347 assertFalse(bitmap.hasAlpha()); 1348 assertFalse(bitmap.isPremultiplied()); 1349 break; 1350 case ALPHA_8: 1351 // ALPHA_8 behaves mostly the same as 8888, except for premultiplied. Fall through. 1352 case ARGB_8888: 1353 // Since 565 is necessarily opaque, we revert to hasAlpha when switching to a type 1354 // that can have alpha. 1355 if (convertedFrom565) { 1356 assertTrue(bitmap.hasAlpha()); 1357 } else { 1358 assertEquals(bitmap.hasAlpha(), expectedAlpha); 1359 } 1360 1361 if (bitmap.hasAlpha()) { 1362 // ALPHA_8's premultiplied status is undefined. 1363 if (bitmap.getConfig() != Config.ALPHA_8) { 1364 assertEquals(bitmap.isPremultiplied(), expectedPremul); 1365 } 1366 } else { 1367 // Opaque bitmap is never considered premultiplied. 1368 assertFalse(bitmap.isPremultiplied()); 1369 } 1370 break; 1371 } 1372 } 1373 1374 @Test testSetColorSpace()1375 public void testSetColorSpace() { 1376 // Use arbitrary colors and assign to various ColorSpaces. 1377 for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f), 1378 new ARGB(1.0f, .3f, .6f, .9f), 1379 new ARGB(0.5f, .2f, .8f, .7f) }) { 1380 1381 int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue); 1382 for (ColorSpace cs : getRgbColorSpaces()) { 1383 for (Config config : new Config[] { 1384 // F16 is tested elsewhere, since it defaults to EXTENDED_SRGB, and 1385 // many of these calls to setColorSpace would reduce the range, resulting 1386 // in an Exception. 1387 Config.ARGB_8888, 1388 Config.RGB_565, 1389 }) { 1390 mBitmap = Bitmap.createBitmap(10, 10, config); 1391 mBitmap.eraseColor(srgbColor); 1392 mBitmap.setColorSpace(cs); 1393 ColorSpace actual = mBitmap.getColorSpace(); 1394 if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) { 1395 assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual); 1396 } else if (cs == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) { 1397 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), actual); 1398 } else { 1399 assertSame(cs, actual); 1400 } 1401 1402 // This tolerance was chosen by trial and error. It is expected that 1403 // some conversions do not round-trip perfectly. 1404 int tolerance = 2; 1405 Color c = Color.valueOf(color.red, color.green, color.blue, color.alpha, cs); 1406 ColorUtils.verifyColor("Mismatch after setting the colorSpace to " 1407 + cs.getName(), c.convert(mBitmap.getColorSpace()), 1408 mBitmap.getColor(5, 5), tolerance); 1409 } 1410 } 1411 } 1412 } 1413 1414 @Test(expected = IllegalStateException.class) testSetColorSpaceRecycled()1415 public void testSetColorSpaceRecycled() { 1416 mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 1417 mBitmap.recycle(); 1418 mBitmap.setColorSpace(ColorSpace.get(Named.DISPLAY_P3)); 1419 } 1420 1421 @Test(expected = IllegalArgumentException.class) testSetColorSpaceNull()1422 public void testSetColorSpaceNull() { 1423 mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 1424 mBitmap.setColorSpace(null); 1425 } 1426 1427 @Test(expected = IllegalArgumentException.class) testSetColorSpaceXYZ()1428 public void testSetColorSpaceXYZ() { 1429 mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 1430 mBitmap.setColorSpace(ColorSpace.get(Named.CIE_XYZ)); 1431 } 1432 1433 @Test(expected = IllegalArgumentException.class) testSetColorSpaceNoTransferParameters()1434 public void testSetColorSpaceNoTransferParameters() { 1435 mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 1436 ColorSpace cs = new ColorSpace.Rgb("NoTransferParams", 1437 new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 1438 ColorSpace.ILLUMINANT_D50, 1439 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f), 1440 0, 1); 1441 mBitmap.setColorSpace(cs); 1442 } 1443 1444 @Test(expected = IllegalArgumentException.class) testSetColorSpaceAlpha8()1445 public void testSetColorSpaceAlpha8() { 1446 mBitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8); 1447 assertNull(mBitmap.getColorSpace()); 1448 mBitmap.setColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); 1449 } 1450 1451 @Test testSetColorSpaceReducedRange()1452 public void testSetColorSpaceReducedRange() { 1453 ColorSpace aces = ColorSpace.get(Named.ACES); 1454 mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, aces); 1455 try { 1456 mBitmap.setColorSpace(ColorSpace.get(Named.SRGB)); 1457 fail("Expected IllegalArgumentException!"); 1458 } catch (IllegalArgumentException e) { 1459 assertSame(aces, mBitmap.getColorSpace()); 1460 } 1461 } 1462 1463 @Test testSetColorSpaceNotReducedRange()1464 public void testSetColorSpaceNotReducedRange() { 1465 ColorSpace extended = ColorSpace.get(Named.EXTENDED_SRGB); 1466 mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, 1467 extended); 1468 mBitmap.setColorSpace(ColorSpace.get(Named.SRGB)); 1469 assertSame(mBitmap.getColorSpace(), extended); 1470 } 1471 1472 @Test testSetColorSpaceNotReducedRangeLinear()1473 public void testSetColorSpaceNotReducedRangeLinear() { 1474 ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB); 1475 mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, 1476 linearExtended); 1477 mBitmap.setColorSpace(ColorSpace.get(Named.LINEAR_SRGB)); 1478 assertSame(mBitmap.getColorSpace(), linearExtended); 1479 } 1480 1481 @Test testSetColorSpaceIncreasedRange()1482 public void testSetColorSpaceIncreasedRange() { 1483 mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, 1484 ColorSpace.get(Named.DISPLAY_P3)); 1485 ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB); 1486 mBitmap.setColorSpace(linearExtended); 1487 assertSame(mBitmap.getColorSpace(), linearExtended); 1488 } 1489 1490 @Test testSetConfig()1491 public void testSetConfig() { 1492 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1493 int alloc = mBitmap.getAllocationByteCount(); 1494 1495 // test shrinking 1496 mBitmap.setConfig(Bitmap.Config.ALPHA_8); 1497 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1498 assertEquals(mBitmap.getByteCount() * 2, alloc); 1499 } 1500 1501 @Test(expected=IllegalArgumentException.class) testSetConfigExpanding()1502 public void testSetConfigExpanding() { 1503 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1504 // test expanding 1505 mBitmap.setConfig(Bitmap.Config.ARGB_8888); 1506 } 1507 1508 @Test(expected=IllegalStateException.class) testSetConfigMutable()1509 public void testSetConfigMutable() { 1510 // test mutable 1511 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1512 mBitmap.setConfig(Bitmap.Config.ALPHA_8); 1513 } 1514 1515 @Test testSetHeight()1516 public void testSetHeight() { 1517 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1518 int alloc = mBitmap.getAllocationByteCount(); 1519 1520 // test shrinking 1521 mBitmap.setHeight(100); 1522 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1523 assertEquals(mBitmap.getByteCount() * 2, alloc); 1524 } 1525 1526 @Test(expected=IllegalArgumentException.class) testSetHeightExpanding()1527 public void testSetHeightExpanding() { 1528 // test expanding 1529 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1530 mBitmap.setHeight(201); 1531 } 1532 1533 @Test(expected=IllegalStateException.class) testSetHeightMutable()1534 public void testSetHeightMutable() { 1535 // test mutable 1536 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1537 mBitmap.setHeight(1); 1538 } 1539 1540 @Test(expected=IllegalStateException.class) testSetPixelOnRecycled()1541 public void testSetPixelOnRecycled() { 1542 int color = 0xff << 24; 1543 1544 mBitmap.recycle(); 1545 mBitmap.setPixel(10, 16, color); 1546 } 1547 1548 @Test(expected=IllegalStateException.class) testSetPixelOnImmutable()1549 public void testSetPixelOnImmutable() { 1550 int color = 0xff << 24; 1551 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1552 1553 mBitmap.setPixel(10, 16, color); 1554 } 1555 1556 @Test(expected=IllegalArgumentException.class) testSetPixelXIsTooLarge()1557 public void testSetPixelXIsTooLarge() { 1558 int color = 0xff << 24; 1559 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1560 1561 // abnormal case: x bigger than the source bitmap's width 1562 mBitmap.setPixel(200, 16, color); 1563 } 1564 1565 @Test(expected=IllegalArgumentException.class) testSetPixelYIsTooLarge()1566 public void testSetPixelYIsTooLarge() { 1567 int color = 0xff << 24; 1568 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1569 1570 // abnormal case: y bigger than the source bitmap's height 1571 mBitmap.setPixel(10, 300, color); 1572 } 1573 1574 @Test testSetPixel()1575 public void testSetPixel() { 1576 int color = 0xff << 24; 1577 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 1578 1579 // normal case 1580 mBitmap.setPixel(10, 16, color); 1581 assertEquals(color, mBitmap.getPixel(10, 16)); 1582 } 1583 1584 @Test(expected=IllegalStateException.class) testSetPixelsOnRecycled()1585 public void testSetPixelsOnRecycled() { 1586 int[] colors = createColors(100); 1587 1588 mBitmap.recycle(); 1589 mBitmap.setPixels(colors, 0, 0, 0, 0, 0, 0); 1590 } 1591 1592 @Test(expected=IllegalStateException.class) testSetPixelsOnImmutable()1593 public void testSetPixelsOnImmutable() { 1594 int[] colors = createColors(100); 1595 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1596 1597 mBitmap.setPixels(colors, 0, 0, 0, 0, 0, 0); 1598 } 1599 1600 @Test(expected=IllegalArgumentException.class) testSetPixelsXYNegative()1601 public void testSetPixelsXYNegative() { 1602 int[] colors = createColors(100); 1603 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1604 1605 // abnormal case: x and/or y less than 0 1606 mBitmap.setPixels(colors, 0, 0, -1, -1, 200, 16); 1607 } 1608 1609 @Test(expected=IllegalArgumentException.class) testSetPixelsWidthHeightNegative()1610 public void testSetPixelsWidthHeightNegative() { 1611 int[] colors = createColors(100); 1612 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1613 1614 // abnormal case: width and/or height less than 0 1615 mBitmap.setPixels(colors, 0, 0, 0, 0, -1, -1); 1616 } 1617 1618 @Test(expected=IllegalArgumentException.class) testSetPixelsXTooHigh()1619 public void testSetPixelsXTooHigh() { 1620 int[] colors = createColors(100); 1621 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1622 1623 // abnormal case: (x + width) bigger than the source bitmap's width 1624 mBitmap.setPixels(colors, 0, 0, 10, 10, 95, 50); 1625 } 1626 1627 @Test(expected=IllegalArgumentException.class) testSetPixelsYTooHigh()1628 public void testSetPixelsYTooHigh() { 1629 int[] colors = createColors(100); 1630 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1631 1632 // abnormal case: (y + height) bigger than the source bitmap's height 1633 mBitmap.setPixels(colors, 0, 0, 10, 10, 50, 95); 1634 } 1635 1636 @Test(expected=IllegalArgumentException.class) testSetPixelsStrideIllegal()1637 public void testSetPixelsStrideIllegal() { 1638 int[] colors = createColors(100); 1639 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1640 1641 // abnormal case: stride less than width and bigger than -width 1642 mBitmap.setPixels(colors, 0, 10, 10, 10, 50, 50); 1643 } 1644 1645 @Test(expected=ArrayIndexOutOfBoundsException.class) testSetPixelsOffsetNegative()1646 public void testSetPixelsOffsetNegative() { 1647 int[] colors = createColors(100); 1648 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1649 1650 // abnormal case: offset less than 0 1651 mBitmap.setPixels(colors, -1, 50, 10, 10, 50, 50); 1652 } 1653 1654 @Test(expected=ArrayIndexOutOfBoundsException.class) testSetPixelsOffsetTooBig()1655 public void testSetPixelsOffsetTooBig() { 1656 int[] colors = createColors(100); 1657 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1658 1659 // abnormal case: (offset + width) bigger than the length of colors 1660 mBitmap.setPixels(colors, 60, 50, 10, 10, 50, 50); 1661 } 1662 1663 @Test(expected=ArrayIndexOutOfBoundsException.class) testSetPixelsLastScanlineNegative()1664 public void testSetPixelsLastScanlineNegative() { 1665 int[] colors = createColors(100); 1666 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1667 1668 // abnormal case: lastScanline less than 0 1669 mBitmap.setPixels(colors, 10, -50, 10, 10, 50, 50); 1670 } 1671 1672 @Test(expected=ArrayIndexOutOfBoundsException.class) testSetPixelsLastScanlineTooBig()1673 public void testSetPixelsLastScanlineTooBig() { 1674 int[] colors = createColors(100); 1675 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1676 1677 // abnormal case: (lastScanline + width) bigger than the length of colors 1678 mBitmap.setPixels(colors, 10, 50, 10, 10, 50, 50); 1679 } 1680 1681 @Test testSetPixels()1682 public void testSetPixels() { 1683 int[] colors = createColors(100 * 100); 1684 mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 1685 mBitmap.setPixels(colors, 0, 100, 0, 0, 100, 100); 1686 int[] ret = new int[100 * 100]; 1687 mBitmap.getPixels(ret, 0, 100, 0, 0, 100, 100); 1688 1689 for(int i = 0; i < 10000; i++){ 1690 assertEquals(ret[i], colors[i]); 1691 } 1692 } 1693 verifyPremultipliedBitmapConfig(Config config, boolean expectedPremul)1694 private void verifyPremultipliedBitmapConfig(Config config, boolean expectedPremul) { 1695 Bitmap bitmap = Bitmap.createBitmap(1, 1, config); 1696 bitmap.setPremultiplied(true); 1697 bitmap.setPixel(0, 0, Color.TRANSPARENT); 1698 assertTrue(bitmap.isPremultiplied() == expectedPremul); 1699 1700 bitmap.setHasAlpha(false); 1701 assertFalse(bitmap.isPremultiplied()); 1702 } 1703 1704 @Test testSetPremultipliedSimple()1705 public void testSetPremultipliedSimple() { 1706 verifyPremultipliedBitmapConfig(Bitmap.Config.ALPHA_8, true); 1707 verifyPremultipliedBitmapConfig(Bitmap.Config.RGB_565, false); 1708 verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_4444, true); 1709 verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_8888, true); 1710 } 1711 1712 @Test testSetPremultipliedData()1713 public void testSetPremultipliedData() { 1714 // with premul, will store 2,2,2,2, so it doesn't get value correct 1715 Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1716 bitmap.setPixel(0, 0, PREMUL_COLOR); 1717 assertEquals(bitmap.getPixel(0, 0), PREMUL_ROUNDED_COLOR); 1718 1719 // read premultiplied value directly 1720 bitmap.setPremultiplied(false); 1721 assertEquals(bitmap.getPixel(0, 0), PREMUL_STORED_COLOR); 1722 1723 // value can now be stored/read correctly 1724 bitmap.setPixel(0, 0, PREMUL_COLOR); 1725 assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR); 1726 1727 // verify with array methods 1728 int testArray[] = new int[] { PREMUL_COLOR }; 1729 bitmap.setPixels(testArray, 0, 1, 0, 0, 1, 1); 1730 bitmap.getPixels(testArray, 0, 1, 0, 0, 1, 1); 1731 assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR); 1732 } 1733 1734 @Test testPremultipliedCanvas()1735 public void testPremultipliedCanvas() { 1736 Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1737 bitmap.setHasAlpha(true); 1738 bitmap.setPremultiplied(false); 1739 assertFalse(bitmap.isPremultiplied()); 1740 1741 Canvas c = new Canvas(); 1742 try { 1743 c.drawBitmap(bitmap, 0, 0, null); 1744 fail("canvas should fail with exception"); 1745 } catch (RuntimeException e) { 1746 } 1747 } 1748 getBitmapRawInt(Bitmap bitmap)1749 private int getBitmapRawInt(Bitmap bitmap) { 1750 IntBuffer buffer = IntBuffer.allocate(1); 1751 bitmap.copyPixelsToBuffer(buffer); 1752 return buffer.get(0); 1753 } 1754 bitmapStoreRawInt(Bitmap bitmap, int value)1755 private void bitmapStoreRawInt(Bitmap bitmap, int value) { 1756 IntBuffer buffer = IntBuffer.allocate(1); 1757 buffer.put(0, value); 1758 bitmap.copyPixelsFromBuffer(buffer); 1759 } 1760 1761 @Test testSetPremultipliedToBuffer()1762 public void testSetPremultipliedToBuffer() { 1763 Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1764 bitmap.setPixel(0, 0, PREMUL_COLOR); 1765 int storedPremul = getBitmapRawInt(bitmap); 1766 1767 bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1768 bitmap.setPremultiplied(false); 1769 bitmap.setPixel(0, 0, PREMUL_STORED_COLOR); 1770 1771 assertEquals(getBitmapRawInt(bitmap), storedPremul); 1772 } 1773 1774 @Test testSetPremultipliedFromBuffer()1775 public void testSetPremultipliedFromBuffer() { 1776 Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1777 bitmap.setPremultiplied(false); 1778 bitmap.setPixel(0, 0, PREMUL_COLOR); 1779 int rawTestColor = getBitmapRawInt(bitmap); 1780 1781 bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 1782 bitmap.setPremultiplied(false); 1783 bitmapStoreRawInt(bitmap, rawTestColor); 1784 assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR); 1785 } 1786 1787 @Test testSetWidth()1788 public void testSetWidth() { 1789 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1790 int alloc = mBitmap.getAllocationByteCount(); 1791 1792 // test shrinking 1793 mBitmap.setWidth(50); 1794 assertEquals(mBitmap.getAllocationByteCount(), alloc); 1795 assertEquals(mBitmap.getByteCount() * 2, alloc); 1796 } 1797 1798 @Test(expected=IllegalArgumentException.class) testSetWidthExpanding()1799 public void testSetWidthExpanding() { 1800 // test expanding 1801 mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 1802 1803 mBitmap.setWidth(101); 1804 } 1805 1806 @Test(expected=IllegalStateException.class) testSetWidthMutable()1807 public void testSetWidthMutable() { 1808 // test mutable 1809 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1810 1811 mBitmap.setWidth(1); 1812 } 1813 1814 @Test(expected=IllegalStateException.class) testWriteToParcelRecycled()1815 public void testWriteToParcelRecycled() { 1816 mBitmap.recycle(); 1817 1818 mBitmap.writeToParcel(null, 0); 1819 } 1820 1821 @Test testWriteToParcel()1822 public void testWriteToParcel() { 1823 // abnormal case: failed to unparcel Bitmap 1824 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions); 1825 Parcel p = Parcel.obtain(); 1826 mBitmap.writeToParcel(p, 0); 1827 1828 try { 1829 Bitmap.CREATOR.createFromParcel(p); 1830 fail("shouldn't come to here"); 1831 } catch(RuntimeException e){ 1832 } 1833 1834 p.recycle(); 1835 // normal case 1836 p = Parcel.obtain(); 1837 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1838 mBitmap.writeToParcel(p, 0); 1839 p.setDataPosition(0); 1840 assertTrue(mBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p))); 1841 1842 p.recycle(); 1843 } 1844 1845 /** 1846 * Although not specified as required behavior, it's something that some apps appear 1847 * to rely upon when sending bitmaps between themselves 1848 */ 1849 @Test testWriteToParcelPreserveMutability()1850 public void testWriteToParcelPreserveMutability() { 1851 Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1852 assertTrue(source.isMutable()); 1853 Parcel p = Parcel.obtain(); 1854 source.writeToParcel(p, 0); 1855 p.setDataPosition(0); 1856 Bitmap result = Bitmap.CREATOR.createFromParcel(p); 1857 p.recycle(); 1858 assertTrue(result.isMutable()); 1859 } 1860 1861 @Test testWriteToParcelPreserveImmutability()1862 public void testWriteToParcelPreserveImmutability() { 1863 // Kinda silly way to create an immutable bitmap but it works 1864 Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888) 1865 .copy(Config.ARGB_8888, false); 1866 assertFalse(source.isMutable()); 1867 Parcel p = Parcel.obtain(); 1868 source.writeToParcel(p, 0); 1869 p.setDataPosition(0); 1870 Bitmap result = Bitmap.CREATOR.createFromParcel(p); 1871 p.recycle(); 1872 assertFalse(result.isMutable()); 1873 } 1874 1875 @Test testWriteHwBitmapToParcel()1876 public void testWriteHwBitmapToParcel() { 1877 mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 1878 Parcel p = Parcel.obtain(); 1879 mBitmap.writeToParcel(p, 0); 1880 p.setDataPosition(0); 1881 Bitmap expectedBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot); 1882 assertTrue(expectedBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p))); 1883 1884 p.recycle(); 1885 } 1886 1887 @Test testParcelF16ColorSpace()1888 public void testParcelF16ColorSpace() { 1889 for (ColorSpace.Named e : new ColorSpace.Named[] { 1890 ColorSpace.Named.EXTENDED_SRGB, 1891 ColorSpace.Named.LINEAR_EXTENDED_SRGB, 1892 ColorSpace.Named.PRO_PHOTO_RGB, 1893 ColorSpace.Named.DISPLAY_P3 1894 }) { 1895 final ColorSpace cs = ColorSpace.get(e); 1896 Bitmap b = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, cs); 1897 assertSame(cs, b.getColorSpace()); 1898 1899 Parcel p = Parcel.obtain(); 1900 b.writeToParcel(p, 0); 1901 p.setDataPosition(0); 1902 Bitmap unparceled = Bitmap.CREATOR.createFromParcel(p); 1903 assertSame(cs, unparceled.getColorSpace()); 1904 } 1905 } 1906 1907 @Test testGetScaledHeight1()1908 public void testGetScaledHeight1() { 1909 int dummyDensity = 5; 1910 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1911 int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), dummyDensity); 1912 assertNotNull(ret); 1913 assertEquals(scaledHeight, ret.getScaledHeight(dummyDensity)); 1914 } 1915 1916 @Test testGetScaledHeight2()1917 public void testGetScaledHeight2() { 1918 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1919 DisplayMetrics metrics = 1920 InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics(); 1921 int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), metrics.densityDpi); 1922 assertEquals(scaledHeight, ret.getScaledHeight(metrics)); 1923 } 1924 1925 @Test testGetScaledHeight3()1926 public void testGetScaledHeight3() { 1927 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1928 Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888); 1929 Canvas mCanvas = new Canvas(mMutableBitmap); 1930 // set Density 1931 mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH); 1932 int scaledHeight = scaleFromDensity( 1933 ret.getHeight(), ret.getDensity(), mCanvas.getDensity()); 1934 assertEquals(scaledHeight, ret.getScaledHeight(mCanvas)); 1935 } 1936 1937 @Test testGetScaledWidth1()1938 public void testGetScaledWidth1() { 1939 int dummyDensity = 5; 1940 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1941 int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), dummyDensity); 1942 assertNotNull(ret); 1943 assertEquals(scaledWidth, ret.getScaledWidth(dummyDensity)); 1944 } 1945 1946 @Test testGetScaledWidth2()1947 public void testGetScaledWidth2() { 1948 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1949 DisplayMetrics metrics = 1950 InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics(); 1951 int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), metrics.densityDpi); 1952 assertEquals(scaledWidth, ret.getScaledWidth(metrics)); 1953 } 1954 1955 @Test testGetScaledWidth3()1956 public void testGetScaledWidth3() { 1957 Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565); 1958 Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888); 1959 Canvas mCanvas = new Canvas(mMutableBitmap); 1960 // set Density 1961 mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH); 1962 int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), mCanvas.getDensity()); 1963 assertEquals(scaledWidth, ret.getScaledWidth(mCanvas)); 1964 } 1965 1966 @Test testSameAs_simpleSuccess()1967 public void testSameAs_simpleSuccess() { 1968 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1969 Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1970 bitmap1.eraseColor(Color.BLACK); 1971 bitmap2.eraseColor(Color.BLACK); 1972 assertTrue(bitmap1.sameAs(bitmap2)); 1973 assertTrue(bitmap2.sameAs(bitmap1)); 1974 } 1975 1976 @Test testSameAs_simpleFail()1977 public void testSameAs_simpleFail() { 1978 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1979 Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1980 bitmap1.eraseColor(Color.BLACK); 1981 bitmap2.eraseColor(Color.BLACK); 1982 bitmap2.setPixel(20, 10, Color.WHITE); 1983 assertFalse(bitmap1.sameAs(bitmap2)); 1984 assertFalse(bitmap2.sameAs(bitmap1)); 1985 } 1986 1987 @Test testSameAs_reconfigure()1988 public void testSameAs_reconfigure() { 1989 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 1990 Bitmap bitmap2 = Bitmap.createBitmap(150, 150, Config.ARGB_8888); 1991 bitmap2.reconfigure(100, 100, Config.ARGB_8888); // now same size, so should be same 1992 bitmap1.eraseColor(Color.BLACK); 1993 bitmap2.eraseColor(Color.BLACK); 1994 assertTrue(bitmap1.sameAs(bitmap2)); 1995 assertTrue(bitmap2.sameAs(bitmap1)); 1996 } 1997 1998 @Test testSameAs_config()1999 public void testSameAs_config() { 2000 Bitmap bitmap1 = Bitmap.createBitmap(100, 200, Config.RGB_565); 2001 Bitmap bitmap2 = Bitmap.createBitmap(100, 200, Config.ARGB_8888); 2002 2003 // both bitmaps can represent black perfectly 2004 bitmap1.eraseColor(Color.BLACK); 2005 bitmap2.eraseColor(Color.BLACK); 2006 2007 // but not same due to config 2008 assertFalse(bitmap1.sameAs(bitmap2)); 2009 assertFalse(bitmap2.sameAs(bitmap1)); 2010 } 2011 2012 @Test testSameAs_width()2013 public void testSameAs_width() { 2014 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2015 Bitmap bitmap2 = Bitmap.createBitmap(101, 100, Config.ARGB_8888); 2016 bitmap1.eraseColor(Color.BLACK); 2017 bitmap2.eraseColor(Color.BLACK); 2018 assertFalse(bitmap1.sameAs(bitmap2)); 2019 assertFalse(bitmap2.sameAs(bitmap1)); 2020 } 2021 2022 @Test testSameAs_height()2023 public void testSameAs_height() { 2024 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2025 Bitmap bitmap2 = Bitmap.createBitmap(102, 100, Config.ARGB_8888); 2026 bitmap1.eraseColor(Color.BLACK); 2027 bitmap2.eraseColor(Color.BLACK); 2028 assertFalse(bitmap1.sameAs(bitmap2)); 2029 assertFalse(bitmap2.sameAs(bitmap1)); 2030 } 2031 2032 @Test testSameAs_opaque()2033 public void testSameAs_opaque() { 2034 Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2035 Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2036 bitmap1.eraseColor(Color.BLACK); 2037 bitmap2.eraseColor(Color.BLACK); 2038 bitmap1.setHasAlpha(true); 2039 bitmap2.setHasAlpha(false); 2040 assertFalse(bitmap1.sameAs(bitmap2)); 2041 assertFalse(bitmap2.sameAs(bitmap1)); 2042 } 2043 2044 @Test testSameAs_hardware()2045 public void testSameAs_hardware() { 2046 Bitmap bitmap1 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2047 Bitmap bitmap2 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2048 Bitmap bitmap3 = BitmapFactory.decodeResource(mRes, R.drawable.robot); 2049 Bitmap bitmap4 = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS); 2050 assertTrue(bitmap1.sameAs(bitmap2)); 2051 assertTrue(bitmap2.sameAs(bitmap1)); 2052 assertFalse(bitmap1.sameAs(bitmap3)); 2053 assertFalse(bitmap1.sameAs(bitmap4)); 2054 } 2055 2056 @Test testSameAs_wrappedHardwareBuffer()2057 public void testSameAs_wrappedHardwareBuffer() { 2058 try (HardwareBuffer hwBufferA = createTestBuffer(512, 512, true); 2059 HardwareBuffer hwBufferB = createTestBuffer(512, 512, true); 2060 HardwareBuffer hwBufferC = createTestBuffer(512, 512, true);) { 2061 // Fill buffer C with generated data 2062 nFillRgbaHwBuffer(hwBufferC); 2063 2064 // Create the test bitmaps 2065 Bitmap bitmap1 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB)); 2066 Bitmap bitmap2 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB)); 2067 Bitmap bitmap3 = BitmapFactory.decodeResource(mRes, R.drawable.robot); 2068 Bitmap bitmap4 = Bitmap.wrapHardwareBuffer(hwBufferB, ColorSpace.get(Named.SRGB)); 2069 Bitmap bitmap5 = Bitmap.wrapHardwareBuffer(hwBufferC, ColorSpace.get(Named.SRGB)); 2070 2071 // Run the compare-a-thon 2072 assertTrue(bitmap1.sameAs(bitmap2)); // SAME UNDERLYING BUFFER 2073 assertTrue(bitmap2.sameAs(bitmap1)); // SAME UNDERLYING BUFFER 2074 assertFalse(bitmap1.sameAs(bitmap3)); // HW vs. NON-HW 2075 assertTrue(bitmap1.sameAs(bitmap4)); // DIFFERENT BUFFERS, SAME CONTENT 2076 assertFalse(bitmap1.sameAs(bitmap5)); // DIFFERENT BUFFERS, DIFFERENT CONTENT 2077 } 2078 } 2079 2080 @Test(expected=IllegalStateException.class) testHardwareGetPixel()2081 public void testHardwareGetPixel() { 2082 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2083 bitmap.getPixel(0, 0); 2084 } 2085 2086 @Test(expected=IllegalStateException.class) testHardwareGetPixels()2087 public void testHardwareGetPixels() { 2088 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2089 bitmap.getPixels(new int[5], 0, 5, 0, 0, 5, 1); 2090 } 2091 2092 @Test testGetConfigOnRecycled()2093 public void testGetConfigOnRecycled() { 2094 Bitmap bitmap1 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2095 bitmap1.recycle(); 2096 assertEquals(Config.HARDWARE, bitmap1.getConfig()); 2097 Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2098 bitmap2.recycle(); 2099 assertEquals(Config.ARGB_8888, bitmap2.getConfig()); 2100 } 2101 2102 @Test(expected = IllegalStateException.class) testHardwareSetWidth()2103 public void testHardwareSetWidth() { 2104 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2105 bitmap.setWidth(30); 2106 } 2107 2108 @Test(expected = IllegalStateException.class) testHardwareSetHeight()2109 public void testHardwareSetHeight() { 2110 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2111 bitmap.setHeight(30); 2112 } 2113 2114 @Test(expected = IllegalStateException.class) testHardwareSetConfig()2115 public void testHardwareSetConfig() { 2116 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2117 bitmap.setConfig(Config.ARGB_8888); 2118 } 2119 2120 @Test(expected = IllegalStateException.class) testHardwareReconfigure()2121 public void testHardwareReconfigure() { 2122 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2123 bitmap.reconfigure(30, 30, Config.ARGB_8888); 2124 } 2125 2126 @Test(expected = IllegalStateException.class) testHardwareSetPixels()2127 public void testHardwareSetPixels() { 2128 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2129 bitmap.setPixels(new int[10], 0, 1, 0, 0, 1, 1); 2130 } 2131 2132 @Test(expected = IllegalStateException.class) testHardwareSetPixel()2133 public void testHardwareSetPixel() { 2134 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2135 bitmap.setPixel(1, 1, 0); 2136 } 2137 2138 @Test(expected = IllegalStateException.class) testHardwareEraseColor()2139 public void testHardwareEraseColor() { 2140 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2141 bitmap.eraseColor(0); 2142 } 2143 2144 @Test(expected = IllegalStateException.class) testHardwareEraseColorLong()2145 public void testHardwareEraseColorLong() { 2146 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS); 2147 bitmap.eraseColor(Color.pack(0)); 2148 } 2149 2150 @Test(expected = IllegalStateException.class) testHardwareCopyPixelsToBuffer()2151 public void testHardwareCopyPixelsToBuffer() { 2152 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS); 2153 ByteBuffer byteBuf = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight()); 2154 bitmap.copyPixelsToBuffer(byteBuf); 2155 } 2156 2157 @Test(expected = IllegalStateException.class) testHardwareCopyPixelsFromBuffer()2158 public void testHardwareCopyPixelsFromBuffer() { 2159 IntBuffer intBuf1 = IntBuffer.allocate(mBitmap.getRowBytes() * mBitmap.getHeight()); 2160 assertEquals(0, intBuf1.position()); 2161 mBitmap.copyPixelsToBuffer(intBuf1); 2162 Bitmap hwBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS); 2163 hwBitmap.copyPixelsFromBuffer(intBuf1); 2164 } 2165 2166 @Test testUseMetadataAfterRecycle()2167 public void testUseMetadataAfterRecycle() { 2168 Bitmap bitmap = Bitmap.createBitmap(10, 20, Config.RGB_565); 2169 bitmap.recycle(); 2170 assertEquals(10, bitmap.getWidth()); 2171 assertEquals(20, bitmap.getHeight()); 2172 assertEquals(Config.RGB_565, bitmap.getConfig()); 2173 } 2174 2175 @Test testCopyHWBitmapInStrictMode()2176 public void testCopyHWBitmapInStrictMode() { 2177 strictModeTest(()->{ 2178 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2179 Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); 2180 hwBitmap.copy(Config.ARGB_8888, false); 2181 }); 2182 } 2183 2184 @Test testCreateScaledFromHWInStrictMode()2185 public void testCreateScaledFromHWInStrictMode() { 2186 strictModeTest(()->{ 2187 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2188 Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); 2189 Bitmap.createScaledBitmap(hwBitmap, 200, 200, false); 2190 }); 2191 } 2192 2193 @Test testExtractAlphaFromHWInStrictMode()2194 public void testExtractAlphaFromHWInStrictMode() { 2195 strictModeTest(()->{ 2196 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2197 Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); 2198 hwBitmap.extractAlpha(); 2199 }); 2200 } 2201 2202 @Test testCompressInStrictMode()2203 public void testCompressInStrictMode() { 2204 strictModeTest(()->{ 2205 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2206 bitmap.compress(CompressFormat.JPEG, 90, new ByteArrayOutputStream()); 2207 }); 2208 } 2209 2210 @Test testParcelHWInStrictMode()2211 public void testParcelHWInStrictMode() { 2212 strictModeTest(()->{ 2213 mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2214 Bitmap hwBitmap = mBitmap.copy(Config.HARDWARE, false); 2215 hwBitmap.writeToParcel(Parcel.obtain(), 0); 2216 }); 2217 } 2218 2219 @Test testSameAsFirstHWInStrictMode()2220 public void testSameAsFirstHWInStrictMode() { 2221 strictModeTest(()->{ 2222 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2223 Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); 2224 hwBitmap.sameAs(bitmap); 2225 }); 2226 } 2227 2228 @Test testSameAsSecondHWInStrictMode()2229 public void testSameAsSecondHWInStrictMode() { 2230 strictModeTest(()->{ 2231 Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888); 2232 Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); 2233 bitmap.sameAs(hwBitmap); 2234 }); 2235 } 2236 2237 @Test testNdkAccessAfterRecycle()2238 public void testNdkAccessAfterRecycle() { 2239 Bitmap bitmap = Bitmap.createBitmap(10, 20, Config.RGB_565); 2240 Bitmap hardware = bitmap.copy(Config.HARDWARE, false); 2241 nValidateBitmapInfo(bitmap, 10, 20, true); 2242 nValidateBitmapInfo(hardware, 10, 20, true); 2243 2244 bitmap.recycle(); 2245 hardware.recycle(); 2246 2247 nValidateBitmapInfo(bitmap, 10, 20, true); 2248 nValidateBitmapInfo(hardware, 10, 20, true); 2249 nValidateNdkAccessFails(bitmap); 2250 } 2251 2252 @Test bitmapIsMutable()2253 public void bitmapIsMutable() { 2254 Bitmap b = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 2255 assertTrue("CreateBitmap w/ params should be mutable", b.isMutable()); 2256 assertTrue("CreateBitmap from bitmap should be mutable", 2257 Bitmap.createBitmap(b).isMutable()); 2258 } 2259 runGcAndFinalizersSync()2260 private static void runGcAndFinalizersSync() { 2261 Runtime.getRuntime().gc(); 2262 Runtime.getRuntime().runFinalization(); 2263 2264 final CountDownLatch fence = new CountDownLatch(1); 2265 new Object() { 2266 @Override 2267 protected void finalize() throws Throwable { 2268 try { 2269 fence.countDown(); 2270 } finally { 2271 super.finalize(); 2272 } 2273 } 2274 }; 2275 try { 2276 do { 2277 Runtime.getRuntime().gc(); 2278 Runtime.getRuntime().runFinalization(); 2279 } while (!fence.await(100, TimeUnit.MILLISECONDS)); 2280 } catch (InterruptedException ex) { 2281 throw new RuntimeException(ex); 2282 } 2283 } 2284 2285 private static File sProcSelfFd = new File("/proc/self/fd"); getFdCount()2286 private static int getFdCount() { 2287 return sProcSelfFd.listFiles().length; 2288 } 2289 assertNotLeaking(int iteration, Debug.MemoryInfo start, Debug.MemoryInfo end)2290 private static void assertNotLeaking(int iteration, 2291 Debug.MemoryInfo start, Debug.MemoryInfo end) { 2292 Debug.getMemoryInfo(end); 2293 assertNotEquals(0, start.getTotalPss()); 2294 assertNotEquals(0, end.getTotalPss()); 2295 if (end.getTotalPss() - start.getTotalPss() > 5000 /* kB */) { 2296 runGcAndFinalizersSync(); 2297 Debug.getMemoryInfo(end); 2298 if (end.getTotalPss() - start.getTotalPss() > 7000 /* kB */) { 2299 // Guarded by if so we don't continually generate garbage for the 2300 // assertion string. 2301 assertEquals("Memory leaked, iteration=" + iteration, 2302 start.getTotalPss(), end.getTotalPss(), 2303 7000 /* kb */); 2304 } 2305 } 2306 } 2307 runNotLeakingTest(Runnable test)2308 private static void runNotLeakingTest(Runnable test) { 2309 Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo(); 2310 Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo(); 2311 int fdCount = -1; 2312 // Do a warmup to reach steady-state memory usage 2313 for (int i = 0; i < 50; i++) { 2314 test.run(); 2315 } 2316 runGcAndFinalizersSync(); 2317 Debug.getMemoryInfo(meminfoStart); 2318 fdCount = getFdCount(); 2319 // Now run the test 2320 for (int i = 0; i < 2000; i++) { 2321 if (i % 100 == 5) { 2322 assertNotLeaking(i, meminfoStart, meminfoEnd); 2323 final int curFdCount = getFdCount(); 2324 if (curFdCount - fdCount > 10) { 2325 fail(String.format("FDs leaked. Expected=%d, current=%d, iteration=%d", 2326 fdCount, curFdCount, i)); 2327 } 2328 } 2329 test.run(); 2330 } 2331 assertNotLeaking(2000, meminfoStart, meminfoEnd); 2332 final int curFdCount = getFdCount(); 2333 if (curFdCount - fdCount > 10) { 2334 fail(String.format("FDs leaked. Expected=%d, current=%d", fdCount, curFdCount)); 2335 } 2336 } 2337 2338 @Test 2339 @LargeTest testHardwareBitmapNotLeaking()2340 public void testHardwareBitmapNotLeaking() { 2341 BitmapFactory.Options opts = new BitmapFactory.Options(); 2342 opts.inPreferredConfig = Config.HARDWARE; 2343 opts.inScaled = false; 2344 2345 runNotLeakingTest(() -> { 2346 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, opts); 2347 assertNotNull(bitmap); 2348 // Make sure nothing messed with the bitmap 2349 assertEquals(128, bitmap.getWidth()); 2350 assertEquals(128, bitmap.getHeight()); 2351 assertEquals(Config.HARDWARE, bitmap.getConfig()); 2352 bitmap.recycle(); 2353 }); 2354 } 2355 2356 @Test 2357 @LargeTest testWrappedHardwareBufferBitmapNotLeaking()2358 public void testWrappedHardwareBufferBitmapNotLeaking() { 2359 final ColorSpace colorSpace = ColorSpace.get(Named.SRGB); 2360 try (HardwareBuffer hwBuffer = createTestBuffer(1024, 512, false)) { 2361 runNotLeakingTest(() -> { 2362 Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, colorSpace); 2363 assertNotNull(bitmap); 2364 // Make sure nothing messed with the bitmap 2365 assertEquals(1024, bitmap.getWidth()); 2366 assertEquals(512, bitmap.getHeight()); 2367 assertEquals(Config.HARDWARE, bitmap.getConfig()); 2368 bitmap.recycle(); 2369 }); 2370 } 2371 } 2372 2373 @Test 2374 @LargeTest testDrawingHardwareBitmapNotLeaking()2375 public void testDrawingHardwareBitmapNotLeaking() { 2376 BitmapFactory.Options opts = new BitmapFactory.Options(); 2377 opts.inPreferredConfig = Config.HARDWARE; 2378 opts.inScaled = false; 2379 RenderTarget renderTarget = RenderTarget.create(); 2380 renderTarget.setDefaultSize(128, 128); 2381 final Surface surface = renderTarget.getSurface(); 2382 2383 runNotLeakingTest(() -> { 2384 Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, opts); 2385 assertNotNull(bitmap); 2386 // Make sure nothing messed with the bitmap 2387 assertEquals(128, bitmap.getWidth()); 2388 assertEquals(128, bitmap.getHeight()); 2389 assertEquals(Config.HARDWARE, bitmap.getConfig()); 2390 Canvas canvas = surface.lockHardwareCanvas(); 2391 canvas.drawBitmap(bitmap, 0, 0, null); 2392 surface.unlockCanvasAndPost(canvas); 2393 bitmap.recycle(); 2394 }); 2395 renderTarget.destroy(); 2396 } 2397 2398 @Test testWrapHardwareBufferHoldsReference()2399 public void testWrapHardwareBufferHoldsReference() { 2400 Bitmap bitmap; 2401 // Create hardware-buffer and wrap it in a Bitmap 2402 try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, true)) { 2403 // Fill buffer with colors (x, y, 42, 255) 2404 nFillRgbaHwBuffer(hwBuffer); 2405 bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 2406 } 2407 2408 // Buffer is closed at this point. Ensure bitmap still works by drawing it 2409 assertEquals(128, bitmap.getWidth()); 2410 assertEquals(128, bitmap.getHeight()); 2411 assertEquals(Config.HARDWARE, bitmap.getConfig()); 2412 2413 // Copy bitmap to target bitmap we can read from 2414 Bitmap dstBitmap = bitmap.copy(Config.ARGB_8888, false); 2415 bitmap.recycle(); 2416 2417 // Ensure that the bitmap has valid contents 2418 int pixel = dstBitmap.getPixel(0, 0); 2419 assertEquals(255 << 24 | 42, pixel); 2420 dstBitmap.recycle(); 2421 } 2422 2423 @Test testWrapHardwareBufferPreservesColors()2424 public void testWrapHardwareBufferPreservesColors() { 2425 try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, true)) { 2426 // Fill buffer with colors (x, y, 42, 255) 2427 nFillRgbaHwBuffer(hwBuffer); 2428 2429 // Create HW bitmap from this buffer 2430 Bitmap srcBitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB)); 2431 assertNotNull(srcBitmap); 2432 2433 // Copy it to target non-HW bitmap 2434 Bitmap dstBitmap = srcBitmap.copy(Config.ARGB_8888, false); 2435 srcBitmap.recycle(); 2436 2437 // Ensure all colors are as expected (matches the nFillRgbaHwBuffer call used above). 2438 for (int y = 0; y < 128; ++y) { 2439 for (int x = 0; x < 128; ++x) { 2440 int pixel = dstBitmap.getPixel(x, y); 2441 short a = 255; 2442 short r = (short) (x % 255); 2443 short g = (short) (y % 255); 2444 short b = 42; 2445 assertEquals(a << 24 | r << 16 | g << 8 | b, pixel); 2446 } 2447 } 2448 dstBitmap.recycle(); 2449 } 2450 } 2451 compressToPng(Bitmap bitmap)2452 private static byte[] compressToPng(Bitmap bitmap) { 2453 try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 2454 assertTrue("Failed to encode a Bitmap with Config " + bitmap.getConfig() 2455 + " and ColorSpace " + bitmap.getColorSpace() + "!", 2456 bitmap.compress(CompressFormat.PNG, 100, stream)); 2457 return stream.toByteArray(); 2458 } catch (IOException e) { 2459 fail("Failed to compress with " + e); 2460 return null; 2461 } 2462 } 2463 parametersForTestAsShared()2464 private static Object[] parametersForTestAsShared() { 2465 return Utils.crossProduct(Config.values(), getRgbColorSpaces().toArray(new Object[0])); 2466 } 2467 2468 @Test 2469 @Parameters(method = "parametersForTestAsShared") testAsShared(Config config, ColorSpace colorSpace)2470 public void testAsShared(Config config, ColorSpace colorSpace) { 2471 Bitmap original = Bitmap.createBitmap(10, 10, 2472 config == Config.HARDWARE ? Config.ARGB_8888 : config, true /*hasAlpha*/, 2473 colorSpace); 2474 drawGradient(original); 2475 2476 if (config == Config.HARDWARE) { 2477 original = original.copy(Config.HARDWARE, false /*mutable*/); 2478 } 2479 2480 // There's no visible way to test that the memory is allocated in shared memory, but we can 2481 // verify that the Bitmaps look the same. 2482 Bitmap shared = original.asShared(); 2483 assertNotNull(shared); 2484 2485 if (config == Config.HARDWARE) { 2486 int expectedFormat = nGetFormat(original); 2487 assertEquals(expectedFormat, configToFormat(shared.getConfig())); 2488 2489 // There's no public way to look at the pixels in the HARDWARE Bitmap, but if we 2490 // compress each as a lossless PNG, they should look identical. 2491 byte[] origBytes = compressToPng(original); 2492 byte[] sharedBytes = compressToPng(shared); 2493 assertTrue(Arrays.equals(origBytes, sharedBytes)); 2494 } else { 2495 assertSame(original.getConfig(), shared.getConfig()); 2496 assertTrue(shared.sameAs(original)); 2497 } 2498 assertSame(original.getColorSpace(), shared.getColorSpace()); 2499 2500 // The Bitmap is already in shared memory, so no work is done. 2501 Bitmap shared2 = shared.asShared(); 2502 assertSame(shared, shared2); 2503 } 2504 2505 @Test(expected = IllegalStateException.class) testAsSharedRecycled()2506 public void testAsSharedRecycled() { 2507 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 2508 bitmap.recycle(); 2509 bitmap.asShared(); 2510 } 2511 2512 @Test testAsSharedDensity()2513 public void testAsSharedDensity() { 2514 DisplayMetrics metrics = 2515 InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics(); 2516 Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888); 2517 for (int density : new int[] { Bitmap.DENSITY_NONE, metrics.densityDpi, 2518 DisplayMetrics.DENSITY_HIGH, DisplayMetrics.DENSITY_DEVICE_STABLE, 2519 DisplayMetrics.DENSITY_MEDIUM }) { 2520 bitmap.setDensity(density); 2521 Bitmap shared = bitmap.asShared(); 2522 assertEquals(density, shared.getDensity()); 2523 shared.recycle(); 2524 } 2525 } 2526 2527 @Test 2528 @Parameters({"true", "false"}) testAsSharedImageDecoder(boolean mutable)2529 public void testAsSharedImageDecoder(boolean mutable) { 2530 Resources res = InstrumentationRegistry.getTargetContext().getResources(); 2531 ImageDecoder.Source source = ImageDecoder.createSource(res.getAssets(), 2532 "grayscale-16bit-linearSrgb.png"); 2533 try { 2534 Bitmap bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> { 2535 decoder.setAllocator(ImageDecoder.ALLOCATOR_SHARED_MEMORY); 2536 if (mutable) decoder.setMutableRequired(true); 2537 }); 2538 2539 Bitmap shared = bitmap.asShared(); 2540 if (mutable) { 2541 // bitmap is mutable, so asShared must make a copy. 2542 assertNotEquals(bitmap, shared); 2543 assertTrue(bitmap.sameAs(shared)); 2544 } else { 2545 // bitmap is already immutable and in shared memory, so asShared will return 2546 // itself. 2547 assertSame(bitmap, shared); 2548 } 2549 } catch (IOException e) { 2550 fail("Failed to decode with " + e); 2551 } 2552 } 2553 2554 @Test testNdkFormats()2555 public void testNdkFormats() { 2556 for (ConfigToFormat pair : CONFIG_TO_FORMAT) { 2557 Bitmap bm = Bitmap.createBitmap(10, 10, pair.config); 2558 assertNotNull(bm); 2559 int nativeFormat = nGetFormat(bm); 2560 assertEquals("Config: " + pair.config, pair.format, nativeFormat); 2561 } 2562 } 2563 2564 @Test testNdkFormatsHardware()2565 public void testNdkFormatsHardware() { 2566 for (ConfigToFormat pair : CONFIG_TO_FORMAT) { 2567 Bitmap bm = Bitmap.createBitmap(10, 10, pair.config); 2568 bm = bm.copy(Bitmap.Config.HARDWARE, false); 2569 2570 // ALPHA_8 may not be supported in HARDWARE. 2571 if (bm == null) { 2572 assertEquals(Bitmap.Config.ALPHA_8, pair.config); 2573 continue; 2574 } 2575 2576 int nativeFormat = nGetFormat(bm); 2577 if (pair.config == Bitmap.Config.RGBA_F16) { 2578 // It is possible the system does not support RGBA_F16 in HARDWARE. 2579 // In that case, it will fall back to ARGB_8888. 2580 assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888 2581 || nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_F16); 2582 } else if (pair.config == Bitmap.Config.RGBA_1010102) { 2583 // Devices not supporting RGBA_1010102 in hardware should fallback to ARGB_8888 2584 assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888 2585 || nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_1010102); 2586 } else { 2587 assertEquals("Config: " + pair.config, pair.format, nativeFormat); 2588 } 2589 } 2590 } 2591 2592 @Test testNullBitmapNdk()2593 public void testNullBitmapNdk() { 2594 Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); 2595 nTestNullBitmap(bitmap); 2596 } 2597 parametersForTestNdkInfo()2598 private Object[] parametersForTestNdkInfo() { 2599 return new Object[] { 2600 new Object[] { Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8 }, 2601 new Object[] { Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888 }, 2602 new Object[] { Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565 }, 2603 new Object[] { Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16 }, 2604 new Object[] { Config.RGBA_1010102, ANDROID_BITMAP_FORMAT_RGBA_1010102 }, 2605 }; 2606 } 2607 2608 @Test 2609 @Parameters(method = "parametersForTestNdkInfo") testNdkInfo(Config config, final int expectedFormat)2610 public void testNdkInfo(Config config, final int expectedFormat) { 2611 // Arbitrary width and height. 2612 final int width = 13; 2613 final int height = 7; 2614 boolean[] trueFalse = new boolean[] { true, false }; 2615 for (boolean hasAlpha : trueFalse) { 2616 for (boolean premultiplied : trueFalse) { 2617 Bitmap bm = Bitmap.createBitmap(width, height, config, hasAlpha); 2618 bm.setPremultiplied(premultiplied); 2619 nTestInfo(bm, expectedFormat, width, height, bm.hasAlpha(), 2620 bm.isPremultiplied(), false); 2621 Bitmap hwBitmap = bm.copy(Bitmap.Config.HARDWARE, false); 2622 if (hwBitmap == null) { 2623 // Some devices do not support ALPHA_8 + HARDWARE. 2624 assertEquals(Bitmap.Config.ALPHA_8, config); 2625 } else { 2626 // Some devices do not support (F16 | 1010102) + HARDWARE. These fall back to 2627 // 8888. Check the HWB to confirm. 2628 int tempExpectedFormat = expectedFormat; 2629 if (config == Config.RGBA_F16 || config == Config.RGBA_1010102) { 2630 HardwareBuffer buffer = hwBitmap.getHardwareBuffer(); 2631 if (buffer.getFormat() == HardwareBuffer.RGBA_8888) { 2632 tempExpectedFormat = ANDROID_BITMAP_FORMAT_RGBA_8888; 2633 } 2634 } 2635 nTestInfo(hwBitmap, tempExpectedFormat, width, height, hwBitmap.hasAlpha(), 2636 hwBitmap.isPremultiplied(), true); 2637 hwBitmap.recycle(); 2638 } 2639 bm.recycle(); 2640 } 2641 } 2642 } 2643 2644 @Test testNdkDataSpaceF16Extended()2645 public void testNdkDataSpaceF16Extended() { 2646 // In RGBA_F16 we force EXTENDED in these cases. 2647 for (ColorSpace colorSpace : new ColorSpace[] { 2648 ColorSpace.get(Named.SRGB), 2649 ColorSpace.get(Named.EXTENDED_SRGB), 2650 }) { 2651 Bitmap bm = Bitmap.createBitmap(10, 10, Config.RGBA_F16, false, colorSpace); 2652 assertNotNull(bm); 2653 2654 assertEquals(ColorSpace.get(Named.EXTENDED_SRGB), bm.getColorSpace()); 2655 assertEquals(DataSpace.ADATASPACE_SCRGB, nGetDataSpace(bm)); 2656 } 2657 2658 for (ColorSpace colorSpace : new ColorSpace[] { 2659 ColorSpace.get(Named.LINEAR_SRGB), 2660 ColorSpace.get(Named.LINEAR_EXTENDED_SRGB), 2661 }) { 2662 Bitmap bm = Bitmap.createBitmap(10, 10, Config.RGBA_F16, false, colorSpace); 2663 assertNotNull(bm); 2664 2665 assertEquals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB), bm.getColorSpace()); 2666 assertEquals(DataSpace.ADATASPACE_SCRGB_LINEAR, nGetDataSpace(bm)); 2667 } 2668 } 2669 2670 @Test testNdkDataSpaceNonExtended()2671 public void testNdkDataSpaceNonExtended() { 2672 // In 565 and 8888, these force non-extended. 2673 for (ColorSpace colorSpace : new ColorSpace[] { 2674 ColorSpace.get(Named.SRGB), 2675 ColorSpace.get(Named.EXTENDED_SRGB), 2676 }) { 2677 for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565 }) { 2678 Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace); 2679 assertNotNull(bm); 2680 2681 assertEquals(ColorSpace.get(Named.SRGB), bm.getColorSpace()); 2682 assertEquals(DataSpace.ADATASPACE_SRGB, nGetDataSpace(bm)); 2683 } 2684 } 2685 2686 for (ColorSpace colorSpace : new ColorSpace[] { 2687 ColorSpace.get(Named.LINEAR_SRGB), 2688 ColorSpace.get(Named.LINEAR_EXTENDED_SRGB), 2689 }) { 2690 for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565 }) { 2691 Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace); 2692 assertNotNull(bm); 2693 2694 assertEquals(ColorSpace.get(Named.LINEAR_SRGB), bm.getColorSpace()); 2695 assertEquals(DataSpace.ADATASPACE_SRGB_LINEAR, nGetDataSpace(bm)); 2696 } 2697 } 2698 } 2699 2700 @Test testNdkDataSpace()2701 public void testNdkDataSpace() { 2702 // DataSpace.ADATASPACEs that do not depend on the Config. 2703 for (ColorSpace colorSpace : new ColorSpace[] { 2704 // These have corresponding DataSpace.ADATASPACEs that are independent of the Config 2705 ColorSpace.get(Named.DISPLAY_P3), 2706 ColorSpace.get(Named.BT2020), 2707 ColorSpace.get(Named.ADOBE_RGB), 2708 ColorSpace.get(Named.BT709), 2709 ColorSpace.get(Named.DCI_P3), 2710 2711 // These have no public ADATASPACE. 2712 ColorSpace.get(Named.ACES), 2713 ColorSpace.get(Named.ACESCG), 2714 ColorSpace.get(Named.NTSC_1953), 2715 ColorSpace.get(Named.PRO_PHOTO_RGB), 2716 ColorSpace.get(Named.SMPTE_C), 2717 }) { 2718 for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16 }) { 2719 Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace); 2720 assertNotNull(bm); 2721 2722 int dataSpace = nGetDataSpace(bm); 2723 assertEquals("Bitmap with " + c + " and " + bm.getColorSpace() 2724 + " has unexpected data space", DataSpace.fromColorSpace(colorSpace), 2725 dataSpace); 2726 } 2727 } 2728 } 2729 2730 @Test testNdkDataSpaceAlpha8()2731 public void testNdkDataSpaceAlpha8() { 2732 // ALPHA_8 doesn't support ColorSpaces 2733 Bitmap bm = Bitmap.createBitmap(10, 10, Config.ALPHA_8); 2734 assertNotNull(bm); 2735 assertNull(bm.getColorSpace()); 2736 int dataSpace = nGetDataSpace(bm); 2737 assertEquals(DataSpace.ADATASPACE_UNKNOWN, dataSpace); 2738 } 2739 2740 @Test testNdkDataSpaceNullBitmap()2741 public void testNdkDataSpaceNullBitmap() { 2742 assertEquals(DataSpace.ADATASPACE_UNKNOWN, nGetDataSpace(null)); 2743 } 2744 nGetDataSpace(Bitmap bm)2745 private static native int nGetDataSpace(Bitmap bm); 2746 2747 // These match the NDK APIs. 2748 private static final int ANDROID_BITMAP_COMPRESS_FORMAT_JPEG = 0; 2749 private static final int ANDROID_BITMAP_COMPRESS_FORMAT_PNG = 1; 2750 private static final int ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY = 3; 2751 private static final int ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS = 4; 2752 nativeCompressFormat(CompressFormat format)2753 private int nativeCompressFormat(CompressFormat format) { 2754 switch (format) { 2755 case JPEG: 2756 return ANDROID_BITMAP_COMPRESS_FORMAT_JPEG; 2757 case PNG: 2758 return ANDROID_BITMAP_COMPRESS_FORMAT_PNG; 2759 case WEBP_LOSSY: 2760 return ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY; 2761 case WEBP_LOSSLESS: 2762 return ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS; 2763 default: 2764 fail("format " + format + " has no corresponding native compress format!"); 2765 return -1; 2766 } 2767 } 2768 parametersForNdkCompress()2769 private static Object[] parametersForNdkCompress() { 2770 // Skip WEBP, which has no corresponding native compress format. 2771 Object[] formats = new Object[] { 2772 CompressFormat.JPEG, 2773 CompressFormat.PNG, 2774 CompressFormat.WEBP_LOSSY, 2775 CompressFormat.WEBP_LOSSLESS, 2776 }; 2777 // These are the ColorSpaces with corresponding ADataSpaces 2778 Object[] colorSpaces = new Object[] { 2779 ColorSpace.get(Named.SRGB), 2780 ColorSpace.get(Named.EXTENDED_SRGB), 2781 ColorSpace.get(Named.LINEAR_SRGB), 2782 ColorSpace.get(Named.LINEAR_EXTENDED_SRGB), 2783 2784 ColorSpace.get(Named.DISPLAY_P3), 2785 ColorSpace.get(Named.DCI_P3), 2786 ColorSpace.get(Named.BT2020), 2787 ColorSpace.get(Named.BT709), 2788 ColorSpace.get(Named.ADOBE_RGB), 2789 }; 2790 2791 Object[] configs = new Object[] { 2792 Config.ARGB_8888, 2793 Config.RGB_565, 2794 Config.RGBA_F16, 2795 }; 2796 2797 return crossProduct(formats, colorSpaces, configs); 2798 } 2799 crossProduct(Object[] a, Object[] b, Object[] c)2800 private static Object[] crossProduct(Object[] a, Object[] b, Object[] c) { 2801 final int length = a.length * b.length * c.length; 2802 Object[] ret = new Object[length]; 2803 for (int i = 0; i < a.length; i++) { 2804 for (int j = 0; j < b.length; j++) { 2805 for (int k = 0; k < c.length; k++) { 2806 int index = i * (b.length * c.length) + j * c.length + k; 2807 assertNull(ret[index]); 2808 ret[index] = new Object[] { a[i], b[j], c[k] }; 2809 } 2810 } 2811 } 2812 return ret; 2813 } 2814 isSrgb(ColorSpace cs)2815 private static boolean isSrgb(ColorSpace cs) { 2816 return cs == ColorSpace.get(Named.SRGB) 2817 || cs == ColorSpace.get(Named.EXTENDED_SRGB) 2818 || cs == ColorSpace.get(Named.LINEAR_SRGB) 2819 || cs == ColorSpace.get(Named.LINEAR_EXTENDED_SRGB); 2820 } 2821 2822 // Helper method for populating a Bitmap with interesting pixels for comparison. drawGradient(Bitmap bitmap)2823 private static void drawGradient(Bitmap bitmap) { 2824 // Use different colors and alphas. 2825 Canvas canvas = new Canvas(bitmap); 2826 ColorSpace cs = bitmap.getColorSpace(); 2827 if (cs == null) { 2828 assertSame(Config.ALPHA_8, bitmap.getConfig()); 2829 cs = ColorSpace.get(ColorSpace.Named.SRGB); 2830 } 2831 long color0 = Color.pack(0, 0, 1, 1, cs); 2832 long color1 = Color.pack(1, 0, 0, 0, cs); 2833 LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1, 2834 Shader.TileMode.CLAMP); 2835 Paint paint = new Paint(); 2836 paint.setShader(gradient); 2837 canvas.drawPaint(paint); 2838 } 2839 2840 @Test 2841 @Parameters(method = "parametersForNdkCompress") testNdkCompress(CompressFormat format, ColorSpace cs, Config config)2842 public void testNdkCompress(CompressFormat format, ColorSpace cs, Config config) 2843 throws IOException { 2844 // Verify that ndk compress behaves the same as Bitmap#compress 2845 Bitmap bitmap = Bitmap.createBitmap(10, 10, config, true /* hasAlpha */, cs); 2846 assertNotNull(bitmap); 2847 2848 { 2849 drawGradient(bitmap); 2850 } 2851 2852 byte[] storage = new byte[16 * 1024]; 2853 for (int quality : new int[] { 50, 80, 100 }) { 2854 byte[] expected = null; 2855 try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 2856 assertTrue("Failed to encode a Bitmap with " + cs + " to " + format + " at quality " 2857 + quality + " from Java API", bitmap.compress(format, quality, stream)); 2858 expected = stream.toByteArray(); 2859 } 2860 2861 try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 2862 boolean success = nCompress(bitmap, nativeCompressFormat(format), 2863 quality, stream, storage); 2864 assertTrue("Failed to encode pixels with " + cs + " to " + format + " at quality " 2865 + quality + " from NDK API", success); 2866 byte[] actual = stream.toByteArray(); 2867 2868 if (isSrgb(cs)) { 2869 if (!Arrays.equals(expected, actual)) { 2870 fail("NDK compression did not match for " + cs + " and format " + format 2871 + " at quality " + quality); 2872 } 2873 } else { 2874 // The byte arrays will match exactly for SRGB and its variants, because those 2875 // are treated specially. For the others, there are some small differences 2876 // between Skia's and ColorSpace's values that result in the ICC profiles being 2877 // written slightly differently. They should still look the same, though. 2878 Bitmap expectedBitmap = decodeBytes(expected); 2879 Bitmap actualBitmap = decodeBytes(actual); 2880 boolean matched = BitmapUtils.compareBitmapsMse(expectedBitmap, actualBitmap, 2881 5, true, false); 2882 expectedBitmap.recycle(); 2883 actualBitmap.recycle(); 2884 assertTrue("NDK compression did not match for " + cs + " and format " + format 2885 + " at quality " + quality, matched); 2886 } 2887 } 2888 } 2889 } 2890 2891 @Test testNdkCompressBadParameter()2892 public void testNdkCompressBadParameter() throws IOException { 2893 try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 2894 nTestNdkCompressBadParameter(mBitmap, stream, new byte[16 * 1024]); 2895 } 2896 } 2897 nCompress(Bitmap bitmap, int format, int quality, OutputStream stream, byte[] storage)2898 private static native boolean nCompress(Bitmap bitmap, int format, int quality, 2899 OutputStream stream, byte[] storage); nTestNdkCompressBadParameter(Bitmap bitmap, OutputStream stream, byte[] storage)2900 private static native void nTestNdkCompressBadParameter(Bitmap bitmap, 2901 OutputStream stream, byte[] storage); 2902 strictModeTest(Runnable runnable)2903 private void strictModeTest(Runnable runnable) { 2904 StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy(); 2905 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 2906 .detectCustomSlowCalls().penaltyDeath().build()); 2907 try { 2908 runnable.run(); 2909 fail("Shouldn't reach it"); 2910 } catch (RuntimeException expected){ 2911 // expect to receive StrictModeViolation 2912 } finally { 2913 StrictMode.setThreadPolicy(originalPolicy); 2914 } 2915 } 2916 nValidateBitmapInfo(Bitmap bitmap, int width, int height, boolean is565)2917 private static native void nValidateBitmapInfo(Bitmap bitmap, int width, int height, 2918 boolean is565); nValidateNdkAccessFails(Bitmap bitmap)2919 private static native void nValidateNdkAccessFails(Bitmap bitmap); 2920 nFillRgbaHwBuffer(HardwareBuffer hwBuffer)2921 private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer); nTestNullBitmap(Bitmap bitmap)2922 private static native void nTestNullBitmap(Bitmap bitmap); 2923 2924 private static final int ANDROID_BITMAP_FORMAT_NONE = 0; 2925 static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1; 2926 private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4; 2927 private static final int ANDROID_BITMAP_FORMAT_A_8 = 8; 2928 private static final int ANDROID_BITMAP_FORMAT_RGBA_F16 = 9; 2929 private static final int ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10; 2930 2931 private static class ConfigToFormat { 2932 public final Config config; 2933 public final int format; 2934 ConfigToFormat(Config c, int f)2935 ConfigToFormat(Config c, int f) { 2936 this.config = c; 2937 this.format = f; 2938 } 2939 } 2940 configToFormat(Config config)2941 private static int configToFormat(Config config) { 2942 for (ConfigToFormat pair : CONFIG_TO_FORMAT) { 2943 if (config == pair.config) { 2944 return pair.format; 2945 } 2946 } 2947 return ANDROID_BITMAP_FORMAT_NONE; 2948 } 2949 2950 private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] { 2951 new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888), 2952 // ARGB_4444 is deprecated, and createBitmap converts to 8888. 2953 new ConfigToFormat(Bitmap.Config.ARGB_4444, ANDROID_BITMAP_FORMAT_RGBA_8888), 2954 new ConfigToFormat(Bitmap.Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565), 2955 new ConfigToFormat(Bitmap.Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8), 2956 new ConfigToFormat(Bitmap.Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16), 2957 new ConfigToFormat(Bitmap.Config.RGBA_1010102, ANDROID_BITMAP_FORMAT_RGBA_1010102), 2958 }; 2959 nGetFormat(Bitmap bitmap)2960 static native int nGetFormat(Bitmap bitmap); 2961 nTestInfo(Bitmap bm, int androidBitmapFormat, int width, int height, boolean hasAlpha, boolean premultiplied, boolean hardware)2962 private static native void nTestInfo(Bitmap bm, int androidBitmapFormat, int width, int height, 2963 boolean hasAlpha, boolean premultiplied, boolean hardware); 2964 createTestBuffer(int width, int height, boolean cpuAccess)2965 private static HardwareBuffer createTestBuffer(int width, int height, boolean cpuAccess) { 2966 long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE; 2967 if (cpuAccess) { 2968 usage |= HardwareBuffer.USAGE_CPU_WRITE_RARELY; 2969 } 2970 // We can assume that RGBA_8888 format is supported for every platform. 2971 HardwareBuffer hwBuffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 2972 1, usage); 2973 return hwBuffer; 2974 } 2975 scaleFromDensity(int size, int sdensity, int tdensity)2976 private static int scaleFromDensity(int size, int sdensity, int tdensity) { 2977 if (sdensity == Bitmap.DENSITY_NONE || sdensity == tdensity) { 2978 return size; 2979 } 2980 2981 // Scale by tdensity / sdensity, rounding up. 2982 return ((size * tdensity) + (sdensity >> 1)) / sdensity; 2983 } 2984 createColors(int size)2985 private static int[] createColors(int size) { 2986 int[] colors = new int[size]; 2987 2988 for (int i = 0; i < size; i++) { 2989 colors[i] = (0xFF << 24) | (i << 16) | (i << 8) | i; 2990 } 2991 2992 return colors; 2993 } 2994 createHardwareBitmapOptions()2995 private static BitmapFactory.Options createHardwareBitmapOptions() { 2996 BitmapFactory.Options options = new BitmapFactory.Options(); 2997 options.inPreferredConfig = Config.HARDWARE; 2998 return options; 2999 } 3000 3001 @Test testCopyAlpha8ToHardware()3002 public void testCopyAlpha8ToHardware() { 3003 Bitmap bm = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8); 3004 assertNotNull(bm); 3005 Bitmap hwBitmap = bm.copy(Bitmap.Config.HARDWARE, false /* mutable */); 3006 // Some devices may not support ALPHA_8 + HARDWARE 3007 if (hwBitmap != null) { 3008 assertNull(hwBitmap.getColorSpace()); 3009 } 3010 3011 bm.recycle(); 3012 } 3013 } 3014