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.graphics.Bitmap; 19 import android.graphics.BlendMode; 20 import android.graphics.Canvas; 21 import android.graphics.LinearGradient; 22 import android.graphics.Paint; 23 import android.graphics.Path; 24 import android.graphics.PorterDuff; 25 import android.graphics.PorterDuffColorFilter; 26 import android.graphics.RadialGradient; 27 import android.graphics.Rect; 28 import android.graphics.RectF; 29 import android.graphics.RuntimeShader; 30 import android.graphics.Shader; 31 import android.graphics.SweepGradient; 32 import android.graphics.Typeface; 33 34 import com.android.internal.widget.remotecompose.core.PaintContext; 35 import com.android.internal.widget.remotecompose.core.RemoteContext; 36 import com.android.internal.widget.remotecompose.core.operations.ClipPath; 37 import com.android.internal.widget.remotecompose.core.operations.ShaderData; 38 import com.android.internal.widget.remotecompose.core.operations.Utils; 39 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; 40 import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges; 41 42 /** 43 * An implementation of PaintContext for the Android Canvas. 44 * This is used to play the RemoteCompose operations on Android. 45 */ 46 public class AndroidPaintContext extends PaintContext { 47 Paint mPaint = new Paint(); 48 Canvas mCanvas; 49 Rect mTmpRect = new Rect(); // use in calculation of bounds 50 AndroidPaintContext(RemoteContext context, Canvas canvas)51 public AndroidPaintContext(RemoteContext context, Canvas canvas) { 52 super(context); 53 this.mCanvas = canvas; 54 } 55 getCanvas()56 public Canvas getCanvas() { 57 return mCanvas; 58 } 59 setCanvas(Canvas canvas)60 public void setCanvas(Canvas canvas) { 61 this.mCanvas = canvas; 62 } 63 64 /** 65 * Draw an image onto the canvas 66 * 67 * @param imageId the id of the image 68 * @param srcLeft left coordinate of the source area 69 * @param srcTop top coordinate of the source area 70 * @param srcRight right coordinate of the source area 71 * @param srcBottom bottom coordinate of the source area 72 * @param dstLeft left coordinate of the destination area 73 * @param dstTop top coordinate of the destination area 74 * @param dstRight right coordinate of the destination area 75 * @param dstBottom bottom coordinate of the destination area 76 */ 77 78 @Override drawBitmap(int imageId, int srcLeft, int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight, int dstBottom, int cdId)79 public void drawBitmap(int imageId, 80 int srcLeft, 81 int srcTop, 82 int srcRight, 83 int srcBottom, 84 int dstLeft, 85 int dstTop, 86 int dstRight, 87 int dstBottom, 88 int cdId) { 89 AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; 90 if (androidContext.mRemoteComposeState.containsId(imageId)) { 91 Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState 92 .getFromId(imageId); 93 mCanvas.drawBitmap( 94 bitmap, 95 new Rect(srcLeft, srcTop, srcRight, srcBottom), 96 new Rect(dstLeft, dstTop, dstRight, dstBottom), mPaint 97 ); 98 } 99 } 100 101 @Override scale(float scaleX, float scaleY)102 public void scale(float scaleX, float scaleY) { 103 mCanvas.scale(scaleX, scaleY); 104 } 105 106 @Override translate(float translateX, float translateY)107 public void translate(float translateX, float translateY) { 108 mCanvas.translate(translateX, translateY); 109 } 110 111 @Override drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)112 public void drawArc(float left, 113 float top, 114 float right, 115 float bottom, 116 float startAngle, 117 float sweepAngle) { 118 mCanvas.drawArc(left, top, right, bottom, startAngle, 119 sweepAngle, true, mPaint); 120 } 121 122 @Override drawBitmap(int id, float left, float top, float right, float bottom)123 public void drawBitmap(int id, 124 float left, 125 float top, 126 float right, 127 float bottom) { 128 AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; 129 if (androidContext.mRemoteComposeState.containsId(id)) { 130 Bitmap bitmap = 131 (Bitmap) androidContext.mRemoteComposeState.getFromId(id); 132 Rect src = new Rect(0, 0, 133 bitmap.getWidth(), bitmap.getHeight()); 134 RectF dst = new RectF(left, top, right, bottom); 135 mCanvas.drawBitmap(bitmap, src, dst, mPaint); 136 } 137 } 138 139 @Override drawCircle(float centerX, float centerY, float radius)140 public void drawCircle(float centerX, float centerY, float radius) { 141 mCanvas.drawCircle(centerX, centerY, radius, mPaint); 142 } 143 144 @Override drawLine(float x1, float y1, float x2, float y2)145 public void drawLine(float x1, float y1, float x2, float y2) { 146 mCanvas.drawLine(x1, y1, x2, y2, mPaint); 147 } 148 149 @Override drawOval(float left, float top, float right, float bottom)150 public void drawOval(float left, float top, float right, float bottom) { 151 mCanvas.drawOval(left, top, right, bottom, mPaint); 152 } 153 154 @Override drawPath(int id, float start, float end)155 public void drawPath(int id, float start, float end) { 156 mCanvas.drawPath(getPath(id, start, end), mPaint); 157 } 158 159 @Override drawRect(float left, float top, float right, float bottom)160 public void drawRect(float left, float top, float right, float bottom) { 161 mCanvas.drawRect(left, top, right, bottom, mPaint); 162 } 163 164 @Override drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY)165 public void drawRoundRect(float left, 166 float top, 167 float right, 168 float bottom, 169 float radiusX, 170 float radiusY) { 171 mCanvas.drawRoundRect(left, top, right, bottom, 172 radiusX, radiusY, mPaint); 173 } 174 175 @Override drawTextOnPath(int textId, int pathId, float hOffset, float vOffset)176 public void drawTextOnPath(int textId, 177 int pathId, 178 float hOffset, 179 float vOffset) { 180 mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint); 181 } 182 183 @Override getTextBounds(int textId, int start, int end, boolean monospace, float[] bounds)184 public void getTextBounds(int textId, int start, int end, boolean monospace, float[] bounds) { 185 String str = getText(textId); 186 if (end == -1) { 187 end = str.length(); 188 } 189 190 mPaint.getTextBounds(str, start, end, mTmpRect); 191 192 bounds[0] = mTmpRect.left; 193 bounds[1] = mTmpRect.top; 194 bounds[2] = monospace ? (mPaint.measureText(str, start, end) - mTmpRect.left) 195 : mTmpRect.right; 196 bounds[3] = mTmpRect.bottom; 197 } 198 199 @Override drawTextRun(int textID, int start, int end, int contextStart, int contextEnd, float x, float y, boolean rtl)200 public void drawTextRun(int textID, 201 int start, 202 int end, 203 int contextStart, 204 int contextEnd, 205 float x, 206 float y, 207 boolean rtl) { 208 209 String textToPaint = getText(textID); 210 if (end == -1) { 211 if (start != 0) { 212 textToPaint = textToPaint.substring(start); 213 } 214 } else { 215 textToPaint = textToPaint.substring(start, end); 216 } 217 218 mCanvas.drawText(textToPaint, x, y, mPaint); 219 } 220 221 @Override drawTweenPath(int path1Id, int path2Id, float tween, float start, float end)222 public void drawTweenPath(int path1Id, 223 int path2Id, 224 float tween, 225 float start, 226 float end) { 227 mCanvas.drawPath(getPath(path1Id, path2Id, tween, start, end), mPaint); 228 } 229 origamiToPorterDuffMode(int mode)230 private static PorterDuff.Mode origamiToPorterDuffMode(int mode) { 231 switch (mode) { 232 case PaintBundle.BLEND_MODE_CLEAR: 233 return PorterDuff.Mode.CLEAR; 234 case PaintBundle.BLEND_MODE_SRC: 235 return PorterDuff.Mode.SRC; 236 case PaintBundle.BLEND_MODE_DST: 237 return PorterDuff.Mode.DST; 238 case PaintBundle.BLEND_MODE_SRC_OVER: 239 return PorterDuff.Mode.SRC_OVER; 240 case PaintBundle.BLEND_MODE_DST_OVER: 241 return PorterDuff.Mode.DST_OVER; 242 case PaintBundle.BLEND_MODE_SRC_IN: 243 return PorterDuff.Mode.SRC_IN; 244 case PaintBundle.BLEND_MODE_DST_IN: 245 return PorterDuff.Mode.DST_IN; 246 case PaintBundle.BLEND_MODE_SRC_OUT: 247 return PorterDuff.Mode.SRC_OUT; 248 case PaintBundle.BLEND_MODE_DST_OUT: 249 return PorterDuff.Mode.DST_OUT; 250 case PaintBundle.BLEND_MODE_SRC_ATOP: 251 return PorterDuff.Mode.SRC_ATOP; 252 case PaintBundle.BLEND_MODE_DST_ATOP: 253 return PorterDuff.Mode.DST_ATOP; 254 case PaintBundle.BLEND_MODE_XOR: 255 return PorterDuff.Mode.XOR; 256 case PaintBundle.BLEND_MODE_SCREEN: 257 return PorterDuff.Mode.SCREEN; 258 case PaintBundle.BLEND_MODE_OVERLAY: 259 return PorterDuff.Mode.OVERLAY; 260 case PaintBundle.BLEND_MODE_DARKEN: 261 return PorterDuff.Mode.DARKEN; 262 case PaintBundle.BLEND_MODE_LIGHTEN: 263 return PorterDuff.Mode.LIGHTEN; 264 case PaintBundle.BLEND_MODE_MULTIPLY: 265 return PorterDuff.Mode.MULTIPLY; 266 case PaintBundle.PORTER_MODE_ADD: 267 return PorterDuff.Mode.ADD; 268 } 269 return PorterDuff.Mode.SRC_OVER; 270 } 271 origamiToBlendMode(int mode)272 public static BlendMode origamiToBlendMode(int mode) { 273 switch (mode) { 274 case PaintBundle.BLEND_MODE_CLEAR: 275 return BlendMode.CLEAR; 276 case PaintBundle.BLEND_MODE_SRC: 277 return BlendMode.SRC; 278 case PaintBundle.BLEND_MODE_DST: 279 return BlendMode.DST; 280 case PaintBundle.BLEND_MODE_SRC_OVER: 281 return BlendMode.SRC_OVER; 282 case PaintBundle.BLEND_MODE_DST_OVER: 283 return BlendMode.DST_OVER; 284 case PaintBundle.BLEND_MODE_SRC_IN: 285 return BlendMode.SRC_IN; 286 case PaintBundle.BLEND_MODE_DST_IN: 287 return BlendMode.DST_IN; 288 case PaintBundle.BLEND_MODE_SRC_OUT: 289 return BlendMode.SRC_OUT; 290 case PaintBundle.BLEND_MODE_DST_OUT: 291 return BlendMode.DST_OUT; 292 case PaintBundle.BLEND_MODE_SRC_ATOP: 293 return BlendMode.SRC_ATOP; 294 case PaintBundle.BLEND_MODE_DST_ATOP: 295 return BlendMode.DST_ATOP; 296 case PaintBundle.BLEND_MODE_XOR: 297 return BlendMode.XOR; 298 case PaintBundle.BLEND_MODE_PLUS: 299 return BlendMode.PLUS; 300 case PaintBundle.BLEND_MODE_MODULATE: 301 return BlendMode.MODULATE; 302 case PaintBundle.BLEND_MODE_SCREEN: 303 return BlendMode.SCREEN; 304 case PaintBundle.BLEND_MODE_OVERLAY: 305 return BlendMode.OVERLAY; 306 case PaintBundle.BLEND_MODE_DARKEN: 307 return BlendMode.DARKEN; 308 case PaintBundle.BLEND_MODE_LIGHTEN: 309 return BlendMode.LIGHTEN; 310 case PaintBundle.BLEND_MODE_COLOR_DODGE: 311 return BlendMode.COLOR_DODGE; 312 case PaintBundle.BLEND_MODE_COLOR_BURN: 313 return BlendMode.COLOR_BURN; 314 case PaintBundle.BLEND_MODE_HARD_LIGHT: 315 return BlendMode.HARD_LIGHT; 316 case PaintBundle.BLEND_MODE_SOFT_LIGHT: 317 return BlendMode.SOFT_LIGHT; 318 case PaintBundle.BLEND_MODE_DIFFERENCE: 319 return BlendMode.DIFFERENCE; 320 case PaintBundle.BLEND_MODE_EXCLUSION: 321 return BlendMode.EXCLUSION; 322 case PaintBundle.BLEND_MODE_MULTIPLY: 323 return BlendMode.MULTIPLY; 324 case PaintBundle.BLEND_MODE_HUE: 325 return BlendMode.HUE; 326 case PaintBundle.BLEND_MODE_SATURATION: 327 return BlendMode.SATURATION; 328 case PaintBundle.BLEND_MODE_COLOR: 329 return BlendMode.COLOR; 330 case PaintBundle.BLEND_MODE_LUMINOSITY: 331 return BlendMode.LUMINOSITY; 332 case PaintBundle.BLEND_MODE_NULL: 333 return null; 334 } 335 return null; 336 } 337 338 @Override applyPaint(PaintBundle mPaintData)339 public void applyPaint(PaintBundle mPaintData) { 340 mPaintData.applyPaintChange((PaintContext) this, new PaintChanges() { 341 @Override 342 public void setTextSize(float size) { 343 mPaint.setTextSize(size); 344 } 345 346 @Override 347 public void setTypeFace(int fontType, int weight, boolean italic) { 348 int[] type = new int[]{Typeface.NORMAL, Typeface.BOLD, 349 Typeface.ITALIC, Typeface.BOLD_ITALIC}; 350 351 switch (fontType) { 352 case PaintBundle.FONT_TYPE_DEFAULT: { 353 if (weight == 400 && !italic) { // for normal case 354 mPaint.setTypeface(Typeface.DEFAULT); 355 } else { 356 mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, 357 weight, italic)); 358 } 359 break; 360 } 361 case PaintBundle.FONT_TYPE_SERIF: { 362 if (weight == 400 && !italic) { // for normal case 363 mPaint.setTypeface(Typeface.SERIF); 364 } else { 365 mPaint.setTypeface(Typeface.create(Typeface.SERIF, 366 weight, italic)); 367 } 368 break; 369 } 370 case PaintBundle.FONT_TYPE_SANS_SERIF: { 371 if (weight == 400 && !italic) { // for normal case 372 mPaint.setTypeface(Typeface.SANS_SERIF); 373 } else { 374 mPaint.setTypeface( 375 Typeface.create(Typeface.SANS_SERIF, 376 weight, italic)); 377 } 378 break; 379 } 380 case PaintBundle.FONT_TYPE_MONOSPACE: { 381 if (weight == 400 && !italic) { // for normal case 382 mPaint.setTypeface(Typeface.MONOSPACE); 383 } else { 384 mPaint.setTypeface( 385 Typeface.create(Typeface.MONOSPACE, 386 weight, italic)); 387 } 388 389 break; 390 } 391 } 392 393 } 394 395 @Override 396 public void setStrokeWidth(float width) { 397 mPaint.setStrokeWidth(width); 398 } 399 400 @Override 401 public void setColor(int color) { 402 mPaint.setColor(color); 403 } 404 405 @Override 406 public void setStrokeCap(int cap) { 407 mPaint.setStrokeCap(Paint.Cap.values()[cap]); 408 } 409 410 @Override 411 public void setStyle(int style) { 412 mPaint.setStyle(Paint.Style.values()[style]); 413 } 414 415 @Override 416 public void setShader(int shaderId) { 417 // TODO this stuff should check the shader creation 418 if (shaderId == 0) { 419 mPaint.setShader(null); 420 return; 421 } 422 ShaderData data = getShaderData(shaderId); 423 RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId())); 424 String[] names = data.getUniformFloatNames(); 425 for (int i = 0; i < names.length; i++) { 426 String name = names[i]; 427 float[] val = data.getUniformFloats(name); 428 shader.setFloatUniform(name, val); 429 } 430 names = data.getUniformIntegerNames(); 431 for (int i = 0; i < names.length; i++) { 432 String name = names[i]; 433 int[] val = data.getUniformInts(name); 434 shader.setIntUniform(name, val); 435 } 436 names = data.getUniformBitmapNames(); 437 for (int i = 0; i < names.length; i++) { 438 String name = names[i]; 439 int val = data.getUniformBitmapId(name); 440 } 441 mPaint.setShader(shader); 442 } 443 444 @Override 445 public void setImageFilterQuality(int quality) { 446 Utils.log(" quality =" + quality); 447 } 448 449 @Override 450 public void setBlendMode(int mode) { 451 mPaint.setBlendMode(origamiToBlendMode(mode)); 452 } 453 454 @Override 455 public void setAlpha(float a) { 456 mPaint.setAlpha((int) (255 * a)); 457 } 458 459 @Override 460 public void setStrokeMiter(float miter) { 461 mPaint.setStrokeMiter(miter); 462 } 463 464 @Override 465 public void setStrokeJoin(int join) { 466 mPaint.setStrokeJoin(Paint.Join.values()[join]); 467 } 468 469 @Override 470 public void setFilterBitmap(boolean filter) { 471 mPaint.setFilterBitmap(filter); 472 } 473 474 @Override 475 public void setAntiAlias(boolean aa) { 476 mPaint.setAntiAlias(aa); 477 } 478 479 @Override 480 public void clear(long mask) { 481 if (true) return; 482 long m = mask; 483 int k = 1; 484 while (m > 0) { 485 if ((m & 1) == 1L) { 486 switch (k) { 487 488 case PaintBundle.COLOR_FILTER: 489 mPaint.setColorFilter(null); 490 break; 491 } 492 } 493 k++; 494 m = m >> 1; 495 } 496 } 497 498 Shader.TileMode[] mTileModes = new Shader.TileMode[]{ 499 Shader.TileMode.CLAMP, 500 Shader.TileMode.REPEAT, 501 Shader.TileMode.MIRROR}; 502 503 @Override 504 public void setLinearGradient(int[] colors, 505 float[] stops, 506 float startX, 507 float startY, 508 float endX, 509 float endY, 510 int tileMode) { 511 mPaint.setShader(new LinearGradient(startX, 512 startY, 513 endX, 514 endY, colors, stops, mTileModes[tileMode])); 515 516 } 517 518 @Override 519 public void setRadialGradient(int[] colors, 520 float[] stops, 521 float centerX, 522 float centerY, 523 float radius, 524 int tileMode) { 525 mPaint.setShader(new RadialGradient(centerX, centerY, radius, 526 colors, stops, mTileModes[tileMode])); 527 } 528 529 @Override 530 public void setSweepGradient(int[] colors, 531 float[] stops, 532 float centerX, 533 float centerY) { 534 mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops)); 535 536 } 537 538 @Override 539 public void setColorFilter(int color, int mode) { 540 PorterDuff.Mode pmode = origamiToPorterDuffMode(mode); 541 if (pmode != null) { 542 mPaint.setColorFilter( 543 new PorterDuffColorFilter(color, pmode)); 544 } 545 } 546 }); 547 } 548 549 @Override matrixScale(float scaleX, float scaleY, float centerX, float centerY)550 public void matrixScale(float scaleX, 551 float scaleY, 552 float centerX, 553 float centerY) { 554 if (Float.isNaN(centerX)) { 555 mCanvas.scale(scaleX, scaleY); 556 } else { 557 mCanvas.scale(scaleX, scaleY, centerX, centerY); 558 } 559 } 560 561 @Override matrixTranslate(float translateX, float translateY)562 public void matrixTranslate(float translateX, float translateY) { 563 mCanvas.translate(translateX, translateY); 564 } 565 566 @Override matrixSkew(float skewX, float skewY)567 public void matrixSkew(float skewX, float skewY) { 568 mCanvas.skew(skewX, skewY); 569 } 570 571 @Override matrixRotate(float rotate, float pivotX, float pivotY)572 public void matrixRotate(float rotate, float pivotX, float pivotY) { 573 if (Float.isNaN(pivotX)) { 574 mCanvas.rotate(rotate); 575 } else { 576 mCanvas.rotate(rotate, pivotX, pivotY); 577 578 } 579 } 580 581 @Override matrixSave()582 public void matrixSave() { 583 mCanvas.save(); 584 } 585 586 @Override matrixRestore()587 public void matrixRestore() { 588 mCanvas.restore(); 589 } 590 591 @Override clipRect(float left, float top, float right, float bottom)592 public void clipRect(float left, float top, float right, float bottom) { 593 mCanvas.clipRect(left, top, right, bottom); 594 } 595 596 @Override clipPath(int pathId, int regionOp)597 public void clipPath(int pathId, int regionOp) { 598 Path path = getPath(pathId, 0, 1); 599 if (regionOp == ClipPath.DIFFERENCE) { 600 mCanvas.clipOutPath(path); // DIFFERENCE 601 } else { 602 mCanvas.clipPath(path); // INTERSECT 603 } 604 } 605 606 @Override reset()607 public void reset() { 608 mPaint.reset(); 609 } 610 getPath(int path1Id, int path2Id, float tween, float start, float end)611 private Path getPath(int path1Id, 612 int path2Id, 613 float tween, 614 float start, 615 float end) { 616 if (tween == 0.0f) { 617 return getPath(path1Id, start, end); 618 } 619 if (tween == 1.0f) { 620 return getPath(path2Id, start, end); 621 } 622 AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; 623 float[] data1 = 624 (float[]) androidContext.mRemoteComposeState.getFromId(path1Id); 625 float[] data2 = 626 (float[]) androidContext.mRemoteComposeState.getFromId(path2Id); 627 float[] tmp = new float[data2.length]; 628 for (int i = 0; i < tmp.length; i++) { 629 if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) { 630 tmp[i] = data1[i]; 631 } else { 632 tmp[i] = (data2[i] - data1[i]) * tween + data1[i]; 633 } 634 } 635 Path path = new Path(); 636 FloatsToPath.genPath(path, tmp, start, end); 637 return path; 638 } 639 getPath(int id, float start, float end)640 private Path getPath(int id, float start, float end) { 641 AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; 642 Path path = new Path(); 643 if (androidContext.mRemoteComposeState.containsId(id)) { 644 float[] data = 645 (float[]) androidContext.mRemoteComposeState.getFromId(id); 646 FloatsToPath.genPath(path, data, start, end); 647 } 648 return path; 649 } 650 getText(int id)651 private String getText(int id) { 652 return (String) mContext.mRemoteComposeState.getFromId(id); 653 } 654 getShaderData(int id)655 private ShaderData getShaderData(int id) { 656 return (ShaderData) mContext.mRemoteComposeState.getFromId(id); 657 } 658 } 659 660