ClipArea.cpp revision a2a70723b8cbda4354d23f901f995623e819012c
1/*
2 * Copyright (C) 2015 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#include "ClipArea.h"
17
18#include <SkPath.h>
19#include <limits>
20
21#include "Rect.h"
22
23namespace android {
24namespace uirenderer {
25
26static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
27    Vertex v = {x, y};
28    transform.mapPoint(v.x, v.y);
29    transformedBounds.expandToCover(v.x, v.y);
30}
31
32Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
33    const float kMinFloat = std::numeric_limits<float>::lowest();
34    const float kMaxFloat = std::numeric_limits<float>::max();
35    Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat };
36    handlePoint(transformedBounds, transform, r.left, r.top);
37    handlePoint(transformedBounds, transform, r.right, r.top);
38    handlePoint(transformedBounds, transform, r.left, r.bottom);
39    handlePoint(transformedBounds, transform, r.right, r.bottom);
40    return transformedBounds;
41}
42
43/*
44 * TransformedRectangle
45 */
46
47TransformedRectangle::TransformedRectangle() {
48}
49
50TransformedRectangle::TransformedRectangle(const Rect& bounds,
51        const Matrix4& transform)
52        : mBounds(bounds)
53        , mTransform(transform) {
54}
55
56bool TransformedRectangle::canSimplyIntersectWith(
57        const TransformedRectangle& other) const {
58
59    return mTransform == other.mTransform;
60}
61
62void TransformedRectangle::intersectWith(const TransformedRectangle& other) {
63    mBounds.doIntersect(other.mBounds);
64}
65
66bool TransformedRectangle::isEmpty() const {
67    return mBounds.isEmpty();
68}
69
70/*
71 * RectangleList
72 */
73
74RectangleList::RectangleList()
75        : mTransformedRectanglesCount(0) {
76}
77
78bool RectangleList::isEmpty() const {
79    if (mTransformedRectanglesCount < 1) {
80        return true;
81    }
82
83    for (int i = 0; i < mTransformedRectanglesCount; i++) {
84        if (mTransformedRectangles[i].isEmpty()) {
85            return true;
86        }
87    }
88    return false;
89}
90
91int RectangleList::getTransformedRectanglesCount() const {
92    return mTransformedRectanglesCount;
93}
94
95const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
96    return mTransformedRectangles[i];
97}
98
99void RectangleList::setEmpty() {
100    mTransformedRectanglesCount = 0;
101}
102
103void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
104    mTransformedRectanglesCount = 1;
105    mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
106}
107
108bool RectangleList::intersectWith(const Rect& bounds,
109        const Matrix4& transform) {
110    TransformedRectangle newRectangle(bounds, transform);
111
112    // Try to find a rectangle with a compatible transformation
113    int index = 0;
114    for (; index < mTransformedRectanglesCount; index++) {
115        TransformedRectangle& tr(mTransformedRectangles[index]);
116        if (tr.canSimplyIntersectWith(newRectangle)) {
117            tr.intersectWith(newRectangle);
118            return true;
119        }
120    }
121
122    // Add it to the list if there is room
123    if (index < kMaxTransformedRectangles) {
124        mTransformedRectangles[index] = newRectangle;
125        mTransformedRectanglesCount += 1;
126        return true;
127    }
128
129    // This rectangle list is full
130    return false;
131}
132
133Rect RectangleList::calculateBounds() const {
134    Rect bounds;
135    for (int index = 0; index < mTransformedRectanglesCount; index++) {
136        const TransformedRectangle& tr(mTransformedRectangles[index]);
137        if (index == 0) {
138            bounds = tr.transformedBounds();
139        } else {
140            bounds.doIntersect(tr.transformedBounds());
141        }
142    }
143    return bounds;
144}
145
146static SkPath pathFromTransformedRectangle(const Rect& bounds,
147        const Matrix4& transform) {
148    SkPath rectPath;
149    SkPath rectPathTransformed;
150    rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
151    SkMatrix skTransform;
152    transform.copyTo(skTransform);
153    rectPath.transform(skTransform, &rectPathTransformed);
154    return rectPathTransformed;
155}
156
157SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
158    SkRegion rectangleListAsRegion;
159    for (int index = 0; index < mTransformedRectanglesCount; index++) {
160        const TransformedRectangle& tr(mTransformedRectangles[index]);
161        SkPath rectPathTransformed = pathFromTransformedRectangle(
162                tr.getBounds(), tr.getTransform());
163        if (index == 0) {
164            rectangleListAsRegion.setPath(rectPathTransformed, clip);
165        } else {
166            SkRegion rectRegion;
167            rectRegion.setPath(rectPathTransformed, clip);
168            rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
169        }
170    }
171    return rectangleListAsRegion;
172}
173
174/*
175 * ClipArea
176 */
177
178ClipArea::ClipArea()
179        : mMode(Mode::Rectangle) {
180}
181
182/*
183 * Interface
184 */
185
186void ClipArea::setViewportDimensions(int width, int height) {
187    mViewportBounds.set(0, 0, width, height);
188    mClipRect = mViewportBounds;
189}
190
191void ClipArea::setEmpty() {
192    mMode = Mode::Rectangle;
193    mClipRect.setEmpty();
194    mClipRegion.setEmpty();
195    mRectangleList.setEmpty();
196}
197
198void ClipArea::setClip(float left, float top, float right, float bottom) {
199    mMode = Mode::Rectangle;
200    mClipRect.set(left, top, right, bottom);
201    mClipRegion.setEmpty();
202}
203
204void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
205        SkRegion::Op op) {
206    switch (mMode) {
207    case Mode::Rectangle:
208        rectangleModeClipRectWithTransform(r, transform, op);
209        break;
210    case Mode::RectangleList:
211        rectangleListModeClipRectWithTransform(r, transform, op);
212        break;
213    case Mode::Region:
214        regionModeClipRectWithTransform(r, transform, op);
215        break;
216    }
217}
218
219void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
220    enterRegionMode();
221    mClipRegion.op(region, op);
222    onClipRegionUpdated();
223}
224
225void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
226        SkRegion::Op op) {
227    SkMatrix skTransform;
228    transform->copyTo(skTransform);
229    SkPath transformed;
230    path.transform(skTransform, &transformed);
231    SkRegion region;
232    regionFromPath(transformed, region);
233    clipRegion(region, op);
234}
235
236/*
237 * Rectangle mode
238 */
239
240void ClipArea::enterRectangleMode() {
241    // Entering rectangle mode discards any
242    // existing clipping information from the other modes.
243    // The only way this occurs is by a clip setting operation.
244    mMode = Mode::Rectangle;
245}
246
247void ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
248        const mat4* transform, SkRegion::Op op) {
249
250    if (op == SkRegion::kReplace_Op && transform->rectToRect()) {
251        mClipRect = r;
252        transform->mapRect(mClipRect);
253        return;
254    } else if (op != SkRegion::kIntersect_Op) {
255        enterRegionMode();
256        regionModeClipRectWithTransform(r, transform, op);
257        return;
258    }
259
260    if (transform->rectToRect()) {
261        Rect transformed(r);
262        transform->mapRect(transformed);
263        mClipRect.doIntersect(transformed);
264        return;
265    }
266
267    enterRectangleListMode();
268    rectangleListModeClipRectWithTransform(r, transform, op);
269}
270
271/*
272 * RectangleList mode implementation
273 */
274
275void ClipArea::enterRectangleListMode() {
276    // Is is only legal to enter rectangle list mode from
277    // rectangle mode, since rectangle list mode cannot represent
278    // all clip areas that can be represented by a region.
279    ALOG_ASSERT(mMode == Mode::Rectangle);
280    mMode = Mode::RectangleList;
281    mRectangleList.set(mClipRect, Matrix4::identity());
282}
283
284void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
285        const mat4* transform, SkRegion::Op op) {
286    if (op != SkRegion::kIntersect_Op
287            || !mRectangleList.intersectWith(r, *transform)) {
288        enterRegionMode();
289        regionModeClipRectWithTransform(r, transform, op);
290    }
291}
292
293/*
294 * Region mode implementation
295 */
296
297void ClipArea::enterRegionMode() {
298    Mode oldMode = mMode;
299    mMode = Mode::Region;
300    if (oldMode != Mode::Region) {
301        if (oldMode == Mode::Rectangle) {
302            mClipRegion.setRect(mClipRect.left, mClipRect.top,
303                    mClipRect.right, mClipRect.bottom);
304        } else {
305            mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
306            onClipRegionUpdated();
307        }
308    }
309}
310
311void ClipArea::regionModeClipRectWithTransform(const Rect& r,
312        const mat4* transform, SkRegion::Op op) {
313    SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
314    SkRegion transformedRectRegion;
315    regionFromPath(transformedRect, transformedRectRegion);
316    mClipRegion.op(transformedRectRegion, op);
317    onClipRegionUpdated();
318}
319
320void ClipArea::onClipRegionUpdated() {
321    if (!mClipRegion.isEmpty()) {
322        mClipRect.set(mClipRegion.getBounds());
323
324        if (mClipRegion.isRect()) {
325            mClipRegion.setEmpty();
326            enterRectangleMode();
327        }
328    } else {
329        mClipRect.setEmpty();
330    }
331}
332
333} /* namespace uirenderer */
334} /* namespace android */
335