Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2003, 2006 Apple Computer, Inc.  All rights reserved.
      3  *                     2006 Rob Buis <buis (at) kde.org>
      4  * Copyright (C) 2007 Eric Seidel <eric (at) webkit.org>
      5  * Copyright (C) 2013 Google Inc. All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 #include "core/platform/graphics/Path.h"
     31 
     32 #include <math.h>
     33 #include "core/platform/graphics/FloatPoint.h"
     34 #include "core/platform/graphics/FloatRect.h"
     35 #include "core/platform/graphics/GraphicsContext.h"
     36 #include "core/platform/graphics/skia/SkiaUtils.h"
     37 #include "core/platform/graphics/transforms/AffineTransform.h"
     38 #include "third_party/skia/include/core/SkPath.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::kWinding_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(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, tangent.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 FloatPoint& 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::addArc(const FloatPoint& p, float r, float sa, float ea, bool anticlockwise)
    280 {
    281     SkScalar cx = WebCoreFloatToSkScalar(p.x());
    282     SkScalar cy = WebCoreFloatToSkScalar(p.y());
    283     SkScalar radius = WebCoreFloatToSkScalar(r);
    284     SkScalar s360 = SkIntToScalar(360);
    285 
    286     SkRect oval;
    287     oval.set(cx - radius, cy - radius, cx + radius, cy + radius);
    288 
    289     float sweep = ea - sa;
    290     SkScalar startDegrees = WebCoreFloatToSkScalar(sa * 180 / piFloat);
    291     SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat);
    292     // Check for a circle.
    293     if (sweepDegrees >= s360 || sweepDegrees <= -s360) {
    294         // Move to the start position (0 sweep means we add a single point).
    295         m_path.arcTo(oval, startDegrees, 0, false);
    296         // Draw the circle.
    297         m_path.addOval(oval, anticlockwise ?
    298             SkPath::kCCW_Direction : SkPath::kCW_Direction);
    299         // Force a moveTo the end position.
    300         m_path.arcTo(oval, startDegrees + sweepDegrees, 0, true);
    301         return;
    302     }
    303 
    304     // Counterclockwise arcs should be drawn with negative sweeps, while
    305     // clockwise arcs should be drawn with positive sweeps. Check to see
    306     // if the situation is reversed and correct it by adding or subtracting
    307     // a full circle
    308     if (anticlockwise && sweepDegrees > 0)
    309         sweepDegrees -= s360;
    310     else if (!anticlockwise && sweepDegrees < 0)
    311         sweepDegrees += s360;
    312 
    313     m_path.arcTo(oval, startDegrees, sweepDegrees, false);
    314 }
    315 
    316 void Path::addRect(const FloatRect& rect)
    317 {
    318     m_path.addRect(rect);
    319 }
    320 
    321 void Path::addEllipse(const FloatRect& rect)
    322 {
    323     m_path.addOval(rect);
    324 }
    325 
    326 void Path::addRoundedRect(const RoundedRect& r)
    327 {
    328     addRoundedRect(r.rect(), r.radii().topLeft(), r.radii().topRight(), r.radii().bottomLeft(), r.radii().bottomRight());
    329 }
    330 
    331 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii)
    332 {
    333     if (rect.isEmpty())
    334         return;
    335 
    336     FloatSize radius(roundingRadii);
    337     FloatSize halfSize(rect.width() / 2, rect.height() / 2);
    338 
    339     // Apply the SVG corner radius constraints, per the rect section of the SVG shapes spec: if
    340     // one of rx,ry is negative, then the other corner radius value is used. If both values are
    341     // negative then rx = ry = 0. If rx is greater than half of the width of the rectangle
    342     // then set rx to half of the width; ry is handled similarly.
    343 
    344     if (radius.width() < 0)
    345         radius.setWidth((radius.height() < 0) ? 0 : radius.height());
    346 
    347     if (radius.height() < 0)
    348         radius.setHeight(radius.width());
    349 
    350     if (radius.width() > halfSize.width())
    351         radius.setWidth(halfSize.width());
    352 
    353     if (radius.height() > halfSize.height())
    354         radius.setHeight(halfSize.height());
    355 
    356     addPathForRoundedRect(rect, radius, radius, radius, radius);
    357 }
    358 
    359 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
    360 {
    361     if (rect.isEmpty())
    362         return;
    363 
    364     if (rect.width() < topLeftRadius.width() + topRightRadius.width()
    365             || rect.width() < bottomLeftRadius.width() + bottomRightRadius.width()
    366             || rect.height() < topLeftRadius.height() + bottomLeftRadius.height()
    367             || rect.height() < topRightRadius.height() + bottomRightRadius.height()) {
    368         // If all the radii cannot be accommodated, return a rect.
    369         addRect(rect);
    370         return;
    371     }
    372 
    373     addPathForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
    374 }
    375 
    376 void Path::addPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
    377 {
    378     addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
    379 }
    380 
    381 // Approximation of control point positions on a bezier to simulate a quarter of a circle.
    382 // This is 1-kappa, where kappa = 4 * (sqrt(2) - 1) / 3
    383 static const float gCircleControlPoint = 0.447715f;
    384 
    385 void Path::addBeziersForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
    386 {
    387     moveTo(FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
    388 
    389     addLineTo(FloatPoint(rect.maxX() - topRightRadius.width(), rect.y()));
    390     if (topRightRadius.width() > 0 || topRightRadius.height() > 0)
    391         addBezierCurveTo(FloatPoint(rect.maxX() - topRightRadius.width() * gCircleControlPoint, rect.y()),
    392             FloatPoint(rect.maxX(), rect.y() + topRightRadius.height() * gCircleControlPoint),
    393             FloatPoint(rect.maxX(), rect.y() + topRightRadius.height()));
    394     addLineTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height()));
    395     if (bottomRightRadius.width() > 0 || bottomRightRadius.height() > 0)
    396         addBezierCurveTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height() * gCircleControlPoint),
    397             FloatPoint(rect.maxX() - bottomRightRadius.width() * gCircleControlPoint, rect.maxY()),
    398             FloatPoint(rect.maxX() - bottomRightRadius.width(), rect.maxY()));
    399     addLineTo(FloatPoint(rect.x() + bottomLeftRadius.width(), rect.maxY()));
    400     if (bottomLeftRadius.width() > 0 || bottomLeftRadius.height() > 0)
    401         addBezierCurveTo(FloatPoint(rect.x() + bottomLeftRadius.width() * gCircleControlPoint, rect.maxY()),
    402             FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height() * gCircleControlPoint),
    403             FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height()));
    404     addLineTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height()));
    405     if (topLeftRadius.width() > 0 || topLeftRadius.height() > 0)
    406         addBezierCurveTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height() * gCircleControlPoint),
    407             FloatPoint(rect.x() + topLeftRadius.width() * gCircleControlPoint, rect.y()),
    408             FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
    409 
    410     closeSubpath();
    411 }
    412 
    413 void Path::translate(const FloatSize& size)
    414 {
    415     m_path.offset(WebCoreFloatToSkScalar(size.width()), WebCoreFloatToSkScalar(size.height()));
    416 }
    417 
    418 bool Path::unionPath(const Path& other)
    419 {
    420     return Op(m_path, other.m_path, kUnion_PathOp, &m_path);
    421 }
    422 
    423 }
    424