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