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