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