1 /*
2      * Copyright (C) 2014 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/LICENSE2.0
9      *
10      * Unless required by applicable law or agreed to in riting, 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.testinfrastructure;
17 
18 import static java.util.Map.entry;
19 
20 import android.graphics.Canvas;
21 import android.graphics.ColorFilter;
22 import android.graphics.ColorMatrix;
23 import android.graphics.ColorMatrixColorFilter;
24 import android.graphics.Paint;
25 import android.graphics.PorterDuff;
26 import android.graphics.PorterDuffColorFilter;
27 import android.graphics.PorterDuffXfermode;
28 import android.graphics.RectF;
29 import android.graphics.Xfermode;
30 
31 import java.util.ArrayList;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 
35 /**
36  * Modifies the canvas and paint objects when called.
37  */
38 public abstract class DisplayModifier {
39     private static final RectF RECT = new RectF(0, 0, 100, 100);
40     private static final float[] POINTS = new float[]{
41             0.5f, 40.5f, 40.5f, 0.5f, 40.5f, 80.5f, 80.5f, 40.5f
42     };
43     private static final float[] TRIANGLE_POINTS = new float[]{
44             40, 0, 80, 80, 80, 80, 0, 80, 0, 80, 40, 0
45     };
46     private static final int NUM_PARALLEL_LINES = 10;
47     private static final float[] LINES = new float[NUM_PARALLEL_LINES * 8
48             + TRIANGLE_POINTS.length];
49 
50     public static final PorterDuff.Mode[] PORTERDUFF_MODES = new PorterDuff.Mode[] {
51         PorterDuff.Mode.SRC, PorterDuff.Mode.DST, PorterDuff.Mode.SRC_OVER,
52         PorterDuff.Mode.DST_OVER, PorterDuff.Mode.SRC_IN, PorterDuff.Mode.DST_IN,
53         PorterDuff.Mode.SRC_OUT, PorterDuff.Mode.DST_OUT, PorterDuff.Mode.SRC_ATOP,
54         PorterDuff.Mode.DST_ATOP, PorterDuff.Mode.XOR, PorterDuff.Mode.MULTIPLY,
55         PorterDuff.Mode.SCREEN
56     };
57 
58     static {
System.arraycopy(TRIANGLE_POINTS, 0, LINES, 0, TRIANGLE_POINTS.length)59         System.arraycopy(TRIANGLE_POINTS, 0, LINES, 0, TRIANGLE_POINTS.length);
60         int index = TRIANGLE_POINTS.length;
61         float val = 0;
62         for (int i = 0; i < NUM_PARALLEL_LINES; i++) {
63             LINES[index + 0] = 40;
64             LINES[index + 1] = val;
65             LINES[index + 2] = 80;
66             LINES[index + 3] = val;
67             index += 4;
68             val += 8 + (2.0f / NUM_PARALLEL_LINES);
69         }
70         val = 0;
71         for (int i = 0; i < NUM_PARALLEL_LINES; i++) {
72             LINES[index + 0] = val;
73             LINES[index + 1] = 40;
74             LINES[index + 2] = val;
75             LINES[index + 3] = 80;
76             index += 4;
77             val += 8 + (2.0f / NUM_PARALLEL_LINES);
78         }
79     }
80 
81     // This linked hash map contains each of the different things that can be done to a canvas and
82     // paint object, like anti-aliasing or drawing. Within those LinkedHashMaps are the various
83     // options for that specific topic, which contains a displaymodifier which will affect the
84     // given canvas and paint objects.
85     public static final Map<String, Map<String, DisplayModifier>> MAPS =
86             new LinkedHashMap<String, Map<String, DisplayModifier>>();
87     static {
88         MAPS.put("aa", Map.of(
89                 "true", new DisplayModifier() {
90                     @Override
91                     public void modifyDrawing(Paint paint, Canvas canvas) {
92                         paint.setAntiAlias(true);
93                     }
94                 },
95                 "false", new DisplayModifier() {
96                     @Override
97                     public void modifyDrawing(Paint paint, Canvas canvas) {
98                         paint.setAntiAlias(false);
99                     }
100                 })
101         );
102         MAPS.put("style", Map.of(
103                 "fill", new DisplayModifier() {
104                     @Override
105                     public void modifyDrawing(Paint paint, Canvas canvas) {
106                         paint.setStyle(Paint.Style.FILL);
107                     }
108                 },
109                 "stroke", new DisplayModifier() {
110                     @Override
111                     public void modifyDrawing(Paint paint, Canvas canvas) {
112                         paint.setStyle(Paint.Style.STROKE);
113                     }
114                 },
115                 "fillAndStroke", new DisplayModifier() {
116                     @Override
117                     public void modifyDrawing(Paint paint, Canvas canvas) {
118                         paint.setStyle(Paint.Style.FILL_AND_STROKE);
119                     }
120                 })
121         );
122         MAPS.put("strokeWidth", Map.of(
123                 "hair", new DisplayModifier() {
124                     @Override
125                     public void modifyDrawing(Paint paint, Canvas canvas) {
126                         paint.setStrokeWidth(0);
127                     }
128                 },
129                 "0.3", new DisplayModifier() {
130                     @Override
131                     public void modifyDrawing(Paint paint, Canvas canvas) {
132                         paint.setStrokeWidth(0.3f);
133                     }
134                 },
135                 "1", new DisplayModifier() {
136                     @Override
137                     public void modifyDrawing(Paint paint, Canvas canvas) {
138                         paint.setStrokeWidth(1);
139                     }
140                 },
141                 "5", new DisplayModifier() {
142                     @Override
143                     public void modifyDrawing(Paint paint, Canvas canvas) {
144                         paint.setStrokeWidth(5);
145                     }
146                 },
147                 "30", new DisplayModifier() {
148                     @Override
149                     public void modifyDrawing(Paint paint, Canvas canvas) {
150                         paint.setStrokeWidth(30);
151                     }
152                 })
153         );
154         MAPS.put("strokeCap", Map.of(
155                 "butt", new DisplayModifier() {
156                     @Override
157                     public void modifyDrawing(Paint paint, Canvas canvas) {
158                         paint.setStrokeCap(Paint.Cap.BUTT);
159                     }
160                 },
161                 "round", new DisplayModifier() {
162                     @Override
163                     public void modifyDrawing(Paint paint, Canvas canvas) {
164                         paint.setStrokeCap(Paint.Cap.ROUND);
165                     }
166                 },
167                 "square", new DisplayModifier() {
168                     @Override
169                     public void modifyDrawing(Paint paint, Canvas canvas) {
170                         paint.setStrokeCap(Paint.Cap.SQUARE);
171                     }
172                 })
173         );
174         MAPS.put("strokeJoin", Map.of(
175                 "bevel", new DisplayModifier() {
176                     @Override
177                     public void modifyDrawing(Paint paint, Canvas canvas) {
178                         paint.setStrokeJoin(Paint.Join.BEVEL);
179                     }
180                 },
181                 "round", new DisplayModifier() {
182                     @Override
183                     public void modifyDrawing(Paint paint, Canvas canvas) {
184                         paint.setStrokeJoin(Paint.Join.ROUND);
185                     }
186                 },
187                 "miter", new DisplayModifier() {
188                     @Override
189                     public void modifyDrawing(Paint paint, Canvas canvas) {
190                         paint.setStrokeJoin(Paint.Join.MITER);
191                     }
192                 })
193                 // TODO: add miter0, miter1 etc to test miter distances
194         );
195         MAPS.put("transform", Map.of(
196                 "noTransform", new DisplayModifier() {
197                     @Override
198                     public void modifyDrawing(Paint paint, Canvas canvas) {
199                     }
200                 },
201                 "rotate5", new DisplayModifier() {
202                     @Override
203                     public void modifyDrawing(Paint paint, Canvas canvas) {
204                         canvas.rotate(5);
205                     }
206                 },
207                 "rotate45", new DisplayModifier() {
208                     @Override
209                     public void modifyDrawing(Paint paint, Canvas canvas) {
210                         canvas.rotate(45);
211                     }
212                 },
213                 "rotate90", new DisplayModifier() {
214                     @Override
215                     public void modifyDrawing(Paint paint, Canvas canvas) {
216                         canvas.rotate(90);
217                         canvas.translate(0, -100);
218                     }
219                 },
220                 "scale2x2", new DisplayModifier() {
221                     @Override
222                     public void modifyDrawing(Paint paint, Canvas canvas) {
223                         canvas.scale(2, 2);
224                     }
225                 },
226                 "rot20scl1x4", new DisplayModifier() {
227                     @Override
228                     public void modifyDrawing(Paint paint, Canvas canvas) {
229                         canvas.rotate(20);
230                         canvas.scale(1, 4);
231                     }
232                 })
233         );
234         MAPS.put("shader", Map.of(
235                 "noShader", new DisplayModifier() {
236                     @Override
237                     public void modifyDrawing(Paint paint, Canvas canvas) {
238                     }
239                 },
240                 "repeatShader", new DisplayModifier() {
241                     @Override
242                     public void modifyDrawing(Paint paint, Canvas canvas) {
243                         paint.setShader(ResourceModifier.instance().repeatShader);
244                     }
245                 },
246                 "translatedShader", new DisplayModifier() {
247                     @Override
248                     public void modifyDrawing(Paint paint, Canvas canvas) {
249                         paint.setShader(ResourceModifier.instance().translatedShader);
250                     }
251                 },
252                 "scaledShader", new DisplayModifier() {
253                     @Override
254                     public void modifyDrawing(Paint paint, Canvas canvas) {
255                         paint.setShader(ResourceModifier.instance().scaledShader);
256                     }
257                 },
258                 "composeShader", new DisplayModifier() {
259                     @Override
260                     public void modifyDrawing(Paint paint, Canvas canvas) {
261                         paint.setShader(ResourceModifier.instance().composeShader);
262                     }
263                 },
264                 /*
265                 "bad composeShader", new DisplayModifier() {
266                     @Override
267                     public void modifyDrawing(Paint paint, Canvas canvas) {
268                         paint.setShader(ResourceModifier.instance().nestedComposeShader);
269                     }
270                 },
271                 "bad composeShader 2", new DisplayModifier() {
272                     @Override
273                     public void modifyDrawing(Paint paint, Canvas canvas) {
274                         paint.setShader(
275                                 ResourceModifier.instance().doubleGradientComposeShader);
276                     }
277                 },
278                 */
279                 "horGradient", new DisplayModifier() {
280                     @Override
281                     public void modifyDrawing(Paint paint, Canvas canvas) {
282                         paint.setShader(ResourceModifier.instance().horGradient);
283                     }
284                 },
285                 "diagGradient", new DisplayModifier() {
286                     @Override
287                     public void modifyDrawing(Paint paint, Canvas canvas) {
288                         paint.setShader(ResourceModifier.instance().diagGradient);
289                     }
290                 },
291                 "vertGradient", new DisplayModifier() {
292                     @Override
293                     public void modifyDrawing(Paint paint, Canvas canvas) {
294                         paint.setShader(ResourceModifier.instance().vertGradient);
295                     }
296                 },
297                 "radGradient", new DisplayModifier() {
298                     @Override
299                     public void modifyDrawing(Paint paint, Canvas canvas) {
300                         paint.setShader(ResourceModifier.instance().radGradient);
301                     }
302                 },
303                 "sweepGradient", new DisplayModifier() {
304                     @Override
305                     public void modifyDrawing(Paint paint, Canvas canvas) {
306                         paint.setShader(ResourceModifier.instance().sweepGradient);
307                     }
308                 })
309         );
310         Map<String, DisplayModifier> xfermodes = new LinkedHashMap<String, DisplayModifier>();
311         for (int i = 0; i < PORTERDUFF_MODES.length; i++) {
xfermodes.put(PORTERDUFF_MODES[i].toString(), new XfermodeModifier(PORTERDUFF_MODES[i]))312             xfermodes.put(PORTERDUFF_MODES[i].toString(),
313                     new XfermodeModifier(PORTERDUFF_MODES[i]));
314         }
315         xfermodes.put("lowSaturationColorMatrix", new DisplayModifier() {
316             @Override
317             public void modifyDrawing(Paint paint, Canvas canvas) {
318                 ColorMatrix matrix = new ColorMatrix();
319                 matrix.setSaturation(0.1f);
320                 paint.setColorFilter(new ColorMatrixColorFilter(matrix));
321             }
322         });
323         xfermodes.put("highSaturationColorMatrix", new DisplayModifier() {
324             @Override
325             public void modifyDrawing(Paint paint, Canvas canvas) {
326                 ColorMatrix matrix = new ColorMatrix();
327                 matrix.setSaturation(10.0f);
328                 paint.setColorFilter(new ColorMatrixColorFilter(matrix));
329             }
330         });
331         MAPS.put("xfermodes", xfermodes);
332 
333         Map<String, DisplayModifier> colorfilters = new LinkedHashMap<String, DisplayModifier>();
334         for (int i = 0; i < PORTERDUFF_MODES.length; i++) {
colorfilters.put(PORTERDUFF_MODES[i].toString(), new ColorFilterModifier(PORTERDUFF_MODES[i]))335             colorfilters.put(PORTERDUFF_MODES[i].toString(),
336                     new ColorFilterModifier(PORTERDUFF_MODES[i]));
337         }
338         MAPS.put("colorfilters", colorfilters);
339 
340         // FINAL MAP: DOES ACTUAL DRAWING
341         MAPS.put("drawing", Map.ofEntries(
342                 entry("roundRect", new DisplayModifier() {
343                     @Override
344                     public void modifyDrawing(Paint paint, Canvas canvas) {
345                         canvas.drawRoundRect(RECT, 20, 20, paint);
346                     }
347                 }),
348                 entry("rect", new DisplayModifier() {
349                     @Override
350                     public void modifyDrawing(Paint paint, Canvas canvas) {
351                         canvas.drawRect(RECT, paint);
352                     }
353                 }),
354                 entry("circle", new DisplayModifier() {
355                     @Override
356                     public void modifyDrawing(Paint paint, Canvas canvas) {
357                         canvas.drawCircle(100, 100, 75, paint);
358                     }
359                 }),
360                 entry("oval", new DisplayModifier() {
361                     @Override
362                     public void modifyDrawing(Paint paint, Canvas canvas) {
363                         canvas.drawOval(RECT, paint);
364                     }
365                 }),
366                 entry("lines", new DisplayModifier() {
367                     @Override
368                     public void modifyDrawing(Paint paint, Canvas canvas) {
369                         canvas.drawLines(LINES, paint);
370                     }
371                 }),
372                 entry("plusPoints", new DisplayModifier() {
373                     @Override
374                     public void modifyDrawing(Paint paint, Canvas canvas) {
375                         canvas.drawPoints(POINTS, paint);
376                     }
377                 }),
378                 entry("text", new DisplayModifier() {
379                     @Override
380                     public void modifyDrawing(Paint paint, Canvas canvas) {
381                         paint.setTextSize(20);
382                         canvas.drawText("TEXTTEST", 0, 50, paint);
383                     }
384                 }),
385                 entry("shadowtext", new DisplayModifier() {
386                     @Override
387                     public void modifyDrawing(Paint paint, Canvas canvas) {
388                         paint.setTextSize(20);
389                         paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
390                         canvas.drawText("TEXTTEST", 0, 50, paint);
391                     }
392                 }),
393                 entry("bitmapMesh", new DisplayModifier() {
394                     @Override
395                     public void modifyDrawing(Paint paint, Canvas canvas) {
396                         // skip some verts to exercise fix in P
397                         // (previously this was ignored)
398                         int vertsToSkip = 2;
399                         canvas.drawBitmapMesh(ResourceModifier.instance().bitmap, 3, 3,
400                                 ResourceModifier.instance().bitmapVertices, vertsToSkip,
401                                 null, 0, null);
402                     }
403                 }),
404                 entry("arc", new DisplayModifier() {
405                     @Override
406                     public void modifyDrawing(Paint paint, Canvas canvas) {
407                         canvas.drawArc(RECT, 260, 285, false, paint);
408                     }
409                 }),
410                 entry("arcFromCenter", new DisplayModifier() {
411                     @Override
412                     public void modifyDrawing(Paint paint, Canvas canvas) {
413                         canvas.drawArc(RECT, 260, 285, true, paint);
414                     }
415                 }))
416         );
417         // WARNING: DON'T PUT MORE MAPS BELOW THIS
418     }
419 
modifyDrawing(Paint paint, Canvas canvas)420     abstract public void modifyDrawing(Paint paint, Canvas canvas);
421 
422     public static class Accessor {
423         public final static int AA_MASK =               0x1 << 0;
424         public final static int STYLE_MASK =            0x1 << 1;
425         public final static int STROKE_WIDTH_MASK =     0x1 << 2;
426         public final static int STROKE_CAP_MASK =       0x1 << 3;
427         public final static int STROKE_JOIN_MASK =      0x1 << 4;
428         public final static int TRANSFORM_MASK =        0x1 << 5;
429         public final static int SHADER_MASK =           0x1 << 6;
430         public final static int XFERMODE_MASK =         0x1 << 7;
431         public final static int COLOR_FILTER_MASK =     0x1 << 8;
432         public final static int SHAPES_MASK =           0x1 << 9;
433         public final static int ALL_OPTIONS_MASK =      (0x1 << 10) - 1;
434         public final static int SHAPES_INDEX = 9;
435         public final static int XFERMODE_INDEX = 7;
436         private final int mMask;
437 
438         private String mDebugString;
439         private int[] mIndices;
440         private Map<String, Map<String, DisplayModifier>> mDisplayMap;
441 
Accessor(int mask)442         public Accessor(int mask) {
443             int totalModifiers = Integer.bitCount(mask);
444             mIndices = new int[totalModifiers];
445             mMask = mask;
446             // Create a Display Map of the valid indices
447             mDisplayMap = new LinkedHashMap<String, Map<String, DisplayModifier>>();
448             int index = 0;
449             for (String key : DisplayModifier.MAPS.keySet()) {
450                 if (validIndex(index)) {
451                     mDisplayMap.put(key, DisplayModifier.MAPS.get(key));
452                 }
453                 index++;
454             }
455             mDebugString = "";
456         }
457 
getMapAtIndex(int index)458         private Map<String, DisplayModifier> getMapAtIndex(int index) {
459             int i = 0;
460             for (Map<String, DisplayModifier> map : mDisplayMap.values()) {
461                 if (i == index) {
462                     return map;
463                 }
464                 i++;
465             }
466             return null;
467         }
468 
469         /**
470          * This will create the next combination of drawing commands. If we have done every combination,
471          * then we will return false.
472          * @return true if there is more combinations to do
473          */
step()474         public boolean step() {
475             int modifierMapIndex = mIndices.length - 1;
476             // Start from the last map, and loop until it is at the front
477             while (modifierMapIndex >= 0) {
478                 Map<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
479                 mIndices[modifierMapIndex]++;
480 
481                 // If we are still at a valid index, then we don't need to update any others
482                 if (mIndices[modifierMapIndex] < map.size()) {
483                     break;
484                 }
485 
486                 // If we updated and it was outside the boundary, and it was the last index then
487                 // we are done
488                 if (modifierMapIndex == 0) {
489                     return false;
490                 }
491                 // If we ran off the end of the map, we need to update one more down the list
492                 mIndices[modifierMapIndex] = 0;
493 
494                 modifierMapIndex--;
495             }
496             getModifierList(); // Just to update mDebugString
497             return true;
498         }
499 
500         /**
501          * Modifies the canvas and paint given for the particular combination currently
502          */
modifyDrawing(Canvas canvas, Paint paint)503         public void modifyDrawing(Canvas canvas, Paint paint) {
504             final ArrayList<DisplayModifier> modifierArrayList = getModifierList();
505             for (DisplayModifier modifier : modifierArrayList) {
506                 modifier.modifyDrawing(paint, canvas);
507             }
508         }
509 
510         /**
511          * Gets a list of all the current modifications to be used.
512          */
getModifierList()513         private ArrayList<DisplayModifier> getModifierList() {
514             ArrayList<DisplayModifier> modifierArrayList = new ArrayList<DisplayModifier>();
515             int mapIndex = 0;
516             mDebugString = "";
517 
518             // Through each possible category of modification
519             for (Map.Entry<String, Map<String, DisplayModifier>> entry :
520                     mDisplayMap.entrySet()) {
521                 int displayModifierIndex = mIndices[mapIndex];
522                 if (!mDebugString.isEmpty()) {
523                     mDebugString += ", ";
524                 }
525                 mDebugString += entry.getKey();
526                 // Loop until we find the modification we are going to use
527                 for (Map.Entry<String, DisplayModifier> modifierEntry :
528                         entry.getValue().entrySet()) {
529                     // Once we find the modification we want, then we will add it to the list,
530                     // and the last applied modifications
531                     if (displayModifierIndex == 0) {
532                         mDebugString += ": " + modifierEntry.getKey();
533                         modifierArrayList.add(modifierEntry.getValue());
534                         break;
535                     }
536                     displayModifierIndex--;
537                 }
538                 mapIndex++;
539             }
540             return modifierArrayList;
541         }
542 
getDebugString()543         public String getDebugString() {
544             return mDebugString;
545         }
546 
547         /**
548          * Using the given masks, it tells if the map at the given index should be used, or not.
549          */
validIndex(int index)550         private boolean validIndex(int index) {
551             return (mMask & (0x1 << index)) != 0;
552         }
553     }
554 
555     private static class XfermodeModifier extends DisplayModifier {
556         private Xfermode mXfermode;
557 
XfermodeModifier(PorterDuff.Mode mode)558         public XfermodeModifier(PorterDuff.Mode mode) {
559             mXfermode = new PorterDuffXfermode(mode);
560         }
561 
562         @Override
modifyDrawing(Paint paint, Canvas canvas)563         public void modifyDrawing(Paint paint, Canvas canvas) {
564             paint.setXfermode(mXfermode);
565         }
566     }
567 
568     private static class ColorFilterModifier extends DisplayModifier {
569         private static final int FILTER_COLOR = 0xFFBB0000;
570         private ColorFilter mColorFilter;
571 
ColorFilterModifier(PorterDuff.Mode mode)572         public ColorFilterModifier(PorterDuff.Mode mode) {
573             mColorFilter = new PorterDuffColorFilter(FILTER_COLOR, mode);
574         }
575 
576         @Override
modifyDrawing(Paint paint, Canvas canvas)577         public void modifyDrawing(Paint paint, Canvas canvas) {
578             paint.setColorFilter(mColorFilter);
579         }
580     }
581 }
582