OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. | |
3 * 2006 Rob Buis <buis@kde.org> | |
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org> | |
5 * Copyright (C) 2013 Google Inc. All rights reserved. | |
6 * Copyright (C) 2013 Intel Corporation. All rights reserved. | |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions | |
10 * are met: | |
11 * 1. Redistributions of source code must retain the above copyright | |
12 * notice, this list of conditions and the following disclaimer. | |
13 * 2. Redistributions in binary form must reproduce the above copyright | |
14 * notice, this list of conditions and the following disclaimer in the | |
15 * documentation and/or other materials provided with the distribution. | |
16 * | |
17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 */ | |
29 | |
30 #include "config.h" | |
31 #include "core/platform/graphics/Path.h" | |
32 | |
33 #include <math.h> | |
34 #include "core/platform/graphics/GraphicsContext.h" | |
35 #include "core/platform/graphics/skia/SkiaUtils.h" | |
36 #include "platform/geometry/FloatPoint.h" | |
37 #include "platform/geometry/FloatRect.h" | |
38 #include "platform/transforms/AffineTransform.h" | |
39 #include "third_party/skia/include/core/SkPathMeasure.h" | |
40 #include "third_party/skia/include/pathops/SkPathOps.h" | |
41 #include "wtf/MathExtras.h" | |
42 | |
43 namespace WebCore { | |
44 | |
45 Path::Path() | |
46 : m_path() | |
47 { | |
48 } | |
49 | |
50 Path::Path(const Path& other) | |
51 { | |
52 m_path = SkPath(other.m_path); | |
53 } | |
54 | |
55 Path::~Path() | |
56 { | |
57 } | |
58 | |
59 Path& Path::operator=(const Path& other) | |
60 { | |
61 m_path = SkPath(other.m_path); | |
62 return *this; | |
63 } | |
64 | |
65 bool Path::operator==(const Path& other) const | |
66 { | |
67 return m_path == other.m_path; | |
68 } | |
69 | |
70 bool Path::contains(const FloatPoint& point, WindRule rule) const | |
71 { | |
72 return SkPathContainsPoint(m_path, point, rule == RULE_NONZERO ? SkPath::kWi
nding_FillType : SkPath::kEvenOdd_FillType); | |
73 } | |
74 | |
75 bool Path::strokeContains(const FloatPoint& point, const StrokeData& strokeData)
const | |
76 { | |
77 SkPaint paint; | |
78 strokeData.setupPaint(&paint); | |
79 SkPath strokePath; | |
80 paint.getFillPath(m_path, &strokePath); | |
81 | |
82 return SkPathContainsPoint(strokePath, point, SkPath::kWinding_FillType); | |
83 } | |
84 | |
85 FloatRect Path::boundingRect() const | |
86 { | |
87 return m_path.getBounds(); | |
88 } | |
89 | |
90 FloatRect Path::strokeBoundingRect(const StrokeData& strokeData) const | |
91 { | |
92 SkPaint paint; | |
93 strokeData.setupPaint(&paint); | |
94 SkPath boundingPath; | |
95 paint.getFillPath(m_path, &boundingPath); | |
96 | |
97 return boundingPath.getBounds(); | |
98 } | |
99 | |
100 static FloatPoint* convertPathPoints(FloatPoint dst[], const SkPoint src[], int
count) | |
101 { | |
102 for (int i = 0; i < count; i++) { | |
103 dst[i].setX(SkScalarToFloat(src[i].fX)); | |
104 dst[i].setY(SkScalarToFloat(src[i].fY)); | |
105 } | |
106 return dst; | |
107 } | |
108 | |
109 void Path::apply(void* info, PathApplierFunction function) const | |
110 { | |
111 SkPath::RawIter iter(m_path); | |
112 SkPoint pts[4]; | |
113 PathElement pathElement; | |
114 FloatPoint pathPoints[3]; | |
115 | |
116 for (;;) { | |
117 switch (iter.next(pts)) { | |
118 case SkPath::kMove_Verb: | |
119 pathElement.type = PathElementMoveToPoint; | |
120 pathElement.points = convertPathPoints(pathPoints, &pts[0], 1); | |
121 break; | |
122 case SkPath::kLine_Verb: | |
123 pathElement.type = PathElementAddLineToPoint; | |
124 pathElement.points = convertPathPoints(pathPoints, &pts[1], 1); | |
125 break; | |
126 case SkPath::kQuad_Verb: | |
127 pathElement.type = PathElementAddQuadCurveToPoint; | |
128 pathElement.points = convertPathPoints(pathPoints, &pts[1], 2); | |
129 break; | |
130 case SkPath::kCubic_Verb: | |
131 pathElement.type = PathElementAddCurveToPoint; | |
132 pathElement.points = convertPathPoints(pathPoints, &pts[1], 3); | |
133 break; | |
134 case SkPath::kClose_Verb: | |
135 pathElement.type = PathElementCloseSubpath; | |
136 pathElement.points = convertPathPoints(pathPoints, 0, 0); | |
137 break; | |
138 case SkPath::kDone_Verb: | |
139 return; | |
140 default: // place-holder for kConic_Verb, when that lands from skia | |
141 break; | |
142 } | |
143 function(info, &pathElement); | |
144 } | |
145 } | |
146 | |
147 void Path::transform(const AffineTransform& xform) | |
148 { | |
149 m_path.transform(affineTransformToSkMatrix(xform)); | |
150 } | |
151 | |
152 float Path::length() const | |
153 { | |
154 SkScalar length = 0; | |
155 SkPathMeasure measure(m_path, false); | |
156 | |
157 do { | |
158 length += measure.getLength(); | |
159 } while (measure.nextContour()); | |
160 | |
161 return SkScalarToFloat(length); | |
162 } | |
163 | |
164 FloatPoint Path::pointAtLength(float length, bool& ok) const | |
165 { | |
166 FloatPoint point; | |
167 float normal; | |
168 ok = pointAndNormalAtLength(length, point, normal); | |
169 return point; | |
170 } | |
171 | |
172 float Path::normalAngleAtLength(float length, bool& ok) const | |
173 { | |
174 FloatPoint point; | |
175 float normal; | |
176 ok = pointAndNormalAtLength(length, point, normal); | |
177 return normal; | |
178 } | |
179 | |
180 bool Path::pointAndNormalAtLength(float length, FloatPoint& point, float& normal
) const | |
181 { | |
182 SkPathMeasure measure(m_path, false); | |
183 | |
184 do { | |
185 SkScalar contourLength = measure.getLength(); | |
186 if (length <= contourLength) { | |
187 SkVector tangent; | |
188 SkPoint position; | |
189 | |
190 if (measure.getPosTan(length, &position, &tangent)) { | |
191 normal = rad2deg(SkScalarToFloat(SkScalarATan2(tangent.fY, tange
nt.fX))); | |
192 point = FloatPoint(SkScalarToFloat(position.fX), SkScalarToFloat
(position.fY)); | |
193 return true; | |
194 } | |
195 } | |
196 length -= contourLength; | |
197 } while (measure.nextContour()); | |
198 | |
199 normal = 0; | |
200 point = FloatPoint(0, 0); | |
201 return false; | |
202 } | |
203 | |
204 void Path::clear() | |
205 { | |
206 m_path.reset(); | |
207 } | |
208 | |
209 bool Path::isEmpty() const | |
210 { | |
211 return m_path.isEmpty(); | |
212 } | |
213 | |
214 bool Path::hasCurrentPoint() const | |
215 { | |
216 return m_path.getPoints(0, 0); | |
217 } | |
218 | |
219 FloatPoint Path::currentPoint() const | |
220 { | |
221 if (m_path.countPoints() > 0) { | |
222 SkPoint skResult; | |
223 m_path.getLastPt(&skResult); | |
224 FloatPoint result; | |
225 result.setX(SkScalarToFloat(skResult.fX)); | |
226 result.setY(SkScalarToFloat(skResult.fY)); | |
227 return result; | |
228 } | |
229 | |
230 // FIXME: Why does this return quietNaN? Other ports return 0,0. | |
231 float quietNaN = std::numeric_limits<float>::quiet_NaN(); | |
232 return FloatPoint(quietNaN, quietNaN); | |
233 } | |
234 | |
235 WindRule Path::windRule() const | |
236 { | |
237 return m_path.getFillType() == SkPath::kEvenOdd_FillType | |
238 ? RULE_EVENODD | |
239 : RULE_NONZERO; | |
240 } | |
241 | |
242 void Path::setWindRule(const WindRule rule) | |
243 { | |
244 m_path.setFillType(rule == RULE_EVENODD | |
245 ? SkPath::kEvenOdd_FillType | |
246 : SkPath::kWinding_FillType); | |
247 } | |
248 | |
249 void Path::moveTo(const FloatPoint& point) | |
250 { | |
251 m_path.moveTo(point); | |
252 } | |
253 | |
254 void Path::addLineTo(const FloatPoint& point) | |
255 { | |
256 m_path.lineTo(point); | |
257 } | |
258 | |
259 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& ep) | |
260 { | |
261 m_path.quadTo(cp, ep); | |
262 } | |
263 | |
264 void Path::addBezierCurveTo(const FloatPoint& p1, const FloatPoint& p2, const Fl
oatPoint& ep) | |
265 { | |
266 m_path.cubicTo(p1, p2, ep); | |
267 } | |
268 | |
269 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) | |
270 { | |
271 m_path.arcTo(p1, p2, WebCoreFloatToSkScalar(radius)); | |
272 } | |
273 | |
274 void Path::closeSubpath() | |
275 { | |
276 m_path.close(); | |
277 } | |
278 | |
279 void Path::addEllipse(const FloatPoint& p, float radiusX, float radiusY, float s
tartAngle, float endAngle, bool anticlockwise) | |
280 { | |
281 ASSERT(std::abs(endAngle - startAngle) < 4 * piFloat); | |
282 ASSERT(startAngle >= 0 && startAngle < 2 * piFloat); | |
283 ASSERT((anticlockwise && (startAngle - endAngle) >= 0) || (!anticlockwise &&
(endAngle - startAngle) >= 0)); | |
284 | |
285 SkScalar cx = WebCoreFloatToSkScalar(p.x()); | |
286 SkScalar cy = WebCoreFloatToSkScalar(p.y()); | |
287 SkScalar radiusXScalar = WebCoreFloatToSkScalar(radiusX); | |
288 SkScalar radiusYScalar = WebCoreFloatToSkScalar(radiusY); | |
289 SkScalar s360 = SkIntToScalar(360); | |
290 | |
291 SkRect oval; | |
292 oval.set(cx - radiusXScalar, cy - radiusYScalar, cx + radiusXScalar, cy + ra
diusYScalar); | |
293 | |
294 float sweep = endAngle - startAngle; | |
295 SkScalar startDegrees = WebCoreFloatToSkScalar(startAngle * 180 / piFloat); | |
296 SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat); | |
297 | |
298 // We can't use SkPath::addOval(), because addOval() makes new sub-path. add
Oval() calls moveTo() and close() internally. | |
299 | |
300 // Use s180, not s360, because SkPath::arcTo(oval, angle, s360, false) draws
nothing. | |
301 SkScalar s180 = SkIntToScalar(180); | |
302 if (sweepDegrees >= s360) { | |
303 // SkPath::arcTo can't handle the sweepAngle that is equal to or greater
than 2Pi. | |
304 m_path.arcTo(oval, startDegrees, s180, false); | |
305 m_path.arcTo(oval, startDegrees + s180, s180, false); | |
306 m_path.arcTo(oval, startDegrees + s360, sweepDegrees - s360, false); | |
307 return; | |
308 } | |
309 if (sweepDegrees <= -s360) { | |
310 m_path.arcTo(oval, startDegrees, -s180, false); | |
311 m_path.arcTo(oval, startDegrees - s180, -s180, false); | |
312 m_path.arcTo(oval, startDegrees - s360, sweepDegrees + s360, false); | |
313 return; | |
314 } | |
315 | |
316 m_path.arcTo(oval, startDegrees, sweepDegrees, false); | |
317 } | |
318 | |
319 void Path::addArc(const FloatPoint& p, float radius, float startAngle, float end
Angle, bool anticlockwise) | |
320 { | |
321 addEllipse(p, radius, radius, startAngle, endAngle, anticlockwise); | |
322 } | |
323 | |
324 void Path::addRect(const FloatRect& rect) | |
325 { | |
326 m_path.addRect(rect); | |
327 } | |
328 | |
329 void Path::addEllipse(const FloatPoint& p, float radiusX, float radiusY, float r
otation, float startAngle, float endAngle, bool anticlockwise) | |
330 { | |
331 ASSERT(std::abs(endAngle - startAngle) < 4 * piFloat); | |
332 ASSERT(startAngle >= 0 && startAngle < 2 * piFloat); | |
333 ASSERT((anticlockwise && (startAngle - endAngle) >= 0) || (!anticlockwise &&
(endAngle - startAngle) >= 0)); | |
334 | |
335 if (!rotation) { | |
336 addEllipse(FloatPoint(p.x(), p.y()), radiusX, radiusY, startAngle, endAn
gle, anticlockwise); | |
337 return; | |
338 } | |
339 | |
340 // Add an arc after the relevant transform. | |
341 AffineTransform ellipseTransform = AffineTransform::translation(p.x(), p.y()
).rotate(rad2deg(rotation)); | |
342 ASSERT(ellipseTransform.isInvertible()); | |
343 AffineTransform inverseEllipseTransform = ellipseTransform.inverse(); | |
344 transform(inverseEllipseTransform); | |
345 addEllipse(FloatPoint::zero(), radiusX, radiusY, startAngle, endAngle, antic
lockwise); | |
346 transform(ellipseTransform); | |
347 } | |
348 | |
349 void Path::addEllipse(const FloatRect& rect) | |
350 { | |
351 m_path.addOval(rect); | |
352 } | |
353 | |
354 void Path::addRoundedRect(const RoundedRect& r) | |
355 { | |
356 addRoundedRect(r.rect(), r.radii().topLeft(), r.radii().topRight(), r.radii(
).bottomLeft(), r.radii().bottomRight()); | |
357 } | |
358 | |
359 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii) | |
360 { | |
361 if (rect.isEmpty()) | |
362 return; | |
363 | |
364 FloatSize radius(roundingRadii); | |
365 FloatSize halfSize(rect.width() / 2, rect.height() / 2); | |
366 | |
367 // Apply the SVG corner radius constraints, per the rect section of the SVG
shapes spec: if | |
368 // one of rx,ry is negative, then the other corner radius value is used. If
both values are | |
369 // negative then rx = ry = 0. If rx is greater than half of the width of the
rectangle | |
370 // then set rx to half of the width; ry is handled similarly. | |
371 | |
372 if (radius.width() < 0) | |
373 radius.setWidth((radius.height() < 0) ? 0 : radius.height()); | |
374 | |
375 if (radius.height() < 0) | |
376 radius.setHeight(radius.width()); | |
377 | |
378 if (radius.width() > halfSize.width()) | |
379 radius.setWidth(halfSize.width()); | |
380 | |
381 if (radius.height() > halfSize.height()) | |
382 radius.setHeight(halfSize.height()); | |
383 | |
384 addPathForRoundedRect(rect, radius, radius, radius, radius); | |
385 } | |
386 | |
387 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius,
const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const Float
Size& bottomRightRadius) | |
388 { | |
389 if (rect.isEmpty()) | |
390 return; | |
391 | |
392 if (rect.width() < topLeftRadius.width() + topRightRadius.width() | |
393 || rect.width() < bottomLeftRadius.width() + bottomRightRadius.width
() | |
394 || rect.height() < topLeftRadius.height() + bottomLeftRadius.height(
) | |
395 || rect.height() < topRightRadius.height() + bottomRightRadius.heigh
t()) { | |
396 // If all the radii cannot be accommodated, return a rect. | |
397 addRect(rect); | |
398 return; | |
399 } | |
400 | |
401 addPathForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius,
bottomRightRadius); | |
402 } | |
403 | |
404 void Path::addPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeft
Radius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, cons
t FloatSize& bottomRightRadius) | |
405 { | |
406 addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadi
us, bottomRightRadius); | |
407 } | |
408 | |
409 // Approximation of control point positions on a bezier to simulate a quarter of
a circle. | |
410 // This is 1-kappa, where kappa = 4 * (sqrt(2) - 1) / 3 | |
411 static const float gCircleControlPoint = 0.447715f; | |
412 | |
413 void Path::addBeziersForRoundedRect(const FloatRect& rect, const FloatSize& topL
eftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, c
onst FloatSize& bottomRightRadius) | |
414 { | |
415 moveTo(FloatPoint(rect.x() + topLeftRadius.width(), rect.y())); | |
416 | |
417 addLineTo(FloatPoint(rect.maxX() - topRightRadius.width(), rect.y())); | |
418 if (topRightRadius.width() > 0 || topRightRadius.height() > 0) | |
419 addBezierCurveTo(FloatPoint(rect.maxX() - topRightRadius.width() * gCirc
leControlPoint, rect.y()), | |
420 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height() * gCircle
ControlPoint), | |
421 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height())); | |
422 addLineTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height()))
; | |
423 if (bottomRightRadius.width() > 0 || bottomRightRadius.height() > 0) | |
424 addBezierCurveTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius
.height() * gCircleControlPoint), | |
425 FloatPoint(rect.maxX() - bottomRightRadius.width() * gCircleControlP
oint, rect.maxY()), | |
426 FloatPoint(rect.maxX() - bottomRightRadius.width(), rect.maxY())); | |
427 addLineTo(FloatPoint(rect.x() + bottomLeftRadius.width(), rect.maxY())); | |
428 if (bottomLeftRadius.width() > 0 || bottomLeftRadius.height() > 0) | |
429 addBezierCurveTo(FloatPoint(rect.x() + bottomLeftRadius.width() * gCircl
eControlPoint, rect.maxY()), | |
430 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height() * gCirc
leControlPoint), | |
431 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height())); | |
432 addLineTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height())); | |
433 if (topLeftRadius.width() > 0 || topLeftRadius.height() > 0) | |
434 addBezierCurveTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height()
* gCircleControlPoint), | |
435 FloatPoint(rect.x() + topLeftRadius.width() * gCircleControlPoint, r
ect.y()), | |
436 FloatPoint(rect.x() + topLeftRadius.width(), rect.y())); | |
437 | |
438 closeSubpath(); | |
439 } | |
440 | |
441 void Path::translate(const FloatSize& size) | |
442 { | |
443 m_path.offset(WebCoreFloatToSkScalar(size.width()), WebCoreFloatToSkScalar(s
ize.height())); | |
444 } | |
445 | |
446 bool Path::unionPath(const Path& other) | |
447 { | |
448 return Op(m_path, other.m_path, kUnion_PathOp, &m_path); | |
449 } | |
450 | |
451 } | |
OLD | NEW |