blob: a90abdc23780d9d2d760a7bc1e9cf8c8062d2791 [file] [log] [blame]
/*
* Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/geometry/region.h"
#include <stdio.h>
namespace blink {
Region::Region() = default;
Region::Region(const IntRect& rect) : bounds_(rect), shape_(rect) {}
Vector<IntRect> Region::Rects() const {
Vector<IntRect> rects;
for (Shape::SpanIterator span = shape_.SpansBegin(), end = shape_.SpansEnd();
span != end && span + 1 != end; ++span) {
int y = span->y;
int height = (span + 1)->y - y;
for (Shape::SegmentIterator segment = shape_.SegmentsBegin(span),
segment_end = shape_.SegmentsEnd(span);
segment != segment_end && segment + 1 != segment_end; segment += 2) {
int x = *segment;
int width = *(segment + 1) - x;
rects.push_back(IntRect(x, y, width, height));
}
}
return rects;
}
bool Region::Contains(const Region& region) const {
if (!bounds_.Contains(region.bounds_))
return false;
return Shape::CompareShapes<Shape::CompareContainsOperation>(shape_,
region.shape_);
}
bool Region::Contains(const IntPoint& point) const {
if (!bounds_.Contains(point))
return false;
for (Shape::SpanIterator span = shape_.SpansBegin(), end = shape_.SpansEnd();
span != end && span + 1 != end; ++span) {
int y = span->y;
int max_y = (span + 1)->y;
if (y > point.Y())
break;
if (max_y <= point.Y())
continue;
for (Shape::SegmentIterator segment = shape_.SegmentsBegin(span),
segment_end = shape_.SegmentsEnd(span);
segment != segment_end && segment + 1 != segment_end; segment += 2) {
int x = *segment;
int max_x = *(segment + 1);
if (x > point.X())
break;
if (max_x > point.X())
return true;
}
}
return false;
}
bool Region::Intersects(const Region& region) const {
if (!bounds_.Intersects(region.bounds_))
return false;
return Shape::CompareShapes<Shape::CompareIntersectsOperation>(shape_,
region.shape_);
}
uint64_t Region::Area() const {
uint64_t area = 0;
for (Shape::SpanIterator span = shape_.SpansBegin(), end = shape_.SpansEnd();
span != end && span + 1 != end; ++span) {
int height = (span + 1)->y - span->y;
for (Shape::SegmentIterator segment = shape_.SegmentsBegin(span),
segment_end = shape_.SegmentsEnd(span);
segment != segment_end && segment + 1 != segment_end; segment += 2) {
int width = *(segment + 1) - *segment;
area += (uint64_t)height * (uint64_t)width;
}
}
return area;
}
template <typename CompareOperation>
bool Region::Shape::CompareShapes(const Shape& a_shape, const Shape& b_shape) {
bool result = CompareOperation::kDefaultResult;
Shape::SpanIterator a_span = a_shape.SpansBegin();
Shape::SpanIterator a_span_end = a_shape.SpansEnd();
Shape::SpanIterator b_span = b_shape.SpansBegin();
Shape::SpanIterator b_span_end = b_shape.SpansEnd();
bool a_had_segment_in_previous_span = false;
bool b_had_segment_in_previous_span = false;
while (a_span != a_span_end && a_span + 1 != a_span_end &&
b_span != b_span_end && b_span + 1 != b_span_end) {
int a_y = a_span->y;
int a_max_y = (a_span + 1)->y;
int b_y = b_span->y;
int b_max_y = (b_span + 1)->y;
Shape::SegmentIterator a_segment = a_shape.SegmentsBegin(a_span);
Shape::SegmentIterator a_segment_end = a_shape.SegmentsEnd(a_span);
Shape::SegmentIterator b_segment = b_shape.SegmentsBegin(b_span);
Shape::SegmentIterator b_segment_end = b_shape.SegmentsEnd(b_span);
// Look for a non-overlapping part of the spans. If B had a segment in its
// previous span, then we already tested A against B within that span.
bool a_has_segment_in_span = a_segment != a_segment_end;
bool b_has_segment_in_span = b_segment != b_segment_end;
if (a_y < b_y && !b_had_segment_in_previous_span && a_has_segment_in_span &&
CompareOperation::AOutsideB(result))
return result;
if (b_y < a_y && !a_had_segment_in_previous_span && b_has_segment_in_span &&
CompareOperation::BOutsideA(result))
return result;
a_had_segment_in_previous_span = a_has_segment_in_span;
b_had_segment_in_previous_span = b_has_segment_in_span;
bool spans_overlap = b_max_y > a_y && b_y < a_max_y;
if (spans_overlap) {
while (a_segment != a_segment_end && b_segment != b_segment_end) {
int a_x = *a_segment;
int a_max_x = *(a_segment + 1);
int b_x = *b_segment;
int b_max_x = *(b_segment + 1);
bool segments_overlap = b_max_x > a_x && b_x < a_max_x;
if (segments_overlap && CompareOperation::AOverlapsB(result))
return result;
if (a_x < b_x && CompareOperation::AOutsideB(result))
return result;
if (b_x < a_x && CompareOperation::BOutsideA(result))
return result;
if (a_max_x < b_max_x) {
a_segment += 2;
} else if (b_max_x < a_max_x) {
b_segment += 2;
} else {
a_segment += 2;
b_segment += 2;
}
}
if (a_segment != a_segment_end && CompareOperation::AOutsideB(result))
return result;
if (b_segment != b_segment_end && CompareOperation::BOutsideA(result))
return result;
}
if (a_max_y < b_max_y) {
a_span += 1;
} else if (b_max_y < a_max_y) {
b_span += 1;
} else {
a_span += 1;
b_span += 1;
}
}
if (a_span != a_span_end && a_span + 1 != a_span_end &&
CompareOperation::AOutsideB(result))
return result;
if (b_span != b_span_end && b_span + 1 != b_span_end &&
CompareOperation::BOutsideA(result))
return result;
return result;
}
void Region::Shape::TrimCapacities() {
segments_.ShrinkToReasonableCapacity();
spans_.ShrinkToReasonableCapacity();
}
struct Region::Shape::CompareContainsOperation {
STATIC_ONLY(CompareContainsOperation);
const static bool kDefaultResult = true;
inline static bool AOutsideB(bool& /* result */) { return false; }
inline static bool BOutsideA(bool& result) {
result = false;
return true;
}
inline static bool AOverlapsB(bool& /* result */) { return false; }
};
struct Region::Shape::CompareIntersectsOperation {
STATIC_ONLY(CompareIntersectsOperation);
const static bool kDefaultResult = false;
inline static bool AOutsideB(bool& /* result */) { return false; }
inline static bool BOutsideA(bool& /* result */) { return false; }
inline static bool AOverlapsB(bool& result) {
result = true;
return true;
}
};
Region::Shape::Shape() = default;
Region::Shape::Shape(const IntRect& rect) {
AppendSpan(rect.Y());
AppendSegment(rect.X());
AppendSegment(rect.MaxX());
AppendSpan(rect.MaxY());
}
Region::Shape::Shape(wtf_size_t segments_capacity, wtf_size_t spans_capacity) {
segments_.ReserveCapacity(segments_capacity);
spans_.ReserveCapacity(spans_capacity);
}
void Region::Shape::AppendSpan(int y) {
spans_.push_back(Span(y, segments_.size()));
}
bool Region::Shape::CanCoalesce(SegmentIterator begin, SegmentIterator end) {
if (spans_.IsEmpty())
return false;
SegmentIterator last_span_begin =
segments_.data() + spans_.back().segment_index;
SegmentIterator last_span_end = segments_.data() + segments_.size();
// Check if both spans have an equal number of segments.
if (last_span_end - last_span_begin != end - begin)
return false;
// Check if both spans are equal.
if (!std::equal(begin, end, last_span_begin))
return false;
// Since the segments are equal the second segment can just be ignored.
return true;
}
void Region::Shape::AppendSpan(int y,
SegmentIterator begin,
SegmentIterator end) {
if (CanCoalesce(begin, end))
return;
AppendSpan(y);
segments_.AppendRange(begin, end);
}
void Region::Shape::AppendSpans(const Shape& shape,
SpanIterator begin,
SpanIterator end) {
for (SpanIterator it = begin; it != end; ++it)
AppendSpan(it->y, shape.SegmentsBegin(it), shape.SegmentsEnd(it));
}
void Region::Shape::AppendSegment(int x) {
segments_.push_back(x);
}
Region::Shape::SpanIterator Region::Shape::SpansBegin() const {
return spans_.data();
}
Region::Shape::SpanIterator Region::Shape::SpansEnd() const {
return spans_.data() + spans_.size();
}
Region::Shape::SegmentIterator Region::Shape::SegmentsBegin(
SpanIterator it) const {
DCHECK_GE(it, spans_.data());
DCHECK_LT(it, spans_.data() + spans_.size());
// Check if this span has any segments.
if (it->segment_index == segments_.size())
return nullptr;
return &segments_[it->segment_index];
}
Region::Shape::SegmentIterator Region::Shape::SegmentsEnd(
SpanIterator it) const {
DCHECK_GE(it, spans_.data());
DCHECK_LT(it, spans_.data() + spans_.size());
// Check if this span has any segments.
if (it->segment_index == segments_.size())
return nullptr;
DCHECK_LT(it + 1, spans_.data() + spans_.size());
wtf_size_t segment_index = (it + 1)->segment_index;
SECURITY_DCHECK(segment_index <= segments_.size());
return segments_.data() + segment_index;
}
#ifndef NDEBUG
void Region::Shape::Dump() const {
for (Shape::SpanIterator span = SpansBegin(), end = SpansEnd(); span != end;
++span) {
printf("%6d: (", span->y);
for (Shape::SegmentIterator segment = SegmentsBegin(span),
segment_end = SegmentsEnd(span);
segment != segment_end; ++segment)
printf("%d ", *segment);
printf(")\n");
}
printf("\n");
}
#endif
IntRect Region::Shape::Bounds() const {
if (IsEmpty())
return IntRect();
SpanIterator span = SpansBegin();
int min_y = span->y;
SpanIterator last_span = SpansEnd() - 1;
int max_y = last_span->y;
int min_x = std::numeric_limits<int>::max();
int max_x = std::numeric_limits<int>::min();
while (span != last_span) {
SegmentIterator first_segment = SegmentsBegin(span);
SegmentIterator last_segment = SegmentsEnd(span) - 1;
if (first_segment && last_segment) {
DCHECK_NE(first_segment, last_segment);
if (*first_segment < min_x)
min_x = *first_segment;
if (*last_segment > max_x)
max_x = *last_segment;
}
++span;
}
DCHECK_LE(min_x, max_x);
DCHECK_LE(min_y, max_y);
return IntRect(min_x, min_y, max_x - min_x, max_y - min_y);
}
void Region::Shape::Translate(const IntSize& offset) {
for (wtf_size_t i = 0; i < segments_.size(); ++i)
segments_[i] += offset.Width();
for (wtf_size_t i = 0; i < spans_.size(); ++i)
spans_[i].y += offset.Height();
}
void Region::Shape::Swap(Shape& other) {
segments_.swap(other.segments_);
spans_.swap(other.spans_);
}
enum {
kShape1,
kShape2,
};
template <typename Operation>
Region::Shape Region::Shape::ShapeOperation(const Shape& shape1,
const Shape& shape2) {
static_assert(!(!Operation::kShouldAddRemainingSegmentsFromSpan1 &&
Operation::kShouldAddRemainingSegmentsFromSpan2),
"invalid segment combination");
static_assert(!(!Operation::kShouldAddRemainingSpansFromShape1 &&
Operation::kShouldAddRemainingSpansFromShape2),
"invalid span combination");
wtf_size_t segments_capacity = shape1.SegmentsSize() + shape2.SegmentsSize();
wtf_size_t spans_capacity = shape1.SpansSize() + shape2.SpansSize();
Shape result(segments_capacity, spans_capacity);
if (Operation::TrySimpleOperation(shape1, shape2, result))
return result;
SpanIterator spans1 = shape1.SpansBegin();
SpanIterator spans1_end = shape1.SpansEnd();
SpanIterator spans2 = shape2.SpansBegin();
SpanIterator spans2_end = shape2.SpansEnd();
SegmentIterator segments1 = nullptr;
SegmentIterator segments1_end = nullptr;
SegmentIterator segments2 = nullptr;
SegmentIterator segments2_end = nullptr;
Vector<int, 32> segments;
segments.ReserveCapacity(
std::max(shape1.SegmentsSize(), shape2.SegmentsSize()));
// Iterate over all spans.
while (spans1 != spans1_end && spans2 != spans2_end) {
int y = 0;
int y_diff = spans1->y - spans2->y;
if (y_diff <= 0) {
y = spans1->y;
segments1 = shape1.SegmentsBegin(spans1);
segments1_end = shape1.SegmentsEnd(spans1);
++spans1;
}
if (y_diff >= 0) {
y = spans2->y;
segments2 = shape2.SegmentsBegin(spans2);
segments2_end = shape2.SegmentsEnd(spans2);
++spans2;
}
int flag = 0;
int old_flag = 0;
SegmentIterator s1 = segments1;
SegmentIterator s2 = segments2;
// Clear vector without dropping capacity.
segments.resize(0);
DCHECK(segments.capacity());
// Now iterate over the segments in each span and construct a new vector of
// segments.
while (s1 != segments1_end && s2 != segments2_end) {
int s_diff = *s1 - *s2;
int x;
if (s_diff <= 0) {
x = *s1;
flag = flag ^ 1;
++s1;
}
if (s_diff >= 0) {
x = *s2;
flag = flag ^ 2;
++s2;
}
if (flag == Operation::kOpCode || old_flag == Operation::kOpCode)
segments.push_back(x);
old_flag = flag;
}
// Add any remaining segments.
if (Operation::kShouldAddRemainingSegmentsFromSpan1 && s1 != segments1_end)
segments.AppendRange(s1, segments1_end);
else if (Operation::kShouldAddRemainingSegmentsFromSpan2 &&
s2 != segments2_end)
segments.AppendRange(s2, segments2_end);
// Add the span.
if (!segments.IsEmpty() || !result.IsEmpty())
result.AppendSpan(y, segments.data(), segments.data() + segments.size());
}
// Add any remaining spans.
if (Operation::kShouldAddRemainingSpansFromShape1 && spans1 != spans1_end)
result.AppendSpans(shape1, spans1, spans1_end);
else if (Operation::kShouldAddRemainingSpansFromShape2 &&
spans2 != spans2_end)
result.AppendSpans(shape2, spans2, spans2_end);
result.TrimCapacities();
return result;
}
struct Region::Shape::UnionOperation {
STATIC_ONLY(UnionOperation);
static bool TrySimpleOperation(const Shape& shape1,
const Shape& shape2,
Shape& result) {
if (shape1.IsEmpty()) {
result = shape2;
return true;
}
return false;
}
static const int kOpCode = 0;
static const bool kShouldAddRemainingSegmentsFromSpan1 = true;
static const bool kShouldAddRemainingSegmentsFromSpan2 = true;
static const bool kShouldAddRemainingSpansFromShape1 = true;
static const bool kShouldAddRemainingSpansFromShape2 = true;
};
Region::Shape Region::Shape::UnionShapes(const Shape& shape1,
const Shape& shape2) {
return ShapeOperation<UnionOperation>(shape1, shape2);
}
struct Region::Shape::IntersectOperation {
STATIC_ONLY(IntersectOperation);
static bool TrySimpleOperation(const Shape&, const Shape&, Shape&) {
return false;
}
static const int kOpCode = 3;
static const bool kShouldAddRemainingSegmentsFromSpan1 = false;
static const bool kShouldAddRemainingSegmentsFromSpan2 = false;
static const bool kShouldAddRemainingSpansFromShape1 = false;
static const bool kShouldAddRemainingSpansFromShape2 = false;
};
Region::Shape Region::Shape::IntersectShapes(const Shape& shape1,
const Shape& shape2) {
return ShapeOperation<IntersectOperation>(shape1, shape2);
}
struct Region::Shape::SubtractOperation {
STATIC_ONLY(SubtractOperation);
static bool TrySimpleOperation(const Shape&, const Shape&, Region::Shape&) {
return false;
}
static const int kOpCode = 1;
static const bool kShouldAddRemainingSegmentsFromSpan1 = true;
static const bool kShouldAddRemainingSegmentsFromSpan2 = false;
static const bool kShouldAddRemainingSpansFromShape1 = true;
static const bool kShouldAddRemainingSpansFromShape2 = false;
};
Region::Shape Region::Shape::SubtractShapes(const Shape& shape1,
const Shape& shape2) {
return ShapeOperation<SubtractOperation>(shape1, shape2);
}
#ifndef NDEBUG
void Region::Dump() const {
printf("Bounds: (%d, %d, %d, %d)\n", bounds_.X(), bounds_.Y(),
bounds_.Width(), bounds_.Height());
shape_.Dump();
}
#endif
void Region::Intersect(const Region& region) {
if (bounds_.IsEmpty())
return;
if (!bounds_.Intersects(region.bounds_)) {
shape_ = Shape();
bounds_ = IntRect();
return;
}
Shape intersected_shape = Shape::IntersectShapes(shape_, region.shape_);
shape_.Swap(intersected_shape);
bounds_ = shape_.Bounds();
}
void Region::Unite(const Region& region) {
if (region.IsEmpty())
return;
if (IsRect() && bounds_.Contains(region.bounds_))
return;
if (region.IsRect() && region.bounds_.Contains(bounds_)) {
shape_ = region.shape_;
bounds_ = region.bounds_;
return;
}
// FIXME: We may want another way to construct a Region without doing this
// test when we expect it to be false.
if (!IsRect() && Contains(region))
return;
Shape united_shape = Shape::UnionShapes(shape_, region.shape_);
shape_.Swap(united_shape);
bounds_.Unite(region.bounds_);
}
void Region::Subtract(const Region& region) {
if (bounds_.IsEmpty())
return;
if (region.IsEmpty())
return;
if (!bounds_.Intersects(region.bounds_))
return;
Shape subtracted_shape = Shape::SubtractShapes(shape_, region.shape_);
shape_.Swap(subtracted_shape);
bounds_ = shape_.Bounds();
}
void Region::Translate(const IntSize& offset) {
bounds_.Move(offset);
shape_.Translate(offset);
}
} // namespace blink