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 
17 package android.graphics;
18 
19 import com.android.ide.common.rendering.api.LayoutLog;
20 import com.android.layoutlib.bridge.Bridge;
21 import com.android.layoutlib.bridge.impl.DelegateManager;
22 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23 
24 import android.os.Parcel;
25 
26 import java.awt.Rectangle;
27 import java.awt.Shape;
28 import java.awt.geom.AffineTransform;
29 import java.awt.geom.Area;
30 import java.awt.geom.Rectangle2D;
31 
32 /**
33  * Delegate implementing the native methods of android.graphics.Region
34  *
35  * Through the layoutlib_create tool, the original native methods of Region have been replaced
36  * by calls to methods of the same name in this delegate class.
37  *
38  * This class behaves like the original native implementation, but in Java, keeping previously
39  * native data into its own objects and mapping them to int that are sent back and forth between
40  * it and the original Region class.
41  *
42  * This also serve as a base class for all Region delegate classes.
43  *
44  * @see DelegateManager
45  *
46  */
47 public class Region_Delegate {
48 
49     // ---- delegate manager ----
50     protected static final DelegateManager<Region_Delegate> sManager =
51             new DelegateManager<Region_Delegate>(Region_Delegate.class);
52 
53     // ---- delegate helper data ----
54 
55     // ---- delegate data ----
56     private Area mArea = new Area();
57 
58     // ---- Public Helper methods ----
59 
getDelegate(long nativeShader)60     public static Region_Delegate getDelegate(long nativeShader) {
61         return sManager.getDelegate(nativeShader);
62     }
63 
getJavaArea()64     public Area getJavaArea() {
65         return mArea;
66     }
67 
68     /**
69      * Combines two {@link Shape} into another one (actually an {@link Area}), according
70      * to the given {@link Region.Op}.
71      *
72      * If the Op is not one that combines two shapes, then this return null
73      *
74      * @param shape1 the firt shape to combine which can be null if there's no original clip.
75      * @param shape2 the 2nd shape to combine
76      * @param regionOp the operande for the combine
77      * @return a new area or null.
78      */
combineShapes(Shape shape1, Shape shape2, int regionOp)79     public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
80         if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
81             // if shape1 is null (empty), then the result is null.
82             if (shape1 == null) {
83                 return null;
84             }
85 
86             // result is always a new area.
87             Area result = new Area(shape1);
88             result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
89             return result;
90 
91         } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
92             // if shape1 is null, then the result is simply shape2.
93             if (shape1 == null) {
94                 return new Area(shape2);
95             }
96 
97             // result is always a new area.
98             Area result = new Area(shape1);
99             result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
100             return result;
101 
102         } else if (regionOp == Region.Op.UNION.nativeInt) {
103             // if shape1 is null, then the result is simply shape2.
104             if (shape1 == null) {
105                 return new Area(shape2);
106             }
107 
108             // result is always a new area.
109             Area result = new Area(shape1);
110             result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
111             return result;
112 
113         } else if (regionOp == Region.Op.XOR.nativeInt) {
114             // if shape1 is null, then the result is simply shape2
115             if (shape1 == null) {
116                 return new Area(shape2);
117             }
118 
119             // result is always a new area.
120             Area result = new Area(shape1);
121             result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
122             return result;
123 
124         } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
125             // result is always a new area.
126             Area result = new Area(shape2);
127 
128             if (shape1 != null) {
129                 result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
130             }
131 
132             return result;
133         }
134 
135         return null;
136     }
137 
138     // ---- native methods ----
139 
140     @LayoutlibDelegate
isEmpty(Region thisRegion)141     /*package*/ static boolean isEmpty(Region thisRegion) {
142         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
143         if (regionDelegate == null) {
144             return true;
145         }
146 
147         return regionDelegate.mArea.isEmpty();
148     }
149 
150     @LayoutlibDelegate
isRect(Region thisRegion)151     /*package*/ static boolean isRect(Region thisRegion) {
152         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
153         if (regionDelegate == null) {
154             return true;
155         }
156 
157         return regionDelegate.mArea.isRectangular();
158     }
159 
160     @LayoutlibDelegate
isComplex(Region thisRegion)161     /*package*/ static boolean isComplex(Region thisRegion) {
162         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
163         if (regionDelegate == null) {
164             return true;
165         }
166 
167         return regionDelegate.mArea.isSingular() == false;
168     }
169 
170     @LayoutlibDelegate
contains(Region thisRegion, int x, int y)171     /*package*/ static boolean contains(Region thisRegion, int x, int y) {
172         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
173         if (regionDelegate == null) {
174             return false;
175         }
176 
177         return regionDelegate.mArea.contains(x, y);
178     }
179 
180     @LayoutlibDelegate
quickContains(Region thisRegion, int left, int top, int right, int bottom)181     /*package*/ static boolean quickContains(Region thisRegion,
182             int left, int top, int right, int bottom) {
183         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
184         if (regionDelegate == null) {
185             return false;
186         }
187 
188         return regionDelegate.mArea.isRectangular() &&
189                 regionDelegate.mArea.contains(left, top, right - left, bottom - top);
190     }
191 
192     @LayoutlibDelegate
quickReject(Region thisRegion, int left, int top, int right, int bottom)193     /*package*/ static boolean quickReject(Region thisRegion,
194             int left, int top, int right, int bottom) {
195         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
196         if (regionDelegate == null) {
197             return false;
198         }
199 
200         return regionDelegate.mArea.isEmpty() ||
201                 regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false;
202     }
203 
204     @LayoutlibDelegate
quickReject(Region thisRegion, Region rgn)205     /*package*/ static boolean quickReject(Region thisRegion, Region rgn) {
206         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
207         if (regionDelegate == null) {
208             return false;
209         }
210 
211         Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion);
212         if (targetRegionDelegate == null) {
213             return false;
214         }
215 
216         return regionDelegate.mArea.isEmpty() ||
217                 regionDelegate.mArea.getBounds().intersects(
218                         targetRegionDelegate.mArea.getBounds()) == false;
219 
220     }
221 
222     @LayoutlibDelegate
translate(Region thisRegion, int dx, int dy, Region dst)223     /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) {
224         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
225         if (regionDelegate == null) {
226             return;
227         }
228 
229         Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
230         if (targetRegionDelegate == null) {
231             return;
232         }
233 
234         if (regionDelegate.mArea.isEmpty()) {
235             targetRegionDelegate.mArea = new Area();
236         } else {
237             targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
238             AffineTransform mtx = new AffineTransform();
239             mtx.translate(dx, dy);
240             targetRegionDelegate.mArea.transform(mtx);
241         }
242     }
243 
244     @LayoutlibDelegate
scale(Region thisRegion, float scale, Region dst)245     /*package*/ static void scale(Region thisRegion, float scale, Region dst) {
246         Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
247         if (regionDelegate == null) {
248             return;
249         }
250 
251         Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
252         if (targetRegionDelegate == null) {
253             return;
254         }
255 
256         if (regionDelegate.mArea.isEmpty()) {
257             targetRegionDelegate.mArea = new Area();
258         } else {
259             targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
260             AffineTransform mtx = new AffineTransform();
261             mtx.scale(scale, scale);
262             targetRegionDelegate.mArea.transform(mtx);
263         }
264     }
265 
266     @LayoutlibDelegate
nativeConstructor()267     /*package*/ static long nativeConstructor() {
268         Region_Delegate newDelegate = new Region_Delegate();
269         return sManager.addNewDelegate(newDelegate);
270     }
271 
272     @LayoutlibDelegate
nativeDestructor(long native_region)273     /*package*/ static void nativeDestructor(long native_region) {
274         sManager.removeJavaReferenceFor(native_region);
275     }
276 
277     @LayoutlibDelegate
nativeSetRegion(long native_dst, long native_src)278     /*package*/ static void nativeSetRegion(long native_dst, long native_src) {
279         Region_Delegate dstRegion = sManager.getDelegate(native_dst);
280         if (dstRegion == null) {
281             return;
282         }
283 
284         Region_Delegate srcRegion = sManager.getDelegate(native_src);
285         if (srcRegion == null) {
286             return;
287         }
288 
289         dstRegion.mArea.reset();
290         dstRegion.mArea.add(srcRegion.mArea);
291 
292     }
293 
294     @LayoutlibDelegate
nativeSetRect(long native_dst, int left, int top, int right, int bottom)295     /*package*/ static boolean nativeSetRect(long native_dst,
296             int left, int top, int right, int bottom) {
297         Region_Delegate dstRegion = sManager.getDelegate(native_dst);
298         if (dstRegion == null) {
299             return true;
300         }
301 
302         dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
303         return dstRegion.mArea.getBounds().isEmpty() == false;
304     }
305 
306     @LayoutlibDelegate
nativeSetPath(long native_dst, long native_path, long native_clip)307     /*package*/ static boolean nativeSetPath(long native_dst, long native_path, long native_clip) {
308         Region_Delegate dstRegion = sManager.getDelegate(native_dst);
309         if (dstRegion == null) {
310             return true;
311         }
312 
313         Path_Delegate path = Path_Delegate.getDelegate(native_path);
314         if (path == null) {
315             return true;
316         }
317 
318         dstRegion.mArea = new Area(path.getJavaShape());
319 
320         Region_Delegate clip = sManager.getDelegate(native_clip);
321         if (clip != null) {
322             dstRegion.mArea.subtract(clip.getJavaArea());
323         }
324 
325         return dstRegion.mArea.getBounds().isEmpty() == false;
326     }
327 
328     @LayoutlibDelegate
nativeGetBounds(long native_region, Rect rect)329     /*package*/ static boolean nativeGetBounds(long native_region, Rect rect) {
330         Region_Delegate region = sManager.getDelegate(native_region);
331         if (region == null) {
332             return true;
333         }
334 
335         Rectangle bounds = region.mArea.getBounds();
336         if (bounds.isEmpty()) {
337             rect.left = rect.top = rect.right = rect.bottom = 0;
338             return false;
339         }
340 
341         rect.left = bounds.x;
342         rect.top = bounds.y;
343         rect.right = bounds.x + bounds.width;
344         rect.bottom = bounds.y + bounds.height;
345         return true;
346     }
347 
348     @LayoutlibDelegate
nativeGetBoundaryPath(long native_region, long native_path)349     /*package*/ static boolean nativeGetBoundaryPath(long native_region, long native_path) {
350         Region_Delegate region = sManager.getDelegate(native_region);
351         if (region == null) {
352             return false;
353         }
354 
355         Path_Delegate path = Path_Delegate.getDelegate(native_path);
356         if (path == null) {
357             return false;
358         }
359 
360         if (region.mArea.isEmpty()) {
361             path.reset();
362             return false;
363         }
364 
365         path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
366         return true;
367     }
368 
369     @LayoutlibDelegate
nativeOp(long native_dst, int left, int top, int right, int bottom, int op)370     /*package*/ static boolean nativeOp(long native_dst,
371             int left, int top, int right, int bottom, int op) {
372         Region_Delegate region = sManager.getDelegate(native_dst);
373         if (region == null) {
374             return false;
375         }
376 
377         region.mArea = combineShapes(region.mArea,
378                 new Rectangle2D.Float(left, top, right - left, bottom - top), op);
379 
380         assert region.mArea != null;
381         if (region.mArea != null) {
382             region.mArea = new Area();
383         }
384 
385         return region.mArea.getBounds().isEmpty() == false;
386     }
387 
388     @LayoutlibDelegate
nativeOp(long native_dst, Rect rect, long native_region, int op)389     /*package*/ static boolean nativeOp(long native_dst, Rect rect, long native_region, int op) {
390         Region_Delegate region = sManager.getDelegate(native_dst);
391         if (region == null) {
392             return false;
393         }
394 
395         region.mArea = combineShapes(region.mArea,
396                 new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
397 
398         assert region.mArea != null;
399         if (region.mArea != null) {
400             region.mArea = new Area();
401         }
402 
403         return region.mArea.getBounds().isEmpty() == false;
404     }
405 
406     @LayoutlibDelegate
nativeOp(long native_dst, long native_region1, long native_region2, int op)407     /*package*/ static boolean nativeOp(long native_dst,
408             long native_region1, long native_region2, int op) {
409         Region_Delegate dstRegion = sManager.getDelegate(native_dst);
410         if (dstRegion == null) {
411             return true;
412         }
413 
414         Region_Delegate region1 = sManager.getDelegate(native_region1);
415         if (region1 == null) {
416             return false;
417         }
418 
419         Region_Delegate region2 = sManager.getDelegate(native_region2);
420         if (region2 == null) {
421             return false;
422         }
423 
424         dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
425 
426         assert dstRegion.mArea != null;
427         if (dstRegion.mArea != null) {
428             dstRegion.mArea = new Area();
429         }
430 
431         return dstRegion.mArea.getBounds().isEmpty() == false;
432 
433     }
434 
435     @LayoutlibDelegate
nativeCreateFromParcel(Parcel p)436     /*package*/ static long nativeCreateFromParcel(Parcel p) {
437         // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
438         // used during aidl call so really this should not be called.
439         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
440                 "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
441                 null /*data*/);
442         return 0;
443     }
444 
445     @LayoutlibDelegate
nativeWriteToParcel(long native_region, Parcel p)446     /*package*/ static boolean nativeWriteToParcel(long native_region,
447                                                       Parcel p) {
448         // This is only called when sending a region through aidl, so really this should not
449         // be called.
450         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
451                 "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
452                 null /*data*/);
453         return false;
454     }
455 
456     @LayoutlibDelegate
nativeEquals(long native_r1, long native_r2)457     /*package*/ static boolean nativeEquals(long native_r1, long native_r2) {
458         Region_Delegate region1 = sManager.getDelegate(native_r1);
459         if (region1 == null) {
460             return false;
461         }
462 
463         Region_Delegate region2 = sManager.getDelegate(native_r2);
464         if (region2 == null) {
465             return false;
466         }
467 
468         return region1.mArea.equals(region2.mArea);
469     }
470 
471     @LayoutlibDelegate
nativeToString(long native_region)472     /*package*/ static String nativeToString(long native_region) {
473         Region_Delegate region = sManager.getDelegate(native_region);
474         if (region == null) {
475             return "not found";
476         }
477 
478         return region.mArea.toString();
479     }
480 
481     // ---- Private delegate/helper methods ----
482 
483 }
484