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 17 package android.graphics.drawable.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 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 27 import android.content.res.Resources; 28 import android.content.res.Resources.Theme; 29 import android.content.res.XmlResourceParser; 30 import android.graphics.Bitmap; 31 import android.graphics.Bitmap.Config; 32 import android.graphics.BitmapFactory; 33 import android.graphics.BlendMode; 34 import android.graphics.Canvas; 35 import android.graphics.Color; 36 import android.graphics.ColorFilter; 37 import android.graphics.NinePatch; 38 import android.graphics.Outline; 39 import android.graphics.Paint; 40 import android.graphics.PixelFormat; 41 import android.graphics.PorterDuff.Mode; 42 import android.graphics.Rect; 43 import android.graphics.Region; 44 import android.graphics.cts.R; 45 import android.graphics.drawable.Drawable; 46 import android.graphics.drawable.Drawable.ConstantState; 47 import android.graphics.drawable.NinePatchDrawable; 48 import android.util.AttributeSet; 49 import android.util.DisplayMetrics; 50 import android.util.Xml; 51 52 import androidx.test.InstrumentationRegistry; 53 import androidx.test.filters.SmallTest; 54 import androidx.test.runner.AndroidJUnit4; 55 56 import org.junit.Before; 57 import org.junit.Ignore; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 import org.xmlpull.v1.XmlPullParser; 61 import org.xmlpull.v1.XmlPullParserException; 62 63 import java.io.File; 64 import java.io.FileOutputStream; 65 import java.io.IOException; 66 67 @SmallTest 68 @RunWith(AndroidJUnit4.class) 69 public class NinePatchDrawableTest { 70 // A small value is actually making sure that the values are matching 71 // exactly with the golden image. 72 // We can increase the threshold if the Skia is drawing with some variance 73 // on different devices. So far, the tests show they are matching correctly. 74 private static final float PIXEL_ERROR_THRESHOLD = 0.03f; 75 private static final float PIXEL_ERROR_COUNT_THRESHOLD = 0.005f; 76 77 private static final int MIN_CHUNK_SIZE = 32; 78 79 // Set true to generate golden images, false for normal tests. 80 private static final boolean DBG_DUMP_PNG = false; 81 82 private NinePatchDrawable mNinePatchDrawable; 83 84 private Resources mResources; 85 86 @Before 87 public void setup() { 88 mResources = InstrumentationRegistry.getTargetContext().getResources(); 89 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_0); 90 } 91 92 @SuppressWarnings("deprecation") 93 @Test 94 public void testConstructors() { 95 byte[] chunk = new byte[MIN_CHUNK_SIZE]; 96 chunk[MIN_CHUNK_SIZE - 1] = 1; 97 98 Rect r = new Rect(); 99 100 Bitmap bmp = BitmapFactory.decodeResource(mResources, R.drawable.ninepatch_0); 101 String name = mResources.getResourceName(R.drawable.ninepatch_0); 102 103 new NinePatchDrawable(bmp, chunk, r, name); 104 105 new NinePatchDrawable(new NinePatch(bmp, chunk, name)); 106 107 chunk = new byte[MIN_CHUNK_SIZE - 1]; 108 chunk[MIN_CHUNK_SIZE - 2] = 1; 109 try { 110 new NinePatchDrawable(bmp, chunk, r, name); 111 fail("The constructor should check whether the chunk is illegal."); 112 } catch (RuntimeException e) { 113 // This exception is thrown by native method. 114 } 115 } 116 117 @Test 118 public void testDraw() { 119 Bitmap bmp = Bitmap.createBitmap(9, 9, Config.ARGB_8888); 120 Canvas c = new Canvas(bmp); 121 122 int ocean = Color.rgb(0, 0xFF, 0x80); 123 124 mNinePatchDrawable.setBounds(0, 0, 9, 9); 125 mNinePatchDrawable.draw(c); 126 verifyColorFillRect(bmp, 0, 0, 4, 4, Color.RED); 127 verifyColorFillRect(bmp, 5, 0, 4, 4, Color.BLUE); 128 verifyColorFillRect(bmp, 0, 5, 4, 4, ocean); 129 verifyColorFillRect(bmp, 5, 5, 4, 4, Color.YELLOW); 130 verifyColorFillRect(bmp, 4, 0, 1, 9, Color.WHITE); 131 verifyColorFillRect(bmp, 0, 4, 9, 1, Color.WHITE); 132 133 bmp.eraseColor(0xff000000); 134 135 mNinePatchDrawable.setBounds(0, 0, 3, 3); 136 mNinePatchDrawable.draw(c); 137 verifyColorFillRect(bmp, 0, 0, 1, 1, Color.RED); 138 verifyColorFillRect(bmp, 2, 0, 1, 1, Color.BLUE); 139 verifyColorFillRect(bmp, 0, 2, 1, 1, ocean); 140 verifyColorFillRect(bmp, 2, 2, 1, 1, Color.YELLOW); 141 verifyColorFillRect(bmp, 1, 0, 1, 3, Color.WHITE); 142 verifyColorFillRect(bmp, 0, 1, 3, 1, Color.WHITE); 143 } 144 145 @Test(expected=NullPointerException.class) 146 public void testDrawNullCanvas() { 147 mNinePatchDrawable.draw(null); 148 } 149 150 @Test 151 public void testGetChangingConfigurations() { 152 ConstantState constantState = mNinePatchDrawable.getConstantState(); 153 154 // default 155 assertEquals(0, constantState.getChangingConfigurations()); 156 assertEquals(0, mNinePatchDrawable.getChangingConfigurations()); 157 158 // change the drawable's configuration does not affect the state's configuration 159 mNinePatchDrawable.setChangingConfigurations(0xff); 160 assertEquals(0xff, mNinePatchDrawable.getChangingConfigurations()); 161 assertEquals(0, constantState.getChangingConfigurations()); 162 163 // the state's configuration get refreshed 164 constantState = mNinePatchDrawable.getConstantState(); 165 assertEquals(0xff, constantState.getChangingConfigurations()); 166 167 // set a new configuration to drawable 168 mNinePatchDrawable.setChangingConfigurations(0xff00); 169 assertEquals(0xff, constantState.getChangingConfigurations()); 170 assertEquals(0xffff, mNinePatchDrawable.getChangingConfigurations()); 171 } 172 173 @Test 174 public void testGetPadding() { 175 Rect r = new Rect(); 176 NinePatchDrawable npd = (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatch_0); 177 assertTrue(npd.getPadding(r)); 178 // exact padding unknown due to possible density scaling 179 assertEquals(0, r.left); 180 assertEquals(0, r.top); 181 assertTrue(r.right > 0); 182 assertTrue(r.bottom > 0); 183 184 npd = (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatch_1); 185 assertTrue(npd.getPadding(r)); 186 assertTrue(r.left > 0); 187 assertTrue(r.top > 0); 188 assertTrue(r.right > 0); 189 assertTrue(r.bottom > 0); 190 } 191 192 @Test 193 public void testSetAlpha() { 194 assertEquals(0xff, mNinePatchDrawable.getPaint().getAlpha()); 195 196 mNinePatchDrawable.setAlpha(0); 197 assertEquals(0, mNinePatchDrawable.getPaint().getAlpha()); 198 199 mNinePatchDrawable.setAlpha(-1); 200 assertTrue(0 <= mNinePatchDrawable.getPaint().getAlpha() 201 && mNinePatchDrawable.getPaint().getAlpha() <= 255); 202 203 mNinePatchDrawable.setAlpha(0xfffe); 204 assertTrue(0 <= mNinePatchDrawable.getPaint().getAlpha() 205 && mNinePatchDrawable.getPaint().getAlpha() <= 255); 206 } 207 208 @Test 209 public void testSetColorFilter() { 210 assertNull(mNinePatchDrawable.getPaint().getColorFilter()); 211 212 ColorFilter cf = new ColorFilter(); 213 mNinePatchDrawable.setColorFilter(cf); 214 assertSame(cf, mNinePatchDrawable.getPaint().getColorFilter()); 215 216 mNinePatchDrawable.setColorFilter(null); 217 assertNull(mNinePatchDrawable.getPaint().getColorFilter()); 218 } 219 220 @Test 221 public void testSetTint() { 222 mNinePatchDrawable.setTint(Color.BLACK); 223 mNinePatchDrawable.setTintMode(Mode.SRC_OVER); 224 assertEquals("Nine-patch is tinted", Color.BLACK, 225 DrawableTestUtils.getPixel(mNinePatchDrawable, 0, 0)); 226 } 227 228 @Test 229 public void testSetBlendMode() { 230 mNinePatchDrawable.setTint(Color.BLACK); 231 mNinePatchDrawable.setTintBlendMode(BlendMode.SRC_OVER); 232 assertEquals("Nine-patch is tinted", Color.BLACK, 233 DrawableTestUtils.getPixel(mNinePatchDrawable, 0, 0)); 234 235 mNinePatchDrawable.setTintList(null); 236 mNinePatchDrawable.setTintBlendMode((BlendMode) null); 237 } 238 239 @Test 240 public void testSetDither() { 241 mNinePatchDrawable.setDither(false); 242 assertFalse(mNinePatchDrawable.getPaint().isDither()); 243 244 mNinePatchDrawable.setDither(true); 245 assertTrue(mNinePatchDrawable.getPaint().isDither()); 246 } 247 248 @Test 249 public void testSetFilterBitmap() { 250 mNinePatchDrawable.setFilterBitmap(false); 251 assertFalse(mNinePatchDrawable.getPaint().isFilterBitmap()); 252 253 mNinePatchDrawable.setFilterBitmap(true); 254 assertTrue(mNinePatchDrawable.getPaint().isFilterBitmap()); 255 } 256 257 @Test 258 public void testIsFilterBitmap() { 259 mNinePatchDrawable.setFilterBitmap(false); 260 assertFalse(mNinePatchDrawable.isFilterBitmap()); 261 assertEquals(mNinePatchDrawable.isFilterBitmap(), 262 mNinePatchDrawable.getPaint().isFilterBitmap()); 263 264 265 mNinePatchDrawable.setFilterBitmap(true); 266 assertTrue(mNinePatchDrawable.isFilterBitmap()); 267 assertEquals(mNinePatchDrawable.isFilterBitmap(), 268 mNinePatchDrawable.getPaint().isFilterBitmap()); 269 } 270 271 @Test 272 public void testGetPaint() { 273 Paint paint = mNinePatchDrawable.getPaint(); 274 assertNotNull(paint); 275 276 assertSame(paint, mNinePatchDrawable.getPaint()); 277 } 278 279 @Test 280 public void testGetIntrinsicWidth() { 281 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 282 assertEquals(bmp.getWidth(), mNinePatchDrawable.getIntrinsicWidth()); 283 assertEquals(5, mNinePatchDrawable.getIntrinsicWidth()); 284 285 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 286 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 287 assertEquals(bmp.getWidth(), mNinePatchDrawable.getIntrinsicWidth()); 288 assertEquals(9, mNinePatchDrawable.getIntrinsicWidth()); 289 } 290 291 @Test 292 public void testGetMinimumWidth() { 293 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 294 assertEquals(bmp.getWidth(), mNinePatchDrawable.getMinimumWidth()); 295 assertEquals(5, mNinePatchDrawable.getMinimumWidth()); 296 297 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 298 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 299 assertEquals(bmp.getWidth(), mNinePatchDrawable.getMinimumWidth()); 300 assertEquals(9, mNinePatchDrawable.getMinimumWidth()); 301 } 302 303 @Test 304 public void testGetIntrinsicHeight() { 305 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 306 assertEquals(bmp.getHeight(), mNinePatchDrawable.getIntrinsicHeight()); 307 assertEquals(5, mNinePatchDrawable.getIntrinsicHeight()); 308 309 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 310 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 311 assertEquals(bmp.getHeight(), mNinePatchDrawable.getIntrinsicHeight()); 312 assertEquals(9, mNinePatchDrawable.getIntrinsicHeight()); 313 } 314 315 @Test 316 public void testGetMinimumHeight() { 317 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 318 assertEquals(bmp.getHeight(), mNinePatchDrawable.getMinimumHeight()); 319 assertEquals(5, mNinePatchDrawable.getMinimumHeight()); 320 321 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 322 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 323 assertEquals(bmp.getHeight(), mNinePatchDrawable.getMinimumHeight()); 324 assertEquals(9, mNinePatchDrawable.getMinimumHeight()); 325 } 326 327 // Known failure: Bug 2834281 - Bitmap#hasAlpha seems to return true for 328 // images without alpha 329 @Ignore 330 @Test 331 public void testGetOpacity() { 332 assertEquals(PixelFormat.OPAQUE, mNinePatchDrawable.getOpacity()); 333 334 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 335 assertEquals(PixelFormat.TRANSLUCENT, mNinePatchDrawable.getOpacity()); 336 } 337 338 @Test 339 public void testGetTransparentRegion() { 340 // opaque image 341 Region r = mNinePatchDrawable.getTransparentRegion(); 342 assertNull(r); 343 344 mNinePatchDrawable.setBounds(0, 0, 7, 7); 345 r = mNinePatchDrawable.getTransparentRegion(); 346 assertNull(r); 347 348 // translucent image 349 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 350 r = mNinePatchDrawable.getTransparentRegion(); 351 assertNull(r); 352 353 mNinePatchDrawable.setBounds(1, 1, 7, 7); 354 r = mNinePatchDrawable.getTransparentRegion(); 355 assertNotNull(r); 356 assertEquals(new Rect(1, 1, 7, 7), r.getBounds()); 357 } 358 359 @Test 360 public void testGetConstantState() { 361 assertNotNull(mNinePatchDrawable.getConstantState()); 362 363 ConstantState constantState = mNinePatchDrawable.getConstantState(); 364 // change the drawable's configuration does not affect the state's configuration 365 mNinePatchDrawable.setChangingConfigurations(0xff); 366 assertEquals(0, constantState.getChangingConfigurations()); 367 // the state's configuration refreshed when getConstantState is called. 368 constantState = mNinePatchDrawable.getConstantState(); 369 assertEquals(0xff, constantState.getChangingConfigurations()); 370 } 371 372 @Test 373 public void testInflate() throws XmlPullParserException, IOException { 374 int sourceWidth = 80; 375 int sourceHeight = 120; 376 int[] colors = new int[sourceWidth * sourceHeight]; 377 Bitmap bitmap = Bitmap.createBitmap( 378 colors, sourceWidth, sourceHeight, Bitmap.Config.RGB_565); 379 NinePatchDrawable ninePatchDrawable = new NinePatchDrawable( 380 mResources, bitmap, new byte[1000], null, "TESTNAME"); 381 382 int sourceDensity = bitmap.getDensity(); 383 int targetDensity = mResources.getDisplayMetrics().densityDpi; 384 int targetWidth = DrawableTestUtils.scaleBitmapFromDensity( 385 sourceWidth, sourceDensity, targetDensity); 386 int targetHeight = DrawableTestUtils.scaleBitmapFromDensity( 387 sourceHeight, sourceDensity, targetDensity); 388 assertEquals(targetWidth, ninePatchDrawable.getIntrinsicWidth()); 389 assertEquals(targetHeight, ninePatchDrawable.getIntrinsicHeight()); 390 391 XmlResourceParser parser = mResources.getXml(R.drawable.ninepatchdrawable); 392 int type; 393 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 394 && type != XmlPullParser.START_TAG) { 395 } 396 AttributeSet attrs = Xml.asAttributeSet(parser); 397 ninePatchDrawable.inflate(mResources, parser, attrs); 398 399 assertTrue(ninePatchDrawable.getPaint().isDither()); 400 assertTrue(sourceHeight != ninePatchDrawable.getIntrinsicHeight()); 401 assertTrue(sourceWidth != ninePatchDrawable.getIntrinsicWidth()); 402 } 403 404 @Test 405 public void testMutate() { 406 NinePatchDrawable d1 = 407 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 408 NinePatchDrawable d2 = 409 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 410 NinePatchDrawable d3 = 411 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 412 413 // the state is not shared before mutate. 414 d1.setDither(false); 415 assertFalse(d1.getPaint().isDither()); 416 assertTrue(d2.getPaint().isDither()); 417 assertTrue(d3.getPaint().isDither()); 418 419 // cannot test if mutate worked, since state was not shared before 420 d1.mutate(); 421 } 422 423 private static final int[] DENSITY_VALUES = new int[] { 424 160, 80, 320 425 }; 426 427 private static final int[] DENSITY_IMAGES = new int[] { 428 R.drawable.nine_patch_density 429 }; 430 431 private static final int[][] DENSITY_GOLDEN_IMAGES = new int[][] { 432 { 433 R.drawable.nine_patch_density_golden_160, 434 R.drawable.nine_patch_density_golden_80, 435 R.drawable.nine_patch_density_golden_320, 436 } 437 }; 438 439 private interface TargetDensitySetter { 440 void setTargetDensity(NinePatchDrawable dr, int density); 441 } 442 443 private void verifySetTargetDensityOuter(TargetDensitySetter densitySetter) { 444 final Resources res = mResources; 445 final int densityDpi = res.getConfiguration().densityDpi; 446 try { 447 verifySetTargetDensityInner(res, DENSITY_IMAGES[0], DENSITY_VALUES, densitySetter); 448 } catch (IOException | XmlPullParserException e) { 449 throw new RuntimeException(e); 450 } finally { 451 DrawableTestUtils.setResourcesDensity(res, densityDpi); 452 } 453 } 454 455 private void verifySetTargetDensityInner(Resources res, int sourceResId, int[] densities, 456 TargetDensitySetter densitySetter) throws XmlPullParserException, IOException { 457 final Rect tempPadding = new Rect(); 458 459 // Capture initial state at preload density. 460 final int preloadDensityDpi = densities[0]; 461 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 462 463 final NinePatchDrawable preloadedDrawable = 464 (NinePatchDrawable) res.getDrawable(sourceResId).mutate(); 465 final int origWidth = preloadedDrawable.getIntrinsicWidth(); 466 final int origHeight = preloadedDrawable.getIntrinsicHeight(); 467 final Rect origPadding = new Rect(); 468 preloadedDrawable.getPadding(origPadding); 469 470 for (int i = 1; i < densities.length; i++) { 471 final int scaledDensityDpi = densities[i]; 472 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 473 474 final NinePatchDrawable scaledDrawable = 475 (NinePatchDrawable) res.getDrawable(sourceResId).mutate(); 476 densitySetter.setTargetDensity(scaledDrawable, scaledDensityDpi); 477 478 // Sizes are rounded. 479 assertEquals(Math.round(origWidth * scale), scaledDrawable.getIntrinsicWidth()); 480 assertEquals(Math.round(origHeight * scale), scaledDrawable.getIntrinsicHeight()); 481 482 // Padding is truncated. 483 assertTrue(scaledDrawable.getPadding(tempPadding)); 484 assertEquals((int) (origPadding.left * scale), tempPadding.left); 485 assertEquals((int) (origPadding.top * scale), tempPadding.top); 486 assertEquals((int) (origPadding.right * scale), tempPadding.right); 487 assertEquals((int) (origPadding.bottom * scale), tempPadding.bottom); 488 489 // Ensure theme density is applied correctly. Unlike most 490 // drawables, we don't have any loss of accuracy because density 491 // changes are re-computed from the source every time. 492 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 493 494 final Theme t = res.newTheme(); 495 scaledDrawable.applyTheme(t); 496 assertEquals(origWidth, scaledDrawable.getIntrinsicWidth()); 497 assertEquals(origHeight, scaledDrawable.getIntrinsicHeight()); 498 assertTrue(scaledDrawable.getPadding(tempPadding)); 499 assertEquals(origPadding, tempPadding); 500 } 501 } 502 503 @Test 504 public void testSetTargetDensity() { 505 verifySetTargetDensityOuter((dr, density) -> dr.setTargetDensity(density)); 506 } 507 508 @Test 509 public void testSetTargetDensity_Canvas() { 510 // This should be identical to calling setTargetDensity(int) with the 511 // value returned by Canvas.getDensity(). 512 verifySetTargetDensityOuter((dr, density) -> { 513 Canvas c = new Canvas(); 514 c.setDensity(density); 515 dr.setTargetDensity(c); 516 }); 517 } 518 519 @Test 520 public void testSetTargetDensity_DisplayMetrics() { 521 // This should be identical to calling setTargetDensity(int) with the 522 // value of DisplayMetrics.densityDpi. 523 verifySetTargetDensityOuter((dr, density) -> { 524 DisplayMetrics dm = new DisplayMetrics(); 525 dm.densityDpi = density; 526 dr.setTargetDensity(dm); 527 }); 528 } 529 530 @Test 531 public void testPreloadDensity() throws XmlPullParserException, IOException { 532 final Resources res = mResources; 533 final int densityDpi = res.getConfiguration().densityDpi; 534 try { 535 verifyPreloadDensityInner(res, DENSITY_IMAGES[0], DENSITY_VALUES, 536 DENSITY_GOLDEN_IMAGES[0]); 537 } finally { 538 DrawableTestUtils.setResourcesDensity(res, densityDpi); 539 } 540 } 541 542 private void verifyPreloadDensityInner(Resources res, int sourceResId, int[] densities, 543 int[] goldenResIds) throws XmlPullParserException, IOException { 544 // Capture initial state at preload density. 545 final int preloadDensityDpi = densities[0]; 546 final NinePatchDrawable preloadedDrawable = preloadedDrawable(res, 547 densities[0], sourceResId); 548 549 final ConstantState preloadedConstantState = preloadedDrawable.getConstantState(); 550 final int origWidth = preloadedDrawable.getIntrinsicWidth(); 551 final int origHeight = preloadedDrawable.getIntrinsicHeight(); 552 final Rect origPadding = new Rect(); 553 preloadedDrawable.getPadding(origPadding); 554 555 compareOrSave(preloadedDrawable, preloadDensityDpi, sourceResId, goldenResIds[0]); 556 557 for (int i = 1; i < densities.length; i++) { 558 final int scaledDensityDpi = densities[i]; 559 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 560 DrawableTestUtils.setResourcesDensity(res, scaledDensityDpi); 561 562 final NinePatchDrawable scaledDrawable = 563 (NinePatchDrawable) preloadedConstantState.newDrawable(res); 564 565 assertEquals(Math.round(origWidth * scale), scaledDrawable.getIntrinsicWidth()); 566 assertEquals(Math.round(origHeight * scale), scaledDrawable.getIntrinsicHeight()); 567 568 // Padding is truncated. 569 final Rect tempPadding = new Rect(); 570 assertTrue(scaledDrawable.getPadding(tempPadding)); 571 assertEquals((int) (origPadding.left * scale), tempPadding.left); 572 assertEquals((int) (origPadding.top * scale), tempPadding.top); 573 assertEquals((int) (origPadding.right * scale), tempPadding.right); 574 assertEquals((int) (origPadding.bottom * scale), tempPadding.bottom); 575 576 compareOrSave(scaledDrawable, scaledDensityDpi, sourceResId, goldenResIds[i]); 577 578 // Ensure theme density is applied correctly. Unlike most 579 // drawables, we don't have any loss of accuracy because density 580 // changes are re-computed from the source every time. 581 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 582 583 final Theme t = res.newTheme(); 584 scaledDrawable.applyTheme(t); 585 assertEquals(origWidth, scaledDrawable.getIntrinsicWidth()); 586 assertEquals(origHeight, scaledDrawable.getIntrinsicHeight()); 587 assertTrue(scaledDrawable.getPadding(tempPadding)); 588 assertEquals(origPadding, tempPadding); 589 } 590 } 591 592 private static NinePatchDrawable preloadedDrawable(Resources res, int densityDpi, int sourceResId) 593 throws XmlPullParserException, IOException { 594 DrawableTestUtils.setResourcesDensity(res, densityDpi); 595 final XmlResourceParser parser = DrawableTestUtils.getResourceParser(res, sourceResId); 596 final NinePatchDrawable preloadedDrawable = new NinePatchDrawable(null); 597 preloadedDrawable.inflate(res, parser, Xml.asAttributeSet(parser)); 598 return preloadedDrawable; 599 } 600 601 @Test 602 public void testOutlinePreloadDensity() throws XmlPullParserException, IOException { 603 final Resources res = mResources; 604 final int densityDpi = res.getConfiguration().densityDpi; 605 try { 606 verifyOutlinePreloadDensityInner(res); 607 } finally { 608 DrawableTestUtils.setResourcesDensity(res, densityDpi); 609 } 610 } 611 612 private static void verifyOutlinePreloadDensityInner(Resources res) 613 throws XmlPullParserException, IOException { 614 // Capture initial state at preload density. 615 final int preloadDensityDpi = DENSITY_VALUES[0]; 616 final NinePatchDrawable preloadedDrawable = preloadedDrawable(res, preloadDensityDpi, 617 R.drawable.nine_patch_odd_insets); 618 619 final ConstantState preloadedConstantState = preloadedDrawable.getConstantState(); 620 final int bound = 40; 621 final int expectedInset = 5; 622 preloadedDrawable.setBounds(0, 0, bound, bound); 623 final Outline origOutline = new Outline(); 624 preloadedDrawable.getOutline(origOutline); 625 final Rect origOutlineRect = new Rect(); 626 origOutline.getRect(origOutlineRect); 627 assertEquals(new Rect(expectedInset, expectedInset, bound - expectedInset, 628 bound - expectedInset), origOutlineRect); 629 final float origOutlineRadius = origOutline.getRadius(); 630 float expectedRadius = 6.8f; 631 assertEquals(expectedRadius, origOutlineRadius, 0.1f); 632 for (int i = 1; i < DENSITY_VALUES.length; i++) { 633 final int scaledDensityDpi = DENSITY_VALUES[i]; 634 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 635 DrawableTestUtils.setResourcesDensity(res, scaledDensityDpi); 636 final NinePatchDrawable scaledDrawable = 637 (NinePatchDrawable) preloadedConstantState.newDrawable(res); 638 639 int scaledBound = (int) (bound * scale); 640 scaledDrawable.setBounds(0, 0, scaledBound, scaledBound); 641 642 final Outline tempOutline = new Outline(); 643 scaledDrawable.getOutline(tempOutline); 644 final Rect tempOutlineRect = new Rect(); 645 assertTrue(tempOutline.getRect(tempOutlineRect)); 646 assertEquals((int) Math.ceil(origOutlineRect.left * scale), tempOutlineRect.left); 647 assertEquals((int) Math.ceil(origOutlineRect.top * scale), tempOutlineRect.top); 648 assertEquals((int) Math.floor(origOutlineRect.right * scale), tempOutlineRect.right); 649 assertEquals((int) Math.floor(origOutlineRect.bottom * scale), tempOutlineRect.bottom); 650 assertEquals(origOutlineRadius * scale, tempOutline.getRadius(), 0.1f); 651 } 652 } 653 654 private void verifyColorFillRect(Bitmap bmp, int x, int y, int w, int h, int color) { 655 for (int i = x; i < x + w; i++) { 656 for (int j = y; j < y + h; j++) { 657 assertEquals(color, bmp.getPixel(i, j)); 658 } 659 } 660 } 661 662 private NinePatchDrawable getNinePatchDrawable(int resId) { 663 // jump through hoops to avoid scaling the tiny ninepatch, which would skew the results 664 // depending on device density 665 Bitmap bitmap = getBitmapUnscaled(resId); 666 NinePatch np = new NinePatch(bitmap, bitmap.getNinePatchChunk(), null); 667 return new NinePatchDrawable(mResources, np); 668 } 669 670 private Bitmap getBitmapUnscaled(int resId) { 671 BitmapFactory.Options opts = new BitmapFactory.Options(); 672 opts.inDensity = opts.inTargetDensity = mResources.getDisplayMetrics().densityDpi; 673 Bitmap bitmap = BitmapFactory.decodeResource(mResources, resId, opts); 674 return bitmap; 675 } 676 677 private void compareOrSave(Drawable dr, int densityDpi, int sourceResId, int goldenResId) { 678 final int width = dr.getIntrinsicWidth(); 679 final int height = dr.getIntrinsicHeight(); 680 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 681 bitmap.setDensity(0); 682 683 final Canvas canvas = new Canvas(bitmap); 684 dr.setBounds(0, 0, width, height); 685 dr.draw(canvas); 686 687 if (DBG_DUMP_PNG) { 688 saveGoldenImage(bitmap, sourceResId, densityDpi); 689 } else { 690 final Bitmap golden = BitmapFactory.decodeResource(mResources, goldenResId); 691 DrawableTestUtils.compareImages(densityDpi + " dpi", golden, bitmap, 692 PIXEL_ERROR_THRESHOLD, PIXEL_ERROR_COUNT_THRESHOLD, 0 /* tolerance */); 693 } 694 } 695 696 private void saveGoldenImage(Bitmap bitmap, int sourceResId, int densityDpi) { 697 // Save the image to the disk. 698 FileOutputStream out = null; 699 700 try { 701 final String outputFolder = "/sdcard/temp/"; 702 final File folder = new File(outputFolder); 703 if (!folder.exists()) { 704 folder.mkdir(); 705 } 706 707 final String sourceFilename = new File(mResources.getString(sourceResId)).getName(); 708 final String sourceTitle = sourceFilename.substring(0, sourceFilename.lastIndexOf(".")); 709 final String outputTitle = sourceTitle + "_golden_" + densityDpi; 710 final String outputFilename = outputFolder + outputTitle + ".png"; 711 final File outputFile = new File(outputFilename); 712 if (!outputFile.exists()) { 713 outputFile.createNewFile(); 714 } 715 716 out = new FileOutputStream(outputFile, false); 717 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 718 } catch (Exception e) { 719 e.printStackTrace(); 720 } finally { 721 if (out != null) { 722 try { 723 out.close(); 724 } catch (IOException e) { 725 e.printStackTrace(); 726 } 727 } 728 } 729 } 730 } 731