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