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