1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.uirendering.cts.testclasses;
17 
18 import static org.junit.Assert.assertFalse;
19 
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.BitmapShader;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.ComposeShader;
27 import android.graphics.Matrix44;
28 import android.graphics.Paint;
29 import android.graphics.Path;
30 import android.graphics.Picture;
31 import android.graphics.Point;
32 import android.graphics.PorterDuff;
33 import android.graphics.RadialGradient;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.graphics.Region;
37 import android.graphics.Shader;
38 import android.platform.test.annotations.RequiresFlagsEnabled;
39 import android.platform.test.flag.junit.CheckFlagsRule;
40 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
41 import android.uirendering.cts.R;
42 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
43 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
44 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
45 
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.filters.MediumTest;
48 
49 import com.android.graphics.hwui.flags.Flags;
50 
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import junitparams.JUnitParamsRunner;
56 import junitparams.Parameters;
57 
58 @MediumTest
59 @RunWith(JUnitParamsRunner.class)
60 public class CanvasTests extends ActivityTestBase {
61 
62     private static final int PAINT_COLOR = 0xff00ff00;
63     private static final int BITMAP_WIDTH = 10;
64     private static final int BITMAP_HEIGHT = 28;
65 
66     @Rule
67     public final CheckFlagsRule mCheckFlagsRule =
68             DeviceFlagsValueProvider.createCheckFlagsRule();
69 
getPaint()70     private Paint getPaint() {
71         Paint paint = new Paint();
72         paint.setColor(PAINT_COLOR);
73         return paint;
74     }
75 
getImmutableBitmap()76     public Bitmap getImmutableBitmap() {
77         final Resources res = InstrumentationRegistry.getTargetContext().getResources();
78         BitmapFactory.Options opt = new BitmapFactory.Options();
79         opt.inScaled = false; // bitmap will only be immutable if not scaled during load
80         Bitmap immutableBitmap = BitmapFactory.decodeResource(res, R.drawable.sunset1, opt);
81         assertFalse(immutableBitmap.isMutable());
82         return immutableBitmap;
83     }
84 
getMutableBitmap(Bitmap.Config config)85     public Bitmap getMutableBitmap(Bitmap.Config config) {
86         return Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, config);
87     }
88 
getMutableBitmap()89     public Bitmap getMutableBitmap() {
90         return getMutableBitmap(Bitmap.Config.ARGB_8888);
91     }
92 
93     @Test
testDrawDoubleRoundRect()94     public void testDrawDoubleRoundRect() {
95         Point[] testPoints = {
96                 new Point(0, 0),
97                 new Point(89, 0),
98                 new Point(89, 89),
99                 new Point(0, 89),
100                 new Point(10, 10),
101                 new Point(79, 10),
102                 new Point(79, 79),
103                 new Point(10, 79)
104         };
105         int[] colors = {
106                 Color.WHITE,
107                 Color.WHITE,
108                 Color.WHITE,
109                 Color.WHITE,
110                 Color.RED,
111                 Color.RED,
112                 Color.RED,
113                 Color.RED
114         };
115         createTest()
116                 .addCanvasClient((canvas, width, height) -> {
117                     Paint paint = new Paint();
118                     paint.setColor(Color.WHITE);
119                     canvas.drawRect(0, 0, width, height, paint);
120 
121                     paint.setColor(Color.RED);
122                     int inset = 20;
123                     RectF outer = new RectF(0, 0, width, height);
124                     RectF inner = new RectF(inset,
125                             inset,
126                             width - inset,
127                             height - inset);
128                     canvas.drawDoubleRoundRect(outer, 5, 5,
129                             inner, 10, 10, paint);
130                 })
131                 .runWithVerifier(new SamplePointVerifier(testPoints, colors));
132     }
133 
134     @Test
testDrawDoubleRoundRectWithRadii()135     public void testDrawDoubleRoundRectWithRadii() {
136         Point[] testPoints = {
137                 new Point(0, 0),
138                 new Point(89, 0),
139                 new Point(89, 89),
140                 new Point(0, 89),
141                 new Point(9, 7),
142                 new Point(81, 7),
143                 new Point(75, 75),
144                 new Point(21, 67)
145         };
146         int[] colors = {
147                 Color.RED,
148                 Color.WHITE,
149                 Color.WHITE,
150                 Color.WHITE,
151                 Color.RED,
152                 Color.RED,
153                 Color.RED,
154                 Color.RED
155         };
156         createTest()
157                 .addCanvasClient((canvas, width, height) -> {
158                     Paint paint = new Paint();
159                     paint.setColor(Color.WHITE);
160                     canvas.drawRect(0, 0, width, height, paint);
161 
162                     paint.setColor(Color.RED);
163                     int inset = 30;
164                     RectF outer = new RectF(0, 0, width, height);
165                     RectF inner = new RectF(inset,
166                             inset,
167                             width - inset,
168                             height - inset);
169 
170                     float[] outerRadii = {
171                             0.0f, 0.0f, // top left
172                             5.0f, 5.0f, // top right
173                             10.0f, 10.0f, // bottom right
174                             20.0f, 20.0f // bottom left
175                     };
176 
177                     float[] innerRadii = {
178                             20.0f, 20.0f,
179                             15.0f, 15.0f,
180                             8.0f, 8.0f,
181                             3.0f, 3.0f
182                     };
183                     canvas.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint);
184                 })
185                 .runWithVerifier(new SamplePointVerifier(testPoints, colors));
186     }
187 
188     @RequiresFlagsEnabled(Flags.FLAG_MATRIX_44)
189     @Test
testDrawWithConcatenatedMatrix44()190     public void testDrawWithConcatenatedMatrix44() {
191         Point[] testPoints = {
192                 new Point(0, 0),
193                 new Point(45, 45),
194                 new Point(45, 30),
195                 new Point(45, 60),
196                 new Point(20, 45),
197                 new Point(60, 30),
198                 new Point(60, 60)
199 
200         };
201         int[] colors = {
202                 Color.WHITE,
203                 Color.RED,
204                 Color.RED,
205                 Color.RED,
206                 Color.RED,
207                 Color.WHITE,
208                 Color.WHITE
209         };
210         createTest()
211                 .addCanvasClient((canvas, width, height) -> {
212                     canvas.drawColor(Color.WHITE);
213 
214                     Paint paint = new Paint();
215                     paint.setColor(Color.RED);
216                     paint.setStyle(Paint.Style.FILL);
217 
218                     Matrix44 mat = new Matrix44();
219                     // column vector operations are read backwards, so the rect should be centered
220                     mat.translate((float) width / 2, (float) height / 2, 0)
221                             .rotate(45, 1, 0, 0)
222                             .scale(2, 2, 0)
223                             .rotate(45, 0, 0, 1);
224                     canvas.concat(mat);
225                     canvas.drawRect(-10, -10, 10, 10, paint);
226                 })
227                 .runWithVerifier(new SamplePointVerifier(testPoints, colors));
228     }
229 
drawRotatedBitmap(boolean aa, Canvas canvas, Bitmap.Config config)230     private void drawRotatedBitmap(boolean aa, Canvas canvas, Bitmap.Config config) {
231         // create a black bitmap to be drawn to the canvas
232         Bitmap bm = getMutableBitmap();
233         bm.eraseColor(Color.BLACK);
234 
235         // canvas density and bitmap density must match in order for no scaling to occur
236         // and aa to be distinguishable from non-aa
237         bm.setDensity(canvas.getDensity());
238 
239         canvas.drawColor(Color.WHITE);
240 
241         Paint aaPaint = new Paint();
242         aaPaint.setAntiAlias(aa);
243 
244         canvas.rotate(-1.0f, 0, 0);
245         canvas.drawBitmap(bm, 0, 0, aaPaint);
246     }
247 
testConfigs()248     private Object[] testConfigs() {
249         return new Object[] {
250             Bitmap.Config.ARGB_8888,
251             Bitmap.Config.RGBA_1010102
252         };
253     }
254 
255     @Test
256     @Parameters(method = "testConfigs")
testDrawRotatedBitmapWithAA(Bitmap.Config config)257     public void testDrawRotatedBitmapWithAA(Bitmap.Config config) {
258         createTest()
259                 .addCanvasClient((canvas, width, height) -> {
260                     canvas.setDensity(400);
261                     drawRotatedBitmap(true, canvas, config);
262                 })
263                 // Test asserts there are more than 10 grey pixels.
264                 .runWithVerifier(AntiAliasPixelCounter.aaVerifier(Color.WHITE, Color.BLACK, 10));
265     }
266 
267     @Test
268     @Parameters(method = "testConfigs")
testDrawRotatedBitmapWithoutAA(Bitmap.Config config)269     public void testDrawRotatedBitmapWithoutAA(Bitmap.Config config) {
270         createTest()
271                 .addCanvasClient((canvas, width, height) -> {
272                     canvas.setDensity(400);
273                     drawRotatedBitmap(false, canvas, config);
274                 })
275                 // Test asserts there are no grey pixels.
276                 .runWithVerifier(AntiAliasPixelCounter.noAAVerifier(Color.WHITE, Color.BLACK));
277     }
278 
279     @Test(expected = IllegalArgumentException.class)
testDrawHwBitmap_inSwCanvas()280     public void testDrawHwBitmap_inSwCanvas() {
281         Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
282         // we verify this specific call should IAE
283         new Canvas(getMutableBitmap()).drawBitmap(hwBitmap, 0, 0, null);
284     }
285 
286     @Test(expected = IllegalArgumentException.class)
testDrawHwBitmap_inPictureCanvas_inSwCanvas()287     public void testDrawHwBitmap_inPictureCanvas_inSwCanvas() {
288         Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
289         Picture picture = new Picture();
290         Canvas pictureCanvas = picture.beginRecording(100, 100);
291         pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
292         // we verify this specific call should IAE
293         new Canvas(getMutableBitmap()).drawPicture(picture);
294     }
295 
296     @Test(expected = IllegalArgumentException.class)
testDrawHwBitmap_inPictureCanvas_inPictureCanvas_inSwCanvas()297     public void testDrawHwBitmap_inPictureCanvas_inPictureCanvas_inSwCanvas() {
298         Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
299         Picture innerPicture = new Picture();
300         Canvas pictureCanvas = innerPicture.beginRecording(100, 100);
301         pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
302 
303         Picture outerPicture = new Picture();
304         Canvas outerPictureCanvas = outerPicture.beginRecording(100, 100);
305         outerPictureCanvas.drawPicture(innerPicture);
306         // we verify this specific call should IAE
307         new Canvas(getMutableBitmap()).drawPicture(outerPicture);
308     }
309 
310     @Test(expected = IllegalArgumentException.class)
testHwBitmapShaderInSwCanvas1()311     public void testHwBitmapShaderInSwCanvas1() {
312         Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
313         BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
314                 Shader.TileMode.REPEAT);
315         RadialGradient gradientShader = new RadialGradient(10, 10, 30,
316                 Color.BLACK, Color.CYAN, Shader.TileMode.REPEAT);
317         Shader shader = new ComposeShader(gradientShader, bitmapShader, PorterDuff.Mode.OVERLAY);
318         Paint p = new Paint();
319         p.setShader(shader);
320         new Canvas(getMutableBitmap()).drawRect(0, 0, 10, 10, p);
321     }
322 
323     @Test(expected = IllegalArgumentException.class)
testHwBitmapShaderInSwCanvas2()324     public void testHwBitmapShaderInSwCanvas2() {
325         Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
326         BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
327                 Shader.TileMode.REPEAT);
328         RadialGradient gradientShader = new RadialGradient(10, 10, 30,
329                 Color.BLACK, Color.CYAN, Shader.TileMode.REPEAT);
330         Shader shader = new ComposeShader(bitmapShader, gradientShader, PorterDuff.Mode.OVERLAY);
331         Paint p = new Paint();
332         p.setShader(shader);
333         new Canvas(getMutableBitmap()).drawRect(0, 0, 10, 10, p);
334     }
335 
336     @Test(expected = IllegalStateException.class)
testCanvasFromImmutableBitmap()337     public void testCanvasFromImmutableBitmap() {
338         // Should throw out IllegalStateException when creating Canvas with an ImmutableBitmap
339         new Canvas(getImmutableBitmap());
340     }
341 
342     @Test(expected = RuntimeException.class)
testCanvasFromRecycledBitmap()343     public void testCanvasFromRecycledBitmap() {
344         // Should throw out RuntimeException when creating Canvas with a MutableBitmap which
345         // is recycled
346         Bitmap bitmap = getMutableBitmap();
347         bitmap.recycle();
348         new Canvas(bitmap);
349     }
350 
351     @Test(expected = IllegalStateException.class)
testSetBitmapToImmutableBitmap()352     public void testSetBitmapToImmutableBitmap() {
353         // Should throw out IllegalStateException when setting an ImmutableBitmap to a Canvas
354         new Canvas(getMutableBitmap()).setBitmap(getImmutableBitmap());
355     }
356 
357     @Test(expected = RuntimeException.class)
testSetBitmapToRecycledBitmap()358     public void testSetBitmapToRecycledBitmap() {
359         // Should throw out RuntimeException when setting Bitmap which is recycled to a Canvas
360         Bitmap bitmap = getMutableBitmap();
361         bitmap.recycle();
362         new Canvas(getMutableBitmap()).setBitmap(bitmap);
363     }
364 
365     @Test(expected = IllegalStateException.class)
testRestoreWithoutSave()366     public void testRestoreWithoutSave() {
367         // Should throw out IllegalStateException because cannot restore Canvas before save
368         new Canvas(getMutableBitmap()).restore();
369     }
370 
371     @Test(expected = IllegalArgumentException.class)
testRestoreToCountIllegalSaveCount()372     public void testRestoreToCountIllegalSaveCount() {
373         // Should throw out IllegalArgumentException because saveCount is less than 1
374         new Canvas(getMutableBitmap()).restoreToCount(0);
375     }
376 
377     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawPointsInvalidOffset()378     public void testDrawPointsInvalidOffset() {
379         // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
380         new Canvas(getMutableBitmap()).drawPoints(new float[]{
381                 10.0f, 29.0f
382         }, -1, 2, getPaint());
383     }
384 
385     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawPointsInvalidCount()386     public void testDrawPointsInvalidCount() {
387         // Should throw out ArrayIndexOutOfBoundsException because of invalid count
388         new Canvas(getMutableBitmap()).drawPoints(new float[]{
389                 10.0f, 29.0f
390         }, 0, 31, getPaint());
391     }
392 
393     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawLinesInvalidOffset()394     public void testDrawLinesInvalidOffset() {
395         // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
396         new Canvas(getMutableBitmap()).drawLines(new float[]{
397                 0, 0, 10, 31
398         }, 2, 4, new Paint());
399     }
400 
401     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawLinesInvalidCount()402     public void testDrawLinesInvalidCount() {
403         // Should throw out ArrayIndexOutOfBoundsException because of invalid count
404         new Canvas(getMutableBitmap()).drawLines(new float[]{
405                 0, 0, 10, 31
406         }, 0, 8, new Paint());
407     }
408 
409     @Test(expected = NullPointerException.class)
testDrawOvalNull()410     public void testDrawOvalNull() {
411         // Should throw out NullPointerException because oval is null
412         new Canvas(getMutableBitmap()).drawOval(null, getPaint());
413     }
414 
415     @Test(expected = NullPointerException.class)
testDrawArcNullOval()416     public void testDrawArcNullOval() {
417         // Should throw NullPointerException because oval is null
418         new Canvas(getMutableBitmap()).drawArc(null, 10.0f, 29.0f,
419                 true, getPaint());
420     }
421 
422     @Test(expected = NullPointerException.class)
testDrawRoundRectNull()423     public void testDrawRoundRectNull() {
424         // Should throw out NullPointerException because RoundRect is null
425         new Canvas(getMutableBitmap()).drawRoundRect(null, 10.0f, 29.0f, getPaint());
426     }
427 
428     @Test(expected = RuntimeException.class)
testDrawBitmapAtPointRecycled()429     public void testDrawBitmapAtPointRecycled() {
430         Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
431         b.recycle();
432 
433         // Should throw out RuntimeException because bitmap has been recycled
434         new Canvas(getMutableBitmap()).drawBitmap(b, 10.0f, 29.0f, getPaint());
435     }
436 
437     @Test(expected = RuntimeException.class)
testDrawBitmapSrcDstFloatRecycled()438     public void testDrawBitmapSrcDstFloatRecycled() {
439         Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
440         b.recycle();
441 
442         // Should throw out RuntimeException because bitmap has been recycled
443         new Canvas(getMutableBitmap()).drawBitmap(b, null, new RectF(), getPaint());
444     }
445 
446     @Test(expected = RuntimeException.class)
testDrawBitmapSrcDstIntRecycled()447     public void testDrawBitmapSrcDstIntRecycled() {
448         Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
449         b.recycle();
450 
451         // Should throw out RuntimeException because bitmap has been recycled
452         new Canvas(getMutableBitmap()).drawBitmap(b, null, new Rect(), getPaint());
453     }
454 
455     @Test(expected = IllegalArgumentException.class)
testDrawBitmapIntsNegativeWidth()456     public void testDrawBitmapIntsNegativeWidth() {
457         // Should throw out IllegalArgumentException because width is less than 0
458         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10,
459                 10, -1, 10, true, null);
460     }
461 
462     @Test(expected = IllegalArgumentException.class)
testDrawBitmapIntsNegativeHeight()463     public void testDrawBitmapIntsNegativeHeight() {
464         // Should throw out IllegalArgumentException because height is less than 0
465         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10,
466                 10, 10, -1, true, null);
467     }
468 
469     @Test(expected = IllegalArgumentException.class)
testDrawBitmapIntsBadStride()470     public void testDrawBitmapIntsBadStride() {
471         // Should throw out IllegalArgumentException because stride less than width and
472         // bigger than -width
473         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 5, 10,
474                 10, 10, 10, true, null);
475     }
476 
477     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapIntsNegativeOffset()478     public void testDrawBitmapIntsNegativeOffset() {
479         // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
480         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], -1, 10, 10,
481                 10, 10, 10, true, null);
482     }
483 
484     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapIntsBadOffset()485     public void testDrawBitmapIntsBadOffset() {
486         // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
487         // is bigger than colors' length
488         new Canvas(getMutableBitmap()).drawBitmap(new int[29], 10, 29, 10,
489                 10, 20, 10, true, null);
490     }
491 
492     @Test(expected = IllegalArgumentException.class)
testDrawBitmapFloatsNegativeWidth()493     public void testDrawBitmapFloatsNegativeWidth() {
494         // Should throw out IllegalArgumentException because width is less than 0
495         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10.0f,
496                 10.0f, -1, 10, true, null);
497     }
498 
499     @Test(expected = IllegalArgumentException.class)
testDrawBitmapFloatsNegativeHeight()500     public void testDrawBitmapFloatsNegativeHeight() {
501         // Should throw out IllegalArgumentException because height is less than 0
502         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10.0f,
503                 10.0f, 10, -1, true, null);
504     }
505 
506     @Test(expected = IllegalArgumentException.class)
testDrawBitmapFloatsBadStride()507     public void testDrawBitmapFloatsBadStride() {
508         // Should throw out IllegalArgumentException because stride less than width and
509         // bigger than -width
510         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 5, 10.0f,
511                 10.0f, 10, 10, true, null);
512     }
513 
514     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapFloatsNegativeOffset()515     public void testDrawBitmapFloatsNegativeOffset() {
516         // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
517         new Canvas(getMutableBitmap()).drawBitmap(new int[2008], -1, 10, 10.0f,
518                 10.0f, 10, 10, true, null);
519     }
520 
521     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapFloatsBadOffset()522     public void testDrawBitmapFloatsBadOffset() {
523         // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
524         // is bigger than colors' length
525         new Canvas(getMutableBitmap()).drawBitmap(new int[29], 10, 29, 10.0f,
526                 10.0f, 20, 10, true, null);
527     }
528 
529     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshNegativeWidth()530     public void testDrawBitmapMeshNegativeWidth() {
531         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
532 
533         // Should throw out ArrayIndexOutOfBoundsException because meshWidth less than 0
534         new Canvas(getMutableBitmap()).drawBitmapMesh(b, -1, 10,
535                 null, 0, null, 0, null);
536     }
537 
538     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshNegativeHeight()539     public void testDrawBitmapMeshNegativeHeight() {
540         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
541 
542         // Should throw out ArrayIndexOutOfBoundsException because meshHeight is less than 0
543         new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, -1,
544                 null, 0, null, 0, null);
545     }
546 
547     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshNegativeVertOffset()548     public void testDrawBitmapMeshNegativeVertOffset() {
549         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
550 
551         // Should throw out ArrayIndexOutOfBoundsException because vertOffset is less than 0
552         new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
553                 null, -1, null, 0, null);
554     }
555 
556     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshNegativeColorOffset()557     public void testDrawBitmapMeshNegativeColorOffset() {
558         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
559 
560         // Should throw out ArrayIndexOutOfBoundsException because colorOffset is less than 0
561         new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
562                 null, 10, null, -1, null);
563     }
564 
565     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshTooFewVerts()566     public void testDrawBitmapMeshTooFewVerts() {
567         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
568 
569         // Should throw out ArrayIndexOutOfBoundsException because verts' length is too short
570         new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
571                 new float[] { 10.0f, 29.0f}, 10, null, 10, null);
572     }
573 
574     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawBitmapMeshTooFewColors()575     public void testDrawBitmapMeshTooFewColors() {
576         final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
577 
578         // Should throw out ArrayIndexOutOfBoundsException because colors' length is too short
579         // abnormal case: colors' length is too short
580         final float[] verts = new float[2008];
581         new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10, verts,
582                 10, new int[] { 10, 29}, 10, null);
583     }
584 
585     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawVerticesTooFewVerts()586     public void testDrawVerticesTooFewVerts() {
587         final float[] verts = new float[10];
588         final float[] texs = new float[10];
589         final int[] colors = new int[10];
590         final short[] indices = {
591                 0, 1, 2, 3, 4, 1
592         };
593 
594         // Should throw out ArrayIndexOutOfBoundsException because sum of vertOffset and
595         // vertexCount is bigger than verts' length
596         new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
597                 verts, 8, texs, 0, colors, 0, indices,
598                 0, 4, getPaint());
599     }
600 
601     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawVerticesTooFewTexs()602     public void testDrawVerticesTooFewTexs() {
603         final float[] verts = new float[10];
604         final float[] texs = new float[10];
605         final int[] colors = new int[10];
606         final short[] indices = {
607                 0, 1, 2, 3, 4, 1
608         };
609 
610         // Should throw out ArrayIndexOutOfBoundsException because sum of texOffset and
611         // vertexCount is bigger thatn texs' length
612         new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
613                 verts, 0, texs, 30, colors, 0, indices,
614                 0, 4, getPaint());
615     }
616 
617     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawVerticesTooFewColors()618     public void testDrawVerticesTooFewColors() {
619         final float[] verts = new float[10];
620         final float[] texs = new float[10];
621         final int[] colors = new int[10];
622         final short[] indices = {
623                 0, 1, 2, 3, 4, 1
624         };
625 
626         // Should throw out ArrayIndexOutOfBoundsException because sum of colorOffset and
627         // vertexCount is bigger than colors' length
628         new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
629                 verts, 0, texs, 0, colors, 30, indices,
630                 0, 4, getPaint());
631     }
632 
633     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawVerticesTooFewIndices()634     public void testDrawVerticesTooFewIndices() {
635         final float[] verts = new float[10];
636         final float[] texs = new float[10];
637         final int[] colors = new int[10];
638         final short[] indices = {
639                 0, 1, 2, 3, 4, 1
640         };
641 
642         // Should throw out ArrayIndexOutOfBoundsException because sum of indexOffset and
643         // indexCount is bigger than indices' length
644         new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
645                 verts, 0, texs, 0, colors, 0, indices,
646                 10, 30, getPaint());
647     }
648 
649     @Test(expected = IndexOutOfBoundsException.class)
testDrawArrayTextNegativeIndex()650     public void testDrawArrayTextNegativeIndex() {
651         final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
652 
653         // Should throw out IndexOutOfBoundsException because index is less than 0
654         new Canvas(getMutableBitmap()).drawText(text, -1, 7, 10, 10, getPaint());
655     }
656 
657     @Test(expected = IndexOutOfBoundsException.class)
testDrawArrayTextNegativeCount()658     public void testDrawArrayTextNegativeCount() {
659         final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
660 
661         // Should throw out IndexOutOfBoundsException because count is less than 0
662         new Canvas(getMutableBitmap()).drawText(text, 0, -1, 10, 10, getPaint());
663     }
664 
665     @Test(expected = IndexOutOfBoundsException.class)
testDrawArrayTextTextLengthTooSmall()666     public void testDrawArrayTextTextLengthTooSmall() {
667         final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
668 
669         // Should throw out IndexOutOfBoundsException because sum of index and count
670         // is bigger than text's length
671         new Canvas(getMutableBitmap()).drawText(text, 0, 10, 10, 10, getPaint());
672     }
673 
674     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextTextAtPositionWithOffsetsNegativeStart()675     public void testDrawTextTextAtPositionWithOffsetsNegativeStart() {
676         // Should throw out IndexOutOfBoundsException because start is less than 0
677         new Canvas(getMutableBitmap()).drawText("android", -1, 7, 10, 30,
678                 getPaint());
679     }
680 
681     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextTextAtPositionWithOffsetsNegativeEnd()682     public void testDrawTextTextAtPositionWithOffsetsNegativeEnd() {
683         // Should throw out IndexOutOfBoundsException because end is less than 0
684         new Canvas(getMutableBitmap()).drawText("android", 0, -1, 10, 30,
685                 getPaint());
686     }
687 
688     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextTextAtPositionWithOffsetsStartEndMismatch()689     public void testDrawTextTextAtPositionWithOffsetsStartEndMismatch() {
690         // Should throw out IndexOutOfBoundsException because start is bigger than end
691         new Canvas(getMutableBitmap()).drawText("android", 3, 1, 10, 30,
692                 getPaint());
693     }
694 
695     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextTextAtPositionWithOffsetsTextTooLong()696     public void testDrawTextTextAtPositionWithOffsetsTextTooLong() {
697         // Should throw out IndexOutOfBoundsException because end subtracts start should
698         // bigger than text's length
699         new Canvas(getMutableBitmap()).drawText("android", 0, 10, 10, 30,
700                 getPaint());
701     }
702 
703     @Test(expected = NullPointerException.class)
testDrawTextRunNullCharArray()704     public void testDrawTextRunNullCharArray() {
705         // Should throw out NullPointerException because text is null
706         new Canvas(getMutableBitmap()).drawTextRun((char[]) null, 0, 0,
707                 0, 0, 0.0f, 0.0f, false, new Paint());
708     }
709 
710     @Test(expected = NullPointerException.class)
testDrawTextRunNullCharSequence()711     public void testDrawTextRunNullCharSequence() {
712         // Should throw out NullPointerException because text is null
713         new Canvas(getMutableBitmap()).drawTextRun((CharSequence) null, 0, 0,
714                 0, 0, 0.0f, 0.0f, false, new Paint());
715     }
716 
717     @Test(expected = NullPointerException.class)
testDrawTextRunCharArrayNullPaint()718     public void testDrawTextRunCharArrayNullPaint() {
719         // Should throw out NullPointerException because paint is null
720         new Canvas(getMutableBitmap()).drawTextRun("android".toCharArray(), 0, 0,
721                 0, 0, 0.0f, 0.0f, false, null);
722     }
723 
724     @Test(expected = NullPointerException.class)
testDrawTextRunCharSequenceNullPaint()725     public void testDrawTextRunCharSequenceNullPaint() {
726         // Should throw out NullPointerException because paint is null
727         new Canvas(getMutableBitmap()).drawTextRun("android", 0, 0, 0,
728                 0, 0.0f, 0.0f, false, null);
729     }
730 
731     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunNegativeIndex()732     public void testDrawTextRunNegativeIndex() {
733         final String text = "android";
734         final Paint paint = new Paint();
735 
736         // Should throw out IndexOutOfBoundsException because index is less than 0
737         new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), -1, text.length(),
738                 0, text.length(), 0.0f, 0.0f,
739                 false, new Paint());
740     }
741 
742     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunNegativeCount()743     public void testDrawTextRunNegativeCount() {
744         final String text = "android";
745 
746         // Should throw out IndexOutOfBoundsException because count is less than 0
747         new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0, -1,
748                 0, text.length(), 0.0f, 0.0f, false,
749                 new Paint());
750     }
751 
752     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunContestIndexTooLarge()753     public void testDrawTextRunContestIndexTooLarge() {
754         final String text = "android";
755 
756         // Should throw out IndexOutOfBoundsException because contextIndex is bigger than index
757         new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0, text.length(),
758                 1, text.length(), 0.0f, 0.0f,
759                 false, new Paint());
760     }
761 
762     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunContestIndexTooSmall()763     public void testDrawTextRunContestIndexTooSmall() {
764         final String text = "android";
765 
766         // Should throw out IndexOutOfBoundsException because contextIndex + contextCount
767         // is less than index + count
768         new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
769                 text.length() - 1, 0.0f, 0.0f, false,
770                 new Paint());
771     }
772 
773     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunIndexTooLarge()774     public void testDrawTextRunIndexTooLarge() {
775         final String text = "android";
776         final Paint paint = new Paint();
777 
778         // Should throw out IndexOutOfBoundsException because index + count is bigger than
779         // text length
780         new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0,
781                 text.length() + 1, 0, text.length() + 1,
782                 0.0f, 0.0f, false, new Paint());
783     }
784 
785     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunNegativeContextStart()786     public void testDrawTextRunNegativeContextStart() {
787         final String text = "android";
788         final Paint paint = new Paint();
789 
790         // Should throw out IndexOutOfBoundsException because contextStart is less than 0
791         new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), -1,
792                 text.length(), 0.0f, 0.0f, false,
793                 new Paint());
794     }
795 
796     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunStartLessThanContextStart()797     public void testDrawTextRunStartLessThanContextStart() {
798         final String text = "android";
799 
800         // Should throw out IndexOutOfBoundsException because start is less than contextStart
801         new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 1,
802                 text.length(), 0.0f, 0.0f, false,
803                 new Paint());
804     }
805 
806     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunEndLessThanStart()807     public void testDrawTextRunEndLessThanStart() {
808         final String text = "android";
809 
810         // Should throw out IndexOutOfBoundsException because end is less than start
811         new Canvas(getMutableBitmap()).drawTextRun(text, 1, 0, 0,
812                 text.length(), 0.0f, 0.0f, false, new Paint());
813     }
814 
815     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunContextEndLessThanEnd()816     public void testDrawTextRunContextEndLessThanEnd() {
817         final String text = "android";
818 
819         // Should throw out IndexOutOfBoundsException because contextEnd is less than end
820         new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
821                 text.length() - 1, 0.0f, 0.0f, false,
822                 new Paint());
823     }
824 
825     @Test(expected = IndexOutOfBoundsException.class)
testDrawTextRunContextEndLargerThanTextLength()826     public void testDrawTextRunContextEndLargerThanTextLength() {
827         final String text = "android";
828 
829         // Should throw out IndexOutOfBoundsException because contextEnd is bigger than
830         // text length
831         new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
832                 text.length() + 1, 0.0f, 0.0f, false,
833                 new Paint());
834     }
835 
836     @Test(expected = IndexOutOfBoundsException.class)
testDrawPosTextWithIndexAndCountNegativeIndex()837     public void testDrawPosTextWithIndexAndCountNegativeIndex() {
838         final char[] text = {
839                 'a', 'n', 'd', 'r', 'o', 'i', 'd'
840         };
841         final float[] pos = new float[]{
842                 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
843                 7.0f, 7.0f
844         };
845 
846         // Should throw out IndexOutOfBoundsException because index is less than 0
847         new Canvas(getMutableBitmap()).drawPosText(text, -1, 7, pos, getPaint());
848     }
849 
850     @Test(expected = IndexOutOfBoundsException.class)
testDrawPosTextWithIndexAndCountTextTooShort()851     public void testDrawPosTextWithIndexAndCountTextTooShort() {
852         final char[] text = {
853                 'a', 'n', 'd', 'r', 'o', 'i', 'd'
854         };
855         final float[] pos = new float[]{
856                 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
857                 7.0f, 7.0f
858         };
859 
860         // Should throw out IndexOutOfBoundsException because sum of index and count is
861         // bigger than text's length
862         new Canvas(getMutableBitmap()).drawPosText(text, 1, 10, pos, getPaint());
863     }
864 
865     @Test(expected = IndexOutOfBoundsException.class)
testDrawPosTextWithIndexAndCountCountTooLarge()866     public void testDrawPosTextWithIndexAndCountCountTooLarge() {
867         final char[] text = {
868                 'a', 'n', 'd', 'r', 'o', 'i', 'd'
869         };
870 
871         // Should throw out IndexOutOfBoundsException because 2 times of count is
872         // bigger than pos' length
873         new Canvas(getMutableBitmap()).drawPosText(text, 1, 10, new float[] {
874                 10.0f, 30.f
875         }, getPaint());
876     }
877 
878     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawTextOnPathWithIndexAndCountNegativeIndex()879     public void testDrawTextOnPathWithIndexAndCountNegativeIndex() {
880         final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
881 
882         // Should throw out ArrayIndexOutOfBoundsException because index is smaller than 0
883         new Canvas(getMutableBitmap()).drawTextOnPath(text, -1, 7, new Path(),
884                 10.0f, 10.0f, getPaint());
885     }
886 
887     @Test(expected = ArrayIndexOutOfBoundsException.class)
testDrawTextOnPathWithIndexAndCountTextTooShort()888     public void testDrawTextOnPathWithIndexAndCountTextTooShort() {
889         final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
890 
891         // Should throw out ArrayIndexOutOfBoundsException because sum of index and
892         // count is bigger than text's length
893         new Canvas(getMutableBitmap()).drawTextOnPath(text, 0, 10, new Path(),
894                 10.0f, 10.0f, getPaint());
895     }
896 
897     @Test(expected = IndexOutOfBoundsException.class)
testDrawPosTextCountTooLarge()898     public void testDrawPosTextCountTooLarge() {
899         final String text = "android";
900 
901         // Should throw out IndexOutOfBoundsException because 2 times of count is
902         // bigger than pos' length
903         new Canvas(getMutableBitmap()).drawPosText(text, new float[]{
904                 10.0f, 30.f
905         }, getPaint());
906     }
907 
908     @Test
testClipIntersectAndDifference()909     public void testClipIntersectAndDifference() {
910         Point[] testPoints = {
911             new Point(1, 1),
912             new Point(10, 10),
913             new Point(25, 25)
914         };
915         int[] colors = {
916             Color.WHITE,
917             Color.RED,
918             Color.GREEN
919         };
920         createTest()
921                 .addCanvasClient((canvas, width, height) -> {
922                     // Base is white
923                     Paint paint = new Paint();
924                     paint.setColor(Color.WHITE);
925                     canvas.drawRect(0, 0, width, height, paint);
926 
927                     // Fill inset with green, which will later be overwitten with
928                     // red except for a subsequent difference clip op
929                     canvas.clipRect(5, 5, width - 5, height - 5);
930                     paint.setColor(Color.GREEN);
931                     canvas.drawRect(0, 0, width, height, paint);
932 
933                     // Cut out the inner region, so that it remains green
934                     canvas.clipOutRect(20, 20, width - 20, height - 20);
935                     paint.setColor(Color.RED);
936                     canvas.drawRect(0, 0, width, height, paint);
937                 })
938                 .runWithVerifier(new SamplePointVerifier(testPoints, colors));
939     }
940 
941     // Expanding region ops (replace, reverse diff, union, and xor) are not allowed for clipping
942     @Test(expected = IllegalArgumentException.class)
testClipReplace()943     public void testClipReplace() {
944         new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REPLACE);
945     }
946 
947     @Test(expected = IllegalArgumentException.class)
testClipReverseDifference()948     public void testClipReverseDifference() {
949         new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REVERSE_DIFFERENCE);
950     }
951 
952     @Test(expected = IllegalArgumentException.class)
testClipUnion()953     public void testClipUnion() {
954         new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.UNION);
955     }
956 
957     @Test(expected = IllegalArgumentException.class)
testClipXor()958     public void testClipXor() {
959         new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.XOR);
960     }
961 
962     @Test
testAntiAliasClipping()963     public void testAntiAliasClipping() {
964         createTest()
965                 .addCanvasClient((canvas, width, height) -> {
966                     Paint paint = new Paint();
967                     Path clipPath = new Path();
968 
969                     clipPath.addCircle(
970                             width / 2.0f, height / 2.0f,
971                             Math.min(width, height) / 2.0f,
972                             Path.Direction.CW
973                     );
974 
975                     paint.setColor(Color.WHITE);
976                     canvas.drawRect(0, 0, width, height, paint);
977 
978                     paint.setColor(Color.RED);
979 
980                     canvas.save();
981 
982                     canvas.clipPath(clipPath);
983                     canvas.drawRect(0, 0, width, height, paint);
984 
985                     canvas.restore();
986 
987                 })
988                 .runWithVerifier(AntiAliasPixelCounter.aaVerifier(Color.WHITE, Color.RED, 10));
989     }
990 
991     private static class AntiAliasPixelCounter extends BitmapVerifier {
992 
993         private final int mColor1;
994         private final int mColor2;
995         private final int mCountThreshold;
996         // when true mCountThreshold is treated as a maximum
997         // when false mCountThreshold is treated as a minimum
998         private final boolean mThresholdIsAMaxium;
999 
1000         // factory method for a verifier that confirms some non-target-color pixels are present
1001         // this is only a verification that aa has occurred if a single solid color shape has been
1002         // drawn on a solid background with at least one one visible edge, and every other possible
1003         // thing that can change the colors has been disabled.
aaVerifier(int color1, int color2, int countThreshold)1004         public static AntiAliasPixelCounter aaVerifier(int color1, int color2, int countThreshold) {
1005             return new AntiAliasPixelCounter(color1, color2, countThreshold, false);
1006         }
1007         // factory method for a verifier that confirms only target color pixels are present.
noAAVerifier(int color1, int color2)1008         public static AntiAliasPixelCounter noAAVerifier(int color1, int color2) {
1009             return new AntiAliasPixelCounter(color1, color2, 0, true);
1010         }
1011 
AntiAliasPixelCounter(int color1, int color2, int countThreshold, boolean thresholdIsMax)1012         AntiAliasPixelCounter(int color1, int color2, int countThreshold, boolean thresholdIsMax) {
1013             mColor1 = color1;
1014             mColor2 = color2;
1015             mCountThreshold = countThreshold;
1016             mThresholdIsAMaxium = thresholdIsMax;
1017         }
1018 
1019         @Override
verify(int[] bitmap, int offset, int stride, int width, int height)1020         public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
1021             int nonTargetColorCount = 0;
1022             for (int x = 0; x < width; x++) {
1023                 for (int y = 0; y < height; y++) {
1024                     int pixelColor = bitmap[indexFromXAndY(x, y, stride, offset)];
1025                     if (pixelColor != mColor1 && pixelColor != mColor2) {
1026                         nonTargetColorCount++;
1027                     }
1028                 }
1029             }
1030             if (mThresholdIsAMaxium) {
1031                 return nonTargetColorCount <= mCountThreshold;
1032             } else {
1033                 return nonTargetColorCount > mCountThreshold;
1034             }
1035         }
1036     }
1037 }
1038