OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ui/gfx/path_mac.h" |
| 6 |
| 7 #include <cmath> |
| 8 #include <vector> |
| 9 |
| 10 #import <Cocoa/Cocoa.h> |
| 11 |
| 12 #include "testing/gtest/include/gtest/gtest.h" |
| 13 #include "third_party/skia/include/core/SkRegion.h" |
| 14 #include "ui/gfx/geometry/rect.h" |
| 15 #include "ui/gfx/geometry/rect_conversions.h" |
| 16 #include "ui/gfx/path.h" |
| 17 |
| 18 namespace { |
| 19 |
| 20 // Returns the point at a distance of |radius| from the point (|centre_x|, |
| 21 // |centre_y|), and angle |degrees| from the positive horizontal axis, measured |
| 22 // anti-clockwise. |
| 23 NSPoint GetRadialPoint(double radius, |
| 24 double degrees, |
| 25 double centre_x, |
| 26 double centre_y) { |
| 27 const double radian = (degrees * SK_ScalarPI) / 180; |
| 28 return NSMakePoint(centre_x + radius * std::cos(radian), |
| 29 centre_y + radius * std::sin(radian)); |
| 30 } |
| 31 |
| 32 // Returns the area of a circle with the given |radius|. |
| 33 double CalculateCircleArea(double radius) { |
| 34 return SK_ScalarPI * radius * radius; |
| 35 } |
| 36 |
| 37 // Returns the area of a simple polygon. |path| should represent a simple |
| 38 // polygon. |
| 39 double CalculatePolygonArea(NSBezierPath* path) { |
| 40 // If path represents a single polygon, it will have MoveTo, followed by |
| 41 // multiple LineTo, followed By ClosePath, followed by another MoveTo |
| 42 // NSBezierPathElement. |
| 43 const size_t element_count = [path elementCount]; |
| 44 NSPoint points[3]; |
| 45 std::vector<NSPoint> poly; |
| 46 |
| 47 for (size_t i = 0; i < element_count - 1; i++) { |
| 48 NSBezierPathElement element = |
| 49 [path elementAtIndex:i associatedPoints:points]; |
| 50 poly.push_back(points[0]); |
| 51 DCHECK_EQ(element, |
| 52 i ? (i == element_count - 2 ? NSClosePathBezierPathElement |
| 53 : NSLineToBezierPathElement) |
| 54 : NSMoveToBezierPathElement); |
| 55 } |
| 56 DCHECK_EQ([path elementAtIndex:element_count - 1 associatedPoints:points], |
| 57 NSMoveToBezierPathElement); |
| 58 DCHECK(NSEqualPoints(points[0], poly.front())); |
| 59 |
| 60 // Shoelace Algorithm to find the area of a simple polygon. |
| 61 DCHECK(NSEqualPoints(poly.front(), poly.back())); |
| 62 double area = 0; |
| 63 for (size_t i = 0; i < poly.size() - 1; i++) |
| 64 area += poly[i].x * poly[i + 1].y - poly[i].y * poly[i + 1].x; |
| 65 |
| 66 return std::fabs(area) / 2.0; |
| 67 } |
| 68 |
| 69 // Returns the area of a rounded rectangle with the given |width|, |height| and |
| 70 // |radius|. |
| 71 double CalculateRoundedRectangleArea(double width, |
| 72 double height, |
| 73 double radius) { |
| 74 const double inside_width = width - 2 * radius; |
| 75 const double inside_height = height - 2 * radius; |
| 76 return inside_width * inside_height + |
| 77 2 * radius * (inside_width + inside_height) + |
| 78 CalculateCircleArea(radius); |
| 79 } |
| 80 |
| 81 // Returns the bounding box of |path| as a gfx::Rect. |
| 82 gfx::Rect GetBoundingBox(NSBezierPath* path) { |
| 83 const NSRect bounds = [path bounds]; |
| 84 return gfx::ToNearestRect(gfx::RectF(bounds.origin.x, bounds.origin.y, |
| 85 bounds.size.width, bounds.size.height)); |
| 86 } |
| 87 |
| 88 } // namespace |
| 89 |
| 90 namespace gfx { |
| 91 |
| 92 // Check that empty NSBezierPath is returned for empty SkRegion. |
| 93 TEST(CreateNSBezierPathFromSkRegionTest, EmptyRegion) { |
| 94 NSBezierPath* result = CreateNSBezierPathFromSkRegion(SkRegion()); |
| 95 EXPECT_TRUE([result isEmpty]); |
| 96 } |
| 97 |
| 98 // Check that a region containing multiple rectangles is correctly converted to |
| 99 // a NSBezierPath. |
| 100 TEST(CreateNSBezierPathFromSkRegionTest, TwoRectanglesRegion) { |
| 101 const SkIRect rects[] = { |
| 102 {0, 0, 50, 50}, {100, 100, 150, 150}, |
| 103 }; |
| 104 const NSPoint inside_points[] = { |
| 105 {1, 1}, {1, 49}, {49, 49}, {49, 1}, {101, 101}, |
| 106 {101, 149}, {149, 149}, {149, 101}, {25, 25}, {125, 125}}; |
| 107 const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, |
| 108 {99, 99}, {99, 151}, {151, 151}, {151, 99}, |
| 109 {75, 75}, {-5, -5}}; |
| 110 const gfx::Rect expected_bounds(0, 0, 150, 150); // Bounding box of rects. |
| 111 |
| 112 SkRegion region; |
| 113 region.setRects(rects, arraysize(rects)); |
| 114 NSBezierPath* result = CreateNSBezierPathFromSkRegion(region); |
| 115 |
| 116 // Check points near the boundary of the path and verify that they are |
| 117 // reported correctly as being inside/outside the path. |
| 118 for (size_t i = 0; i < arraysize(inside_points); i++) { |
| 119 EXPECT_TRUE([result containsPoint:inside_points[i]]); |
| 120 EXPECT_FALSE([result containsPoint:outside_points[i]]); |
| 121 } |
| 122 |
| 123 // Check that the returned result has the correct bounding box. GetBoundingBox |
| 124 // rounds the coordinates to nearest integer values. |
| 125 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
| 126 } |
| 127 |
| 128 // Check that empty NSBezierPath is returned for empty SkPath. |
| 129 TEST(CreateNSBezierPathFromSkPathTest, EmptyPath) { |
| 130 NSBezierPath* result = CreateNSBezierPathFromSkPath(SkPath()); |
| 131 EXPECT_TRUE([result isEmpty]); |
| 132 } |
| 133 |
| 134 // Check that the returned NSBezierPath has the correct winding rule. |
| 135 TEST(CreateNSBezierPathFromSkPathTest, FillType) { |
| 136 SkPath path; |
| 137 path.setFillType(SkPath::kWinding_FillType); |
| 138 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
| 139 EXPECT_EQ(NSNonZeroWindingRule, [result windingRule]); |
| 140 |
| 141 path.setFillType(SkPath::kEvenOdd_FillType); |
| 142 result = CreateNSBezierPathFromSkPath(path); |
| 143 EXPECT_EQ(NSEvenOddWindingRule, [result windingRule]); |
| 144 } |
| 145 |
| 146 // Check that a path containing multiple subpaths, in this case two rectangles, |
| 147 // is correctly converted to a NSBezierPath. |
| 148 TEST(CreateNSBezierPathFromSkPathTest, TwoRectanglesPath) { |
| 149 const SkRect rects[] = { |
| 150 {0, 0, 50, 50}, {100, 100, 150, 150}, |
| 151 }; |
| 152 const NSPoint inside_points[] = { |
| 153 {1, 1}, {1, 49}, {49, 49}, {49, 1}, {101, 101}, |
| 154 {101, 149}, {149, 149}, {149, 101}, {25, 25}, {125, 125}}; |
| 155 const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, |
| 156 {99, 99}, {99, 151}, {151, 151}, {151, 99}, |
| 157 {75, 75}, {-5, -5}}; |
| 158 const gfx::Rect expected_bounds(0, 0, 150, 150); |
| 159 |
| 160 SkPath path; |
| 161 path.addRect(rects[0]); |
| 162 path.addRect(rects[1]); |
| 163 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
| 164 |
| 165 // Check points near the boundary of the path and verify that they are |
| 166 // reported correctly as being inside/outside the path. |
| 167 for (size_t i = 0; i < arraysize(inside_points); i++) { |
| 168 EXPECT_TRUE([result containsPoint:inside_points[i]]); |
| 169 EXPECT_FALSE([result containsPoint:outside_points[i]]); |
| 170 } |
| 171 |
| 172 // Check that the returned result has the correct bounding box. GetBoundingBox |
| 173 // rounds the coordinates to nearest integer values. |
| 174 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
| 175 } |
| 176 |
| 177 // Test that an SKPath containing a circle is converted correctly to a |
| 178 // NSBezierPath. |
| 179 TEST(CreateNSBezierPathFromSkPathTest, CirclePath) { |
| 180 const int kRadius = 5; |
| 181 const int kCentreX = 10; |
| 182 const int kCentreY = 15; |
| 183 const double kCushion = 0.1; |
| 184 // Expected bounding box of the circle. |
| 185 const gfx::Rect expected_bounds(kCentreX - kRadius, kCentreY - kRadius, |
| 186 2 * kRadius, 2 * kRadius); |
| 187 |
| 188 SkPath path; |
| 189 path.addCircle(SkIntToScalar(kCentreX), SkIntToScalar(kCentreY), |
| 190 SkIntToScalar(kRadius)); |
| 191 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
| 192 |
| 193 // Check points near the boundary of the circle and verify that they are |
| 194 // reported correctly as being inside/outside the path. |
| 195 for (size_t deg = 0; deg < 360; deg++) { |
| 196 NSPoint inside_point = |
| 197 GetRadialPoint(kRadius - kCushion, deg, kCentreX, kCentreY); |
| 198 NSPoint outside_point = |
| 199 GetRadialPoint(kRadius + kCushion, deg, kCentreX, kCentreY); |
| 200 EXPECT_TRUE([result containsPoint:inside_point]); |
| 201 EXPECT_FALSE([result containsPoint:outside_point]); |
| 202 } |
| 203 |
| 204 // Check that the returned result has the correct bounding box. GetBoundingBox |
| 205 // rounds the coordinates to nearest integer values. |
| 206 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
| 207 |
| 208 // Check area of converted path is correct upto a certain tolerance value. To |
| 209 // find the area of the NSBezierPath returned, flatten it i.e. convert it to a |
| 210 // polygon. |
| 211 [NSBezierPath setDefaultFlatness:0.01]; |
| 212 NSBezierPath* polygon = [result bezierPathByFlatteningPath]; |
| 213 const double kTolerance = 0.14; |
| 214 EXPECT_NEAR(CalculateCircleArea(kRadius), CalculatePolygonArea(polygon), |
| 215 kTolerance); |
| 216 } |
| 217 |
| 218 // Test that an SKPath containing a rounded rectangle is converted correctly to |
| 219 // a NSBezierPath. |
| 220 TEST(CreateNSBezierPathFromSkPathTest, RoundedRectanglePath) { |
| 221 const int kRectangleWidth = 50; |
| 222 const int kRectangleHeight = 100; |
| 223 const int kCornerRadius = 5; |
| 224 const double kCushion = 0.1; |
| 225 // Expected bounding box of the rounded rectangle. |
| 226 const gfx::Rect expected_bounds(kRectangleWidth, kRectangleHeight); |
| 227 |
| 228 SkRRect rrect; |
| 229 rrect.setRectXY(SkRect::MakeWH(kRectangleWidth, kRectangleHeight), |
| 230 kCornerRadius, kCornerRadius); |
| 231 |
| 232 const NSPoint inside_points[] = { |
| 233 // Bottom left corner. |
| 234 {kCornerRadius / 2.0, kCornerRadius / 2.0}, |
| 235 // Bottom right corner. |
| 236 {kRectangleWidth - kCornerRadius / 2.0, kCornerRadius / 2.0}, |
| 237 // Top Right corner. |
| 238 {kRectangleWidth - kCornerRadius / 2.0, |
| 239 kRectangleHeight - kCornerRadius / 2.0}, |
| 240 // Top left corner. |
| 241 {kCornerRadius / 2.0, kRectangleHeight - kCornerRadius / 2.0}, |
| 242 // Bottom middle. |
| 243 {kRectangleWidth / 2.0, kCushion}, |
| 244 // Right middle. |
| 245 {kRectangleWidth - kCushion, kRectangleHeight / 2.0}, |
| 246 // Top middle. |
| 247 {kRectangleWidth / 2.0, kRectangleHeight - kCushion}, |
| 248 // Left middle. |
| 249 {kCushion, kRectangleHeight / 2.0}}; |
| 250 const NSPoint outside_points[] = { |
| 251 // Bottom left corner. |
| 252 {0, 0}, |
| 253 // Bottom right corner. |
| 254 {kRectangleWidth, 0}, |
| 255 // Top right corner. |
| 256 {kRectangleWidth, kRectangleHeight}, |
| 257 // Top left corner. |
| 258 {0, kRectangleHeight}, |
| 259 // Bottom middle. |
| 260 {kRectangleWidth / 2.0, -kCushion}, |
| 261 // Right middle. |
| 262 {kRectangleWidth + kCushion, kRectangleHeight / 2.0}, |
| 263 // Top middle. |
| 264 {kRectangleWidth / 2.0, kRectangleHeight + kCushion}, |
| 265 // Left middle. |
| 266 {-kCushion, kRectangleHeight / 2.0}}; |
| 267 const size_t kNumPoints = arraysize(inside_points); |
| 268 |
| 269 SkPath path; |
| 270 path.addRRect(rrect); |
| 271 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
| 272 |
| 273 // Check points near the boundary of the path and verify that they are |
| 274 // reported correctly as being inside/outside the path. |
| 275 for (size_t i = 0; i < kNumPoints; i++) { |
| 276 EXPECT_TRUE([result containsPoint:inside_points[i]]); |
| 277 EXPECT_FALSE([result containsPoint:outside_points[i]]); |
| 278 } |
| 279 |
| 280 // Check that the returned result has the correct bounding box. GetBoundingBox |
| 281 // rounds the coordinates to nearest integer values. |
| 282 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
| 283 |
| 284 // Check area of converted path is correct upto a certain tolerance value. To |
| 285 // find the area of the NSBezierPath returned, flatten it i.e. convert it to a |
| 286 // polygon. |
| 287 [NSBezierPath setDefaultFlatness:0.01]; |
| 288 NSBezierPath* polygon = [result bezierPathByFlatteningPath]; |
| 289 const double kTolerance = 0.14; |
| 290 EXPECT_NEAR(CalculateRoundedRectangleArea(kRectangleWidth, kRectangleHeight, |
| 291 kCornerRadius), |
| 292 CalculatePolygonArea(polygon), kTolerance); |
| 293 } |
| 294 |
| 295 } // namespace gfx |
OLD | NEW |