1/*
2 * Copyright (C) 2014 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 android.annotation.FloatRange;
20import android.annotation.NonNull;
21import android.graphics.drawable.Drawable;
22
23/**
24 * Defines a simple shape, used for bounding graphical regions.
25 * <p>
26 * Can be computed for a View, or computed by a Drawable, to drive the shape of
27 * shadows cast by a View, or to clip the contents of the View.
28 *
29 * @see android.view.ViewOutlineProvider
30 * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
31 * @see Drawable#getOutline(Outline)
32 */
33public final class Outline {
34    /** @hide */
35    public Path mPath;
36
37    /** @hide */
38    public Rect mRect;
39    /** @hide */
40    public float mRadius;
41    /** @hide */
42    public float mAlpha;
43
44    /**
45     * Constructs an empty Outline. Call one of the setter methods to make
46     * the outline valid for use with a View.
47     */
48    public Outline() {}
49
50    /**
51     * Constructs an Outline with a copy of the data in src.
52     */
53    public Outline(@NonNull Outline src) {
54        set(src);
55    }
56
57    /**
58     * Sets the outline to be empty.
59     *
60     * @see #isEmpty()
61     */
62    public void setEmpty() {
63        mPath = null;
64        mRect = null;
65        mRadius = 0;
66    }
67
68    /**
69     * Returns whether the Outline is empty.
70     * <p>
71     * Outlines are empty when constructed, or if {@link #setEmpty()} is called,
72     * until a setter method is called
73     *
74     * @see #setEmpty()
75     */
76    public boolean isEmpty() {
77        return mRect == null && mPath == null;
78    }
79
80
81    /**
82     * Returns whether the outline can be used to clip a View.
83     * <p>
84     * Currently, only Outlines that can be represented as a rectangle, circle,
85     * or round rect support clipping.
86     *
87     * @see {@link android.view.View#setClipToOutline(boolean)}
88     */
89    public boolean canClip() {
90        return !isEmpty() && mRect != null;
91    }
92
93    /**
94     * Sets the alpha represented by the Outline - the degree to which the
95     * producer is guaranteed to be opaque over the Outline's shape.
96     * <p>
97     * An alpha value of <code>0.0f</code> either represents completely
98     * transparent content, or content that isn't guaranteed to fill the shape
99     * it publishes.
100     * <p>
101     * Content producing a fully opaque (alpha = <code>1.0f</code>) outline is
102     * assumed by the drawing system to fully cover content beneath it,
103     * meaning content beneath may be optimized away.
104     */
105    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
106        mAlpha = alpha;
107    }
108
109    /**
110     * Returns the alpha represented by the Outline.
111     */
112    public float getAlpha() {
113        return mAlpha;
114    }
115
116    /**
117     * Replace the contents of this Outline with the contents of src.
118     *
119     * @param src Source outline to copy from.
120     */
121    public void set(@NonNull Outline src) {
122        if (src.mPath != null) {
123            if (mPath == null) {
124                mPath = new Path();
125            }
126            mPath.set(src.mPath);
127            mRect = null;
128        }
129        if (src.mRect != null) {
130            if (mRect == null) {
131                mRect = new Rect();
132            }
133            mRect.set(src.mRect);
134        }
135        mRadius = src.mRadius;
136        mAlpha = src.mAlpha;
137    }
138
139    /**
140     * Sets the Outline to the rounded rect defined by the input rect, and
141     * corner radius.
142     */
143    public void setRect(int left, int top, int right, int bottom) {
144        setRoundRect(left, top, right, bottom, 0.0f);
145    }
146
147    /**
148     * Convenience for {@link #setRect(int, int, int, int)}
149     */
150    public void setRect(@NonNull Rect rect) {
151        setRect(rect.left, rect.top, rect.right, rect.bottom);
152    }
153
154    /**
155     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
156     * <p>
157     * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
158     */
159    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
160        if (left >= right || top >= bottom) {
161            setEmpty();
162            return;
163        }
164
165        if (mRect == null) mRect = new Rect();
166        mRect.set(left, top, right, bottom);
167        mRadius = radius;
168        mPath = null;
169    }
170
171    /**
172     * Convenience for {@link #setRoundRect(int, int, int, int, float)}
173     */
174    public void setRoundRect(@NonNull Rect rect, float radius) {
175        setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
176    }
177
178    /**
179     * Sets the outline to the oval defined by input rect.
180     */
181    public void setOval(int left, int top, int right, int bottom) {
182        if (left >= right || top >= bottom) {
183            setEmpty();
184            return;
185        }
186
187        if ((bottom - top) == (right - left)) {
188            // represent circle as round rect, for efficiency, and to enable clipping
189            setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
190            return;
191        }
192
193        if (mPath == null) mPath = new Path();
194        mPath.reset();
195        mPath.addOval(left, top, right, bottom, Path.Direction.CW);
196        mRect = null;
197    }
198
199    /**
200     * Convenience for {@link #setOval(int, int, int, int)}
201     */
202    public void setOval(@NonNull Rect rect) {
203        setOval(rect.left, rect.top, rect.right, rect.bottom);
204    }
205
206    /**
207     * Sets the Constructs an Outline from a
208     * {@link android.graphics.Path#isConvex() convex path}.
209     */
210    public void setConvexPath(@NonNull Path convexPath) {
211        if (convexPath.isEmpty()) {
212            setEmpty();
213            return;
214        }
215
216        if (!convexPath.isConvex()) {
217            throw new IllegalArgumentException("path must be convex");
218        }
219        if (mPath == null) mPath = new Path();
220
221        mPath.set(convexPath);
222        mRect = null;
223        mRadius = -1.0f;
224    }
225
226    /**
227     * Offsets the Outline by (dx,dy)
228     */
229    public void offset(int dx, int dy) {
230        if (mRect != null) {
231            mRect.offset(dx, dy);
232        } else if (mPath != null) {
233            mPath.offset(dx, dy);
234        }
235    }
236}
237