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