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