/*
* 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);
onClipRegionUpdated();
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::kReplace_Op && transform->rectToRect()) {
mClipRect = r;
transform->mapRect(mClipRect);
return true;
} else 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() {
Mode oldMode = mMode;
mMode = kModeRegion;
if (oldMode != kModeRegion) {
if (oldMode == kModeRectangle) {
mClipRegion.setRect(mClipRect.left, mClipRect.top,
mClipRect.right, mClipRect.bottom);
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
onClipRegionUpdated();
}
}
}
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);
onClipRegionUpdated();
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::onClipRegionUpdated() {
if (!mClipRegion.isEmpty()) {
mClipRect.set(mClipRegion.getBounds());
if (mClipRegion.isRect()) {
mClipRegion.setEmpty();
enterRectangleMode();
}
} else {
mClipRect.setEmpty();
}
}
} /* namespace uirenderer */
} /* namespace android */