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