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