• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 com.android.internal.widget.remotecompose.core.operations.paint;
17 
18 import static com.android.internal.widget.remotecompose.core.serialize.MapSerializer.orderedOf;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 
23 import com.android.internal.widget.remotecompose.core.PaintContext;
24 import com.android.internal.widget.remotecompose.core.RemoteContext;
25 import com.android.internal.widget.remotecompose.core.VariableSupport;
26 import com.android.internal.widget.remotecompose.core.WireBuffer;
27 import com.android.internal.widget.remotecompose.core.operations.Utils;
28 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
29 import com.android.internal.widget.remotecompose.core.serialize.Serializable;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Map;
35 
36 /** Paint Bundle represents a delta of changes to a paint object */
37 public class PaintBundle implements Serializable {
38     @NonNull int[] mArray = new int[200];
39     @Nullable int[] mOutArray = null;
40     int mPos = 0;
41 
42     /**
43      * Apply changes to a PaintChanges interface
44      *
45      * @param paintContext
46      * @param p
47      */
applyPaintChange(@onNull PaintContext paintContext, @NonNull PaintChanges p)48     public void applyPaintChange(@NonNull PaintContext paintContext, @NonNull PaintChanges p) {
49         int i = 0;
50         int mask = 0;
51         if (mOutArray == null) {
52             mOutArray = mArray;
53         }
54         while (i < mPos) {
55             int cmd = mOutArray[i++];
56             mask = mask | (1 << (cmd - 1));
57             switch (cmd & 0xFFFF) {
58                 case TEXT_SIZE:
59                     p.setTextSize(Float.intBitsToFloat(mOutArray[i++]));
60                     break;
61                 case TYPEFACE:
62                     int style = (cmd >> 16);
63                     int weight = style & 0x3ff;
64                     boolean italic = (style >> 10) > 0;
65                     int font_type = mOutArray[i++];
66 
67                     p.setTypeFace(font_type, weight, italic);
68                     break;
69                 case COLOR_ID: // mOutArray should have already decoded it
70                 case COLOR:
71                     p.setColor(mOutArray[i++]);
72                     break;
73                 case STROKE_WIDTH:
74                     p.setStrokeWidth(Float.intBitsToFloat(mOutArray[i++]));
75                     break;
76                 case STROKE_MITER:
77                     p.setStrokeMiter(Float.intBitsToFloat(mOutArray[i++]));
78                     break;
79                 case STROKE_CAP:
80                     p.setStrokeCap(cmd >> 16);
81                     break;
82                 case STYLE:
83                     p.setStyle(cmd >> 16);
84                     break;
85                 case SHADER:
86                     p.setShader(mOutArray[i++]);
87                     break;
88                 case STROKE_JOIN:
89                     p.setStrokeJoin(cmd >> 16);
90                     break;
91                 case IMAGE_FILTER_QUALITY:
92                     p.setImageFilterQuality(cmd >> 16);
93                     break;
94                 case BLEND_MODE:
95                     p.setBlendMode(cmd >> 16);
96                     break;
97                 case FILTER_BITMAP:
98                     p.setFilterBitmap(!((cmd >> 16) == 0));
99                     break;
100                 case GRADIENT:
101                     i = callSetGradient(cmd, mOutArray, i, p);
102                     break;
103                 case COLOR_FILTER_ID:
104                 case COLOR_FILTER:
105                     p.setColorFilter(mOutArray[i++], cmd >> 16);
106                     break;
107                 case ALPHA:
108                     p.setAlpha(Float.intBitsToFloat(mOutArray[i++]));
109                     break;
110                 case CLEAR_COLOR_FILTER:
111                     p.clear(0x1L << PaintBundle.COLOR_FILTER);
112                     break;
113             }
114         }
115     }
116 
117     //    private String toName(int id) {
118     //        switch (id) {
119     //            case TEXT_SIZE:
120     //                return "TEXT_SIZE";
121     //            case COLOR:
122     //                return "COLOR";
123     //            case STROKE_WIDTH:
124     //                return "STROKE_WIDTH";
125     //            case STROKE_MITER:
126     //                return "STROKE_MITER";
127     //            case TYPEFACE:
128     //                return "TYPEFACE";
129     //            case STROKE_CAP:
130     //                return "CAP";
131     //            case STYLE:
132     //                return "STYLE";
133     //            case SHADER:
134     //                return "SHADER";
135     //            case IMAGE_FILTER_QUALITY:
136     //                return "IMAGE_FILTER_QUALITY";
137     //            case BLEND_MODE:
138     //                return "BLEND_MODE";
139     //            case FILTER_BITMAP:
140     //                return "FILTER_BITMAP";
141     //            case GRADIENT:
142     //                return "GRADIENT_LINEAR";
143     //            case ALPHA:
144     //                return "ALPHA";
145     //            case COLOR_FILTER:
146     //                return "COLOR_FILTER";
147     //        }
148     //        return "????" + id + "????";
149     //    }
150 
151     @NonNull
colorInt(int color)152     private static String colorInt(int color) {
153         String str = "000000000000" + Integer.toHexString(color);
154         return "0x" + str.substring(str.length() - 8);
155     }
156 
157     @NonNull
colorInt(@onNull int[] color)158     private static String colorInt(@NonNull int[] color) {
159         String str = "[";
160         for (int i = 0; i < color.length; i++) {
161             if (i > 0) {
162                 str += ", ";
163             }
164             str += colorInt(color[i]);
165         }
166         return str + "]";
167     }
168 
asFloatStr(int value)169     private static String asFloatStr(int value) {
170         float fValue = Float.intBitsToFloat(value);
171         if (Float.isNaN(fValue)) {
172             return "[" + Utils.idFromNan(fValue) + "]";
173         }
174         return Float.toString(fValue);
175     }
176 
177     @NonNull
178     @Override
toString()179     public String toString() {
180         StringBuilder ret = new StringBuilder("\n");
181         int i = 0;
182         while (i < mPos) {
183             int cmd = mArray[i++];
184             int type = cmd & 0xFFFF;
185             switch (type) {
186                 case TEXT_SIZE:
187                     ret.append("    TextSize(" + asFloatStr(mArray[i++]));
188                     break;
189                 case TYPEFACE:
190                     int style = (cmd >> 16);
191                     int weight = style & 0x3ff;
192                     boolean italic = (style >> 10) > 0;
193                     int font_type = mArray[i++];
194                     ret.append("    TypeFace(" + (font_type + ", " + weight + ", " + italic));
195                     break;
196                 case COLOR:
197                     ret.append("    Color(" + colorInt(mArray[i++]));
198                     break;
199                 case COLOR_ID:
200                     ret.append("    ColorId([" + mArray[i++] + "]");
201                     break;
202                 case STROKE_WIDTH:
203                     ret.append("    StrokeWidth(" + asFloatStr(mArray[i++]));
204                     break;
205                 case STROKE_MITER:
206                     ret.append("    StrokeMiter(" + asFloatStr(mArray[i++]));
207                     break;
208                 case STROKE_CAP:
209                     ret.append("    StrokeCap(" + (cmd >> 16));
210                     break;
211                 case STYLE:
212                     ret.append("    Style(" + (cmd >> 16));
213                     break;
214                 case COLOR_FILTER:
215                     ret.append(
216                             "    ColorFilter(color="
217                                     + colorInt(mArray[i++])
218                                     + ", mode="
219                                     + blendModeString(cmd >> 16));
220                     break;
221                 case COLOR_FILTER_ID:
222                     ret.append(
223                             "    ColorFilterID(color=["
224                                     + mArray[i++]
225                                     + "], mode="
226                                     + blendModeString(cmd >> 16));
227                     break;
228                 case CLEAR_COLOR_FILTER:
229                     ret.append("    clearColorFilter");
230                     break;
231                 case SHADER:
232                     ret.append("    Shader(" + mArray[i++]);
233                     break;
234                 case ALPHA:
235                     ret.append("    Alpha(" + asFloatStr(mArray[i++]));
236                     break;
237                 case IMAGE_FILTER_QUALITY:
238                     ret.append("    ImageFilterQuality(" + (cmd >> 16));
239                     break;
240                 case BLEND_MODE:
241                     ret.append("    BlendMode(" + blendModeString(cmd >> 16));
242                     break;
243                 case FILTER_BITMAP:
244                     ret.append("    FilterBitmap(" + !(cmd >> 16 == 0));
245                     break;
246                 case STROKE_JOIN:
247                     ret.append("    StrokeJoin(" + (cmd >> 16));
248                     break;
249                 case ANTI_ALIAS:
250                     ret.append("    AntiAlias(" + (cmd >> 16));
251                     break;
252                 case GRADIENT:
253                     i = callPrintGradient(cmd, mArray, i, ret);
254             }
255             ret.append("),\n");
256         }
257         return ret.toString();
258     }
259 
registerFloat( int iv, @NonNull RemoteContext context, @NonNull VariableSupport support)260     private void registerFloat(
261             int iv, @NonNull RemoteContext context, @NonNull VariableSupport support) {
262         float v = Float.intBitsToFloat(iv);
263         if (Float.isNaN(v)) {
264             context.listensTo(Utils.idFromNan(v), support);
265         }
266     }
267 
callRegisterGradient( int cmd, int[] array, int i, @NonNull RemoteContext context, @NonNull VariableSupport support)268     int callRegisterGradient(
269             int cmd,
270             int[] array,
271             int i,
272             @NonNull RemoteContext context,
273             @NonNull VariableSupport support) {
274         int ret = i;
275         int type = (cmd >> 16);
276         int control = array[ret++];
277         int len = 0xFF & control; // maximum 256 colors
278         int register = 0xFFFF & (control >> 16);
279         int tileMode = 0;
280         switch (type) {
281             /* see {@link #setLinearGradient} */
282             case LINEAR_GRADIENT:
283                 if (len > 0) {
284 
285                     for (int j = 0; j < len; j++) {
286                         int color = array[ret++];
287                         if ((register & (1 << j)) != 0) {
288                             context.listensTo(color, support);
289                         }
290                     }
291                 }
292                 len = array[ret++];
293 
294                 if (len > 0) {
295 
296                     for (int j = 0; j < len; j++) {
297                         registerFloat(array[ret++], context, support);
298                     }
299                 }
300 
301                 //  start x
302                 registerFloat(array[ret++], context, support);
303                 //  start y
304                 registerFloat(array[ret++], context, support);
305                 // end x
306                 registerFloat(array[ret++], context, support);
307                 // end y
308                 registerFloat(array[ret++], context, support);
309                 tileMode = array[ret++];
310                 break;
311             /* see {@link #setRadialGradient} */
312             case RADIAL_GRADIENT:
313                 if (len > 0) {
314 
315                     for (int j = 0; j < len; j++) {
316                         int color = array[ret++];
317                         if ((register & (1 << j)) != 0) {
318                             context.listensTo(color, support);
319                         }
320                     }
321                 }
322                 len = array[ret++]; // stops
323                 for (int j = 0; j < len; j++) {
324                     registerFloat(array[ret++], context, support);
325                 }
326 
327                 //  center x
328                 registerFloat(array[ret++], context, support);
329                 //  center y
330                 registerFloat(array[ret++], context, support);
331                 // radius
332                 registerFloat(array[ret++], context, support);
333 
334                 tileMode = array[ret++]; // tile Mode
335                 break;
336             /* see {@link #setSweepGradient} */
337             case SWEEP_GRADIENT:
338                 if (len > 0) {
339 
340                     for (int j = 0; j < len; j++) {
341                         int color = array[ret++];
342                         if ((register & (1 << j)) != 0) {
343                             context.listensTo(color, support);
344                         }
345                     }
346                 }
347                 len = array[ret++]; // stops
348                 for (int j = 0; j < len; j++) {
349                     registerFloat(array[ret++], context, support);
350                 }
351                 //  center x
352                 registerFloat(array[ret++], context, support);
353                 //  center y
354                 registerFloat(array[ret++], context, support);
355                 break;
356             default:
357                 System.out.println("error ");
358         }
359 
360         return ret;
361     }
362 
callPrintGradient(int cmd, int[] array, int i, @NonNull StringBuilder p)363     int callPrintGradient(int cmd, int[] array, int i, @NonNull StringBuilder p) {
364         int ret = i;
365         int type = (cmd >> 16);
366         int tileMode = 0;
367         int len = array[ret++];
368         int[] colors = null;
369         String[] stops = null;
370         switch (type) {
371             case 0:
372                 p.append("    LinearGradient(\n");
373                 if (len > 0) {
374                     colors = new int[len];
375                     for (int j = 0; j < colors.length; j++) {
376                         colors[j] = array[ret++];
377                     }
378                 }
379                 len = array[ret++];
380                 if (len > 0) {
381                     stops = new String[len];
382                     for (int j = 0; j < stops.length; j++) {
383                         stops[j] = asFloatStr(array[ret++]);
384                     }
385                 }
386 
387                 p.append("      colors = " + colorInt(colors) + ",\n");
388                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
389                 p.append("      start = ");
390                 p.append("[" + asFloatStr(array[ret++]));
391                 p.append(", " + asFloatStr(array[ret++]) + "],\n");
392                 p.append("      end = ");
393                 p.append("[" + asFloatStr(array[ret++]));
394                 p.append(", " + asFloatStr(array[ret++]) + "],\n");
395                 tileMode = array[ret++];
396                 p.append("      tileMode = " + tileMode + "\n    ");
397                 break;
398             case 1:
399                 p.append("    RadialGradient(\n");
400                 if (len > 0) {
401                     colors = new int[len];
402                     for (int j = 0; j < colors.length; j++) {
403                         colors[j] = array[ret++];
404                     }
405                 }
406                 len = array[ret++];
407                 if (len > 0) {
408                     stops = new String[len];
409                     for (int j = 0; j < stops.length; j++) {
410                         stops[j] = asFloatStr(array[ret++]);
411                     }
412                 }
413 
414                 p.append("      colors = " + colorInt(colors) + ",\n");
415                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
416                 p.append("      center = ");
417                 p.append("[" + asFloatStr(array[ret++]));
418                 p.append(", " + asFloatStr(array[ret++]) + "],\n");
419                 p.append("      radius =");
420                 p.append(" " + asFloatStr(array[ret++]) + ",\n");
421                 tileMode = array[ret++];
422                 p.append("      tileMode = " + tileMode + "\n    ");
423                 break;
424             case 2:
425                 p.append("    SweepGradient(\n");
426                 if (len > 0) {
427                     colors = new int[len];
428                     for (int j = 0; j < colors.length; j++) {
429                         colors[j] = array[ret++];
430                     }
431                 }
432                 len = array[ret++];
433                 if (len > 0) {
434                     stops = new String[len];
435                     for (int j = 0; j < stops.length; j++) {
436                         stops[j] = asFloatStr(array[ret++]);
437                     }
438                 }
439                 p.append("      colors = " + colorInt(colors) + ",\n");
440                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
441                 p.append("      center = ");
442                 p.append("[" + asFloatStr(array[ret++]));
443                 p.append(", " + asFloatStr(array[ret++]) + "],\n    ");
444                 break;
445             default:
446                 p.append("GRADIENT_??????!!!!");
447         }
448 
449         return ret;
450     }
451 
callSetGradient(int cmd, @NonNull int[] array, int i, @NonNull PaintChanges p)452     int callSetGradient(int cmd, @NonNull int[] array, int i, @NonNull PaintChanges p) {
453         int ret = i;
454         int gradientType = (cmd >> 16);
455 
456         int len = 0xFF & array[ret++]; // maximum 256 colors
457 
458         int[] colors = null;
459         if (len > 0) {
460             colors = new int[len];
461             for (int j = 0; j < colors.length; j++) {
462                 colors[j] = array[ret++];
463             }
464         }
465         len = array[ret++];
466         float[] stops = null;
467         if (len > 0) {
468             stops = new float[len];
469             for (int j = 0; j < colors.length; j++) {
470                 stops[j] = Float.intBitsToFloat(array[ret++]);
471             }
472         }
473 
474         if (colors == null) {
475             return ret;
476         }
477 
478         int tileMode = 0;
479         float centerX = 0f;
480         float centerY = 0f;
481 
482         switch (gradientType) {
483             case LINEAR_GRADIENT:
484                 float startX = Float.intBitsToFloat(array[ret++]);
485                 float startY = Float.intBitsToFloat(array[ret++]);
486                 float endX = Float.intBitsToFloat(array[ret++]);
487                 float endY = Float.intBitsToFloat(array[ret++]);
488                 tileMode = array[ret++];
489                 p.setLinearGradient(colors, stops, startX, startY, endX, endY, tileMode);
490                 break;
491             case RADIAL_GRADIENT:
492                 centerX = Float.intBitsToFloat(array[ret++]);
493                 centerY = Float.intBitsToFloat(array[ret++]);
494                 float radius = Float.intBitsToFloat(array[ret++]);
495                 tileMode = array[ret++];
496                 p.setRadialGradient(colors, stops, centerX, centerY, radius, tileMode);
497                 break;
498             case SWEEP_GRADIENT:
499                 centerX = Float.intBitsToFloat(array[ret++]);
500                 centerY = Float.intBitsToFloat(array[ret++]);
501                 p.setSweepGradient(colors, stops, centerX, centerY);
502         }
503 
504         return ret;
505     }
506 
507     /**
508      * Write a bundle of paint changes to the buffer
509      *
510      * @param buffer bundle to write
511      */
writeBundle(@onNull WireBuffer buffer)512     public void writeBundle(@NonNull WireBuffer buffer) {
513         buffer.writeInt(mPos);
514         for (int index = 0; index < mPos; index++) {
515             buffer.writeInt(mArray[index]);
516         }
517     }
518 
519     /**
520      * This will read the paint bundle off the wire buffer
521      *
522      * @param buffer the buffer to read
523      */
readBundle(@onNull WireBuffer buffer)524     public void readBundle(@NonNull WireBuffer buffer) {
525         int len = buffer.readInt();
526         if (len <= 0 || len > 1024) {
527             throw new RuntimeException("buffer corrupt paint len = " + len);
528         }
529         mArray = new int[len];
530         for (int i = 0; i < mArray.length; i++) {
531             mArray[i] = buffer.readInt();
532         }
533         mPos = len;
534     }
535 
536     public static final int TEXT_SIZE = 1; // float
537 
538     public static final int COLOR = 4; // int
539     public static final int STROKE_WIDTH = 5; // float
540     public static final int STROKE_MITER = 6;
541     public static final int STROKE_CAP = 7; //  int
542     public static final int STYLE = 8; // int
543     public static final int SHADER = 9; // int
544     public static final int IMAGE_FILTER_QUALITY = 10; // int
545     public static final int GRADIENT = 11;
546     public static final int ALPHA = 12;
547     public static final int COLOR_FILTER = 13;
548     public static final int ANTI_ALIAS = 14;
549     public static final int STROKE_JOIN = 15;
550     public static final int TYPEFACE = 16;
551     public static final int FILTER_BITMAP = 17;
552     public static final int BLEND_MODE = 18;
553     public static final int COLOR_ID = 19;
554     public static final int COLOR_FILTER_ID = 20;
555     public static final int CLEAR_COLOR_FILTER = 21;
556 
557     public static final int BLEND_MODE_CLEAR = 0;
558     public static final int BLEND_MODE_SRC = 1;
559     public static final int BLEND_MODE_DST = 2;
560     public static final int BLEND_MODE_SRC_OVER = 3;
561     public static final int BLEND_MODE_DST_OVER = 4;
562     public static final int BLEND_MODE_SRC_IN = 5;
563     public static final int BLEND_MODE_DST_IN = 6;
564     public static final int BLEND_MODE_SRC_OUT = 7;
565     public static final int BLEND_MODE_DST_OUT = 8;
566     public static final int BLEND_MODE_SRC_ATOP = 9;
567     public static final int BLEND_MODE_DST_ATOP = 10;
568     public static final int BLEND_MODE_XOR = 11;
569     public static final int BLEND_MODE_PLUS = 12;
570     public static final int BLEND_MODE_MODULATE = 13;
571     public static final int BLEND_MODE_SCREEN = 14;
572     public static final int BLEND_MODE_OVERLAY = 15;
573     public static final int BLEND_MODE_DARKEN = 16;
574     public static final int BLEND_MODE_LIGHTEN = 17;
575     public static final int BLEND_MODE_COLOR_DODGE = 18;
576     public static final int BLEND_MODE_COLOR_BURN = 19;
577     public static final int BLEND_MODE_HARD_LIGHT = 20;
578     public static final int BLEND_MODE_SOFT_LIGHT = 21;
579     public static final int BLEND_MODE_DIFFERENCE = 22;
580     public static final int BLEND_MODE_EXCLUSION = 23;
581     public static final int BLEND_MODE_MULTIPLY = 24;
582     public static final int BLEND_MODE_HUE = 25;
583     public static final int BLEND_MODE_SATURATION = 26;
584     public static final int BLEND_MODE_COLOR = 27;
585     public static final int BLEND_MODE_LUMINOSITY = 28;
586     public static final int BLEND_MODE_NULL = 29;
587     public static final int PORTER_MODE_ADD = 30;
588 
589     public static final int FONT_NORMAL = 0;
590     public static final int FONT_BOLD = 1;
591     public static final int FONT_ITALIC = 2;
592     public static final int FONT_BOLD_ITALIC = 3;
593 
594     public static final int FONT_TYPE_DEFAULT = 0;
595     public static final int FONT_TYPE_SANS_SERIF = 1;
596     public static final int FONT_TYPE_SERIF = 2;
597     public static final int FONT_TYPE_MONOSPACE = 3;
598 
599     public static final int STYLE_FILL = 0;
600     public static final int STYLE_STROKE = 1;
601     public static final int STYLE_FILL_AND_STROKE = 2;
602     public static final int LINEAR_GRADIENT = 0;
603     public static final int RADIAL_GRADIENT = 1;
604     public static final int SWEEP_GRADIENT = 2;
605 
606     private int mLastShaderSet = -1;
607     private boolean mColorFilterSet = false;
608 
609     /**
610      * sets a shader that draws a linear gradient along a line.
611      *
612      * @param startX The x-coordinate for the start of the gradient line
613      * @param startY The y-coordinate for the start of the gradient line
614      * @param endX The x-coordinate for the end of the gradient line
615      * @param endY The y-coordinate for the end of the gradient line
616      * @param colors The sRGB colors to be distributed along the gradient line
617      * @param stops May be null. The relative positions [0..1] of each corresponding color in the
618      *     colors array. If this is null, the colors are distributed evenly along the gradient line.
619      * @param tileMode The Shader tiling mode
620      */
setLinearGradient( @onNull int[] colors, int idMask, @Nullable float[] stops, float startX, float startY, float endX, float endY, int tileMode)621     public void setLinearGradient(
622             @NonNull int[] colors,
623             int idMask,
624             @Nullable float[] stops,
625             float startX,
626             float startY,
627             float endX,
628             float endY,
629             int tileMode) {
630         //        int startPos = mPos;
631         int len;
632         mArray[mPos++] = GRADIENT | (LINEAR_GRADIENT << 16);
633         mArray[mPos++] = (idMask << 16) | (len = colors.length);
634         for (int i = 0; i < len; i++) {
635             mArray[mPos++] = colors[i];
636         }
637 
638         mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
639         for (int i = 0; i < len; i++) {
640             mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
641         }
642         mArray[mPos++] = Float.floatToRawIntBits(startX);
643         mArray[mPos++] = Float.floatToRawIntBits(startY);
644         mArray[mPos++] = Float.floatToRawIntBits(endX);
645         mArray[mPos++] = Float.floatToRawIntBits(endY);
646         mArray[mPos++] = tileMode;
647     }
648 
649     /**
650      * Set a shader that draws a sweep gradient around a center point.
651      *
652      * @param centerX The x-coordinate of the center
653      * @param centerY The y-coordinate of the center
654      * @param colors The sRGB colors to be distributed around the center. There must be at least 2
655      *     colors in the array.
656      * @param stops May be NULL. The relative position of each corresponding color in the colors
657      *     array, beginning with 0 and ending with 1.0. If the values are not monotonic, the drawing
658      *     may produce unexpected results. If positions is NULL, then the colors are automatically
659      *     spaced evenly.
660      */
setSweepGradient( @onNull int[] colors, int idMask, @Nullable float[] stops, float centerX, float centerY)661     public void setSweepGradient(
662             @NonNull int[] colors,
663             int idMask,
664             @Nullable float[] stops, // TODO: rename positions to stops or stops to positions, but
665             // don't have both in the same file
666             float centerX,
667             float centerY) {
668         int len;
669         mArray[mPos++] = GRADIENT | (SWEEP_GRADIENT << 16);
670         mArray[mPos++] = (idMask << 16) | (len = (colors == null) ? 0 : colors.length);
671         for (int i = 0; i < len; i++) {
672             mArray[mPos++] = colors[i];
673         }
674 
675         mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
676         for (int i = 0; i < len; i++) {
677             mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
678         }
679         mArray[mPos++] = Float.floatToRawIntBits(centerX);
680         mArray[mPos++] = Float.floatToRawIntBits(centerY);
681     }
682 
683     /**
684      * Sets a shader that draws a radial gradient given the center and radius.
685      *
686      * @param centerX The x-coordinate of the center of the radius
687      * @param centerY The y-coordinate of the center of the radius
688      * @param radius Must be positive. The radius of the gradient.
689      * @param colors The sRGB colors distributed between the center and edge
690      * @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and <code>
691      *                 1.0f</code>. The relative position of each corresponding color in the colors
692      *     array. If <code>null</code>, colors are distributed evenly between the center and edge of
693      *     the circle.
694      * @param tileMode The Shader tiling mode
695      */
setRadialGradient( @onNull int[] colors, int idMask, @Nullable float[] stops, float centerX, float centerY, float radius, int tileMode)696     public void setRadialGradient(
697             @NonNull int[] colors,
698             int idMask,
699             @Nullable float[] stops,
700             float centerX,
701             float centerY,
702             float radius,
703             int tileMode) {
704         //        int startPos = mPos;
705         int len;
706         mArray[mPos++] = GRADIENT | (RADIAL_GRADIENT << 16);
707         mArray[mPos++] = (idMask << 16) | (len = (colors == null) ? 0 : colors.length);
708         for (int i = 0; i < len; i++) {
709             mArray[mPos++] = colors[i];
710         }
711         mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
712 
713         for (int i = 0; i < len; i++) {
714             mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
715         }
716         mArray[mPos++] = Float.floatToRawIntBits(centerX);
717         mArray[mPos++] = Float.floatToRawIntBits(centerY);
718         mArray[mPos++] = Float.floatToRawIntBits(radius);
719         mArray[mPos++] = tileMode;
720     }
721 
722     /**
723      * Create a color filter that uses the specified color and Porter-Duff mode.
724      *
725      * @param color The ARGB source color used with the Porter-Duff mode
726      * @param mode The porter-duff mode that is applied
727      */
setColorFilter(int color, int mode)728     public void setColorFilter(int color, int mode) {
729         mArray[mPos] = COLOR_FILTER | (mode << 16);
730         mPos++;
731         mArray[mPos++] = color;
732     }
733 
734     /**
735      * Create a color filter that uses the specified color and Porter-Duff mode.
736      *
737      * @param color The id source color used with the Porter-Duff mode
738      * @param mode The porter-duff mode that is applied
739      */
setColorFilterId(int color, int mode)740     public void setColorFilterId(int color, int mode) {
741         mArray[mPos] = COLOR_FILTER_ID | (mode << 16);
742         mPos++;
743         mArray[mPos++] = color;
744         mColorFilterSet = true;
745     }
746 
747     /** This sets the color filter to null */
clearColorFilter()748     public void clearColorFilter() {
749         mArray[mPos] = CLEAR_COLOR_FILTER;
750         mPos++;
751         mColorFilterSet = false;
752     }
753 
754     /**
755      * Set the paint's text size. This value must be > 0
756      *
757      * @param size set the paint's text size in pixel units.
758      */
setTextSize(float size)759     public void setTextSize(float size) {
760         mArray[mPos] = TEXT_SIZE;
761         mPos++;
762         mArray[mPos] = Float.floatToRawIntBits(size);
763         mPos++;
764     }
765 
766     /**
767      * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace
768      * @param weight 100-1000
769      * @param italic tur
770      */
setTextStyle(int fontType, int weight, boolean italic)771     public void setTextStyle(int fontType, int weight, boolean italic) {
772         int style = (weight & 0x3FF) | (italic ? 2048 : 0); // pack the weight and italic
773         mArray[mPos++] = TYPEFACE | (style << 16);
774         mArray[mPos++] = fontType;
775     }
776 
777     /**
778      * Set the width for stroking. Pass 0 to stroke in hairline mode. Hairlines always draws a
779      * single pixel independent of the canvas's matrix.
780      *
781      * @param width set the paint's stroke width, used whenever the paint's style is Stroke or
782      *     StrokeAndFill.
783      */
setStrokeWidth(float width)784     public void setStrokeWidth(float width) {
785         mArray[mPos] = STROKE_WIDTH;
786         mPos++;
787         mArray[mPos] = Float.floatToRawIntBits(width);
788         mPos++;
789     }
790 
791     /**
792      * Set the Color based on Color
793      *
794      * @param color
795      */
setColor(int color)796     public void setColor(int color) {
797         mArray[mPos] = COLOR;
798         mPos++;
799         mArray[mPos] = color;
800         mPos++;
801     }
802 
803     /**
804      * Set the color based the R,G,B,A values
805      *
806      * @param r red (0 to 255)
807      * @param g green (0 to 255)
808      * @param b blue (0 to 255)
809      * @param a alpha (0 to 255)
810      */
setColor(int r, int g, int b, int a)811     public void setColor(int r, int g, int b, int a) {
812         int color = (a << 24) | (r << 16) | (g << 8) | b;
813         setColor(color);
814     }
815 
816     /**
817      * Set the color based the R,G,B,A values (Warning this does not support NaN ids)
818      *
819      * @param r red (0.0 to 1.0)
820      * @param g green (0.0 to 1.0)
821      * @param b blue (0.0 to 1.0)
822      * @param a alpha (0.0 to 1.0)
823      */
setColor(float r, float g, float b, float a)824     public void setColor(float r, float g, float b, float a) {
825         setColor(Utils.toARGB(a, r, g, b));
826     }
827 
828     /**
829      * Set the Color based on ID
830      *
831      * @param color
832      */
setColorId(int color)833     public void setColorId(int color) {
834         mArray[mPos] = COLOR_ID;
835         mPos++;
836         mArray[mPos] = color;
837         mPos++;
838     }
839 
840     /**
841      * Set the paint's Cap.
842      *
843      * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or
844      *     StrokeAndFill.
845      */
setStrokeCap(int cap)846     public void setStrokeCap(int cap) {
847         mArray[mPos] = STROKE_CAP | (cap << 16);
848         mPos++;
849     }
850 
851     /**
852      * Set the style STROKE and/or FILL
853      *
854      * @param style
855      */
setStyle(int style)856     public void setStyle(int style) {
857         mArray[mPos] = STYLE | (style << 16);
858         mPos++;
859     }
860 
861     /**
862      * Set the shader id to use
863      *
864      * @param shaderId
865      */
setShader(int shaderId)866     public void setShader(int shaderId) {
867         mLastShaderSet = shaderId;
868         mArray[mPos] = SHADER;
869         mPos++;
870         mArray[mPos] = shaderId;
871         mPos++;
872     }
873 
874     /** Set the Alpha value */
setAlpha(float alpha)875     public void setAlpha(float alpha) {
876         mArray[mPos] = ALPHA;
877         mPos++;
878         mArray[mPos] = Float.floatToRawIntBits(alpha);
879         mPos++;
880     }
881 
882     /**
883      * Set the paint's stroke miter value. This is used to control the behavior of miter joins when
884      * the joins angle is sharp. This value must be >= 0.
885      *
886      * @param miter set the miter limit on the paint, used whenever the paint's style is Stroke or
887      *     StrokeAndFill.
888      */
setStrokeMiter(float miter)889     public void setStrokeMiter(float miter) {
890         mArray[mPos] = STROKE_MITER;
891         mPos++;
892         mArray[mPos] = Float.floatToRawIntBits(miter);
893         mPos++;
894     }
895 
896     /**
897      * Set the paint's Join.
898      *
899      * @param join set the paint's Join, used whenever the paint's style is Stroke or StrokeAndFill.
900      */
setStrokeJoin(int join)901     public void setStrokeJoin(int join) {
902         mArray[mPos] = STROKE_JOIN | (join << 16);
903         mPos++;
904     }
905 
906     /**
907      * set Filter Bitmap
908      *
909      * @param filter set to false to disable interpolation
910      */
setFilterBitmap(boolean filter)911     public void setFilterBitmap(boolean filter) {
912         mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0);
913         mPos++;
914     }
915 
916     /**
917      * Set or clear the blend mode. A blend mode defines how source pixels (generated by a drawing
918      * command) are composited with the destination pixels (content of the render target).
919      *
920      * @param blendmode The blend mode to be installed in the paint
921      */
setBlendMode(int blendmode)922     public void setBlendMode(int blendmode) {
923         mArray[mPos] = BLEND_MODE | (blendmode << 16);
924         mPos++;
925     }
926 
927     /**
928      * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit AntiAliasing smooths out
929      * the edges of what is being drawn, but is has no impact on the interior of the shape. See
930      * setDither() and setFilterBitmap() to affect how colors are treated.
931      *
932      * @param aa true to set the antialias bit in the flags, false to clear it
933      */
setAntiAlias(boolean aa)934     public void setAntiAlias(boolean aa) {
935         mArray[mPos] = ANTI_ALIAS | (aa ? 1 : 0) << 16;
936         mPos++;
937     }
938 
939     /**
940      * clear a series of paint parameters. Currently not used
941      *
942      * @param mask bit pattern of the attributes to clear
943      */
clear(long mask)944     public void clear(long mask) { // unused for now
945     }
946 
947     /** Reset the content of the paint bundle so that it can be reused */
reset()948     public void reset() {
949         mPos = 0;
950         if (mColorFilterSet) {
951             clearColorFilter();
952         }
953         if (mLastShaderSet != -1 && mLastShaderSet != 0) {
954             setShader(0);
955         }
956     }
957 
958     /**
959      * Convert a blend mode integer as a string
960      *
961      * @param mode the blend mode
962      * @return the blend mode as a string
963      */
blendModeString(int mode)964     public static @NonNull String blendModeString(int mode) {
965         switch (mode) {
966             case PaintBundle.BLEND_MODE_CLEAR:
967                 return "CLEAR";
968             case PaintBundle.BLEND_MODE_SRC:
969                 return "SRC";
970             case PaintBundle.BLEND_MODE_DST:
971                 return "DST";
972             case PaintBundle.BLEND_MODE_SRC_OVER:
973                 return "SRC_OVER";
974             case PaintBundle.BLEND_MODE_DST_OVER:
975                 return "DST_OVER";
976             case PaintBundle.BLEND_MODE_SRC_IN:
977                 return "SRC_IN";
978             case PaintBundle.BLEND_MODE_DST_IN:
979                 return "DST_IN";
980             case PaintBundle.BLEND_MODE_SRC_OUT:
981                 return "SRC_OUT";
982             case PaintBundle.BLEND_MODE_DST_OUT:
983                 return "DST_OUT";
984             case PaintBundle.BLEND_MODE_SRC_ATOP:
985                 return "SRC_ATOP";
986             case PaintBundle.BLEND_MODE_DST_ATOP:
987                 return "DST_ATOP";
988             case PaintBundle.BLEND_MODE_XOR:
989                 return "XOR";
990             case PaintBundle.BLEND_MODE_PLUS:
991                 return "PLUS";
992             case PaintBundle.BLEND_MODE_MODULATE:
993                 return "MODULATE";
994             case PaintBundle.BLEND_MODE_SCREEN:
995                 return "SCREEN";
996             case PaintBundle.BLEND_MODE_OVERLAY:
997                 return "OVERLAY";
998             case PaintBundle.BLEND_MODE_DARKEN:
999                 return "DARKEN";
1000             case PaintBundle.BLEND_MODE_LIGHTEN:
1001                 return "LIGHTEN";
1002             case PaintBundle.BLEND_MODE_COLOR_DODGE:
1003                 return "COLOR_DODGE";
1004             case PaintBundle.BLEND_MODE_COLOR_BURN:
1005                 return "COLOR_BURN";
1006             case PaintBundle.BLEND_MODE_HARD_LIGHT:
1007                 return "HARD_LIGHT";
1008             case PaintBundle.BLEND_MODE_SOFT_LIGHT:
1009                 return "SOFT_LIGHT";
1010             case PaintBundle.BLEND_MODE_DIFFERENCE:
1011                 return "DIFFERENCE";
1012             case PaintBundle.BLEND_MODE_EXCLUSION:
1013                 return "EXCLUSION";
1014             case PaintBundle.BLEND_MODE_MULTIPLY:
1015                 return "MULTIPLY";
1016             case PaintBundle.BLEND_MODE_HUE:
1017                 return "HUE";
1018             case PaintBundle.BLEND_MODE_SATURATION:
1019                 return "SATURATION";
1020             case PaintBundle.BLEND_MODE_COLOR:
1021                 return "COLOR";
1022             case PaintBundle.BLEND_MODE_LUMINOSITY:
1023                 return "LUMINOSITY";
1024             case PaintBundle.BLEND_MODE_NULL:
1025                 return "null";
1026             case PaintBundle.PORTER_MODE_ADD:
1027                 return "ADD";
1028         }
1029         return "null";
1030     }
1031 
1032     /**
1033      * Check all the floats for Nan(id) floats and call listenTo
1034      *
1035      * @param context
1036      * @param support
1037      */
registerVars(@onNull RemoteContext context, @NonNull VariableSupport support)1038     public void registerVars(@NonNull RemoteContext context, @NonNull VariableSupport support) {
1039         int i = 0;
1040         while (i < mPos) {
1041             int cmd = mArray[i++];
1042             int type = cmd & 0xFFFF;
1043             switch (type) {
1044                 case STROKE_MITER:
1045                 case STROKE_WIDTH:
1046                 case ALPHA:
1047                 case TEXT_SIZE:
1048                     float v = Float.intBitsToFloat(mArray[i++]);
1049                     if (Float.isNaN(v)) {
1050                         context.listensTo(Utils.idFromNan(v), support);
1051                     }
1052                     break;
1053                 case COLOR_FILTER_ID:
1054                 case COLOR_ID:
1055                     context.listensTo(mArray[i++], support);
1056                     break;
1057                 case COLOR:
1058 
1059                 case TYPEFACE:
1060                 case SHADER:
1061                 case COLOR_FILTER:
1062                     i++;
1063                     break;
1064                 case STROKE_JOIN:
1065                 case FILTER_BITMAP:
1066                 case STROKE_CAP:
1067                 case STYLE:
1068                 case IMAGE_FILTER_QUALITY:
1069                 case BLEND_MODE:
1070                 case ANTI_ALIAS:
1071                     break;
1072 
1073                 case GRADIENT:
1074                     i = callRegisterGradient(cmd, mArray, i, context, support);
1075             }
1076         }
1077     }
1078 
1079     /**
1080      * Update variables if any are float ids
1081      *
1082      * @param context
1083      */
updateVariables(@onNull RemoteContext context)1084     public void updateVariables(@NonNull RemoteContext context) {
1085         if (mOutArray == null) {
1086             mOutArray = Arrays.copyOf(mArray, mArray.length);
1087         } else {
1088             System.arraycopy(mArray, 0, mOutArray, 0, mArray.length);
1089         }
1090         int i = 0;
1091         while (i < mPos) {
1092             int cmd = mArray[i++];
1093             int type = cmd & 0xFFFF;
1094             switch (type) {
1095                 case STROKE_MITER:
1096                 case STROKE_WIDTH:
1097                 case ALPHA:
1098                 case TEXT_SIZE:
1099                     mOutArray[i] = fixFloatVar(mArray[i], context);
1100                     i++;
1101                     break;
1102                 case COLOR_FILTER_ID:
1103                 case COLOR_ID:
1104                     mOutArray[i] = fixColor(mArray[i], context);
1105                     i++;
1106                     break;
1107                 case COLOR:
1108                 case TYPEFACE:
1109                 case SHADER:
1110                 case COLOR_FILTER:
1111                     i++;
1112                     break;
1113                 case STROKE_JOIN:
1114                 case FILTER_BITMAP:
1115                 case STROKE_CAP:
1116                 case STYLE:
1117                 case IMAGE_FILTER_QUALITY:
1118                 case BLEND_MODE:
1119                 case ANTI_ALIAS:
1120                 case CLEAR_COLOR_FILTER:
1121                     break;
1122 
1123                 case GRADIENT:
1124                     // TODO gradients should be handled correctly
1125                     i = updateFloatsInGradient(cmd, mOutArray, mArray, i, context);
1126             }
1127         }
1128     }
1129 
fixFloatVar(int val, @NonNull RemoteContext context)1130     private int fixFloatVar(int val, @NonNull RemoteContext context) {
1131         float v = Float.intBitsToFloat(val);
1132         if (Float.isNaN(v)) {
1133             int id = Utils.idFromNan(v);
1134             return Float.floatToRawIntBits(context.getFloat(id));
1135         }
1136         return val;
1137     }
1138 
fixColor(int colorId, @NonNull RemoteContext context)1139     private int fixColor(int colorId, @NonNull RemoteContext context) {
1140         int n = context.getColor(colorId);
1141         return n;
1142     }
1143 
updateFloatsInGradient( int cmd, int[] out, int[] array, int i, @NonNull RemoteContext context)1144     int updateFloatsInGradient(
1145             int cmd, int[] out, int[] array, int i, @NonNull RemoteContext context) {
1146         int ret = i;
1147         int type = (cmd >> 16);
1148         int control = array[ret++];
1149         int len = 0xFF & control; // maximum 256 colors
1150         int register = 0xFFFF & (control >> 16);
1151         switch (type) {
1152             case LINEAR_GRADIENT:
1153                 if (len > 0) {
1154 
1155                     for (int j = 0; j < len; j++) {
1156                         int color = array[ret];
1157                         if ((register & (1 << j)) != 0) {
1158                             out[ret] = fixColor(color, context);
1159                         }
1160                         ret++;
1161                     }
1162                 }
1163                 len = array[ret++];
1164                 if (len > 0) {
1165                     for (int j = 0; j < len; j++) {
1166                         out[ret] = fixFloatVar(array[ret], context);
1167                         ret++;
1168                     }
1169                 }
1170 
1171                 out[ret] = fixFloatVar(array[ret], context);
1172                 ret++;
1173                 out[ret] = fixFloatVar(array[ret], context);
1174                 ret++;
1175 
1176                 //      end
1177                 out[ret] = fixFloatVar(array[ret], context);
1178                 ret++;
1179                 out[ret] = fixFloatVar(array[ret], context);
1180                 ret++;
1181                 ret++; // tileMode
1182                 break;
1183             case RADIAL_GRADIENT:
1184                 //   RadialGradient
1185                 if (len > 0) {
1186 
1187                     for (int j = 0; j < len; j++) {
1188                         int color = array[ret];
1189                         if ((register & (1 << j)) != 0) {
1190                             out[ret] = fixColor(color, context);
1191                         }
1192                         ret++;
1193                     }
1194                 }
1195                 len = array[ret++];
1196                 if (len > 0) {
1197                     for (int j = 0; j < len; j++) {
1198                         out[ret] = fixFloatVar(array[ret], context);
1199                         ret++;
1200                     }
1201                 }
1202 
1203                 //    center
1204                 out[ret] = fixFloatVar(array[ret], context);
1205                 ret++;
1206                 out[ret] = fixFloatVar(array[ret], context);
1207                 ret++;
1208                 //     radius
1209                 out[ret] = fixFloatVar(array[ret], context);
1210                 ret++;
1211                 ret++; // tileMode
1212                 break;
1213             case SWEEP_GRADIENT:
1214                 //   SweepGradient
1215                 if (len > 0) {
1216 
1217                     for (int j = 0; j < len; j++) {
1218                         int color = array[ret];
1219                         if ((register & (1 << j)) != 0) {
1220                             out[ret] = fixColor(color, context);
1221                         }
1222                         ret++;
1223                     }
1224                 }
1225                 len = array[ret++];
1226                 float[] stops = null;
1227                 if (len > 0) {
1228                     stops = new float[len];
1229                     for (int j = 0; j < stops.length; j++) {
1230                         out[ret] = fixFloatVar(array[ret], context);
1231                         ret++;
1232                     }
1233                 }
1234 
1235                 //      center
1236                 out[ret] = fixFloatVar(array[ret], context);
1237                 ret++;
1238                 out[ret] = fixFloatVar(array[ret], context);
1239                 ret++;
1240 
1241                 break;
1242             default:
1243                 System.err.println("gradient type unknown");
1244         }
1245 
1246         return ret;
1247     }
1248 
1249     @Override
serialize(MapSerializer serializer)1250     public void serialize(MapSerializer serializer) {
1251         serializer.addType("PaintBundle");
1252         List<Map<String, Object>> list = new ArrayList<>();
1253         int i = 0;
1254         while (i < mPos) {
1255             int cmd = mArray[i++];
1256             int type = cmd & 0xFFFF;
1257             switch (type) {
1258                 case TEXT_SIZE:
1259                     list.add(orderedOf("type", "TextSize", "size", getVariable(mArray[i++])));
1260                     break;
1261                 case TYPEFACE:
1262                     int style = (cmd >> 16);
1263                     float weight = (float) (style & 0x3ff);
1264                     boolean italic = (style >> 10) > 0;
1265                     int fontFamily = mArray[i++];
1266                     list.add(orderedOf("type", "FontFamily", "fontFamily", fontFamily));
1267                     list.add(orderedOf("type", "FontWeight", "weight", weight));
1268                     list.add(orderedOf("type", "TypeFace", "italic", italic));
1269                     break;
1270                 case COLOR:
1271                     list.add(orderedOf("type", "Color", "color", colorInt(mArray[i++])));
1272                     break;
1273                 case COLOR_ID:
1274                     list.add(orderedOf("type", "ColorId", "id", mArray[i++]));
1275                     break;
1276                 case STROKE_WIDTH:
1277                     list.add(orderedOf("type", "StrokeWidth", "width", getVariable(mArray[i++])));
1278                     break;
1279                 case STROKE_MITER:
1280                     list.add(orderedOf("type", "StrokeMiter", "miter", getVariable(mArray[i++])));
1281                     break;
1282                 case STROKE_CAP:
1283                     list.add(orderedOf("type", "StrokeCap", "cap", cmd >> 16));
1284                     break;
1285                 case STYLE:
1286                     list.add(orderedOf("type", "Style", "style", cmd >> 16));
1287                     break;
1288                 case COLOR_FILTER:
1289                     list.add(
1290                             orderedOf(
1291                                     "type",
1292                                     "ColorFilter",
1293                                     "color",
1294                                     colorInt(mArray[i++]),
1295                                     "mode",
1296                                     blendModeString(cmd >> 16)));
1297                     break;
1298                 case COLOR_FILTER_ID:
1299                     list.add(
1300                             orderedOf(
1301                                     "type",
1302                                     "ColorFilterID",
1303                                     "id",
1304                                     mArray[i++],
1305                                     "mode",
1306                                     blendModeString(cmd >> 16)));
1307                     break;
1308                 case CLEAR_COLOR_FILTER:
1309                     list.add(orderedOf("type", "ClearColorFilter"));
1310                     break;
1311                 case SHADER:
1312                     list.add(orderedOf("type", "Shader", "id", mArray[i++]));
1313                     break;
1314                 case ALPHA:
1315                     list.add(orderedOf("type", "Alpha", "alpha", getVariable(mArray[i++])));
1316                     break;
1317                 case IMAGE_FILTER_QUALITY:
1318                     list.add(orderedOf("type", "ImageFilterQuality", "quality", cmd >> 16));
1319                     break;
1320                 case BLEND_MODE:
1321                     list.add(orderedOf("type", "BlendMode", "mode", blendModeString(cmd >> 16)));
1322                     break;
1323                 case FILTER_BITMAP:
1324                     list.add(orderedOf("type", "FilterBitmap", "enabled", !(cmd >> 16 == 0)));
1325                     break;
1326                 case STROKE_JOIN:
1327                     list.add(orderedOf("type", "StrokeJoin", "strokeJoin", cmd >> 16));
1328                     break;
1329                 case ANTI_ALIAS:
1330                     list.add(orderedOf("type", "AntiAlias", "enabled", !(cmd >> 16 == 0)));
1331                     break;
1332                 case GRADIENT:
1333                     i = serializeGradient(cmd, mArray, i, list);
1334             }
1335         }
1336         serializer.add("operations", list);
1337     }
1338 
1339     @SuppressWarnings("JdkImmutableCollections")
getVariable(int value)1340     private static Map<String, Object> getVariable(int value) {
1341         float fValue = Float.intBitsToFloat(value);
1342         if (Float.isNaN(fValue)) {
1343             return orderedOf("type", "Variable", "id", Utils.idFromNan(fValue));
1344         }
1345         return orderedOf("type", "Value", "value", fValue);
1346     }
1347 
1348     @SuppressWarnings("JdkImmutableCollections")
serializeGradient( int cmd, int[] array, int i, List<Map<String, Object>> list)1349     private static int serializeGradient(
1350             int cmd, int[] array, int i, List<Map<String, Object>> list) {
1351         int ret = i;
1352         int gradientType = (cmd >> 16);
1353 
1354         int len = 0xFF & array[ret++]; // maximum 256 colors
1355 
1356         String[] colors = null;
1357         if (len > 0) {
1358             colors = new String[len];
1359             for (int j = 0; j < colors.length; j++) {
1360                 colors[j] = colorInt(array[ret++]);
1361             }
1362         }
1363         len = array[ret++];
1364         float[] stops = null;
1365         if (len > 0) {
1366             stops = new float[len];
1367             for (int j = 0; j < colors.length; j++) {
1368                 stops[j] = Float.intBitsToFloat(array[ret++]);
1369             }
1370         }
1371 
1372         if (colors == null) {
1373             return ret;
1374         }
1375 
1376         int tileMode;
1377         int centerX;
1378         int centerY;
1379 
1380         switch (gradientType) {
1381             case LINEAR_GRADIENT:
1382                 int startX = array[ret++];
1383                 int startY = array[ret++];
1384                 int endX = array[ret++];
1385                 int endY = array[ret++];
1386                 tileMode = array[ret++];
1387                 list.add(
1388                         orderedOf(
1389                                 "type",
1390                                 "LinearGradient",
1391                                 "colors",
1392                                 colors,
1393                                 "stops",
1394                                 stops == null ? List.of() : stops,
1395                                 "startX",
1396                                 getVariable(startX),
1397                                 "startY",
1398                                 getVariable(startY),
1399                                 "endX",
1400                                 getVariable(endX),
1401                                 "endY",
1402                                 getVariable(endY),
1403                                 "tileMode",
1404                                 tileMode));
1405                 break;
1406             case RADIAL_GRADIENT:
1407                 centerX = array[ret++];
1408                 centerY = array[ret++];
1409                 int radius = array[ret++];
1410                 tileMode = array[ret++];
1411                 list.add(
1412                         orderedOf(
1413                                 "type",
1414                                 "RadialGradient",
1415                                 "colors",
1416                                 colors,
1417                                 "stops",
1418                                 stops == null ? List.of() : stops,
1419                                 "centerX",
1420                                 getVariable(centerX),
1421                                 "centerY",
1422                                 getVariable(centerY),
1423                                 "radius",
1424                                 getVariable(radius),
1425                                 "tileMode",
1426                                 tileMode));
1427                 break;
1428             case SWEEP_GRADIENT:
1429                 centerX = array[ret++];
1430                 centerY = array[ret++];
1431                 list.add(
1432                         orderedOf(
1433                                 "type",
1434                                 "SweepGradient",
1435                                 "colors",
1436                                 colors,
1437                                 "stops",
1438                                 stops == null ? List.of() : stops,
1439                                 "centerX",
1440                                 getVariable(centerX),
1441                                 "centerY",
1442                                 getVariable(centerY)));
1443         }
1444 
1445         return ret;
1446     }
1447 }
1448