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.tools.layoutlib.annotations.LayoutlibDelegate;
24
25import android.annotation.Nullable;
26import android.graphics.Bitmap.Config;
27
28import java.awt.Graphics2D;
29import java.awt.Rectangle;
30import java.awt.geom.AffineTransform;
31
32import libcore.util.NativeAllocationRegistry_Delegate;
33
34
35/**
36 * Delegate implementing the native methods of android.graphics.Canvas
37 *
38 * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
39 * by calls to methods of the same name in this delegate class.
40 *
41 * This class behaves like the original native implementation, but in Java, keeping previously
42 * native data into its own objects and mapping them to int that are sent back and forth between
43 * it and the original Canvas class.
44 *
45 * @see DelegateManager
46 *
47 */
48public final class Canvas_Delegate extends BaseCanvas_Delegate {
49
50    // ---- delegate manager ----
51    private static long sFinalizer = -1;
52
53    private DrawFilter_Delegate mDrawFilter = null;
54
55    // ---- Public Helper methods ----
56
57    /**
58     * Returns the native delegate associated to a given {@link Canvas} object.
59     */
60    public static Canvas_Delegate getDelegate(Canvas canvas) {
61        return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper());
62    }
63
64    /**
65     * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
66     */
67    public static Canvas_Delegate getDelegate(long native_canvas) {
68        return (Canvas_Delegate) sManager.getDelegate(native_canvas);
69    }
70
71    /**
72     * Returns the current {@link Graphics2D} used to draw.
73     */
74    public GcSnapshot getSnapshot() {
75        return mSnapshot;
76    }
77
78    /**
79     * Returns the {@link DrawFilter} delegate or null if none have been set.
80     *
81     * @return the delegate or null.
82     */
83    public DrawFilter_Delegate getDrawFilter() {
84        return mDrawFilter;
85    }
86
87    // ---- native methods ----
88
89    @LayoutlibDelegate
90    /*package*/ static void nFreeCaches() {
91        // nothing to be done here.
92    }
93
94    @LayoutlibDelegate
95    /*package*/ static void nFreeTextLayoutCaches() {
96        // nothing to be done here yet.
97    }
98
99    @LayoutlibDelegate
100    /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
101        long nativeBitmapOrZero = 0;
102        if (bitmap != null) {
103            nativeBitmapOrZero = bitmap.getNativeInstance();
104        }
105        if (nativeBitmapOrZero > 0) {
106            // get the Bitmap from the int
107            Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
108
109            // create a new Canvas_Delegate with the given bitmap and return its new native int.
110            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
111
112            return sManager.addNewDelegate(newDelegate);
113        }
114
115        // create a new Canvas_Delegate and return its new native int.
116        Canvas_Delegate newDelegate = new Canvas_Delegate();
117
118        return sManager.addNewDelegate(newDelegate);
119    }
120
121    @LayoutlibDelegate
122    public static void nSetBitmap(long canvas, Bitmap bitmap) {
123        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
124        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
125        if (canvasDelegate == null || bitmapDelegate==null) {
126            return;
127        }
128        canvasDelegate.mBitmap = bitmapDelegate;
129        canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate);
130    }
131
132    @LayoutlibDelegate
133    public static boolean nIsOpaque(long nativeCanvas) {
134        // get the delegate from the native int.
135        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
136        if (canvasDelegate == null) {
137            return false;
138        }
139
140        return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
141    }
142
143    @LayoutlibDelegate
144    public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
145
146    @LayoutlibDelegate
147    public static int nGetWidth(long nativeCanvas) {
148        // get the delegate from the native int.
149        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
150        if (canvasDelegate == null) {
151            return 0;
152        }
153
154        return canvasDelegate.mBitmap.getImage().getWidth();
155    }
156
157    @LayoutlibDelegate
158    public static int nGetHeight(long nativeCanvas) {
159        // get the delegate from the native int.
160        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
161        if (canvasDelegate == null) {
162            return 0;
163        }
164
165        return canvasDelegate.mBitmap.getImage().getHeight();
166    }
167
168    @LayoutlibDelegate
169    public static int nSave(long nativeCanvas, int saveFlags) {
170        // get the delegate from the native int.
171        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
172        if (canvasDelegate == null) {
173            return 0;
174        }
175
176        return canvasDelegate.save(saveFlags);
177    }
178
179    @LayoutlibDelegate
180    public static int nSaveLayer(long nativeCanvas, float l,
181                                               float t, float r, float b,
182                                               long paint, int layerFlags) {
183        // get the delegate from the native int.
184        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
185        if (canvasDelegate == null) {
186            return 0;
187        }
188
189        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
190        if (paintDelegate == null) {
191            return 0;
192        }
193
194        return canvasDelegate.saveLayer(new RectF(l, t, r, b),
195                paintDelegate, layerFlags);
196    }
197
198    @LayoutlibDelegate
199    public static int nSaveLayerAlpha(long nativeCanvas, float l,
200                                                    float t, float r, float b,
201                                                    int alpha, int layerFlags) {
202        // get the delegate from the native int.
203        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
204        if (canvasDelegate == null) {
205            return 0;
206        }
207
208        return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
209    }
210
211    @LayoutlibDelegate
212    public static boolean nRestore(long nativeCanvas) {
213        // FIXME: implement throwOnUnderflow.
214        // get the delegate from the native int.
215        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
216        if (canvasDelegate == null) {
217            return false;
218        }
219
220        canvasDelegate.restore();
221        return true;
222    }
223
224    @LayoutlibDelegate
225    public static void nRestoreToCount(long nativeCanvas, int saveCount) {
226        // FIXME: implement throwOnUnderflow.
227        // get the delegate from the native int.
228        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
229        if (canvasDelegate == null) {
230            return;
231        }
232
233        canvasDelegate.restoreTo(saveCount);
234    }
235
236    @LayoutlibDelegate
237    public static int nGetSaveCount(long nativeCanvas) {
238        // get the delegate from the native int.
239        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
240        if (canvasDelegate == null) {
241            return 0;
242        }
243
244        return canvasDelegate.getSnapshot().size();
245    }
246
247    @LayoutlibDelegate
248   public static void nTranslate(long nativeCanvas, float dx, float dy) {
249        // get the delegate from the native int.
250        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
251        if (canvasDelegate == null) {
252            return;
253        }
254
255        canvasDelegate.getSnapshot().translate(dx, dy);
256    }
257
258    @LayoutlibDelegate
259       public static void nScale(long nativeCanvas, float sx, float sy) {
260            // get the delegate from the native int.
261            Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
262            if (canvasDelegate == null) {
263                return;
264            }
265
266            canvasDelegate.getSnapshot().scale(sx, sy);
267        }
268
269    @LayoutlibDelegate
270    public static void nRotate(long nativeCanvas, float degrees) {
271        // get the delegate from the native int.
272        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
273        if (canvasDelegate == null) {
274            return;
275        }
276
277        canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
278    }
279
280    @LayoutlibDelegate
281   public static void nSkew(long nativeCanvas, float kx, float ky) {
282        // get the delegate from the native int.
283        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
284        if (canvasDelegate == null) {
285            return;
286        }
287
288        // get the current top graphics2D object.
289        GcSnapshot g = canvasDelegate.getSnapshot();
290
291        // get its current matrix
292        AffineTransform currentTx = g.getTransform();
293        // get the AffineTransform for the given skew.
294        float[] mtx = Matrix_Delegate.getSkew(kx, ky);
295        AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
296
297        // combine them so that the given matrix is applied after.
298        currentTx.preConcatenate(matrixTx);
299
300        // give it to the graphics2D as a new matrix replacing all previous transform
301        g.setTransform(currentTx);
302    }
303
304    @LayoutlibDelegate
305    public static void nConcat(long nCanvas, long nMatrix) {
306        // get the delegate from the native int.
307        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
308        if (canvasDelegate == null) {
309            return;
310        }
311
312        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
313        if (matrixDelegate == null) {
314            return;
315        }
316
317        // get the current top graphics2D object.
318        GcSnapshot snapshot = canvasDelegate.getSnapshot();
319
320        // get its current matrix
321        AffineTransform currentTx = snapshot.getTransform();
322        // get the AffineTransform of the given matrix
323        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
324
325        // combine them so that the given matrix is applied after.
326        currentTx.concatenate(matrixTx);
327
328        // give it to the graphics2D as a new matrix replacing all previous transform
329        snapshot.setTransform(currentTx);
330    }
331
332    @LayoutlibDelegate
333    public static void nSetMatrix(long nCanvas, long nMatrix) {
334        // get the delegate from the native int.
335        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
336        if (canvasDelegate == null) {
337            return;
338        }
339
340        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
341        if (matrixDelegate == null) {
342            return;
343        }
344
345        // get the current top graphics2D object.
346        GcSnapshot snapshot = canvasDelegate.getSnapshot();
347
348        // get the AffineTransform of the given matrix
349        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
350
351        // give it to the graphics2D as a new matrix replacing all previous transform
352        snapshot.setTransform(matrixTx);
353
354        if (matrixDelegate.hasPerspective()) {
355            assert false;
356            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
357                    "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
358                    "supports affine transformations.", null, null /*data*/);
359        }
360    }
361
362    @LayoutlibDelegate
363    public static boolean nClipRect(long nCanvas,
364                                                  float left, float top,
365                                                  float right, float bottom,
366                                                  int regionOp) {
367        // get the delegate from the native int.
368        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
369        if (canvasDelegate == null) {
370            return false;
371        }
372
373        return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
374    }
375
376    @LayoutlibDelegate
377    public static boolean nClipPath(long nativeCanvas,
378                                                  long nativePath,
379                                                  int regionOp) {
380        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
381        if (canvasDelegate == null) {
382            return true;
383        }
384
385        Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
386        if (pathDelegate == null) {
387            return true;
388        }
389
390        return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
391    }
392
393    @LayoutlibDelegate
394    public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
395        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
396        if (canvasDelegate == null) {
397            return;
398        }
399
400        canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
401
402        if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
403            Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
404                    canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
405        }
406    }
407
408    @LayoutlibDelegate
409    public static boolean nGetClipBounds(long nativeCanvas,
410                                                       Rect bounds) {
411        // get the delegate from the native int.
412        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
413        if (canvasDelegate == null) {
414            return false;
415        }
416
417        Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
418        if (rect != null && !rect.isEmpty()) {
419            bounds.left = rect.x;
420            bounds.top = rect.y;
421            bounds.right = rect.x + rect.width;
422            bounds.bottom = rect.y + rect.height;
423            return true;
424        }
425
426        return false;
427    }
428
429    @LayoutlibDelegate
430    public static void nGetMatrix(long canvas, long matrix) {
431        // get the delegate from the native int.
432        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
433        if (canvasDelegate == null) {
434            return;
435        }
436
437        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
438        if (matrixDelegate == null) {
439            return;
440        }
441
442        AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
443        matrixDelegate.set(Matrix_Delegate.makeValues(transform));
444    }
445
446    @LayoutlibDelegate
447    public static boolean nQuickReject(long nativeCanvas, long path) {
448        // FIXME properly implement quickReject
449        return false;
450    }
451
452    @LayoutlibDelegate
453    public static boolean nQuickReject(long nativeCanvas,
454                                                     float left, float top,
455                                                     float right, float bottom) {
456        // FIXME properly implement quickReject
457        return false;
458    }
459
460    @LayoutlibDelegate
461    /*package*/ static long nGetNativeFinalizer() {
462        synchronized (Canvas_Delegate.class) {
463            if (sFinalizer == -1) {
464                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
465                    Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr);
466                    if (delegate != null) {
467                        delegate.dispose();
468                    }
469                    sManager.removeJavaReferenceFor(nativePtr);
470                });
471            }
472        }
473        return sFinalizer;
474    }
475
476    private Canvas_Delegate(Bitmap_Delegate bitmap) {
477        super(bitmap);
478    }
479
480    private Canvas_Delegate() {
481        super();
482    }
483}
484
485