diff options
Diffstat (limited to 'libs/hwui/ClipArea.cpp')
-rw-r--r-- | libs/hwui/ClipArea.cpp | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp new file mode 100644 index 0000000..852204a --- /dev/null +++ b/libs/hwui/ClipArea.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ClipArea.h" + +#include <SkPath.h> +#include <limits> + +#include "Rect.h" + +namespace android { +namespace uirenderer { + +static bool intersect(Rect& r, const Rect& r2) { + bool hasIntersection = r.intersect(r2); + if (!hasIntersection) { + r.setEmpty(); + } + return hasIntersection; +} + +static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { + Vertex v; + v.x = x; + v.y = y; + transform.mapPoint(v.x, v.y); + transformedBounds.expandToCoverVertex(v.x, v.y); +} + +Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { + const float kMinFloat = std::numeric_limits<float>::lowest(); + const float kMaxFloat = std::numeric_limits<float>::max(); + Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat }; + handlePoint(transformedBounds, transform, r.left, r.top); + handlePoint(transformedBounds, transform, r.right, r.top); + handlePoint(transformedBounds, transform, r.left, r.bottom); + handlePoint(transformedBounds, transform, r.right, r.bottom); + return transformedBounds; +} + +/* + * TransformedRectangle + */ + +TransformedRectangle::TransformedRectangle() { +} + +TransformedRectangle::TransformedRectangle(const Rect& bounds, + const Matrix4& transform) + : mBounds(bounds) + , mTransform(transform) { +} + +bool TransformedRectangle::canSimplyIntersectWith( + const TransformedRectangle& other) const { + + return mTransform == other.mTransform; +} + +bool TransformedRectangle::intersectWith(const TransformedRectangle& other) { + Rect translatedBounds(other.mBounds); + return intersect(mBounds, translatedBounds); +} + +bool TransformedRectangle::isEmpty() const { + return mBounds.isEmpty(); +} + +/* + * RectangleList + */ + +RectangleList::RectangleList() + : mTransformedRectanglesCount(0) { +} + +bool RectangleList::isEmpty() const { + if (mTransformedRectanglesCount < 1) { + return true; + } + + for (int i = 0; i < mTransformedRectanglesCount; i++) { + if (mTransformedRectangles[i].isEmpty()) { + return true; + } + } + return false; +} + +int RectangleList::getTransformedRectanglesCount() const { + return mTransformedRectanglesCount; +} + +const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const { + return mTransformedRectangles[i]; +} + +void RectangleList::setEmpty() { + mTransformedRectanglesCount = 0; +} + +void RectangleList::set(const Rect& bounds, const Matrix4& transform) { + mTransformedRectanglesCount = 1; + mTransformedRectangles[0] = TransformedRectangle(bounds, transform); +} + +bool RectangleList::intersectWith(const Rect& bounds, + const Matrix4& transform) { + TransformedRectangle newRectangle(bounds, transform); + + // Try to find a rectangle with a compatible transformation + int index = 0; + for (; index < mTransformedRectanglesCount; index++) { + TransformedRectangle& tr(mTransformedRectangles[index]); + if (tr.canSimplyIntersectWith(newRectangle)) { + tr.intersectWith(newRectangle); + return true; + } + } + + // Add it to the list if there is room + if (index < kMaxTransformedRectangles) { + mTransformedRectangles[index] = newRectangle; + mTransformedRectanglesCount += 1; + return true; + } + + // This rectangle list is full + return false; +} + +Rect RectangleList::calculateBounds() const { + Rect bounds; + for (int index = 0; index < mTransformedRectanglesCount; index++) { + const TransformedRectangle& tr(mTransformedRectangles[index]); + if (index == 0) { + bounds = tr.transformedBounds(); + } else { + bounds.intersect(tr.transformedBounds()); + } + } + return bounds; +} + +static SkPath pathFromTransformedRectangle(const Rect& bounds, + const Matrix4& transform) { + SkPath rectPath; + SkPath rectPathTransformed; + rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom); + SkMatrix skTransform; + transform.copyTo(skTransform); + rectPath.transform(skTransform, &rectPathTransformed); + return rectPathTransformed; +} + +SkRegion RectangleList::convertToRegion(const SkRegion& clip) const { + SkRegion rectangleListAsRegion; + for (int index = 0; index < mTransformedRectanglesCount; index++) { + const TransformedRectangle& tr(mTransformedRectangles[index]); + SkPath rectPathTransformed = pathFromTransformedRectangle( + tr.getBounds(), tr.getTransform()); + if (index == 0) { + rectangleListAsRegion.setPath(rectPathTransformed, clip); + } else { + SkRegion rectRegion; + rectRegion.setPath(rectPathTransformed, clip); + rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op); + } + } + return rectangleListAsRegion; +} + +/* + * ClipArea + */ + +ClipArea::ClipArea() + : mMode(kModeRectangle) { +} + +/* + * Interface + */ + +void ClipArea::setViewportDimensions(int width, int height) { + mViewportBounds.set(0, 0, width, height); + mClipRect = mViewportBounds; +} + +void ClipArea::setEmpty() { + mMode = kModeRectangle; + mClipRect.setEmpty(); + mClipRegion.setEmpty(); + mRectangleList.setEmpty(); +} + +void ClipArea::setClip(float left, float top, float right, float bottom) { + mMode = kModeRectangle; + mClipRect.set(left, top, right, bottom); + mClipRegion.setEmpty(); +} + +bool ClipArea::clipRectWithTransform(float left, float top, float right, + float bottom, const mat4* transform, SkRegion::Op op) { + Rect r(left, top, right, bottom); + return clipRectWithTransform(r, transform, op); +} + +bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, + SkRegion::Op op) { + switch (mMode) { + case kModeRectangle: + return rectangleModeClipRectWithTransform(r, transform, op); + case kModeRectangleList: + return rectangleListModeClipRectWithTransform(r, transform, op); + case kModeRegion: + return regionModeClipRectWithTransform(r, transform, op); + } + return false; +} + +bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { + enterRegionMode(); + mClipRegion.op(region, op); + setClipRectToRegionBounds(); + return true; +} + +bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, + SkRegion::Op op) { + SkMatrix skTransform; + transform->copyTo(skTransform); + SkPath transformed; + path.transform(skTransform, &transformed); + SkRegion region; + regionFromPath(transformed, region); + return clipRegion(region, op); +} + +/* + * Rectangle mode + */ + +void ClipArea::enterRectangleMode() { + // Entering rectangle mode discards any + // existing clipping information from the other modes. + // The only way this occurs is by a clip setting operation. + mMode = kModeRectangle; +} + +bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r, + const mat4* transform, SkRegion::Op op) { + + if (op != SkRegion::kIntersect_Op) { + enterRegionMode(); + return regionModeClipRectWithTransform(r, transform, op); + } + + if (transform->rectToRect()) { + Rect transformed(r); + transform->mapRect(transformed); + bool hasIntersection = mClipRect.intersect(transformed); + if (!hasIntersection) { + mClipRect.setEmpty(); + } + return true; + } + + enterRectangleListMode(); + return rectangleListModeClipRectWithTransform(r, transform, op); +} + +bool ClipArea::rectangleModeClipRectWithTransform(float left, float top, + float right, float bottom, const mat4* transform, SkRegion::Op op) { + Rect r(left, top, right, bottom); + bool result = rectangleModeClipRectWithTransform(r, transform, op); + mClipRect = mRectangleList.calculateBounds(); + return result; +} + +/* + * RectangleList mode implementation + */ + +void ClipArea::enterRectangleListMode() { + // Is is only legal to enter rectangle list mode from + // rectangle mode, since rectangle list mode cannot represent + // all clip areas that can be represented by a region. + ALOG_ASSERT(mMode == kModeRectangle); + mMode = kModeRectangleList; + mRectangleList.set(mClipRect, Matrix4::identity()); +} + +bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, + const mat4* transform, SkRegion::Op op) { + if (op != SkRegion::kIntersect_Op + || !mRectangleList.intersectWith(r, *transform)) { + enterRegionMode(); + return regionModeClipRectWithTransform(r, transform, op); + } + return true; +} + +bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top, + float right, float bottom, const mat4* transform, SkRegion::Op op) { + Rect r(left, top, right, bottom); + return rectangleListModeClipRectWithTransform(r, transform, op); +} + +/* + * Region mode implementation + */ + +void ClipArea::enterRegionMode() { + if (mMode != kModeRegion) { + if (mMode == kModeRectangle) { + mClipRegion.setRect(mClipRect.left, mClipRect.top, + mClipRect.right, mClipRect.bottom); + } else { + mClipRegion = mRectangleList.convertToRegion(createViewportRegion()); + setClipRectToRegionBounds(); + } + mMode = kModeRegion; + } +} + +bool ClipArea::regionModeClipRectWithTransform(const Rect& r, + const mat4* transform, SkRegion::Op op) { + SkPath transformedRect = pathFromTransformedRectangle(r, *transform); + SkRegion transformedRectRegion; + regionFromPath(transformedRect, transformedRectRegion); + mClipRegion.op(transformedRectRegion, op); + setClipRectToRegionBounds(); + return true; +} + +bool ClipArea::regionModeClipRectWithTransform(float left, float top, + float right, float bottom, const mat4* transform, SkRegion::Op op) { + return regionModeClipRectWithTransform(Rect(left, top, right, bottom), + transform, op); +} + +void ClipArea::setClipRectToRegionBounds() { + if (!mClipRegion.isEmpty()) { + mClipRect.set(mClipRegion.getBounds()); + + if (mClipRegion.isRect()) { + mClipRegion.setEmpty(); + } + } else { + mClipRect.setEmpty(); + } +} + +} /* namespace uirenderer */ +} /* namespace android */ |