1/* 2 * Copyright (C) 2015 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 17package android.uirendering.cts.testclasses; 18 19import static org.junit.Assert.assertEquals; 20 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.graphics.Canvas; 24import android.graphics.Color; 25import android.graphics.ColorMatrix; 26import android.graphics.ColorMatrixColorFilter; 27import android.graphics.Matrix; 28import android.graphics.Paint; 29import android.graphics.Point; 30import android.graphics.PorterDuff; 31import android.graphics.PorterDuffXfermode; 32import android.graphics.Rect; 33import android.graphics.Region.Op; 34import android.support.annotation.ColorInt; 35import android.support.test.filters.LargeTest; 36import android.support.test.filters.MediumTest; 37import android.support.test.runner.AndroidJUnit4; 38import android.uirendering.cts.R; 39import android.uirendering.cts.bitmapverifiers.ColorCountVerifier; 40import android.uirendering.cts.bitmapverifiers.ColorVerifier; 41import android.uirendering.cts.bitmapverifiers.RectVerifier; 42import android.uirendering.cts.bitmapverifiers.SamplePointVerifier; 43import android.uirendering.cts.testinfrastructure.ActivityTestBase; 44import android.uirendering.cts.testinfrastructure.ViewInitializer; 45import android.view.Gravity; 46import android.view.View; 47import android.view.ViewTreeObserver; 48import android.widget.FrameLayout; 49 50import org.junit.Test; 51import org.junit.runner.RunWith; 52 53import java.util.concurrent.CountDownLatch; 54 55@MediumTest 56@RunWith(AndroidJUnit4.class) 57public class LayerTests extends ActivityTestBase { 58 @Test 59 public void testLayerPaintAlpha() { 60 // red channel full strength, other channels 75% strength 61 // (since 25% alpha red subtracts from them) 62 @ColorInt 63 final int expectedColor = Color.rgb(255, 191, 191); 64 createTest() 65 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> { 66 // reduce alpha by 50% 67 Paint paint = new Paint(); 68 paint.setAlpha(128); 69 view.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 70 71 // reduce alpha by another 50% (ensuring two alphas combine correctly) 72 view.setAlpha(0.5f); 73 }) 74 .runWithVerifier(new ColorVerifier(expectedColor)); 75 } 76 77 @Test 78 public void testLayerPaintSimpleAlphaWithHardware() { 79 @ColorInt 80 final int expectedColor = Color.rgb(255, 128, 128); 81 createTest() 82 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> { 83 view.setLayerType(View.LAYER_TYPE_HARDWARE, null); 84 85 // reduce alpha, so that overdraw will result in a different color 86 view.setAlpha(0.5f); 87 }) 88 .runWithVerifier(new ColorVerifier(expectedColor)); 89 } 90 91 @Test 92 public void testLayerPaintSimpleAlphaWithSoftware() { 93 @ColorInt 94 final int expectedColor = Color.rgb(255, 128, 128); 95 createTest() 96 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> { 97 view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 98 99 // reduce alpha, so that overdraw will result in a different color 100 view.setAlpha(0.5f); 101 }) 102 .runWithVerifier(new ColorVerifier(expectedColor)); 103 } 104 105 @Test 106 public void testLayerPaintColorFilter() { 107 // Red, fully desaturated. Note that it's not 255/3 in each channel. 108 // See ColorMatrix#setSaturation() 109 @ColorInt 110 final int expectedColor = Color.rgb(54, 54, 54); 111 createTest() 112 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> { 113 Paint paint = new Paint(); 114 ColorMatrix desatMatrix = new ColorMatrix(); 115 desatMatrix.setSaturation(0.0f); 116 paint.setColorFilter(new ColorMatrixColorFilter(desatMatrix)); 117 view.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 118 }) 119 .runWithVerifier(new ColorVerifier(expectedColor)); 120 } 121 122 @Test 123 public void testLayerPaintBlend() { 124 // Red, drawn underneath opaque white, so output should be white. 125 // TODO: consider doing more interesting blending test here 126 @ColorInt 127 final int expectedColor = Color.WHITE; 128 createTest() 129 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> { 130 Paint paint = new Paint(); 131 /* Note that when drawing in SW, we're blending within an otherwise empty 132 * SW layer, as opposed to in the frame buffer (which has a white 133 * background). 134 * 135 * For this reason we use just use DST, which just throws out the SRC 136 * content, regardless of the DST alpha channel. 137 */ 138 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST)); 139 view.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 140 }) 141 .runWithVerifier(new ColorVerifier(expectedColor)); 142 } 143 144 @LargeTest 145 @Test 146 public void testLayerClear() { 147 ViewInitializer initializer = new ViewInitializer() { 148 ObjectAnimator mAnimator; 149 @Override 150 public void initializeView(View view) { 151 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 152 root.setAlpha(0.5f); 153 154 View child = new View(view.getContext()); 155 child.setBackgroundColor(Color.BLUE); 156 child.setTranslationX(10); 157 child.setTranslationY(10); 158 child.setLayoutParams( 159 new FrameLayout.LayoutParams(50, 50)); 160 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 161 root.addView(child); 162 163 mAnimator = ObjectAnimator.ofInt(child, "translationY", 0, 20); 164 mAnimator.setRepeatMode(ValueAnimator.REVERSE); 165 mAnimator.setRepeatCount(ValueAnimator.INFINITE); 166 mAnimator.setDuration(200); 167 mAnimator.start(); 168 } 169 @Override 170 public void teardownView() { 171 mAnimator.cancel(); 172 } 173 }; 174 175 createTest() 176 .addLayout(R.layout.frame_layout, initializer, true) 177 .runWithAnimationVerifier(new ColorCountVerifier(Color.WHITE, 90 * 90 - 50 * 50)); 178 } 179 180 @Test 181 public void testAlphaLayerChild() { 182 ViewInitializer initializer = new ViewInitializer() { 183 @Override 184 public void initializeView(View view) { 185 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 186 root.setAlpha(0.5f); 187 188 View child = new View(view.getContext()); 189 child.setBackgroundColor(Color.BLUE); 190 child.setTranslationX(10); 191 child.setTranslationY(10); 192 child.setLayoutParams( 193 new FrameLayout.LayoutParams(50, 50)); 194 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 195 root.addView(child); 196 } 197 }; 198 199 createTest() 200 .addLayout(R.layout.frame_layout, initializer) 201 .runWithVerifier(new RectVerifier(Color.WHITE, 0xff8080ff, 202 new Rect(10, 10, 60, 60))); 203 } 204 205 @Test 206 public void testLayerInitialSizeZero() { 207 createTest() 208 .addLayout(R.layout.frame_layout, view -> { 209 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 210 // disable clipChildren, to ensure children aren't rejected by bounds 211 root.setClipChildren(false); 212 for (int i = 0; i < 2; i++) { 213 View child = new View(view.getContext()); 214 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 215 // add rendering content, so View isn't skipped at render time 216 child.setBackgroundColor(Color.RED); 217 218 // add one with width=0, one with height=0 219 root.addView(child, new FrameLayout.LayoutParams( 220 i == 0 ? 0 : 90, 221 i == 0 ? 90 : 0, 222 Gravity.TOP | Gravity.LEFT)); 223 } 224 }, true) 225 .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */)); 226 } 227 228 @Test 229 public void testLayerResizeZero() { 230 final CountDownLatch fence = new CountDownLatch(1); 231 createTest() 232 .addLayout(R.layout.frame_layout, view -> { 233 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 234 // disable clipChildren, to ensure child isn't rejected by bounds 235 root.setClipChildren(false); 236 for (int i = 0; i < 2; i++) { 237 View child = new View(view.getContext()); 238 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 239 // add rendering content, so View isn't skipped at render time 240 child.setBackgroundColor(Color.BLUE); 241 root.addView(child, new FrameLayout.LayoutParams(90, 90, 242 Gravity.TOP | Gravity.LEFT)); 243 } 244 245 // post invalid dimensions a few frames in, so initial layer allocation succeeds 246 // NOTE: this must execute before capture, or verification will fail 247 root.getViewTreeObserver().addOnPreDrawListener( 248 new ViewTreeObserver.OnPreDrawListener() { 249 int mDrawCount = 0; 250 @Override 251 public boolean onPreDraw() { 252 if (mDrawCount++ == 5) { 253 root.getChildAt(0).getLayoutParams().width = 0; 254 root.getChildAt(0).requestLayout(); 255 root.getChildAt(1).getLayoutParams().height = 0; 256 root.getChildAt(1).requestLayout(); 257 root.getViewTreeObserver().removeOnPreDrawListener(this); 258 root.post(fence::countDown); 259 } else { 260 root.postInvalidate(); 261 } 262 return true; 263 } 264 }); 265 }, true, fence) 266 .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */)); 267 } 268 269 @Test 270 public void testSaveLayerClippedWithColorFilter() { 271 // verify that renderer can draw nested clipped layers with chained color filters 272 createTest() 273 .addCanvasClient((canvas, width, height) -> { 274 Paint redPaint = new Paint(); 275 redPaint.setColor(0xffff0000); 276 Paint firstLayerPaint = new Paint(); 277 float[] blueToGreenMatrix = new float[20]; 278 blueToGreenMatrix[7] = blueToGreenMatrix[18] = 1.0f; 279 ColorMatrixColorFilter blueToGreenFilter = new ColorMatrixColorFilter(blueToGreenMatrix); 280 firstLayerPaint.setColorFilter(blueToGreenFilter); 281 Paint secondLayerPaint = new Paint(); 282 float[] redToBlueMatrix = new float[20]; 283 redToBlueMatrix[10] = redToBlueMatrix[18] = 1.0f; 284 ColorMatrixColorFilter redToBlueFilter = new ColorMatrixColorFilter(redToBlueMatrix); 285 secondLayerPaint.setColorFilter(redToBlueFilter); 286 // The color filters are applied starting first with the inner layer and then the 287 // outer layer. 288 canvas.saveLayer(40, 5, 80, 70, firstLayerPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 289 canvas.saveLayer(5, 40, 70, 80, secondLayerPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 290 canvas.drawRect(10, 10, 70, 70, redPaint); 291 canvas.restore(); 292 canvas.restore(); 293 }) 294 .runWithVerifier(new RectVerifier(Color.WHITE, Color.GREEN, new Rect(40, 40, 70, 70))); 295 } 296 297 // Note: This test will fail for Skia pipeline, but that is OK. 298 // TODO: delete this test when Skia pipeline is default and modify next test 299 // testSaveLayerUnclippedWithColorFilterSW to run for both HW and SW 300 @Test 301 public void testSaveLayerUnclippedWithColorFilterHW() { 302 // verify that HW can draw nested unclipped layers with chained color filters 303 createTest() 304 .addCanvasClient((canvas, width, height) -> { 305 Paint redPaint = new Paint(); 306 redPaint.setColor(0xffff0000); 307 Paint firstLayerPaint = new Paint(); 308 float[] blueToGreenMatrix = new float[20]; 309 blueToGreenMatrix[7] = blueToGreenMatrix[18] = 1.0f; 310 ColorMatrixColorFilter blueToGreenFilter = 311 new ColorMatrixColorFilter(blueToGreenMatrix); 312 firstLayerPaint.setColorFilter(blueToGreenFilter); 313 Paint secondLayerPaint = new Paint(); 314 float[] redToBlueMatrix = new float[20]; 315 redToBlueMatrix[10] = redToBlueMatrix[18] = 1.0f; 316 ColorMatrixColorFilter redToBlueFilter = 317 new ColorMatrixColorFilter(redToBlueMatrix); 318 secondLayerPaint.setColorFilter(redToBlueFilter); 319 canvas.saveLayer(40, 5, 80, 70, firstLayerPaint, 0); 320 canvas.saveLayer(5, 40, 70, 80, secondLayerPaint, 0); 321 canvas.drawRect(10, 10, 70, 70, redPaint); 322 canvas.restore(); 323 canvas.restore(); 324 }, true) 325 // HWUI pipeline does not support a color filter for unclipped save layer and draws 326 // as if the filter is not set. 327 .runWithVerifier(new RectVerifier(Color.WHITE, Color.RED, new Rect(10, 10, 70, 70))); 328 } 329 330 @Test 331 public void testSaveLayerUnclippedWithColorFilterSW() { 332 // verify that SW can draw nested unclipped layers with chained color filters 333 createTest() 334 .addCanvasClient((canvas, width, height) -> { 335 Paint redPaint = new Paint(); 336 redPaint.setColor(0xffff0000); 337 Paint firstLayerPaint = new Paint(); 338 float[] blueToGreenMatrix = new float[20]; 339 blueToGreenMatrix[7] = blueToGreenMatrix[18] = 1.0f; 340 ColorMatrixColorFilter blueToGreenFilter = 341 new ColorMatrixColorFilter(blueToGreenMatrix); 342 firstLayerPaint.setColorFilter(blueToGreenFilter); 343 Paint secondLayerPaint = new Paint(); 344 float[] redToBlueMatrix = new float[20]; 345 redToBlueMatrix[10] = redToBlueMatrix[18] = 1.0f; 346 ColorMatrixColorFilter redToBlueFilter = 347 new ColorMatrixColorFilter(redToBlueMatrix); 348 secondLayerPaint.setColorFilter(redToBlueFilter); 349 canvas.saveLayer(40, 5, 80, 70, firstLayerPaint, 0); 350 canvas.saveLayer(5, 40, 70, 80, secondLayerPaint, 0); 351 canvas.drawRect(10, 10, 70, 70, redPaint); 352 canvas.restore(); 353 canvas.restore(); 354 }, false) 355 .runWithVerifier(new SamplePointVerifier( 356 new Point[] { 357 // just outside of rect 358 new Point(9, 9), new Point(70, 10), new Point(10, 70), new Point(70, 70), 359 // red rect 360 new Point(10, 10), new Point(39, 39), 361 // black rect 362 new Point(40, 10), new Point(69, 39), 363 // blue rect 364 new Point(10, 40), new Point(39, 69), 365 // green rect 366 new Point(40, 40), new Point(69, 69), 367 }, 368 new int[] { 369 Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, 370 Color.RED, Color.RED, 371 Color.BLACK, Color.BLACK, 372 Color.BLUE, Color.BLUE, 373 Color.GREEN, Color.GREEN, 374 })); 375 } 376 377 @Test 378 public void testSaveLayerClippedWithAlpha() { 379 // verify that renderer can draw nested clipped layers with different alpha 380 createTest() // picture mode is disable due to bug:34871089 381 .addCanvasClient((canvas, width, height) -> { 382 Paint redPaint = new Paint(); 383 redPaint.setColor(0xffff0000); 384 canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 385 canvas.saveLayerAlpha(5, 40, 70, 80, 0x3f, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 386 canvas.drawRect(10, 10, 70, 70, redPaint); 387 canvas.restore(); 388 canvas.restore(); 389 }) 390 .runWithVerifier(new RectVerifier(Color.WHITE, 0xffffE0E0, new Rect(40, 40, 70, 70))); 391 } 392 393 @Test 394 public void testSaveLayerUnclippedWithAlpha() { 395 // verify that renderer can draw nested unclipped layers with different alpha 396 createTest() // picture mode is disable due to bug:34871089 397 .addCanvasClient((canvas, width, height) -> { 398 Paint redPaint = new Paint(); 399 redPaint.setColor(0xffff0000); 400 canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, 0); 401 canvas.saveLayerAlpha(5, 40, 70, 80, 0x3f, 0); 402 canvas.drawRect(10, 10, 70, 70, redPaint); 403 canvas.restore(); 404 canvas.restore(); 405 }) 406 .runWithVerifier(new SamplePointVerifier( 407 new Point[]{ 408 // just outside of rect 409 new Point(9, 9), new Point(70, 10), new Point(10, 70), new Point(70, 70), 410 // red rect outside both layers 411 new Point(10, 10), new Point(39, 39), 412 // pink rect overlapping one of the layers 413 new Point(40, 10), new Point(69, 39), 414 // pink rect overlapping one of the layers 415 new Point(10, 40), new Point(39, 69), 416 // pink rect overlapping both layers 417 new Point(40, 40), new Point(69, 69), 418 }, 419 new int[]{ 420 Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, 421 Color.RED, Color.RED, 422 0xffff8080, 0xffff8080, 423 0xffffC0C0, 0xffffC0C0, 424 0xffffE0E0, 0xffffE0E0, 425 })); 426 } 427 428 @Test 429 public void testSaveLayerUnclipped_restoreBehavior() { 430 createTest() 431 .addCanvasClient((canvas, width, height) -> { 432 //set identity matrix 433 Matrix identity = new Matrix(); 434 canvas.setMatrix(identity); 435 final Paint p = new Paint(); 436 437 canvas.saveLayer(0, 0, width, height, p, 0); 438 439 //change matrix and clip to something different 440 canvas.clipRect(0, 0, width >> 1, height >> 1, Op.INTERSECT); 441 Matrix scaledMatrix = new Matrix(); 442 scaledMatrix.setScale(4, 5); 443 canvas.setMatrix(scaledMatrix); 444 assertEquals(scaledMatrix, canvas.getMatrix()); 445 446 canvas.drawColor(Color.BLUE); 447 canvas.restore(); 448 449 //check if identity matrix is restored 450 assertEquals(identity, canvas.getMatrix()); 451 452 //should draw to the entire canvas, because clip has been removed 453 canvas.drawColor(Color.RED); 454 }) 455 .runWithVerifier(new ColorVerifier(Color.RED)); 456 } 457 458 @Test 459 public void testSaveLayerClipped_restoreBehavior() { 460 createTest() 461 .addCanvasClient((canvas, width, height) -> { 462 //set identity matrix 463 Matrix identity = new Matrix(); 464 canvas.setMatrix(identity); 465 final Paint p = new Paint(); 466 467 canvas.saveLayer(0, 0, width, height, p, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 468 469 //change matrix and clip to something different 470 canvas.clipRect(0, 0, width >> 1, height >> 1, Op.INTERSECT); 471 Matrix scaledMatrix = new Matrix(); 472 scaledMatrix.setScale(4, 5); 473 canvas.setMatrix(scaledMatrix); 474 assertEquals(scaledMatrix, canvas.getMatrix()); 475 476 canvas.drawColor(Color.BLUE); 477 canvas.restore(); 478 479 //check if identity matrix is restored 480 assertEquals(identity, canvas.getMatrix()); 481 482 //should draw to the entire canvas, because clip has been removed 483 canvas.drawColor(Color.RED); 484 }) 485 .runWithVerifier(new ColorVerifier(Color.RED)); 486 } 487} 488