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(const double radius, | |
tapted
2016/02/05 04:51:07
nit: it's unusual to put `const` on arguments of p
karandeepb
2016/02/08 07:47:09
Done.
| |
24 const double degrees, | |
25 const double centre_x, | |
26 const 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(const 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* const path) { | |
karandeepb
2016/02/08 07:47:09
This function somewhat depends on the internal NSB
| |
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 size_t index = 0; | |
45 NSPoint points[3]; | |
46 std::vector<NSPoint> poly; | |
47 while (index < element_count) { | |
48 NSBezierPathElement element = | |
49 [path elementAtIndex:index++ associatedPoints:points]; | |
tapted
2016/02/05 04:51:07
The `index++` is easy to lose here - can this be a
karandeepb
2016/02/08 07:47:09
Done.
| |
50 poly.push_back(points[0]); | |
51 if (element == NSClosePathBezierPathElement) { | |
52 DCHECK_EQ(index, element_count - 1); | |
53 element = [path elementAtIndex:index++ associatedPoints:points]; | |
tapted
2016/02/05 04:51:07
`index++` doesn't look right on this line
karandeepb
2016/02/08 07:47:09
Done.
| |
54 DCHECK_EQ(element, NSMoveToBezierPathElement); | |
55 break; | |
56 } | |
57 DCHECK_EQ(element, index > 1 ? NSLineToBezierPathElement | |
58 : NSMoveToBezierPathElement); | |
59 } | |
60 | |
61 // Shoelace Algorithm to find the area of a simple polygon. | |
62 const size_t count = poly.size(); | |
63 DCHECK(NSEqualPoints(poly[0], poly[count - 1])); | |
tapted
2016/02/05 04:51:07
poly.front(), poly.back()?
karandeepb
2016/02/08 07:47:09
Done.
| |
64 double area = 0; | |
65 for (size_t i = 0; i < count - 1; i++) | |
tapted
2016/02/05 04:51:07
then poly.size() can be moved in here
karandeepb
2016/02/08 07:47:09
Done.
| |
66 area += poly[i].x * poly[i + 1].y - poly[i].y * poly[i + 1].x; | |
67 | |
68 return std::fabs(area) / 2.0; | |
69 } | |
70 | |
71 // Returns the area of a rounded rectangle with the given |width|, |height| and | |
72 // |radius|. | |
73 double CalculateRoundedRectangleArea(const double width, | |
74 const double height, | |
75 const double radius) { | |
76 const double inside_width = width - 2 * radius; | |
77 const double inside_height = height - 2 * radius; | |
78 return inside_width * inside_height + | |
79 2 * radius * (inside_width + inside_height) + | |
80 CalculateCircleArea(radius); | |
81 } | |
82 | |
83 // Returns the bounding box of |path| as a gfx::Rect. | |
84 gfx::Rect GetBoundingBox(NSBezierPath* const path) { | |
85 const NSRect bounds = [path bounds]; | |
86 return gfx::ToNearestRect(gfx::RectF(bounds.origin.x, bounds.origin.y, | |
87 bounds.size.width, bounds.size.height)); | |
88 } | |
89 | |
90 } // namespace | |
91 | |
92 namespace gfx { | |
93 | |
94 // Check that empty NSBezierPath is returned for empty SkRegion. | |
95 TEST(CreateNSBezierPathFromSkRegionTest, EmptyRegion) { | |
96 NSBezierPath* const result = CreateNSBezierPathFromSkRegion(SkRegion()); | |
tapted
2016/02/05 04:51:07
nit: no const (const typically looks extra weird a
karandeepb
2016/02/08 07:47:09
Done.
| |
97 EXPECT_TRUE([result isEmpty]); | |
98 } | |
99 | |
100 // Check that a region containing multiple rectangles is correctly converted to | |
101 // a NSBezierPath. | |
102 TEST(CreateNSBezierPathFromSkRegionTest, TwoRectanglesRegion) { | |
103 const SkIRect rects[] = { | |
104 {0, 0, 50, 50}, {100, 100, 150, 150}, | |
105 }; | |
106 const NSPoint inside_points[] = { | |
tapted
2016/02/05 04:51:07
these consts look fine though, since NSPoint is a
karandeepb
2016/02/08 07:47:09
Done.
| |
107 {1, 1}, {1, 49}, {49, 49}, {49, 1}, {101, 101}, | |
108 {101, 149}, {149, 149}, {149, 101}, {25, 25}, {125, 125}}; | |
109 const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, | |
110 {99, 99}, {99, 151}, {151, 151}, {151, 99}, | |
111 {75, 75}, {-5, -5}}; | |
112 const size_t kNumPoints = 10; | |
113 const gfx::Rect expected_bounds(0, 0, 150, 150); // Bounding box of rects. | |
114 const size_t kSizeRectArray = 2; | |
tapted
2016/02/05 04:51:07
= arraysize(rects)? or just use arraysize(rects) i
karandeepb
2016/02/08 07:47:09
Done.
| |
115 | |
116 SkRegion region; | |
117 region.setRects(rects, kSizeRectArray); | |
118 NSBezierPath* result = CreateNSBezierPathFromSkRegion(region); | |
119 | |
120 // Check points near the boundary of the path and verify that they are | |
121 // reported correctly as being inside/outside the path. | |
122 for (size_t i = 0; i < kNumPoints; i++) { | |
123 EXPECT_TRUE([result containsPoint:inside_points[i]]); | |
124 EXPECT_FALSE([result containsPoint:outside_points[i]]); | |
125 } | |
126 | |
127 // Verify that the bounding box of |result| is correct. | |
128 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); | |
129 } | |
130 | |
131 // Check that empty NSBezierPath is returned for empty SkPath. | |
132 TEST(CreateNSBezierPathFromSkPathTest, EmptyPath) { | |
133 NSBezierPath* const result = CreateNSBezierPathFromSkPath(SkPath()); | |
134 EXPECT_TRUE([result isEmpty]); | |
135 } | |
136 | |
137 // Check that the returned NSBezierPath has the correct winding rule. | |
138 TEST(CreateNSBezierPathFromSkPathTest, FillType) { | |
139 SkPath path; | |
140 path.setFillType(SkPath::kWinding_FillType); | |
141 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); | |
142 EXPECT_EQ(NSNonZeroWindingRule, [result windingRule]); | |
143 | |
144 path.setFillType(SkPath::kEvenOdd_FillType); | |
145 result = CreateNSBezierPathFromSkPath(path); | |
146 EXPECT_EQ(NSEvenOddWindingRule, [result windingRule]); | |
147 } | |
148 | |
149 // Check that a path containing multiple subpaths, in this case two rectangles, | |
150 // is correctly converted to a NSBezierPath. | |
151 TEST(CreateNSBezierPathFromSkPathTest, TwoRectanglesPath) { | |
152 const SkRect rects[] = { | |
153 {0, 0, 50, 50}, {100, 100, 150, 150}, | |
154 }; | |
155 const NSPoint inside_points[] = { | |
156 {1, 1}, {1, 49}, {49, 49}, {49, 1}, {101, 101}, | |
157 {101, 149}, {149, 149}, {149, 101}, {25, 25}, {125, 125}}; | |
158 const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, | |
159 {99, 99}, {99, 151}, {151, 151}, {151, 99}, | |
160 {75, 75}, {-5, -5}}; | |
161 const size_t kNumPoints = 10; | |
162 const gfx::Rect expected_bounds(0, 0, 150, 150); | |
163 | |
164 SkPath path; | |
165 path.addRect(rects[0]); | |
166 path.addRect(rects[1]); | |
167 NSBezierPath* result = CreateNSBezierPathFromSkPath(path); | |
168 | |
169 // Check points near the boundary of the path and verify that they are | |
170 // reported correctly as being inside/outside the path. | |
171 for (size_t i = 0; i < kNumPoints; i++) { | |
172 EXPECT_TRUE([result containsPoint:inside_points[i]]); | |
173 EXPECT_FALSE([result containsPoint:outside_points[i]]); | |
174 } | |
175 | |
176 // Verify that the bounding box of |result| is correct. | |
177 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); | |
178 } | |
179 | |
180 // Test that an SKPath containing a circle is converted correctly to a | |
181 // NSBezierPath. | |
182 TEST(CreateNSBezierPathFromSkPathTest, CirclePath) { | |
183 const int kRadius = 5; | |
184 const int kCentreX = 10; | |
185 const int kCentreY = 15; | |
186 const double kCushion = 0.1; | |
187 // Expected bounding box of the circle. | |
188 const gfx::Rect expected_bounds(kCentreX - kRadius, kCentreY - kRadius, | |
189 2 * kRadius, 2 * kRadius); | |
190 | |
191 SkPath path; | |
192 path.addCircle(SkIntToScalar(kCentreX), SkIntToScalar(kCentreY), | |
193 SkIntToScalar(kRadius)); | |
194 NSBezierPath* const result = CreateNSBezierPathFromSkPath(path); | |
195 | |
196 // Check points near the boundary of the circle and verify that they are | |
197 // reported correctly as being inside/outside the path. | |
198 for (size_t deg = 0; deg < 360; deg++) { | |
199 NSPoint inside_point = | |
200 GetRadialPoint(kRadius - kCushion, deg, kCentreX, kCentreY); | |
201 NSPoint outside_point = | |
202 GetRadialPoint(kRadius + kCushion, deg, kCentreX, kCentreY); | |
203 EXPECT_TRUE([result containsPoint:inside_point]); | |
204 EXPECT_FALSE([result containsPoint:outside_point]); | |
205 } | |
206 | |
207 // Check that the returned result has the correct bounding box. | |
tapted
2016/02/05 04:51:07
maybe mention that this is rounding to whole numbe
karandeepb
2016/02/08 07:47:08
Done.
| |
208 EXPECT_EQ(expected_bounds, GetBoundingBox(result)); | |
209 | |
210 // Check area of converted path is correct upto a certain tolerance value. To | |
211 // find the area of the NSBezierPath returned, flatten it i.e. convert it to a | |
212 // polygon. | |
213 [NSBezierPath setDefaultFlatness:0.01]; | |
214 NSBezierPath* const polygon = [result bezierPathByFlatteningPath]; | |
tapted
2016/02/05 04:51:07
nit: no const
karandeepb
2016/02/08 07:47:08
Done.
| |
215 const double kTolerance = 0.5; | |
tapted
2016/02/05 04:51:07
Can we tighten this? I guess we should go pretty c
karandeepb
2016/02/08 07:47:09
Done.
| |
216 EXPECT_NEAR(CalculateCircleArea(kRadius), CalculatePolygonArea(polygon), | |
217 kTolerance); | |
218 } | |
219 | |
220 // Test that an SKPath containing a rounded rectangle is converted correctly to | |
221 // a NSBezierPath. | |
222 TEST(CreateNSBezierPathFromSkPathTest, RoundedRectanglePath) { | |
223 const int kRectangleWidth = 50; | |
224 const int kRectangleHeight = 100; | |
225 const int kCornerRadius = 5; | |
226 const double kCushion = 0.1; | |
227 // Expected bounding box of the rounded rectangle. | |
228 const gfx::Rect expected_bounds(kRectangleWidth, kRectangleHeight); | |
229 | |
230 SkRRect rrect; | |
231 rrect.setRectXY(SkRect::MakeWH(kRectangleWidth, kRectangleHeight), | |
232 kCornerRadius, kCornerRadius); | |
233 | |
234 const NSPoint inside_points[] = { | |
235 // Bottom left corner. | |
tapted
2016/02/05 04:51:07
nit: remove extra space
karandeepb
2016/02/08 07:47:09
Done.
| |
236 {kCornerRadius, kCornerRadius}, | |
tapted
2016/02/05 04:51:07
maybe half the radius?
karandeepb
2016/02/08 07:47:09
Done.
| |
237 // Bottom right corner. | |
238 {kRectangleWidth - kCornerRadius, kCornerRadius}, | |
239 // Top Right corner. | |
240 {kRectangleWidth - kCornerRadius, kRectangleHeight - kCornerRadius}, | |
241 // Top left corner. | |
242 {kCornerRadius, kRectangleHeight - kCornerRadius}, | |
243 // Bottom middle. | |
244 {kRectangleWidth / 2.0, kCushion}, | |
245 // Right middle. | |
246 {kRectangleWidth - kCushion, kRectangleHeight / 2.0}, | |
247 // Top middle. | |
248 {kRectangleWidth / 2.0, kRectangleHeight - kCushion}, | |
249 // Left middle. | |
250 {kCushion, kRectangleHeight / 2.0}}; | |
tapted
2016/02/05 04:51:07
nit: }; on a new line? (unless clang format likes
karandeepb
2016/02/08 07:47:09
clang format causes this.
| |
251 const NSPoint outside_points[] = { | |
252 // Bottom left corner. | |
253 {0, 0}, | |
254 // Bottom right corner. | |
255 {kRectangleWidth, 0}, | |
256 // Top right corner. | |
257 {kRectangleWidth, kRectangleHeight}, | |
258 // Top left corner. | |
259 {0, kRectangleHeight}, | |
260 // Bottom middle. | |
261 {kRectangleWidth / 2.0, -kCushion}, | |
262 // Right middle. | |
263 {kRectangleWidth + kCushion, kRectangleHeight / 2.0}, | |
264 // Top middle. | |
265 {kRectangleWidth / 2.0, kRectangleHeight + kCushion}, | |
266 // Left middle. | |
267 {-kCushion, kRectangleHeight / 2.0}}; | |
tapted
2016/02/05 04:51:07
}: -> new line
karandeepb
2016/02/08 07:47:09
clang format causes this.
| |
268 const size_t kNumPoints = 8; | |
269 | |
270 SkPath path; | |
271 path.addRRect(rrect); | |
272 NSBezierPath* const result = CreateNSBezierPathFromSkPath(path); | |
273 | |
274 // Check points near the boundary of the path and verify that they are | |
275 // reported correctly as being inside/outside the path. | |
276 for (size_t i = 0; i < kNumPoints; i++) { | |
277 EXPECT_TRUE([result containsPoint:inside_points[i]]); | |
278 EXPECT_FALSE([result containsPoint:outside_points[i]]); | |
279 } | |
280 | |
281 // Check that the returned result has the correct bounding box. | |
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* const polygon = [result bezierPathByFlatteningPath]; | |
289 const double kTolerance = 0.5; | |
290 EXPECT_NEAR(CalculateRoundedRectangleArea(kRectangleWidth, kRectangleHeight, | |
291 kCornerRadius), | |
292 CalculatePolygonArea(polygon), kTolerance); | |
293 } | |
294 | |
295 } // namespace gfx | |
OLD | NEW |