1 /* 2 * Copyright (C) 2016 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 17 package android.graphics; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 import com.android.layoutlib.bridge.impl.DelegateManager; 22 import com.android.layoutlib.bridge.impl.GcSnapshot; 23 import com.android.layoutlib.bridge.impl.PorterDuffUtility; 24 import com.android.ninepatch.NinePatchChunk; 25 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 26 27 import android.annotation.Nullable; 28 import android.text.TextUtils; 29 30 import java.awt.*; 31 import java.awt.geom.AffineTransform; 32 import java.awt.geom.Arc2D; 33 import java.awt.geom.Rectangle2D; 34 import java.awt.image.BufferedImage; 35 import java.awt.image.ColorModel; 36 import java.awt.image.DataBuffer; 37 38 public class BaseCanvas_Delegate { 39 // ---- delegate manager ---- 40 protected static DelegateManager<BaseCanvas_Delegate> sManager = 41 new DelegateManager<>(BaseCanvas_Delegate.class); 42 43 // ---- delegate helper data ---- 44 private final static boolean[] sBoolOut = new boolean[1]; 45 46 47 // ---- delegate data ---- 48 protected Bitmap_Delegate mBitmap; 49 protected GcSnapshot mSnapshot; 50 51 // ---- Public Helper methods ---- 52 BaseCanvas_Delegate(Bitmap_Delegate bitmap)53 protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) { 54 mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); 55 } 56 BaseCanvas_Delegate()57 protected BaseCanvas_Delegate() { 58 mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); 59 } 60 61 /** 62 * Disposes of the {@link Graphics2D} stack. 63 */ dispose()64 protected void dispose() { 65 mSnapshot.dispose(); 66 } 67 68 /** 69 * Returns the current {@link Graphics2D} used to draw. 70 */ getSnapshot()71 public GcSnapshot getSnapshot() { 72 return mSnapshot; 73 } 74 75 // ---- native methods ---- 76 77 @LayoutlibDelegate nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity)78 /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, 79 long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) { 80 // get the delegate from the native int. 81 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); 82 if (bitmapDelegate == null) { 83 return; 84 } 85 86 BufferedImage image = bitmapDelegate.getImage(); 87 float right = left + image.getWidth(); 88 float bottom = top + image.getHeight(); 89 90 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 91 0, 0, image.getWidth(), image.getHeight(), 92 (int)left, (int)top, (int)right, (int)bottom); 93 } 94 95 @LayoutlibDelegate nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity)96 /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop, 97 float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, 98 float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) { 99 // get the delegate from the native int. 100 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); 101 if (bitmapDelegate == null) { 102 return; 103 } 104 105 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop, 106 (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight, 107 (int) dstBottom); 108 } 109 110 @LayoutlibDelegate nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride, final float x, final float y, int width, int height, boolean hasAlpha, long nativePaintOrZero)111 /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride, 112 final float x, final float y, int width, int height, boolean hasAlpha, 113 long nativePaintOrZero) { 114 // create a temp BufferedImage containing the content. 115 final BufferedImage image = new BufferedImage(width, height, 116 hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); 117 image.setRGB(0, 0, width, height, colors, offset, stride); 118 119 draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, 120 (graphics, paint) -> { 121 if (paint != null && paint.isFilterBitmap()) { 122 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 123 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 124 } 125 126 graphics.drawImage(image, (int) x, (int) y, null); 127 }); 128 } 129 130 @LayoutlibDelegate nDrawColor(long nativeCanvas, final int color, final int mode)131 /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) { 132 // get the delegate from the native int. 133 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); 134 if (canvasDelegate == null) { 135 return; 136 } 137 138 final int w = canvasDelegate.mBitmap.getImage().getWidth(); 139 final int h = canvasDelegate.mBitmap.getImage().getHeight(); 140 draw(nativeCanvas, (graphics, paint) -> { 141 // reset its transform just in case 142 graphics.setTransform(new AffineTransform()); 143 144 // set the color 145 graphics.setColor(new java.awt.Color(color, true /*alpha*/)); 146 147 Composite composite = PorterDuffUtility.getComposite( 148 PorterDuffUtility.getPorterDuffMode(mode), 0xFF); 149 if (composite != null) { 150 graphics.setComposite(composite); 151 } 152 153 graphics.fillRect(0, 0, w, h); 154 }); 155 } 156 157 @LayoutlibDelegate nDrawPaint(long nativeCanvas, long paint)158 /*package*/ static void nDrawPaint(long nativeCanvas, long paint) { 159 // FIXME 160 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 161 "Canvas.drawPaint is not supported.", null, null /*data*/); 162 } 163 164 @LayoutlibDelegate nDrawPoint(long nativeCanvas, float x, float y, long nativePaint)165 /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y, 166 long nativePaint) { 167 // TODO: need to support the attribute (e.g. stroke width) of paint 168 draw(nativeCanvas, nativePaint, false /*compositeOnly*/, false /*forceSrcMode*/, 169 (graphics, paintDelegate) -> graphics.fillRect((int)x, (int)y, 1, 1)); 170 } 171 172 @LayoutlibDelegate nDrawPoints(long nativeCanvas, float[] pts, int offset, int count, long nativePaint)173 /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count, 174 long nativePaint) { 175 if (offset < 0 || count < 0 || offset + count > pts.length) { 176 throw new IllegalArgumentException("Invalid argument set"); 177 } 178 // ignore the last point if the count is odd (It means it is not paired). 179 count = (count >> 1) << 1; 180 for (int i = offset; i < offset + count; i += 2) { 181 nDrawPoint(nativeCanvas, pts[i], pts[i + 1], nativePaint); 182 } 183 } 184 185 @LayoutlibDelegate nDrawLine(long nativeCanvas, final float startX, final float startY, final float stopX, final float stopY, long paint)186 /*package*/ static void nDrawLine(long nativeCanvas, 187 final float startX, final float startY, final float stopX, final float stopY, 188 long paint) { 189 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 190 (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY)); 191 } 192 193 @LayoutlibDelegate nDrawLines(long nativeCanvas, final float[] pts, final int offset, final int count, long nativePaint)194 /*package*/ static void nDrawLines(long nativeCanvas, 195 final float[] pts, final int offset, final int count, 196 long nativePaint) { 197 draw(nativeCanvas, nativePaint, false /*compositeOnly*/, 198 false /*forceSrcMode*/, (graphics, paintDelegate) -> { 199 for (int i = 0; i < count; i += 4) { 200 graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], 201 (int) pts[i + offset + 2], (int) pts[i + offset + 3]); 202 } 203 }); 204 } 205 206 @LayoutlibDelegate nDrawRect(long nativeCanvas, final float left, final float top, final float right, final float bottom, long paint)207 /*package*/ static void nDrawRect(long nativeCanvas, 208 final float left, final float top, final float right, final float bottom, long paint) { 209 210 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 211 (graphics, paintDelegate) -> { 212 int style = paintDelegate.getStyle(); 213 214 // draw 215 if (style == Paint.Style.FILL.nativeInt || 216 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 217 graphics.fillRect((int)left, (int)top, 218 (int)(right-left), (int)(bottom-top)); 219 } 220 221 if (style == Paint.Style.STROKE.nativeInt || 222 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 223 graphics.drawRect((int)left, (int)top, 224 (int)(right-left), (int)(bottom-top)); 225 } 226 }); 227 } 228 229 @LayoutlibDelegate nDrawOval(long nativeCanvas, final float left, final float top, final float right, final float bottom, long paint)230 /*package*/ static void nDrawOval(long nativeCanvas, final float left, 231 final float top, final float right, final float bottom, long paint) { 232 if (right > left && bottom > top) { 233 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 234 (graphics, paintDelegate) -> { 235 int style = paintDelegate.getStyle(); 236 237 // draw 238 if (style == Paint.Style.FILL.nativeInt || 239 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 240 graphics.fillOval((int)left, (int)top, 241 (int)(right - left), (int)(bottom - top)); 242 } 243 244 if (style == Paint.Style.STROKE.nativeInt || 245 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 246 graphics.drawOval((int)left, (int)top, 247 (int)(right - left), (int)(bottom - top)); 248 } 249 }); 250 } 251 } 252 253 @LayoutlibDelegate nDrawCircle(long nativeCanvas, float cx, float cy, float radius, long paint)254 /*package*/ static void nDrawCircle(long nativeCanvas, 255 float cx, float cy, float radius, long paint) { 256 nDrawOval(nativeCanvas, 257 cx - radius, cy - radius, cx + radius, cy + radius, 258 paint); 259 } 260 261 @LayoutlibDelegate nDrawArc(long nativeCanvas, final float left, final float top, final float right, final float bottom, final float startAngle, final float sweep, final boolean useCenter, long paint)262 /*package*/ static void nDrawArc(long nativeCanvas, 263 final float left, final float top, final float right, final float bottom, 264 final float startAngle, final float sweep, 265 final boolean useCenter, long paint) { 266 if (right > left && bottom > top) { 267 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 268 (graphics, paintDelegate) -> { 269 int style = paintDelegate.getStyle(); 270 271 Arc2D.Float arc = new Arc2D.Float( 272 left, top, right - left, bottom - top, 273 -startAngle, -sweep, 274 useCenter ? Arc2D.PIE : Arc2D.OPEN); 275 276 // draw 277 if (style == Paint.Style.FILL.nativeInt || 278 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 279 graphics.fill(arc); 280 } 281 282 if (style == Paint.Style.STROKE.nativeInt || 283 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 284 graphics.draw(arc); 285 } 286 }); 287 } 288 } 289 290 @LayoutlibDelegate nDrawRoundRect(long nativeCanvas, final float left, final float top, final float right, final float bottom, final float rx, final float ry, long paint)291 /*package*/ static void nDrawRoundRect(long nativeCanvas, 292 final float left, final float top, final float right, final float bottom, 293 final float rx, final float ry, long paint) { 294 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 295 (graphics, paintDelegate) -> { 296 int style = paintDelegate.getStyle(); 297 298 // draw 299 if (style == Paint.Style.FILL.nativeInt || 300 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 301 graphics.fillRoundRect( 302 (int)left, (int)top, 303 (int)(right - left), (int)(bottom - top), 304 2 * (int)rx, 2 * (int)ry); 305 } 306 307 if (style == Paint.Style.STROKE.nativeInt || 308 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 309 graphics.drawRoundRect( 310 (int)left, (int)top, 311 (int)(right - left), (int)(bottom - top), 312 2 * (int)rx, 2 * (int)ry); 313 } 314 }); 315 } 316 317 @LayoutlibDelegate nDrawPath(long nativeCanvas, long path, long paint)318 public static void nDrawPath(long nativeCanvas, long path, long paint) { 319 final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); 320 if (pathDelegate == null) { 321 return; 322 } 323 324 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 325 (graphics, paintDelegate) -> { 326 Shape shape = pathDelegate.getJavaShape(); 327 Rectangle2D bounds = shape.getBounds2D(); 328 if (bounds.isEmpty()) { 329 // Apple JRE 1.6 doesn't like drawing empty shapes. 330 // http://b.android.com/178278 331 332 if (pathDelegate.isEmpty()) { 333 // This means that the path doesn't have any lines or curves so 334 // nothing to draw. 335 return; 336 } 337 338 // The stroke width is not consider for the size of the bounds so, 339 // for example, a horizontal line, would be considered as an empty 340 // rectangle. 341 // If the strokeWidth is not 0, we use it to consider the size of the 342 // path as well. 343 float strokeWidth = paintDelegate.getStrokeWidth(); 344 if (strokeWidth <= 0.0f) { 345 return; 346 } 347 bounds.setRect(bounds.getX(), bounds.getY(), 348 Math.max(strokeWidth, bounds.getWidth()), 349 Math.max(strokeWidth, bounds.getHeight())); 350 } 351 352 int style = paintDelegate.getStyle(); 353 354 if (style == Paint.Style.FILL.nativeInt || 355 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 356 graphics.fill(shape); 357 } 358 359 if (style == Paint.Style.STROKE.nativeInt || 360 style == Paint.Style.FILL_AND_STROKE.nativeInt) { 361 graphics.draw(shape); 362 } 363 }); 364 } 365 366 @LayoutlibDelegate nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint)367 /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion, 368 long nativePaint) { 369 // FIXME 370 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 371 "Some canvas paths may not be drawn", null, null); 372 } 373 374 @LayoutlibDelegate nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop, final float dstRight, final float dstBottom, long nativePaintOrZero, final int screenDensity, final int bitmapDensity)375 /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch, 376 final float dstLeft, final float dstTop, final float dstRight, final float dstBottom, 377 long nativePaintOrZero, final int screenDensity, final int bitmapDensity) { 378 379 // get the delegate from the native int. 380 final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap); 381 if (bitmapDelegate == null) { 382 return; 383 } 384 385 byte[] c = NinePatch_Delegate.getChunk(ninePatch); 386 if (c == null) { 387 // not a 9-patch? 388 BufferedImage image = bitmapDelegate.getImage(); 389 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), 390 image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight, 391 (int) dstBottom); 392 return; 393 } 394 395 final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c); 396 assert chunkObject != null; 397 if (chunkObject == null) { 398 return; 399 } 400 401 Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); 402 if (canvasDelegate == null) { 403 return; 404 } 405 406 // this one can be null 407 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); 408 409 canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { 410 @Override 411 public void draw(Graphics2D graphics, Paint_Delegate paint) { 412 chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, 413 (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, 414 bitmapDensity); 415 } 416 }, paintDelegate, true, false); 417 418 } 419 420 @LayoutlibDelegate nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, long nMatrix, long nPaint)421 /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, 422 long nMatrix, long nPaint) { 423 // get the delegate from the native int. 424 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 425 if (canvasDelegate == null) { 426 return; 427 } 428 429 // get the delegate from the native int, which can be null 430 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); 431 432 // get the delegate from the native int. 433 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); 434 if (bitmapDelegate == null) { 435 return; 436 } 437 438 final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); 439 440 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); 441 if (matrixDelegate == null) { 442 return; 443 } 444 445 final AffineTransform mtx = matrixDelegate.getAffineTransform(); 446 447 canvasDelegate.getSnapshot().draw((graphics, paint) -> { 448 if (paint != null && paint.isFilterBitmap()) { 449 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 450 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 451 } 452 453 //FIXME add support for canvas, screen and bitmap densities. 454 graphics.drawImage(image, mtx, null); 455 }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); 456 } 457 458 @LayoutlibDelegate nDrawBitmapMesh(long nCanvas, Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, long nPaint)459 /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap, 460 int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, 461 int colorOffset, long nPaint) { 462 // FIXME 463 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 464 "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); 465 } 466 467 @LayoutlibDelegate nDrawVertices(long nCanvas, int mode, int n, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, long nPaint)468 /*package*/ static void nDrawVertices(long nCanvas, int mode, int n, 469 float[] verts, int vertOffset, 470 float[] texs, int texOffset, 471 int[] colors, int colorOffset, 472 short[] indices, int indexOffset, 473 int indexCount, long nPaint) { 474 // FIXME 475 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 476 "Canvas.drawVertices is not supported.", null, null /*data*/); 477 } 478 479 @LayoutlibDelegate nDrawText(long nativeCanvas, char[] text, int index, int count, float startX, float startY, int flags, long paint, long typeface)480 /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count, 481 float startX, float startY, int flags, long paint, long typeface) { 482 drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0, 483 paint, typeface); 484 } 485 486 @LayoutlibDelegate nDrawText(long nativeCanvas, String text, int start, int end, float x, float y, final int flags, long paint, long typeface)487 /*package*/ static void nDrawText(long nativeCanvas, String text, 488 int start, int end, float x, float y, final int flags, long paint, 489 long typeface) { 490 int count = end - start; 491 char[] buffer = TemporaryBuffer.obtain(count); 492 TextUtils.getChars(text, start, end, buffer, 0); 493 494 nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); 495 } 496 497 @LayoutlibDelegate nDrawTextRun(long nativeCanvas, String text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, long paint, long typeface)498 /*package*/ static void nDrawTextRun(long nativeCanvas, String text, 499 int start, int end, int contextStart, int contextEnd, 500 float x, float y, boolean isRtl, long paint, long typeface) { 501 int count = end - start; 502 char[] buffer = TemporaryBuffer.obtain(count); 503 TextUtils.getChars(text, start, end, buffer, 0); 504 505 drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); 506 } 507 508 @LayoutlibDelegate nDrawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, float x, float y, boolean isRtl, long paint, long typeface)509 /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text, 510 int start, int count, int contextStart, int contextCount, 511 float x, float y, boolean isRtl, long paint, long typeface) { 512 drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); 513 } 514 515 @LayoutlibDelegate nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, long path, float hOffset, float vOffset, int bidiFlags, long paint, long typeface)516 /*package*/ static void nDrawTextOnPath(long nativeCanvas, 517 char[] text, int index, 518 int count, long path, 519 float hOffset, 520 float vOffset, int bidiFlags, 521 long paint, long typeface) { 522 // FIXME 523 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 524 "Canvas.drawTextOnPath is not supported.", null, null /*data*/); 525 } 526 527 @LayoutlibDelegate nDrawTextOnPath(long nativeCanvas, String text, long path, float hOffset, float vOffset, int bidiFlags, long paint, long typeface)528 /*package*/ static void nDrawTextOnPath(long nativeCanvas, 529 String text, long path, 530 float hOffset, 531 float vOffset, 532 int bidiFlags, long paint, 533 long typeface) { 534 // FIXME 535 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 536 "Canvas.drawTextOnPath is not supported.", null, null /*data*/); 537 } 538 539 // ---- Private delegate/helper methods ---- 540 541 /** 542 * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. 543 * <p>Note that the drawable may actually be executed several times if there are 544 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. 545 */ draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, GcSnapshot.Drawable drawable)546 private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, 547 GcSnapshot.Drawable drawable) { 548 // get the delegate from the native int. 549 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 550 if (canvasDelegate == null) { 551 return; 552 } 553 554 // get the paint which can be null if nPaint is 0; 555 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); 556 557 canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); 558 } 559 560 /** 561 * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided 562 * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. 563 * <p>Note that the drawable may actually be executed several times if there are 564 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. 565 */ draw(long nCanvas, GcSnapshot.Drawable drawable)566 private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { 567 // get the delegate from the native int. 568 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 569 if (canvasDelegate == null) { 570 return; 571 } 572 573 canvasDelegate.mSnapshot.draw(drawable); 574 } 575 drawText(long nativeCanvas, final char[] text, final int index, final int count, final float startX, final float startY, final boolean isRtl, long paint, final long typeface)576 private static void drawText(long nativeCanvas, final char[] text, final int index, 577 final int count, final float startX, final float startY, final boolean isRtl, 578 long paint, final long typeface) { 579 580 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 581 (graphics, paintDelegate) -> { 582 // WARNING: the logic in this method is similar to Paint_Delegate.measureText. 583 // Any change to this method should be reflected in Paint.measureText 584 585 // assert that the typeface passed is actually the one stored in paint. 586 assert (typeface == paintDelegate.mNativeTypeface); 587 588 // Paint.TextAlign indicates how the text is positioned relative to X. 589 // LEFT is the default and there's nothing to do. 590 float x = startX; 591 int limit = index + count; 592 if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { 593 RectF bounds = 594 paintDelegate.measureText(text, index, count, null, 0, isRtl); 595 float m = bounds.right - bounds.left; 596 if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { 597 x -= m / 2; 598 } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { 599 x -= m; 600 } 601 } 602 603 new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, 604 startY).renderText(index, limit, isRtl, null, 0, true); 605 }); 606 } 607 drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap, long nativePaintOrZero, final int sleft, final int stop, final int sright, final int sbottom, final int dleft, final int dtop, final int dright, final int dbottom)608 private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap, 609 long nativePaintOrZero, final int sleft, final int stop, final int sright, 610 final int sbottom, final int dleft, final int dtop, final int dright, 611 final int dbottom) { 612 // get the delegate from the native int. 613 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); 614 if (canvasDelegate == null) { 615 return; 616 } 617 618 // get the paint, which could be null if the int is 0 619 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); 620 621 final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); 622 623 draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], 624 (graphics, paint) -> { 625 if (paint != null && paint.isFilterBitmap()) { 626 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 627 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 628 } 629 630 //FIXME add support for canvas, screen and bitmap densities. 631 graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright, 632 sbottom, null); 633 }); 634 } 635 636 /** 637 * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. 638 * The image returns, through a 1-size boolean array, whether the drawing code should 639 * use a SRC composite no matter what the paint says. 640 * 641 * @param bitmap the bitmap 642 * @param paint the paint that will be used to draw 643 * @param forceSrcMode whether the composite will have to be SRC 644 * @return the image to draw 645 */ getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, boolean[] forceSrcMode)646 private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, 647 boolean[] forceSrcMode) { 648 BufferedImage image = bitmap.getImage(); 649 forceSrcMode[0] = false; 650 651 // if the bitmap config is alpha_8, then we erase all color value from it 652 // before drawing it or apply the texture from the shader if present. 653 if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { 654 Shader_Delegate shader = paint.getShader(); 655 java.awt.Paint javaPaint = null; 656 if (shader instanceof BitmapShader_Delegate) { 657 javaPaint = shader.getJavaPaint(); 658 } 659 660 fixAlpha8Bitmap(image, javaPaint); 661 } else if (!bitmap.hasAlpha()) { 662 // hasAlpha is merely a rendering hint. There can in fact be alpha values 663 // in the bitmap but it should be ignored at drawing time. 664 // There is two ways to do this: 665 // - override the composite to be SRC. This can only be used if the composite 666 // was going to be SRC or SRC_OVER in the first place 667 // - Create a different bitmap to draw in which all the alpha channel values is set 668 // to 0xFF. 669 if (paint != null) { 670 PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); 671 672 forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC; 673 } 674 675 // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB 676 if (!forceSrcMode[0]) { 677 image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); 678 } 679 } 680 681 return image; 682 } 683 684 /** 685 * This method will apply the correct color to the passed "only alpha" image. Colors on the 686 * passed image will be destroyed. 687 * If the passed javaPaint is null, the color will be set to 0. If a paint is passed, it will 688 * be used to obtain the color that will be applied. 689 * <p/> 690 * This will destroy the passed image color channel. 691 */ fixAlpha8Bitmap(final BufferedImage image, @Nullable java.awt.Paint javaPaint)692 private static void fixAlpha8Bitmap(final BufferedImage image, 693 @Nullable java.awt.Paint javaPaint) { 694 int w = image.getWidth(); 695 int h = image.getHeight(); 696 697 DataBuffer texture = null; 698 if (javaPaint != null) { 699 PaintContext context = javaPaint.createContext(ColorModel.getRGBdefault(), null, null, 700 new AffineTransform(), null); 701 texture = context.getRaster(0, 0, w, h).getDataBuffer(); 702 } 703 704 int[] argb = new int[w * h]; 705 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 706 707 final int length = argb.length; 708 for (int i = 0; i < length; i++) { 709 argb[i] &= 0xFF000000; 710 if (texture != null) { 711 argb[i] |= texture.getElem(i) & 0x00FFFFFF; 712 } 713 } 714 715 image.setRGB(0, 0, w, h, argb, 0, w); 716 } 717 save(int saveFlags)718 protected int save(int saveFlags) { 719 // get the current save count 720 int count = mSnapshot.size(); 721 722 mSnapshot = mSnapshot.save(saveFlags); 723 724 // return the old save count 725 return count; 726 } 727 saveLayerAlpha(RectF rect, int alpha, int saveFlags)728 protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { 729 Paint_Delegate paint = new Paint_Delegate(); 730 paint.setAlpha(alpha); 731 return saveLayer(rect, paint, saveFlags); 732 } 733 saveLayer(RectF rect, Paint_Delegate paint, int saveFlags)734 protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { 735 // get the current save count 736 int count = mSnapshot.size(); 737 738 mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); 739 740 // return the old save count 741 return count; 742 } 743 744 /** 745 * Restores the {@link GcSnapshot} to <var>saveCount</var> 746 * @param saveCount the saveCount 747 */ restoreTo(int saveCount)748 protected void restoreTo(int saveCount) { 749 mSnapshot = mSnapshot.restoreTo(saveCount); 750 } 751 752 /** 753 * Restores the top {@link GcSnapshot} 754 */ restore()755 protected void restore() { 756 mSnapshot = mSnapshot.restore(); 757 } 758 clipRect(float left, float top, float right, float bottom, int regionOp)759 protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) { 760 return mSnapshot.clipRect(left, top, right, bottom, regionOp); 761 } 762 } 763