/* * Copyright 2019 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrOctoBounds_DEFINED #define GrOctoBounds_DEFINED #include "include/core/SkRect.h" #include /** * This class is composed of two bounding boxes: one in device space, and one in a 45-degree rotated * space. * * The 45-degree bounding box resides in "| 1 -1 | * coords" space. * | 1 1 | * * The intersection of these two boxes defines the bounding octagon of a shape. * * Furthermore, both bounding boxes are fully tightened. This means we can blindly find the * intersections between each diagonal and its vertical and horizontal neighbors, and be left with * 8 points that define a convex (possibly degenerate) octagon. */ class GrOctoBounds { public: GrOctoBounds() = default; GrOctoBounds(const SkRect& bounds, const SkRect& bounds45) { this->set(bounds, bounds45); } void set(const SkRect& bounds, const SkRect& bounds45) { fBounds = bounds; fBounds45 = bounds45; SkDEBUGCODE(this->validateBoundsAreTight()); } bool operator==(const GrOctoBounds& that) const { return fBounds == that.fBounds && fBounds45 == that.fBounds45; } bool operator!=(const GrOctoBounds& that) const { return !(*this == that); } const SkRect& bounds() const { return fBounds; } float left() const { return fBounds.left(); } float top() const { return fBounds.top(); } float right() const { return fBounds.right(); } float bottom() const { return fBounds.bottom(); } // The 45-degree bounding box resides in "| 1 -1 | * coords" space. // | 1 1 | const SkRect& bounds45() const { return fBounds45; } float left45() const { return fBounds45.left(); } float top45() const { return fBounds45.top(); } float right45() const { return fBounds45.right(); } float bottom45() const { return fBounds45.bottom(); } void roundOut(SkIRect* out) const { // The octagon is the intersection of fBounds and fBounds45 (see the comment at the start of // the class). The octagon's bounding box is therefore just fBounds. And the integer // bounding box can be found by simply rounding out fBounds. fBounds.roundOut(out); } GrOctoBounds makeOffset(float dx, float dy) const { GrOctoBounds offset; offset.setOffset(*this, dx, dy); return offset; } void setOffset(const GrOctoBounds& octoBounds, float dx, float dy) { fBounds = octoBounds.fBounds.makeOffset(dx, dy); fBounds45 = octoBounds.fBounds45.makeOffset(dx - dy, dx + dy); SkDEBUGCODE(this->validateBoundsAreTight()); } void outset(float radius) { fBounds.outset(radius, radius); fBounds45.outset(radius*SK_ScalarSqrt2, radius*SK_ScalarSqrt2); SkDEBUGCODE(this->validateBoundsAreTight()); } // Clips the octo bounds by a clip rect and ensures the resulting bounds are fully tightened. // Returns false if the octagon and clipRect do not intersect at all. // // NOTE: Does not perform a trivial containment test before the clip routine. It is probably a // good idea to not call this method if 'this->bounds()' are fully contained within 'clipRect'. bool SK_WARN_UNUSED_RESULT clip(const SkIRect& clipRect); // The 45-degree bounding box resides in "| 1 -1 | * coords" space. // | 1 1 | // // i.e., | x45 | = | x - y | // | y45 | = | x + y | // // These methods transform points between device space and 45-degree space. constexpr static float Get_x45(float x, float y) { return x - y; } constexpr static float Get_y45(float x, float y) { return x + y; } constexpr static float Get_x(float x45, float y45) { return (x45 + y45) * .5f; } constexpr static float Get_y(float x45, float y45) { return (y45 - x45) * .5f; } #if defined(SK_DEBUG) || defined(GR_TEST_UTILS) void validateBoundsAreTight() const; void validateBoundsAreTight(const std::function& validateFn) const; #endif private: SkRect fBounds; SkRect fBounds45; }; #endif