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