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