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