Canvas_Delegate.java revision c93beed4e670f05104c4615d47265d125251b499
1/*
2 * Copyright (C) 2010 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
17package android.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.layoutlib.bridge.impl.GcSnapshot;
23import com.android.layoutlib.bridge.impl.PorterDuffUtility;
24import com.android.ninepatch.NinePatchChunk;
25import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
26
27import android.annotation.Nullable;
28import android.graphics.Bitmap.Config;
29import android.text.TextUtils;
30
31import java.awt.Color;
32import java.awt.Composite;
33import java.awt.Graphics2D;
34import java.awt.Rectangle;
35import java.awt.RenderingHints;
36import java.awt.Shape;
37import java.awt.geom.AffineTransform;
38import java.awt.geom.Arc2D;
39import java.awt.geom.Rectangle2D;
40import java.awt.image.BufferedImage;
41
42import libcore.util.NativeAllocationRegistry_Delegate;
43
44
45/**
46 * Delegate implementing the native methods of android.graphics.Canvas
47 *
48 * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
49 * by calls to methods of the same name in this delegate class.
50 *
51 * This class behaves like the original native implementation, but in Java, keeping previously
52 * native data into its own objects and mapping them to int that are sent back and forth between
53 * it and the original Canvas class.
54 *
55 * @see DelegateManager
56 *
57 */
58public final class Canvas_Delegate {
59
60    // ---- delegate manager ----
61    private static final DelegateManager<Canvas_Delegate> sManager =
62            new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
63    private static long sFinalizer = -1;
64
65
66    // ---- delegate helper data ----
67
68    private final static boolean[] sBoolOut = new boolean[1];
69
70
71    // ---- delegate data ----
72    private Bitmap_Delegate mBitmap;
73    private GcSnapshot mSnapshot;
74
75    private DrawFilter_Delegate mDrawFilter = null;
76
77
78    // ---- Public Helper methods ----
79
80    /**
81     * Returns the native delegate associated to a given {@link Canvas} object.
82     */
83    public static Canvas_Delegate getDelegate(Canvas canvas) {
84        return sManager.getDelegate(canvas.getNativeCanvasWrapper());
85    }
86
87    /**
88     * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
89     */
90    public static Canvas_Delegate getDelegate(long native_canvas) {
91        return sManager.getDelegate(native_canvas);
92    }
93
94    /**
95     * Returns the current {@link Graphics2D} used to draw.
96     */
97    public GcSnapshot getSnapshot() {
98        return mSnapshot;
99    }
100
101    /**
102     * Returns the {@link DrawFilter} delegate or null if none have been set.
103     *
104     * @return the delegate or null.
105     */
106    public DrawFilter_Delegate getDrawFilter() {
107        return mDrawFilter;
108    }
109
110    // ---- native methods ----
111
112    @LayoutlibDelegate
113    /*package*/ static void freeCaches() {
114        // nothing to be done here.
115    }
116
117    @LayoutlibDelegate
118    /*package*/ static void freeTextLayoutCaches() {
119        // nothing to be done here yet.
120    }
121
122    @LayoutlibDelegate
123    /*package*/ static long initRaster(@Nullable Bitmap bitmap) {
124        long nativeBitmapOrZero = 0;
125        if (bitmap != null) {
126            nativeBitmapOrZero = bitmap.getNativeInstance();
127        }
128        if (nativeBitmapOrZero > 0) {
129            // get the Bitmap from the int
130            Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
131
132            // create a new Canvas_Delegate with the given bitmap and return its new native int.
133            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
134
135            return sManager.addNewDelegate(newDelegate);
136        }
137
138        // create a new Canvas_Delegate and return its new native int.
139        Canvas_Delegate newDelegate = new Canvas_Delegate();
140
141        return sManager.addNewDelegate(newDelegate);
142    }
143
144    @LayoutlibDelegate
145    public static void native_setBitmap(long canvas, Bitmap bitmap) {
146        Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
147        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
148        if (canvasDelegate == null || bitmapDelegate==null) {
149            return;
150        }
151        canvasDelegate.mBitmap = bitmapDelegate;
152        canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate);
153    }
154
155    @LayoutlibDelegate
156    public static boolean native_isOpaque(long nativeCanvas) {
157        // get the delegate from the native int.
158        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
159        if (canvasDelegate == null) {
160            return false;
161        }
162
163        return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
164    }
165
166    @LayoutlibDelegate
167    public static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){}
168
169    @LayoutlibDelegate
170    public static int native_getWidth(long nativeCanvas) {
171        // get the delegate from the native int.
172        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
173        if (canvasDelegate == null) {
174            return 0;
175        }
176
177        return canvasDelegate.mBitmap.getImage().getWidth();
178    }
179
180    @LayoutlibDelegate
181    public static int native_getHeight(long nativeCanvas) {
182        // get the delegate from the native int.
183        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
184        if (canvasDelegate == null) {
185            return 0;
186        }
187
188        return canvasDelegate.mBitmap.getImage().getHeight();
189    }
190
191    @LayoutlibDelegate
192    public static int native_save(long nativeCanvas, int saveFlags) {
193        // get the delegate from the native int.
194        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
195        if (canvasDelegate == null) {
196            return 0;
197        }
198
199        return canvasDelegate.save(saveFlags);
200    }
201
202    @LayoutlibDelegate
203    public static int native_saveLayer(long nativeCanvas, float l,
204                                               float t, float r, float b,
205                                               long paint, int layerFlags) {
206        // get the delegate from the native int.
207        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
208        if (canvasDelegate == null) {
209            return 0;
210        }
211
212        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
213        if (paintDelegate == null) {
214            return 0;
215        }
216
217        return canvasDelegate.saveLayer(new RectF(l, t, r, b),
218                paintDelegate, layerFlags);
219    }
220
221    @LayoutlibDelegate
222    public static int native_saveLayerAlpha(long nativeCanvas, float l,
223                                                    float t, float r, float b,
224                                                    int alpha, int layerFlags) {
225        // get the delegate from the native int.
226        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
227        if (canvasDelegate == null) {
228            return 0;
229        }
230
231        return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
232    }
233
234    @LayoutlibDelegate
235    public static void native_restore(long nativeCanvas, boolean throwOnUnderflow) {
236        // FIXME: implement throwOnUnderflow.
237        // get the delegate from the native int.
238        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
239        if (canvasDelegate == null) {
240            return;
241        }
242
243        canvasDelegate.restore();
244    }
245
246    @LayoutlibDelegate
247    public static void native_restoreToCount(long nativeCanvas, int saveCount,
248            boolean throwOnUnderflow) {
249        // FIXME: implement throwOnUnderflow.
250        // get the delegate from the native int.
251        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
252        if (canvasDelegate == null) {
253            return;
254        }
255
256        canvasDelegate.restoreTo(saveCount);
257    }
258
259    @LayoutlibDelegate
260    public static int native_getSaveCount(long nativeCanvas) {
261        // get the delegate from the native int.
262        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
263        if (canvasDelegate == null) {
264            return 0;
265        }
266
267        return canvasDelegate.getSnapshot().size();
268    }
269
270    @LayoutlibDelegate
271   public static void native_translate(long nativeCanvas, float dx, float dy) {
272        // get the delegate from the native int.
273        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
274        if (canvasDelegate == null) {
275            return;
276        }
277
278        canvasDelegate.getSnapshot().translate(dx, dy);
279    }
280
281    @LayoutlibDelegate
282       public static void native_scale(long nativeCanvas, float sx, float sy) {
283            // get the delegate from the native int.
284            Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
285            if (canvasDelegate == null) {
286                return;
287            }
288
289            canvasDelegate.getSnapshot().scale(sx, sy);
290        }
291
292    @LayoutlibDelegate
293    public static void native_rotate(long nativeCanvas, float degrees) {
294        // get the delegate from the native int.
295        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
296        if (canvasDelegate == null) {
297            return;
298        }
299
300        canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
301    }
302
303    @LayoutlibDelegate
304   public static void native_skew(long nativeCanvas, float kx, float ky) {
305        // get the delegate from the native int.
306        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
307        if (canvasDelegate == null) {
308            return;
309        }
310
311        // get the current top graphics2D object.
312        GcSnapshot g = canvasDelegate.getSnapshot();
313
314        // get its current matrix
315        AffineTransform currentTx = g.getTransform();
316        // get the AffineTransform for the given skew.
317        float[] mtx = Matrix_Delegate.getSkew(kx, ky);
318        AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
319
320        // combine them so that the given matrix is applied after.
321        currentTx.preConcatenate(matrixTx);
322
323        // give it to the graphics2D as a new matrix replacing all previous transform
324        g.setTransform(currentTx);
325    }
326
327    @LayoutlibDelegate
328    public static void native_concat(long nCanvas, long nMatrix) {
329        // get the delegate from the native int.
330        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
331        if (canvasDelegate == null) {
332            return;
333        }
334
335        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
336        if (matrixDelegate == null) {
337            return;
338        }
339
340        // get the current top graphics2D object.
341        GcSnapshot snapshot = canvasDelegate.getSnapshot();
342
343        // get its current matrix
344        AffineTransform currentTx = snapshot.getTransform();
345        // get the AffineTransform of the given matrix
346        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
347
348        // combine them so that the given matrix is applied after.
349        currentTx.concatenate(matrixTx);
350
351        // give it to the graphics2D as a new matrix replacing all previous transform
352        snapshot.setTransform(currentTx);
353    }
354
355    @LayoutlibDelegate
356    public static void native_setMatrix(long nCanvas, long nMatrix) {
357        // get the delegate from the native int.
358        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
359        if (canvasDelegate == null) {
360            return;
361        }
362
363        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
364        if (matrixDelegate == null) {
365            return;
366        }
367
368        // get the current top graphics2D object.
369        GcSnapshot snapshot = canvasDelegate.getSnapshot();
370
371        // get the AffineTransform of the given matrix
372        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
373
374        // give it to the graphics2D as a new matrix replacing all previous transform
375        snapshot.setTransform(matrixTx);
376
377        if (matrixDelegate.hasPerspective()) {
378            assert false;
379            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
380                    "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
381                    "supports affine transformations.", null, null /*data*/);
382        }
383    }
384
385    @LayoutlibDelegate
386    public static boolean native_clipRect(long nCanvas,
387                                                  float left, float top,
388                                                  float right, float bottom,
389                                                  int regionOp) {
390        // get the delegate from the native int.
391        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
392        if (canvasDelegate == null) {
393            return false;
394        }
395
396        return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
397    }
398
399    @LayoutlibDelegate
400    public static boolean native_clipPath(long nativeCanvas,
401                                                  long nativePath,
402                                                  int regionOp) {
403        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
404        if (canvasDelegate == null) {
405            return true;
406        }
407
408        Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
409        if (pathDelegate == null) {
410            return true;
411        }
412
413        return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
414    }
415
416    @LayoutlibDelegate
417    public static boolean native_clipRegion(long nativeCanvas,
418                                                    long nativeRegion,
419                                                    int regionOp) {
420        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
421        if (canvasDelegate == null) {
422            return true;
423        }
424
425        Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
426        if (region == null) {
427            return true;
428        }
429
430        return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
431    }
432
433    @LayoutlibDelegate
434    public static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) {
435        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
436        if (canvasDelegate == null) {
437            return;
438        }
439
440        canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
441
442        if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
443            Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
444                    canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
445        }
446    }
447
448    @LayoutlibDelegate
449    public static boolean native_getClipBounds(long nativeCanvas,
450                                                       Rect bounds) {
451        // get the delegate from the native int.
452        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
453        if (canvasDelegate == null) {
454            return false;
455        }
456
457        Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
458        if (rect != null && !rect.isEmpty()) {
459            bounds.left = rect.x;
460            bounds.top = rect.y;
461            bounds.right = rect.x + rect.width;
462            bounds.bottom = rect.y + rect.height;
463            return true;
464        }
465
466        return false;
467    }
468
469    @LayoutlibDelegate
470    public static void native_getCTM(long canvas, long matrix) {
471        // get the delegate from the native int.
472        Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
473        if (canvasDelegate == null) {
474            return;
475        }
476
477        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
478        if (matrixDelegate == null) {
479            return;
480        }
481
482        AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
483        matrixDelegate.set(Matrix_Delegate.makeValues(transform));
484    }
485
486    @LayoutlibDelegate
487    public static boolean native_quickReject(long nativeCanvas, long path) {
488        // FIXME properly implement quickReject
489        return false;
490    }
491
492    @LayoutlibDelegate
493    public static boolean native_quickReject(long nativeCanvas,
494                                                     float left, float top,
495                                                     float right, float bottom) {
496        // FIXME properly implement quickReject
497        return false;
498    }
499
500    @LayoutlibDelegate
501    public static void native_drawColor(long nativeCanvas, final int color, final int mode) {
502        // get the delegate from the native int.
503        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
504        if (canvasDelegate == null) {
505            return;
506        }
507
508        final int w = canvasDelegate.mBitmap.getImage().getWidth();
509        final int h = canvasDelegate.mBitmap.getImage().getHeight();
510        draw(nativeCanvas, new GcSnapshot.Drawable() {
511
512            @Override
513            public void draw(Graphics2D graphics, Paint_Delegate paint) {
514                // reset its transform just in case
515                graphics.setTransform(new AffineTransform());
516
517                // set the color
518                graphics.setColor(new Color(color, true /*alpha*/));
519
520                Composite composite = PorterDuffUtility.getComposite(
521                        PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
522                if (composite != null) {
523                    graphics.setComposite(composite);
524                }
525
526                graphics.fillRect(0, 0, w, h);
527            }
528        });
529    }
530
531    @LayoutlibDelegate
532    public static void native_drawPaint(long nativeCanvas, long paint) {
533        // FIXME
534        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
535                "Canvas.drawPaint is not supported.", null, null /*data*/);
536    }
537
538    @LayoutlibDelegate
539    public static void native_drawPoint(long nativeCanvas, float x, float y,
540            long nativePaint) {
541        // FIXME
542        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
543                "Canvas.drawPoint is not supported.", null, null /*data*/);
544    }
545
546    @LayoutlibDelegate
547    public static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count,
548            long nativePaint) {
549        // FIXME
550        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
551                "Canvas.drawPoint is not supported.", null, null /*data*/);
552    }
553
554    @LayoutlibDelegate
555    public static void native_drawLine(long nativeCanvas,
556            final float startX, final float startY, final float stopX, final float stopY,
557            long paint) {
558        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
559                new GcSnapshot.Drawable() {
560                    @Override
561                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
562                        graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
563                    }
564        });
565    }
566
567    @LayoutlibDelegate
568    public static void native_drawLines(long nativeCanvas,
569            final float[] pts, final int offset, final int count,
570            long nativePaint) {
571        draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
572                false /*forceSrcMode*/, new GcSnapshot.Drawable() {
573                    @Override
574                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
575                        for (int i = 0; i < count; i += 4) {
576                            graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
577                                    (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
578                        }
579                    }
580                });
581    }
582
583    @LayoutlibDelegate
584    public static void native_drawRect(long nativeCanvas,
585            final float left, final float top, final float right, final float bottom, long paint) {
586
587        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
588                new GcSnapshot.Drawable() {
589                    @Override
590                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
591                        int style = paintDelegate.getStyle();
592
593                        // draw
594                        if (style == Paint.Style.FILL.nativeInt ||
595                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
596                            graphics.fillRect((int)left, (int)top,
597                                    (int)(right-left), (int)(bottom-top));
598                        }
599
600                        if (style == Paint.Style.STROKE.nativeInt ||
601                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
602                            graphics.drawRect((int)left, (int)top,
603                                    (int)(right-left), (int)(bottom-top));
604                        }
605                    }
606        });
607    }
608
609    @LayoutlibDelegate
610    public static void native_drawOval(long nativeCanvas, final float left,
611            final float top, final float right, final float bottom, long paint) {
612        if (right > left && bottom > top) {
613            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
614                    new GcSnapshot.Drawable() {
615                        @Override
616                        public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
617                            int style = paintDelegate.getStyle();
618
619                            // draw
620                            if (style == Paint.Style.FILL.nativeInt ||
621                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
622                                graphics.fillOval((int)left, (int)top,
623                                        (int)(right - left), (int)(bottom - top));
624                            }
625
626                            if (style == Paint.Style.STROKE.nativeInt ||
627                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
628                                graphics.drawOval((int)left, (int)top,
629                                        (int)(right - left), (int)(bottom - top));
630                            }
631                        }
632            });
633        }
634    }
635
636    @LayoutlibDelegate
637    public static void native_drawCircle(long nativeCanvas,
638            float cx, float cy, float radius, long paint) {
639        native_drawOval(nativeCanvas,
640                cx - radius, cy - radius, cx + radius, cy + radius,
641                paint);
642    }
643
644    @LayoutlibDelegate
645    public static void native_drawArc(long nativeCanvas,
646            final float left, final float top, final float right, final float bottom,
647            final float startAngle, final float sweep,
648            final boolean useCenter, long paint) {
649        if (right > left && bottom > top) {
650            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
651                    new GcSnapshot.Drawable() {
652                        @Override
653                        public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
654                            int style = paintDelegate.getStyle();
655
656                            Arc2D.Float arc = new Arc2D.Float(
657                                    left, top, right - left, bottom - top,
658                                    -startAngle, -sweep,
659                                    useCenter ? Arc2D.PIE : Arc2D.OPEN);
660
661                            // draw
662                            if (style == Paint.Style.FILL.nativeInt ||
663                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
664                                graphics.fill(arc);
665                            }
666
667                            if (style == Paint.Style.STROKE.nativeInt ||
668                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
669                                graphics.draw(arc);
670                            }
671                        }
672            });
673        }
674    }
675
676    @LayoutlibDelegate
677    public static void native_drawRoundRect(long nativeCanvas,
678            final float left, final float top, final float right, final float bottom,
679            final float rx, final float ry, long paint) {
680        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
681                new GcSnapshot.Drawable() {
682                    @Override
683                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
684                        int style = paintDelegate.getStyle();
685
686                        // draw
687                        if (style == Paint.Style.FILL.nativeInt ||
688                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
689                            graphics.fillRoundRect(
690                                    (int)left, (int)top,
691                                    (int)(right - left), (int)(bottom - top),
692                                    2 * (int)rx, 2 * (int)ry);
693                        }
694
695                        if (style == Paint.Style.STROKE.nativeInt ||
696                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
697                            graphics.drawRoundRect(
698                                    (int)left, (int)top,
699                                    (int)(right - left), (int)(bottom - top),
700                                    2 * (int)rx, 2 * (int)ry);
701                        }
702                    }
703        });
704    }
705
706    @LayoutlibDelegate
707    public static void native_drawPath(long nativeCanvas, long path, long paint) {
708        final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
709        if (pathDelegate == null) {
710            return;
711        }
712
713        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
714                new GcSnapshot.Drawable() {
715                    @Override
716                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
717                        Shape shape = pathDelegate.getJavaShape();
718                        Rectangle2D bounds = shape.getBounds2D();
719                        if (bounds.isEmpty()) {
720                            // Apple JRE 1.6 doesn't like drawing empty shapes.
721                            // http://b.android.com/178278
722
723                            if (pathDelegate.isEmpty()) {
724                                // This means that the path doesn't have any lines or curves so
725                                // nothing to draw.
726                                return;
727                            }
728
729                            // The stroke width is not consider for the size of the bounds so,
730                            // for example, a horizontal line, would be considered as an empty
731                            // rectangle.
732                            // If the strokeWidth is not 0, we use it to consider the size of the
733                            // path as well.
734                            float strokeWidth = paintDelegate.getStrokeWidth();
735                            if (strokeWidth <= 0.0f) {
736                                return;
737                            }
738                            bounds.setRect(bounds.getX(), bounds.getY(),
739                                    Math.max(strokeWidth, bounds.getWidth()),
740                                    Math.max(strokeWidth, bounds.getHeight()));
741                        }
742
743                        int style = paintDelegate.getStyle();
744
745                        if (style == Paint.Style.FILL.nativeInt ||
746                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
747                            graphics.fill(shape);
748                        }
749
750                        if (style == Paint.Style.STROKE.nativeInt ||
751                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
752                            graphics.draw(shape);
753                        }
754                    }
755        });
756    }
757
758    @LayoutlibDelegate
759    public static void native_drawRegion(long nativeCanvas, long nativeRegion,
760            long nativePaint) {
761        // FIXME
762        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
763                "Some canvas paths may not be drawn", null, null);
764    }
765
766    @LayoutlibDelegate
767    public static void native_drawNinePatch(Canvas thisCanvas, long nativeCanvas,
768            long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
769            final float dstRight, final float dstBottom, long nativePaintOrZero,
770            final int screenDensity, final int bitmapDensity) {
771
772        // get the delegate from the native int.
773        final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
774        if (bitmapDelegate == null) {
775            return;
776        }
777
778        byte[] c = NinePatch_Delegate.getChunk(ninePatch);
779        if (c == null) {
780            // not a 9-patch?
781            BufferedImage image = bitmapDelegate.getImage();
782            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
783                    image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
784                    (int) dstBottom);
785            return;
786        }
787
788        final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
789        assert chunkObject != null;
790        if (chunkObject == null) {
791            return;
792        }
793
794        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
795        if (canvasDelegate == null) {
796            return;
797        }
798
799        // this one can be null
800        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
801
802        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
803            @Override
804            public void draw(Graphics2D graphics, Paint_Delegate paint) {
805                chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
806                        (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
807                        bitmapDensity);
808            }
809        }, paintDelegate, true, false);
810
811    }
812
813    @LayoutlibDelegate
814    public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
815                                                 float left, float top,
816                                                 long nativePaintOrZero,
817                                                 int canvasDensity,
818                                                 int screenDensity,
819                                                 int bitmapDensity) {
820        // get the delegate from the native int.
821        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
822        if (bitmapDelegate == null) {
823            return;
824        }
825
826        BufferedImage image = bitmapDelegate.getImage();
827        float right = left + image.getWidth();
828        float bottom = top + image.getHeight();
829
830        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
831                0, 0, image.getWidth(), image.getHeight(),
832                (int)left, (int)top, (int)right, (int)bottom);
833    }
834
835    @LayoutlibDelegate
836    public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
837                                 float srcLeft, float srcTop, float srcRight, float srcBottom,
838                                 float dstLeft, float dstTop, float dstRight, float dstBottom,
839                                 long nativePaintOrZero, int screenDensity, int bitmapDensity) {
840        // get the delegate from the native int.
841        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
842        if (bitmapDelegate == null) {
843            return;
844        }
845
846        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
847                (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom,
848                (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom);
849    }
850
851    @LayoutlibDelegate
852    public static void native_drawBitmap(long nativeCanvas, int[] colors,
853                                                int offset, int stride, final float x,
854                                                 final float y, int width, int height,
855                                                 boolean hasAlpha,
856                                                 long nativePaintOrZero) {
857        // create a temp BufferedImage containing the content.
858        final BufferedImage image = new BufferedImage(width, height,
859                hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
860        image.setRGB(0, 0, width, height, colors, offset, stride);
861
862        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
863                new GcSnapshot.Drawable() {
864                    @Override
865                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
866                        if (paint != null && paint.isFilterBitmap()) {
867                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
868                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
869                        }
870
871                        graphics.drawImage(image, (int) x, (int) y, null);
872                    }
873        });
874    }
875
876    @LayoutlibDelegate
877    public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
878                                                      long nMatrix, long nPaint) {
879        // get the delegate from the native int.
880        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
881        if (canvasDelegate == null) {
882            return;
883        }
884
885        // get the delegate from the native int, which can be null
886        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
887
888        // get the delegate from the native int.
889        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
890        if (bitmapDelegate == null) {
891            return;
892        }
893
894        final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
895
896        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
897        if (matrixDelegate == null) {
898            return;
899        }
900
901        final AffineTransform mtx = matrixDelegate.getAffineTransform();
902
903        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
904                @Override
905                public void draw(Graphics2D graphics, Paint_Delegate paint) {
906                    if (paint != null && paint.isFilterBitmap()) {
907                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
908                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
909                    }
910
911                    //FIXME add support for canvas, screen and bitmap densities.
912                    graphics.drawImage(image, mtx, null);
913                }
914        }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
915    }
916
917    @LayoutlibDelegate
918    public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
919            int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
920            int colorOffset, long nPaint) {
921        // FIXME
922        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
923                "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
924    }
925
926    @LayoutlibDelegate
927    public static void nativeDrawVertices(long nCanvas, int mode, int n,
928            float[] verts, int vertOffset,
929            float[] texs, int texOffset,
930            int[] colors, int colorOffset,
931            short[] indices, int indexOffset,
932            int indexCount, long nPaint) {
933        // FIXME
934        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
935                "Canvas.drawVertices is not supported.", null, null /*data*/);
936    }
937
938    @LayoutlibDelegate
939    public static void native_drawText(long nativeCanvas, char[] text, int index, int count,
940            float startX, float startY, int flags, long paint, long typeface) {
941        drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
942                paint, typeface);
943    }
944
945    @LayoutlibDelegate
946    public static void native_drawText(long nativeCanvas, String text,
947            int start, int end, float x, float y, final int flags, long paint,
948            long typeface) {
949        int count = end - start;
950        char[] buffer = TemporaryBuffer.obtain(count);
951        TextUtils.getChars(text, start, end, buffer, 0);
952
953        native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
954    }
955
956    @LayoutlibDelegate
957    public static void native_drawTextRun(long nativeCanvas, String text,
958            int start, int end, int contextStart, int contextEnd,
959            float x, float y, boolean isRtl, long paint, long typeface) {
960        int count = end - start;
961        char[] buffer = TemporaryBuffer.obtain(count);
962        TextUtils.getChars(text, start, end, buffer, 0);
963
964        drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
965    }
966
967    @LayoutlibDelegate
968    public static void native_drawTextRun(long nativeCanvas, char[] text,
969            int start, int count, int contextStart, int contextCount,
970            float x, float y, boolean isRtl, long paint, long typeface) {
971        drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
972    }
973
974    @LayoutlibDelegate
975    public static void native_drawTextOnPath(long nativeCanvas,
976                                                     char[] text, int index,
977                                                     int count, long path,
978                                                     float hOffset,
979                                                     float vOffset, int bidiFlags,
980                                                     long paint, long typeface) {
981        // FIXME
982        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
983                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
984    }
985
986    @LayoutlibDelegate
987    public static void native_drawTextOnPath(long nativeCanvas,
988                                                     String text, long path,
989                                                     float hOffset,
990                                                     float vOffset,
991                                                     int bidiFlags, long paint,
992                                                     long typeface) {
993        // FIXME
994        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
995                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
996    }
997
998    @LayoutlibDelegate
999    /*package*/ static long getNativeFinalizer() {
1000        synchronized (Canvas_Delegate.class) {
1001            if (sFinalizer == -1) {
1002                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
1003                    Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
1004                    if (delegate != null) {
1005                        delegate.dispose();
1006                    }
1007                    sManager.removeJavaReferenceFor(nativePtr);
1008                });
1009            }
1010        }
1011        return sFinalizer;
1012    }
1013
1014    // ---- Private delegate/helper methods ----
1015
1016    /**
1017     * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
1018     * <p>Note that the drawable may actually be executed several times if there are
1019     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
1020     */
1021    private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
1022            GcSnapshot.Drawable drawable) {
1023        // get the delegate from the native int.
1024        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
1025        if (canvasDelegate == null) {
1026            return;
1027        }
1028
1029        // get the paint which can be null if nPaint is 0;
1030        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
1031
1032        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
1033    }
1034
1035    /**
1036     * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
1037     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
1038     * <p>Note that the drawable may actually be executed several times if there are
1039     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
1040     */
1041    private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
1042        // get the delegate from the native int.
1043        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
1044        if (canvasDelegate == null) {
1045            return;
1046        }
1047
1048        canvasDelegate.mSnapshot.draw(drawable);
1049    }
1050
1051    private static void drawText(long nativeCanvas, final char[] text, final int index,
1052            final int count, final float startX, final float startY, final boolean isRtl,
1053            long paint, final long typeface) {
1054
1055        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
1056                new GcSnapshot.Drawable() {
1057            @Override
1058            public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
1059                // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
1060                // Any change to this method should be reflected in Paint.measureText
1061
1062                // assert that the typeface passed is actually the one stored in paint.
1063                assert (typeface == paintDelegate.mNativeTypeface);
1064
1065                // Paint.TextAlign indicates how the text is positioned relative to X.
1066                // LEFT is the default and there's nothing to do.
1067                float x = startX;
1068                int limit = index + count;
1069                if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
1070                    RectF bounds = paintDelegate.measureText(text, index, count, null, 0,
1071                            isRtl);
1072                    float m = bounds.right - bounds.left;
1073                    if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
1074                        x -= m / 2;
1075                    } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
1076                        x -= m;
1077                    }
1078                }
1079
1080                new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY)
1081                        .renderText(index, limit, isRtl, null, 0, true);
1082            }
1083        });
1084    }
1085
1086    private Canvas_Delegate(Bitmap_Delegate bitmap) {
1087        mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
1088    }
1089
1090    private Canvas_Delegate() {
1091        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
1092    }
1093
1094    /**
1095     * Disposes of the {@link Graphics2D} stack.
1096     */
1097    private void dispose() {
1098        mSnapshot.dispose();
1099    }
1100
1101    private int save(int saveFlags) {
1102        // get the current save count
1103        int count = mSnapshot.size();
1104
1105        mSnapshot = mSnapshot.save(saveFlags);
1106
1107        // return the old save count
1108        return count;
1109    }
1110
1111    private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
1112        Paint_Delegate paint = new Paint_Delegate();
1113        paint.setAlpha(alpha);
1114        return saveLayer(rect, paint, saveFlags);
1115    }
1116
1117    private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
1118        // get the current save count
1119        int count = mSnapshot.size();
1120
1121        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
1122
1123        // return the old save count
1124        return count;
1125    }
1126
1127    /**
1128     * Restores the {@link GcSnapshot} to <var>saveCount</var>
1129     * @param saveCount the saveCount
1130     */
1131    private void restoreTo(int saveCount) {
1132        mSnapshot = mSnapshot.restoreTo(saveCount);
1133    }
1134
1135    /**
1136     * Restores the top {@link GcSnapshot}
1137     */
1138    private void restore() {
1139        mSnapshot = mSnapshot.restore();
1140    }
1141
1142    private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
1143        return mSnapshot.clipRect(left, top, right, bottom, regionOp);
1144    }
1145
1146    private static void drawBitmap(
1147            long nativeCanvas,
1148            Bitmap_Delegate bitmap,
1149            long nativePaintOrZero,
1150            final int sleft, final int stop, final int sright, final int sbottom,
1151            final int dleft, final int dtop, final int dright, final int dbottom) {
1152        // get the delegate from the native int.
1153        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
1154        if (canvasDelegate == null) {
1155            return;
1156        }
1157
1158        // get the paint, which could be null if the int is 0
1159        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
1160
1161        final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
1162
1163        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
1164                new GcSnapshot.Drawable() {
1165                    @Override
1166                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
1167                        if (paint != null && paint.isFilterBitmap()) {
1168                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1169                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
1170                        }
1171
1172                        //FIXME add support for canvas, screen and bitmap densities.
1173                        graphics.drawImage(image, dleft, dtop, dright, dbottom,
1174                                sleft, stop, sright, sbottom, null);
1175                    }
1176        });
1177    }
1178
1179
1180    /**
1181     * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
1182     * The image returns, through a 1-size boolean array, whether the drawing code should
1183     * use a SRC composite no matter what the paint says.
1184     *
1185     * @param bitmap the bitmap
1186     * @param paint the paint that will be used to draw
1187     * @param forceSrcMode whether the composite will have to be SRC
1188     * @return the image to draw
1189     */
1190    private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
1191            boolean[] forceSrcMode) {
1192        BufferedImage image = bitmap.getImage();
1193        forceSrcMode[0] = false;
1194
1195        // if the bitmap config is alpha_8, then we erase all color value from it
1196        // before drawing it.
1197        if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
1198            fixAlpha8Bitmap(image);
1199        } else if (!bitmap.hasAlpha()) {
1200            // hasAlpha is merely a rendering hint. There can in fact be alpha values
1201            // in the bitmap but it should be ignored at drawing time.
1202            // There is two ways to do this:
1203            // - override the composite to be SRC. This can only be used if the composite
1204            //   was going to be SRC or SRC_OVER in the first place
1205            // - Create a different bitmap to draw in which all the alpha channel values is set
1206            //   to 0xFF.
1207            if (paint != null) {
1208                Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
1209                if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
1210                    PorterDuff.Mode mode =
1211                        ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
1212
1213                    forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
1214                            mode == PorterDuff.Mode.SRC;
1215                }
1216            }
1217
1218            // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
1219            if (!forceSrcMode[0]) {
1220                image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
1221            }
1222        }
1223
1224        return image;
1225    }
1226
1227    private static void fixAlpha8Bitmap(final BufferedImage image) {
1228        int w = image.getWidth();
1229        int h = image.getHeight();
1230        int[] argb = new int[w * h];
1231        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
1232
1233        final int length = argb.length;
1234        for (int i = 0 ; i < length; i++) {
1235            argb[i] &= 0xFF000000;
1236        }
1237        image.setRGB(0, 0, w, h, argb, 0, w);
1238    }
1239}
1240
1241