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