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