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