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 if (chunkObject == null) { 397 return; 398 } 399 400 Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); 401 if (canvasDelegate == null) { 402 return; 403 } 404 405 // this one can be null 406 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); 407 408 canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { 409 @Override 410 public void draw(Graphics2D graphics, Paint_Delegate paint) { 411 chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, 412 (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, 413 bitmapDensity); 414 } 415 }, paintDelegate, true, false); 416 417 } 418 419 @LayoutlibDelegate nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, long nMatrix, long nPaint)420 /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, 421 long nMatrix, long nPaint) { 422 // get the delegate from the native int. 423 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 424 if (canvasDelegate == null) { 425 return; 426 } 427 428 // get the delegate from the native int, which can be null 429 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); 430 431 // get the delegate from the native int. 432 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); 433 if (bitmapDelegate == null) { 434 return; 435 } 436 437 final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); 438 439 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); 440 if (matrixDelegate == null) { 441 return; 442 } 443 444 final AffineTransform mtx = matrixDelegate.getAffineTransform(); 445 446 canvasDelegate.getSnapshot().draw((graphics, paint) -> { 447 if (paint != null && paint.isFilterBitmap()) { 448 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 449 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 450 } 451 452 //FIXME add support for canvas, screen and bitmap densities. 453 graphics.drawImage(image, mtx, null); 454 }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); 455 } 456 457 @LayoutlibDelegate nDrawBitmapMesh(long nCanvas, Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, long nPaint)458 /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap, 459 int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, 460 int colorOffset, long nPaint) { 461 // FIXME 462 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 463 "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); 464 } 465 466 @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)467 /*package*/ static void nDrawVertices(long nCanvas, int mode, int n, 468 float[] verts, int vertOffset, 469 float[] texs, int texOffset, 470 int[] colors, int colorOffset, 471 short[] indices, int indexOffset, 472 int indexCount, long nPaint) { 473 // FIXME 474 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 475 "Canvas.drawVertices is not supported.", null, null /*data*/); 476 } 477 478 @LayoutlibDelegate nDrawText(long nativeCanvas, char[] text, int index, int count, float startX, float startY, int flags, long paint)479 /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count, 480 float startX, float startY, int flags, long paint) { 481 drawText(nativeCanvas, text, index, count, startX, startY, flags, 482 paint); 483 } 484 485 @LayoutlibDelegate nDrawText(long nativeCanvas, String text, int start, int end, float x, float y, final int flags, long paint)486 /*package*/ static void nDrawText(long nativeCanvas, String text, 487 int start, int end, float x, float y, final int flags, long paint) { 488 int count = end - start; 489 char[] buffer = TemporaryBuffer.obtain(count); 490 TextUtils.getChars(text, start, end, buffer, 0); 491 492 nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); 493 } 494 495 @LayoutlibDelegate nDrawTextRun(long nativeCanvas, String text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, long paint)496 /*package*/ static void nDrawTextRun(long nativeCanvas, String text, 497 int start, int end, int contextStart, int contextEnd, 498 float x, float y, boolean isRtl, long paint) { 499 int count = end - start; 500 char[] buffer = TemporaryBuffer.obtain(count); 501 TextUtils.getChars(text, start, end, buffer, 0); 502 503 drawText(nativeCanvas, buffer, 0, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, 504 paint); 505 } 506 507 @LayoutlibDelegate nDrawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, float x, float y, boolean isRtl, long paint, long nativeMeasuredText, int measuredTextOffset)508 /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text, 509 int start, int count, int contextStart, int contextCount, 510 float x, float y, boolean isRtl, long paint, 511 long nativeMeasuredText, int measuredTextOffset) { 512 drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint); 513 } 514 515 @LayoutlibDelegate nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, long path, float hOffset, float vOffset, int bidiFlags, long paint)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) { 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)528 /*package*/ static void nDrawTextOnPath(long nativeCanvas, 529 String text, long path, 530 float hOffset, 531 float vOffset, 532 int bidiFlags, long paint) { 533 // FIXME 534 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 535 "Canvas.drawTextOnPath is not supported.", null, null /*data*/); 536 } 537 538 // ---- Private delegate/helper methods ---- 539 540 /** 541 * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. 542 * <p>Note that the drawable may actually be executed several times if there are 543 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. 544 */ draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, GcSnapshot.Drawable drawable)545 private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, 546 GcSnapshot.Drawable drawable) { 547 // get the delegate from the native int. 548 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 549 if (canvasDelegate == null) { 550 return; 551 } 552 553 // get the paint which can be null if nPaint is 0; 554 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); 555 556 canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); 557 } 558 559 /** 560 * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided 561 * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. 562 * <p>Note that the drawable may actually be executed several times if there are 563 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. 564 */ draw(long nCanvas, GcSnapshot.Drawable drawable)565 private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { 566 // get the delegate from the native int. 567 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); 568 if (canvasDelegate == null) { 569 return; 570 } 571 572 canvasDelegate.mSnapshot.draw(drawable); 573 } 574 drawText(long nativeCanvas, final char[] text, final int index, final int count, final float startX, final float startY, final int bidiFlags, long paint)575 private static void drawText(long nativeCanvas, final char[] text, final int index, 576 final int count, final float startX, final float startY, final int bidiFlags, 577 long paint) { 578 579 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, 580 (graphics, paintDelegate) -> { 581 // WARNING: the logic in this method is similar to Paint_Delegate.measureText. 582 // Any change to this method should be reflected in Paint.measureText 583 584 // Paint.TextAlign indicates how the text is positioned relative to X. 585 // LEFT is the default and there's nothing to do. 586 float x = startX; 587 int limit = index + count; 588 if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { 589 RectF bounds = 590 paintDelegate.measureText(text, index, count, null, 0, bidiFlags); 591 float m = bounds.right - bounds.left; 592 if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { 593 x -= m / 2; 594 } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { 595 x -= m; 596 } 597 } 598 599 new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, 600 startY).renderText(index, limit, bidiFlags, null, 0, true); 601 }); 602 } 603 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)604 private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap, 605 long nativePaintOrZero, final int sleft, final int stop, final int sright, 606 final int sbottom, final int dleft, final int dtop, final int dright, 607 final int dbottom) { 608 // get the delegate from the native int. 609 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); 610 if (canvasDelegate == null) { 611 return; 612 } 613 614 // get the paint, which could be null if the int is 0 615 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); 616 617 final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); 618 619 draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], 620 (graphics, paint) -> { 621 if (paint != null && paint.isFilterBitmap()) { 622 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 623 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 624 } 625 626 //FIXME add support for canvas, screen and bitmap densities. 627 graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright, 628 sbottom, null); 629 }); 630 } 631 632 /** 633 * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. 634 * The image returns, through a 1-size boolean array, whether the drawing code should 635 * use a SRC composite no matter what the paint says. 636 * 637 * @param bitmap the bitmap 638 * @param paint the paint that will be used to draw 639 * @param forceSrcMode whether the composite will have to be SRC 640 * @return the image to draw 641 */ getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, boolean[] forceSrcMode)642 private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, 643 boolean[] forceSrcMode) { 644 BufferedImage image = bitmap.getImage(); 645 forceSrcMode[0] = false; 646 647 // if the bitmap config is alpha_8, then we erase all color value from it 648 // before drawing it or apply the texture from the shader if present. 649 if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { 650 Shader_Delegate shader = paint.getShader(); 651 java.awt.Paint javaPaint = null; 652 if (shader instanceof BitmapShader_Delegate) { 653 javaPaint = shader.getJavaPaint(); 654 } 655 656 fixAlpha8Bitmap(image, javaPaint); 657 } else if (!bitmap.hasAlpha()) { 658 // hasAlpha is merely a rendering hint. There can in fact be alpha values 659 // in the bitmap but it should be ignored at drawing time. 660 // There is two ways to do this: 661 // - override the composite to be SRC. This can only be used if the composite 662 // was going to be SRC or SRC_OVER in the first place 663 // - Create a different bitmap to draw in which all the alpha channel values is set 664 // to 0xFF. 665 if (paint != null) { 666 PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); 667 668 forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC; 669 } 670 671 // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB 672 if (!forceSrcMode[0]) { 673 image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); 674 } 675 } 676 677 return image; 678 } 679 680 /** 681 * This method will apply the correct color to the passed "only alpha" image. Colors on the 682 * passed image will be destroyed. 683 * If the passed javaPaint is null, the color will be set to 0. If a paint is passed, it will 684 * be used to obtain the color that will be applied. 685 * <p/> 686 * This will destroy the passed image color channel. 687 */ fixAlpha8Bitmap(final BufferedImage image, @Nullable java.awt.Paint javaPaint)688 private static void fixAlpha8Bitmap(final BufferedImage image, 689 @Nullable java.awt.Paint javaPaint) { 690 int w = image.getWidth(); 691 int h = image.getHeight(); 692 693 DataBuffer texture = null; 694 if (javaPaint != null) { 695 PaintContext context = javaPaint.createContext(ColorModel.getRGBdefault(), null, null, 696 new AffineTransform(), null); 697 texture = context.getRaster(0, 0, w, h).getDataBuffer(); 698 } 699 700 int[] argb = new int[w * h]; 701 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 702 703 final int length = argb.length; 704 for (int i = 0; i < length; i++) { 705 argb[i] &= 0xFF000000; 706 if (texture != null) { 707 argb[i] |= texture.getElem(i) & 0x00FFFFFF; 708 } 709 } 710 711 image.setRGB(0, 0, w, h, argb, 0, w); 712 } 713 save(int saveFlags)714 protected int save(int saveFlags) { 715 // get the current save count 716 int count = mSnapshot.size(); 717 718 mSnapshot = mSnapshot.save(saveFlags); 719 720 // return the old save count 721 return count; 722 } 723 saveLayerAlpha(RectF rect, int alpha, int saveFlags)724 protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { 725 Paint_Delegate paint = new Paint_Delegate(); 726 paint.setAlpha(alpha); 727 return saveLayer(rect, paint, saveFlags); 728 } 729 saveLayer(RectF rect, Paint_Delegate paint, int saveFlags)730 protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { 731 // get the current save count 732 int count = mSnapshot.size(); 733 734 mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); 735 736 // return the old save count 737 return count; 738 } 739 740 /** 741 * Restores the {@link GcSnapshot} to <var>saveCount</var> 742 * @param saveCount the saveCount 743 */ restoreTo(int saveCount)744 protected void restoreTo(int saveCount) { 745 mSnapshot = mSnapshot.restoreTo(saveCount); 746 } 747 748 /** 749 * Restores the top {@link GcSnapshot} 750 */ restore()751 protected void restore() { 752 mSnapshot = mSnapshot.restore(); 753 } 754 clipRect(float left, float top, float right, float bottom, int regionOp)755 protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) { 756 return mSnapshot.clipRect(left, top, right, bottom, regionOp); 757 } 758 } 759