• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.player.platform;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.SuppressLint;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapShader;
23 import android.graphics.BlendMode;
24 import android.graphics.Canvas;
25 import android.graphics.LinearGradient;
26 import android.graphics.Outline;
27 import android.graphics.Paint;
28 import android.graphics.Path;
29 import android.graphics.PorterDuff;
30 import android.graphics.PorterDuffColorFilter;
31 import android.graphics.RadialGradient;
32 import android.graphics.Rect;
33 import android.graphics.RectF;
34 import android.graphics.RenderEffect;
35 import android.graphics.RenderNode;
36 import android.graphics.RuntimeShader;
37 import android.graphics.Shader;
38 import android.graphics.SweepGradient;
39 import android.graphics.Typeface;
40 import android.text.Layout;
41 import android.text.StaticLayout;
42 import android.text.TextPaint;
43 import android.text.TextUtils;
44 
45 import com.android.internal.widget.remotecompose.core.PaintContext;
46 import com.android.internal.widget.remotecompose.core.Platform;
47 import com.android.internal.widget.remotecompose.core.RemoteContext;
48 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
49 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
50 import com.android.internal.widget.remotecompose.core.operations.Utils;
51 import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
52 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
53 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
54 import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges;
55 
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.List;
59 
60 /**
61  * An implementation of PaintContext for the Android Canvas. This is used to play the RemoteCompose
62  * operations on Android.
63  */
64 public class AndroidPaintContext extends PaintContext {
65     Paint mPaint = new Paint();
66     List<Paint> mPaintList = new ArrayList<>();
67     Canvas mCanvas;
68     Rect mTmpRect = new Rect(); // use in calculation of bounds
69     RenderNode mNode = null;
70     Canvas mPreviousCanvas = null;
71 
AndroidPaintContext(RemoteContext context, Canvas canvas)72     public AndroidPaintContext(RemoteContext context, Canvas canvas) {
73         super(context);
74         this.mCanvas = canvas;
75     }
76 
getCanvas()77     public Canvas getCanvas() {
78         return mCanvas;
79     }
80 
setCanvas(Canvas canvas)81     public void setCanvas(Canvas canvas) {
82         this.mCanvas = canvas;
83     }
84 
85     @Override
save()86     public void save() {
87         mCanvas.save();
88     }
89 
90     @Override
saveLayer(float x, float y, float width, float height)91     public void saveLayer(float x, float y, float width, float height) {
92         mCanvas.saveLayer(x, y, x + width, y + height, mPaint);
93     }
94 
95     @Override
restore()96     public void restore() {
97         mCanvas.restore();
98     }
99 
100     /**
101      * Draw an image onto the canvas
102      *
103      * @param imageId the id of the image
104      * @param srcLeft left coordinate of the source area
105      * @param srcTop top coordinate of the source area
106      * @param srcRight right coordinate of the source area
107      * @param srcBottom bottom coordinate of the source area
108      * @param dstLeft left coordinate of the destination area
109      * @param dstTop top coordinate of the destination area
110      * @param dstRight right coordinate of the destination area
111      * @param dstBottom bottom coordinate of the destination area
112      */
113     @Override
drawBitmap( int imageId, int srcLeft, int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight, int dstBottom, int cdId)114     public void drawBitmap(
115             int imageId,
116             int srcLeft,
117             int srcTop,
118             int srcRight,
119             int srcBottom,
120             int dstLeft,
121             int dstTop,
122             int dstRight,
123             int dstBottom,
124             int cdId) {
125         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
126         if (androidContext.mRemoteComposeState.containsId(imageId)) {
127             Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId);
128             mCanvas.drawBitmap(
129                     bitmap,
130                     new Rect(srcLeft, srcTop, srcRight, srcBottom),
131                     new Rect(dstLeft, dstTop, dstRight, dstBottom),
132                     mPaint);
133         }
134     }
135 
136     @Override
scale(float scaleX, float scaleY)137     public void scale(float scaleX, float scaleY) {
138         mCanvas.scale(scaleX, scaleY);
139     }
140 
141     @Override
startGraphicsLayer(int w, int h)142     public void startGraphicsLayer(int w, int h) {
143         mNode = new RenderNode("layer");
144         mNode.setPosition(0, 0, w, h);
145         mPreviousCanvas = mCanvas;
146         mCanvas = mNode.beginRecording();
147     }
148 
149     @Override
setGraphicsLayer(@onNull HashMap<Integer, Object> attributes)150     public void setGraphicsLayer(@NonNull HashMap<Integer, Object> attributes) {
151         if (mNode == null) {
152             return;
153         }
154         boolean hasBlurEffect = false;
155         boolean hasOutline = false;
156         for (Integer key : attributes.keySet()) {
157             Object value = attributes.get(key);
158             switch (key) {
159                 case GraphicsLayerModifierOperation.SCALE_X:
160                     mNode.setScaleX((Float) value);
161                     break;
162                 case GraphicsLayerModifierOperation.SCALE_Y:
163                     mNode.setScaleY((Float) value);
164                     break;
165                 case GraphicsLayerModifierOperation.ROTATION_X:
166                     mNode.setRotationX((Float) value);
167                     break;
168                 case GraphicsLayerModifierOperation.ROTATION_Y:
169                     mNode.setRotationY((Float) value);
170                     break;
171                 case GraphicsLayerModifierOperation.ROTATION_Z:
172                     mNode.setRotationZ((Float) value);
173                     break;
174                 case GraphicsLayerModifierOperation.TRANSFORM_ORIGIN_X:
175                     mNode.setPivotX((Float) value * mNode.getWidth());
176                     break;
177                 case GraphicsLayerModifierOperation.TRANSFORM_ORIGIN_Y:
178                     mNode.setPivotY((Float) value * mNode.getWidth());
179                     break;
180                 case GraphicsLayerModifierOperation.TRANSLATION_X:
181                     mNode.setTranslationX((Float) value);
182                     break;
183                 case GraphicsLayerModifierOperation.TRANSLATION_Y:
184                     mNode.setTranslationY((Float) value);
185                     break;
186                 case GraphicsLayerModifierOperation.TRANSLATION_Z:
187                     mNode.setTranslationZ((Float) value);
188                     break;
189                 case GraphicsLayerModifierOperation.SHAPE:
190                     hasOutline = true;
191                     break;
192                 case GraphicsLayerModifierOperation.SHADOW_ELEVATION:
193                     mNode.setElevation((Float) value);
194                     break;
195                 case GraphicsLayerModifierOperation.ALPHA:
196                     mNode.setAlpha((Float) value);
197                     break;
198                 case GraphicsLayerModifierOperation.CAMERA_DISTANCE:
199                     mNode.setCameraDistance((Float) value);
200                     break;
201                 case GraphicsLayerModifierOperation.SPOT_SHADOW_COLOR:
202                     mNode.setSpotShadowColor((Integer) value);
203                     break;
204                 case GraphicsLayerModifierOperation.AMBIENT_SHADOW_COLOR:
205                     mNode.setAmbientShadowColor((Integer) value);
206                     break;
207                 case GraphicsLayerModifierOperation.HAS_BLUR:
208                     hasBlurEffect = ((Integer) value) != 0;
209                     break;
210             }
211         }
212         if (hasOutline) {
213             Outline outline = new Outline();
214             outline.setAlpha(1f);
215             Object oShape = attributes.get(GraphicsLayerModifierOperation.SHAPE);
216             if (oShape != null) {
217                 Object oShapeRadius = attributes.get(GraphicsLayerModifierOperation.SHAPE_RADIUS);
218                 int type = (Integer) oShape;
219                 if (type == GraphicsLayerModifierOperation.SHAPE_RECT) {
220                     outline.setRect(0, 0, mNode.getWidth(), mNode.getHeight());
221                 } else if (type == GraphicsLayerModifierOperation.SHAPE_ROUND_RECT) {
222                     if (oShapeRadius != null) {
223                         float radius = (Float) oShapeRadius;
224                         outline.setRoundRect(
225                                 new Rect(0, 0, mNode.getWidth(), mNode.getHeight()), radius);
226                     } else {
227                         outline.setRect(0, 0, mNode.getWidth(), mNode.getHeight());
228                     }
229                 } else if (type == GraphicsLayerModifierOperation.SHAPE_CIRCLE) {
230                     float radius = Math.min(mNode.getWidth(), mNode.getHeight()) / 2f;
231                     outline.setRoundRect(
232                             new Rect(0, 0, mNode.getWidth(), mNode.getHeight()), radius);
233                 }
234             }
235             mNode.setOutline(outline);
236         }
237         if (hasBlurEffect) {
238             Object oBlurRadiusX = attributes.get(GraphicsLayerModifierOperation.BLUR_RADIUS_X);
239             float blurRadiusX = 0f;
240             if (oBlurRadiusX != null) {
241                 blurRadiusX = (Float) oBlurRadiusX;
242             }
243             Object oBlurRadiusY = attributes.get(GraphicsLayerModifierOperation.BLUR_RADIUS_Y);
244             float blurRadiusY = 0f;
245             if (oBlurRadiusY != null) {
246                 blurRadiusY = (Float) oBlurRadiusY;
247             }
248             int blurTileMode = 0;
249             Object oBlurTileMode = attributes.get(GraphicsLayerModifierOperation.BLUR_TILE_MODE);
250             if (oBlurTileMode != null) {
251                 blurTileMode = (Integer) oBlurTileMode;
252             }
253             Shader.TileMode tileMode = Shader.TileMode.CLAMP;
254             switch (blurTileMode) {
255                 case GraphicsLayerModifierOperation.TILE_MODE_CLAMP:
256                     tileMode = Shader.TileMode.CLAMP;
257                     break;
258                 case GraphicsLayerModifierOperation.TILE_MODE_DECAL:
259                     tileMode = Shader.TileMode.DECAL;
260 
261                     break;
262                 case GraphicsLayerModifierOperation.TILE_MODE_MIRROR:
263                     tileMode = Shader.TileMode.MIRROR;
264                     break;
265                 case GraphicsLayerModifierOperation.TILE_MODE_REPEATED:
266                     tileMode = Shader.TileMode.REPEAT;
267                     break;
268             }
269 
270             RenderEffect effect = RenderEffect.createBlurEffect(blurRadiusX, blurRadiusY, tileMode);
271             mNode.setRenderEffect(effect);
272         }
273     }
274 
275     @Override
endGraphicsLayer()276     public void endGraphicsLayer() {
277         mNode.endRecording();
278         mCanvas = mPreviousCanvas;
279         if (mCanvas.isHardwareAccelerated()) {
280             mCanvas.enableZ();
281             mCanvas.drawRenderNode(mNode);
282             mCanvas.disableZ();
283         }
284         // node.discardDisplayList();
285         mNode = null;
286     }
287 
288     @Override
translate(float translateX, float translateY)289     public void translate(float translateX, float translateY) {
290         mCanvas.translate(translateX, translateY);
291     }
292 
293     @Override
drawArc( float left, float top, float right, float bottom, float startAngle, float sweepAngle)294     public void drawArc(
295             float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
296         mCanvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, false, mPaint);
297     }
298 
299     @Override
drawSector( float left, float top, float right, float bottom, float startAngle, float sweepAngle)300     public void drawSector(
301             float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
302         mCanvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, true, mPaint);
303     }
304 
305     @Override
drawBitmap(int id, float left, float top, float right, float bottom)306     public void drawBitmap(int id, float left, float top, float right, float bottom) {
307         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
308         if (androidContext.mRemoteComposeState.containsId(id)) {
309             Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(id);
310             Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
311             RectF dst = new RectF(left, top, right, bottom);
312             mCanvas.drawBitmap(bitmap, src, dst, mPaint);
313         }
314     }
315 
316     @Override
drawCircle(float centerX, float centerY, float radius)317     public void drawCircle(float centerX, float centerY, float radius) {
318         mCanvas.drawCircle(centerX, centerY, radius, mPaint);
319     }
320 
321     @Override
drawLine(float x1, float y1, float x2, float y2)322     public void drawLine(float x1, float y1, float x2, float y2) {
323         mCanvas.drawLine(x1, y1, x2, y2, mPaint);
324     }
325 
326     @Override
drawOval(float left, float top, float right, float bottom)327     public void drawOval(float left, float top, float right, float bottom) {
328         mCanvas.drawOval(left, top, right, bottom, mPaint);
329     }
330 
331     @Override
drawPath(int id, float start, float end)332     public void drawPath(int id, float start, float end) {
333         mCanvas.drawPath(getPath(id, start, end), mPaint);
334     }
335 
336     @Override
drawRect(float left, float top, float right, float bottom)337     public void drawRect(float left, float top, float right, float bottom) {
338         mCanvas.drawRect(left, top, right, bottom, mPaint);
339     }
340 
341     @Override
savePaint()342     public void savePaint() {
343         mPaintList.add(new Paint(mPaint));
344     }
345 
346     @Override
restorePaint()347     public void restorePaint() {
348         mPaint = mPaintList.remove(mPaintList.size() - 1);
349     }
350 
351     @Override
replacePaint(PaintBundle paintBundle)352     public void replacePaint(PaintBundle paintBundle) {
353         mPaint.reset();
354         applyPaint(paintBundle);
355     }
356 
357     @Override
drawRoundRect( float left, float top, float right, float bottom, float radiusX, float radiusY)358     public void drawRoundRect(
359             float left, float top, float right, float bottom, float radiusX, float radiusY) {
360         mCanvas.drawRoundRect(left, top, right, bottom, radiusX, radiusY, mPaint);
361     }
362 
363     @Override
drawTextOnPath(int textId, int pathId, float hOffset, float vOffset)364     public void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset) {
365         mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint);
366     }
367 
368     private Paint.FontMetrics mCachedFontMetrics;
369 
370     @Override
getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds)371     public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) {
372         String str = getText(textId);
373         if (end == -1 || end > str.length()) {
374             end = str.length();
375         }
376 
377         if (mCachedFontMetrics == null) {
378             mCachedFontMetrics = mPaint.getFontMetrics();
379         }
380         mPaint.getFontMetrics(mCachedFontMetrics);
381         mPaint.getTextBounds(str, start, end, mTmpRect);
382         if ((flags & PaintContext.TEXT_MEASURE_SPACES) != 0) {
383             bounds[0] = 0f;
384             bounds[2] = mPaint.measureText(str, start, end);
385         } else {
386             bounds[0] = mTmpRect.left;
387             if ((flags & PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH) != 0) {
388                 bounds[2] = mPaint.measureText(str, start, end) - mTmpRect.left;
389             } else {
390                 bounds[2] = mTmpRect.right;
391             }
392         }
393 
394         if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) {
395             bounds[1] = Math.round(mCachedFontMetrics.ascent);
396             bounds[3] = Math.round(mCachedFontMetrics.descent);
397         } else {
398             bounds[1] = mTmpRect.top;
399             bounds[3] = mTmpRect.bottom;
400         }
401     }
402 
403     @Override
layoutComplexText( int textId, int start, int end, int alignment, int overflow, int maxLines, float maxWidth, int flags)404     public Platform.ComputedTextLayout layoutComplexText(
405             int textId,
406             int start,
407             int end,
408             int alignment,
409             int overflow,
410             int maxLines,
411             float maxWidth,
412             int flags) {
413         String str = getText(textId);
414         if (str == null) {
415             return null;
416         }
417         if (end == -1 || end > str.length()) {
418             end = str.length();
419         }
420 
421         TextPaint textPaint = new TextPaint();
422         textPaint.set(mPaint);
423         StaticLayout.Builder staticLayoutBuilder =
424                 StaticLayout.Builder.obtain(str, start, end, textPaint, (int) maxWidth);
425         switch (alignment) {
426             case TextLayout.TEXT_ALIGN_RIGHT:
427             case TextLayout.TEXT_ALIGN_END:
428                 staticLayoutBuilder.setAlignment(Layout.Alignment.ALIGN_OPPOSITE);
429                 break;
430             case TextLayout.TEXT_ALIGN_CENTER:
431                 staticLayoutBuilder.setAlignment(Layout.Alignment.ALIGN_CENTER);
432                 break;
433             default:
434                 staticLayoutBuilder.setAlignment(Layout.Alignment.ALIGN_NORMAL);
435         }
436         switch (overflow) {
437             case TextLayout.OVERFLOW_ELLIPSIS:
438                 staticLayoutBuilder.setEllipsize(TextUtils.TruncateAt.END);
439                 break;
440             case TextLayout.OVERFLOW_MIDDLE_ELLIPSIS:
441                 staticLayoutBuilder.setEllipsize(TextUtils.TruncateAt.MIDDLE);
442                 break;
443             case TextLayout.OVERFLOW_START_ELLIPSIS:
444                 staticLayoutBuilder.setEllipsize(TextUtils.TruncateAt.START);
445                 break;
446             default:
447         }
448         staticLayoutBuilder.setMaxLines(maxLines);
449         staticLayoutBuilder.setIncludePad(false);
450 
451         StaticLayout staticLayout = staticLayoutBuilder.build();
452         return new AndroidComputedTextLayout(
453                 staticLayout, staticLayout.getWidth(), staticLayout.getHeight());
454     }
455 
456     @Override
drawTextRun( int textID, int start, int end, int contextStart, int contextEnd, float x, float y, boolean rtl)457     public void drawTextRun(
458             int textID,
459             int start,
460             int end,
461             int contextStart,
462             int contextEnd,
463             float x,
464             float y,
465             boolean rtl) {
466 
467         String textToPaint = getText(textID);
468         if (textToPaint == null) {
469             return;
470         }
471         if (end == -1) {
472             if (start != 0) {
473                 textToPaint = textToPaint.substring(start);
474             }
475         } else if (end > textToPaint.length()) {
476             textToPaint = textToPaint.substring(start);
477         } else {
478             textToPaint = textToPaint.substring(start, end);
479         }
480 
481         mCanvas.drawText(textToPaint, x, y, mPaint);
482     }
483 
484     @Override
drawComplexText(Platform.ComputedTextLayout computedTextLayout)485     public void drawComplexText(Platform.ComputedTextLayout computedTextLayout) {
486         if (computedTextLayout == null) {
487             return;
488         }
489         StaticLayout staticLayout = ((AndroidComputedTextLayout) computedTextLayout).get();
490         staticLayout.draw(mCanvas);
491     }
492 
493     @Override
drawTweenPath(int path1Id, int path2Id, float tween, float start, float end)494     public void drawTweenPath(int path1Id, int path2Id, float tween, float start, float end) {
495         mCanvas.drawPath(getPath(path1Id, path2Id, tween, start, end), mPaint);
496     }
497 
origamiToPorterDuffMode(int mode)498     private static PorterDuff.Mode origamiToPorterDuffMode(int mode) {
499         switch (mode) {
500             case PaintBundle.BLEND_MODE_CLEAR:
501                 return PorterDuff.Mode.CLEAR;
502             case PaintBundle.BLEND_MODE_SRC:
503                 return PorterDuff.Mode.SRC;
504             case PaintBundle.BLEND_MODE_DST:
505                 return PorterDuff.Mode.DST;
506             case PaintBundle.BLEND_MODE_SRC_OVER:
507                 return PorterDuff.Mode.SRC_OVER;
508             case PaintBundle.BLEND_MODE_DST_OVER:
509                 return PorterDuff.Mode.DST_OVER;
510             case PaintBundle.BLEND_MODE_SRC_IN:
511                 return PorterDuff.Mode.SRC_IN;
512             case PaintBundle.BLEND_MODE_DST_IN:
513                 return PorterDuff.Mode.DST_IN;
514             case PaintBundle.BLEND_MODE_SRC_OUT:
515                 return PorterDuff.Mode.SRC_OUT;
516             case PaintBundle.BLEND_MODE_DST_OUT:
517                 return PorterDuff.Mode.DST_OUT;
518             case PaintBundle.BLEND_MODE_SRC_ATOP:
519                 return PorterDuff.Mode.SRC_ATOP;
520             case PaintBundle.BLEND_MODE_DST_ATOP:
521                 return PorterDuff.Mode.DST_ATOP;
522             case PaintBundle.BLEND_MODE_XOR:
523                 return PorterDuff.Mode.XOR;
524             case PaintBundle.BLEND_MODE_SCREEN:
525                 return PorterDuff.Mode.SCREEN;
526             case PaintBundle.BLEND_MODE_OVERLAY:
527                 return PorterDuff.Mode.OVERLAY;
528             case PaintBundle.BLEND_MODE_DARKEN:
529                 return PorterDuff.Mode.DARKEN;
530             case PaintBundle.BLEND_MODE_LIGHTEN:
531                 return PorterDuff.Mode.LIGHTEN;
532             case PaintBundle.BLEND_MODE_MULTIPLY:
533                 return PorterDuff.Mode.MULTIPLY;
534             case PaintBundle.PORTER_MODE_ADD:
535                 return PorterDuff.Mode.ADD;
536         }
537         return PorterDuff.Mode.SRC_OVER;
538     }
539 
origamiToBlendMode(int mode)540     public static BlendMode origamiToBlendMode(int mode) {
541         switch (mode) {
542             case PaintBundle.BLEND_MODE_CLEAR:
543                 return BlendMode.CLEAR;
544             case PaintBundle.BLEND_MODE_SRC:
545                 return BlendMode.SRC;
546             case PaintBundle.BLEND_MODE_DST:
547                 return BlendMode.DST;
548             case PaintBundle.BLEND_MODE_SRC_OVER:
549                 return BlendMode.SRC_OVER;
550             case PaintBundle.BLEND_MODE_DST_OVER:
551                 return BlendMode.DST_OVER;
552             case PaintBundle.BLEND_MODE_SRC_IN:
553                 return BlendMode.SRC_IN;
554             case PaintBundle.BLEND_MODE_DST_IN:
555                 return BlendMode.DST_IN;
556             case PaintBundle.BLEND_MODE_SRC_OUT:
557                 return BlendMode.SRC_OUT;
558             case PaintBundle.BLEND_MODE_DST_OUT:
559                 return BlendMode.DST_OUT;
560             case PaintBundle.BLEND_MODE_SRC_ATOP:
561                 return BlendMode.SRC_ATOP;
562             case PaintBundle.BLEND_MODE_DST_ATOP:
563                 return BlendMode.DST_ATOP;
564             case PaintBundle.BLEND_MODE_XOR:
565                 return BlendMode.XOR;
566             case PaintBundle.BLEND_MODE_PLUS:
567                 return BlendMode.PLUS;
568             case PaintBundle.BLEND_MODE_MODULATE:
569                 return BlendMode.MODULATE;
570             case PaintBundle.BLEND_MODE_SCREEN:
571                 return BlendMode.SCREEN;
572             case PaintBundle.BLEND_MODE_OVERLAY:
573                 return BlendMode.OVERLAY;
574             case PaintBundle.BLEND_MODE_DARKEN:
575                 return BlendMode.DARKEN;
576             case PaintBundle.BLEND_MODE_LIGHTEN:
577                 return BlendMode.LIGHTEN;
578             case PaintBundle.BLEND_MODE_COLOR_DODGE:
579                 return BlendMode.COLOR_DODGE;
580             case PaintBundle.BLEND_MODE_COLOR_BURN:
581                 return BlendMode.COLOR_BURN;
582             case PaintBundle.BLEND_MODE_HARD_LIGHT:
583                 return BlendMode.HARD_LIGHT;
584             case PaintBundle.BLEND_MODE_SOFT_LIGHT:
585                 return BlendMode.SOFT_LIGHT;
586             case PaintBundle.BLEND_MODE_DIFFERENCE:
587                 return BlendMode.DIFFERENCE;
588             case PaintBundle.BLEND_MODE_EXCLUSION:
589                 return BlendMode.EXCLUSION;
590             case PaintBundle.BLEND_MODE_MULTIPLY:
591                 return BlendMode.MULTIPLY;
592             case PaintBundle.BLEND_MODE_HUE:
593                 return BlendMode.HUE;
594             case PaintBundle.BLEND_MODE_SATURATION:
595                 return BlendMode.SATURATION;
596             case PaintBundle.BLEND_MODE_COLOR:
597                 return BlendMode.COLOR;
598             case PaintBundle.BLEND_MODE_LUMINOSITY:
599                 return BlendMode.LUMINOSITY;
600             case PaintBundle.BLEND_MODE_NULL:
601                 return null;
602         }
603         return null;
604     }
605 
606     PaintChanges mCachedPaintChanges =
607             new PaintChanges() {
608                 @Override
609                 public void setTextSize(float size) {
610                     mPaint.setTextSize(size);
611                 }
612 
613                 @Override
614                 public void setTypeFace(int fontType, int weight, boolean italic) {
615                     int[] type =
616                             new int[] {
617                                 Typeface.NORMAL,
618                                 Typeface.BOLD,
619                                 Typeface.ITALIC,
620                                 Typeface.BOLD_ITALIC
621                             };
622 
623                     switch (fontType) {
624                         case PaintBundle.FONT_TYPE_DEFAULT:
625                             if (weight == 400 && !italic) { // for normal case
626                                 mPaint.setTypeface(Typeface.DEFAULT);
627                             } else {
628                                 mPaint.setTypeface(
629                                         Typeface.create(Typeface.DEFAULT, weight, italic));
630                             }
631                             break;
632                         case PaintBundle.FONT_TYPE_SERIF:
633                             if (weight == 400 && !italic) { // for normal case
634                                 mPaint.setTypeface(Typeface.SERIF);
635                             } else {
636                                 mPaint.setTypeface(Typeface.create(Typeface.SERIF, weight, italic));
637                             }
638                             break;
639                         case PaintBundle.FONT_TYPE_SANS_SERIF:
640                             if (weight == 400 && !italic) { //  for normal case
641                                 mPaint.setTypeface(Typeface.SANS_SERIF);
642                             } else {
643                                 mPaint.setTypeface(
644                                         Typeface.create(Typeface.SANS_SERIF, weight, italic));
645                             }
646                             break;
647                         case PaintBundle.FONT_TYPE_MONOSPACE:
648                             if (weight == 400 && !italic) { //  for normal case
649                                 mPaint.setTypeface(Typeface.MONOSPACE);
650                             } else {
651                                 mPaint.setTypeface(
652                                         Typeface.create(Typeface.MONOSPACE, weight, italic));
653                             }
654 
655                             break;
656                     }
657                 }
658 
659                 @Override
660                 public void setStrokeWidth(float width) {
661                     mPaint.setStrokeWidth(width);
662                 }
663 
664                 @Override
665                 public void setColor(int color) {
666                     mPaint.setColor(color);
667                 }
668 
669                 @Override
670                 public void setStrokeCap(int cap) {
671                     mPaint.setStrokeCap(Paint.Cap.values()[cap]);
672                 }
673 
674                 @Override
675                 public void setStyle(int style) {
676                     mPaint.setStyle(Paint.Style.values()[style]);
677                 }
678 
679                 @SuppressLint("NewApi")
680                 @Override
681                 public void setShader(int shaderId) {
682                     // TODO this stuff should check the shader creation
683                     if (shaderId == 0) {
684                         mPaint.setShader(null);
685                         return;
686                     }
687                     ShaderData data = getShaderData(shaderId);
688                     if (data == null) {
689                         return;
690                     }
691                     RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId()));
692                     String[] names = data.getUniformFloatNames();
693                     for (int i = 0; i < names.length; i++) {
694                         String name = names[i];
695                         float[] val = data.getUniformFloats(name);
696                         shader.setFloatUniform(name, val);
697                     }
698                     names = data.getUniformIntegerNames();
699                     for (int i = 0; i < names.length; i++) {
700                         String name = names[i];
701                         int[] val = data.getUniformInts(name);
702                         shader.setIntUniform(name, val);
703                     }
704                     names = data.getUniformBitmapNames();
705                     for (int i = 0; i < names.length; i++) {
706                         String name = names[i];
707                         int val = data.getUniformBitmapId(name);
708                         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
709                         Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(val);
710                         BitmapShader bitmapShader =
711                                 new BitmapShader(
712                                         bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
713                         shader.setInputShader(name, bitmapShader);
714                     }
715                     mPaint.setShader(shader);
716                 }
717 
718                 @Override
719                 public void setImageFilterQuality(int quality) {
720                     Utils.log(" quality =" + quality);
721                     mPaint.setFilterBitmap(quality == 1);
722                 }
723 
724                 @Override
725                 public void setBlendMode(int mode) {
726                     mPaint.setBlendMode(origamiToBlendMode(mode));
727                 }
728 
729                 @Override
730                 public void setAlpha(float a) {
731                     mPaint.setAlpha((int) (255 * a));
732                 }
733 
734                 @Override
735                 public void setStrokeMiter(float miter) {
736                     mPaint.setStrokeMiter(miter);
737                 }
738 
739                 @Override
740                 public void setStrokeJoin(int join) {
741                     mPaint.setStrokeJoin(Paint.Join.values()[join]);
742                 }
743 
744                 @Override
745                 public void setFilterBitmap(boolean filter) {
746                     mPaint.setFilterBitmap(filter);
747                 }
748 
749                 @Override
750                 public void setAntiAlias(boolean aa) {
751                     mPaint.setAntiAlias(aa);
752                 }
753 
754                 @Override
755                 public void clear(long mask) {
756                     if ((mask & (1L << PaintBundle.COLOR_FILTER)) != 0) {
757                         mPaint.setColorFilter(null);
758                     }
759                 }
760 
761                 Shader.TileMode[] mTileModes =
762                         new Shader.TileMode[] {
763                             Shader.TileMode.CLAMP, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR
764                         };
765 
766                 @Override
767                 public void setLinearGradient(
768                         @NonNull int[] colors,
769                         @NonNull float[] stops,
770                         float startX,
771                         float startY,
772                         float endX,
773                         float endY,
774                         int tileMode) {
775                     mPaint.setShader(
776                             new LinearGradient(
777                                     startX,
778                                     startY,
779                                     endX,
780                                     endY,
781                                     colors,
782                                     stops,
783                                     mTileModes[tileMode]));
784                 }
785 
786                 @Override
787                 public void setRadialGradient(
788                         @NonNull int[] colors,
789                         @NonNull float[] stops,
790                         float centerX,
791                         float centerY,
792                         float radius,
793                         int tileMode) {
794                     mPaint.setShader(
795                             new RadialGradient(
796                                     centerX, centerY, radius, colors, stops, mTileModes[tileMode]));
797                 }
798 
799                 @Override
800                 public void setSweepGradient(
801                         @NonNull int[] colors,
802                         @NonNull float[] stops,
803                         float centerX,
804                         float centerY) {
805                     mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops));
806                 }
807 
808                 @Override
809                 public void setColorFilter(int color, int mode) {
810                     PorterDuff.Mode pmode = origamiToPorterDuffMode(mode);
811                     if (pmode != null) {
812                         mPaint.setColorFilter(new PorterDuffColorFilter(color, pmode));
813                     }
814                 }
815             };
816 
817     /**
818      * This applies paint changes to the current paint
819      *
820      * @param paintData the list change to the paint
821      */
822     @Override
applyPaint(@onNull PaintBundle paintData)823     public void applyPaint(@NonNull PaintBundle paintData) {
824         paintData.applyPaintChange(this, mCachedPaintChanges);
825     }
826 
827     @Override
matrixScale(float scaleX, float scaleY, float centerX, float centerY)828     public void matrixScale(float scaleX, float scaleY, float centerX, float centerY) {
829         if (Float.isNaN(centerX)) {
830             mCanvas.scale(scaleX, scaleY);
831         } else {
832             mCanvas.scale(scaleX, scaleY, centerX, centerY);
833         }
834     }
835 
836     @Override
matrixTranslate(float translateX, float translateY)837     public void matrixTranslate(float translateX, float translateY) {
838         mCanvas.translate(translateX, translateY);
839     }
840 
841     @Override
matrixSkew(float skewX, float skewY)842     public void matrixSkew(float skewX, float skewY) {
843         mCanvas.skew(skewX, skewY);
844     }
845 
846     @Override
matrixRotate(float rotate, float pivotX, float pivotY)847     public void matrixRotate(float rotate, float pivotX, float pivotY) {
848         if (Float.isNaN(pivotX)) {
849             mCanvas.rotate(rotate);
850         } else {
851             mCanvas.rotate(rotate, pivotX, pivotY);
852         }
853     }
854 
855     @Override
matrixSave()856     public void matrixSave() {
857         mCanvas.save();
858     }
859 
860     @Override
matrixRestore()861     public void matrixRestore() {
862         mCanvas.restore();
863     }
864 
865     @Override
clipRect(float left, float top, float right, float bottom)866     public void clipRect(float left, float top, float right, float bottom) {
867         mCanvas.clipRect(left, top, right, bottom);
868     }
869 
870     @Override
roundedClipRect( float width, float height, float topStart, float topEnd, float bottomStart, float bottomEnd)871     public void roundedClipRect(
872             float width,
873             float height,
874             float topStart,
875             float topEnd,
876             float bottomStart,
877             float bottomEnd) {
878         Path roundedPath = new Path();
879         float[] radii =
880                 new float[] {
881                     topStart,
882                     topStart,
883                     topEnd,
884                     topEnd,
885                     bottomEnd,
886                     bottomEnd,
887                     bottomStart,
888                     bottomStart
889                 };
890 
891         roundedPath.addRoundRect(0f, 0f, width, height, radii, android.graphics.Path.Direction.CW);
892         mCanvas.clipPath(roundedPath);
893     }
894 
895     @Override
clipPath(int pathId, int regionOp)896     public void clipPath(int pathId, int regionOp) {
897         Path path = getPath(pathId, 0, 1);
898         if (regionOp == ClipPath.DIFFERENCE) {
899             mCanvas.clipOutPath(path); // DIFFERENCE
900         } else {
901             mCanvas.clipPath(path); // INTERSECT
902         }
903     }
904 
905     @Override
tweenPath(int out, int path1, int path2, float tween)906     public void tweenPath(int out, int path1, int path2, float tween) {
907         float[] p = getPathArray(path1, path2, tween);
908         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
909         androidContext.mRemoteComposeState.putPathData(out, p);
910     }
911 
912     @Override
combinePath(int out, int path1, int path2, byte operation)913     public void combinePath(int out, int path1, int path2, byte operation) {
914         Path p1 = getPath(path1, 0, 1);
915         Path p2 = getPath(path2, 0, 1);
916         Path.Op[] op = {
917             Path.Op.DIFFERENCE,
918             Path.Op.INTERSECT,
919             Path.Op.REVERSE_DIFFERENCE,
920             Path.Op.UNION,
921             Path.Op.XOR,
922         };
923         Path p = new Path(p1);
924         p.op(p2, op[operation]);
925 
926         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
927         androidContext.mRemoteComposeState.putPath(out, p);
928     }
929 
930     @Override
reset()931     public void reset() {
932         mPaint.reset();
933     }
934 
getPath(int path1Id, int path2Id, float tween, float start, float end)935     private Path getPath(int path1Id, int path2Id, float tween, float start, float end) {
936         return getPath(getPathArray(path1Id, path2Id, tween), start, end);
937     }
938 
getPathArray(int path1Id, int path2Id, float tween)939     private float[] getPathArray(int path1Id, int path2Id, float tween) {
940         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
941         if (tween == 0.0f) {
942             return androidContext.mRemoteComposeState.getPathData(path1Id);
943         }
944         if (tween == 1.0f) {
945             return androidContext.mRemoteComposeState.getPathData(path2Id);
946         }
947 
948         float[] data1 = androidContext.mRemoteComposeState.getPathData(path1Id);
949         float[] data2 = androidContext.mRemoteComposeState.getPathData(path2Id);
950         float[] tmp = new float[data2.length];
951         for (int i = 0; i < tmp.length; i++) {
952             if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) {
953                 tmp[i] = data1[i];
954             } else {
955                 tmp[i] = (data2[i] - data1[i]) * tween + data1[i];
956             }
957         }
958         return tmp;
959     }
960 
getPath(float[] tmp, float start, float end)961     private Path getPath(float[] tmp, float start, float end) {
962         Path path = new Path();
963         FloatsToPath.genPath(path, tmp, start, end);
964         return path;
965     }
966 
getPath(int id, float start, float end)967     private Path getPath(int id, float start, float end) {
968         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
969         Path p = (Path) androidContext.mRemoteComposeState.getPath(id);
970         if (p != null) {
971             return p;
972         }
973         Path path = new Path();
974         float[] pathData = androidContext.mRemoteComposeState.getPathData(id);
975         if (pathData != null) {
976             FloatsToPath.genPath(path, pathData, start, end);
977             androidContext.mRemoteComposeState.putPath(id, path);
978         }
979 
980         return path;
981     }
982 
983     @Override
getText(int id)984     public @Nullable String getText(int id) {
985         return (String) mContext.mRemoteComposeState.getFromId(id);
986     }
987 
getShaderData(int id)988     private ShaderData getShaderData(int id) {
989         return (ShaderData) mContext.mRemoteComposeState.getFromId(id);
990     }
991 }
992