Home | History | Annotate | Download | only in openvg
      1 /*
      2  * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  */
     19 
     20 #include "config.h"
     21 #include "Path.h"
     22 
     23 #include "AffineTransform.h"
     24 #include "FloatRect.h"
     25 #include "GraphicsContext.h"
     26 #include "NotImplemented.h"
     27 #include "PainterOpenVG.h"
     28 #include "PlatformPathOpenVG.h"
     29 #include "PlatformString.h"
     30 #include "StrokeStyleApplier.h"
     31 #include "VGUtils.h"
     32 
     33 #include <openvg.h>
     34 #include <wtf/MathExtras.h>
     35 
     36 #define WEBKIT_VG_PATH_CAPABILITIES VG_PATH_CAPABILITY_ALL
     37 
     38 #define FUZZY_COMPARE(number, reference, delta) \
     39     (number >= (reference - delta) && number <= (reference + delta))
     40 
     41 namespace WebCore {
     42 
     43 PlatformPathOpenVG::PlatformPathOpenVG()
     44     : SharedResourceOpenVG()
     45 {
     46     createPath();
     47 }
     48 
     49 PlatformPathOpenVG::PlatformPathOpenVG(const PlatformPathOpenVG& other)
     50     : SharedResourceOpenVG()
     51     , m_currentPoint(other.m_currentPoint)
     52     , m_subpathStartPoint(other.m_subpathStartPoint)
     53 {
     54     createPath();
     55     // makeCompatibleContextCurrent() is called by createPath(), so not necessary here.
     56     vgAppendPath(m_vgPath, other.m_vgPath);
     57     ASSERT_VG_NO_ERROR();
     58 }
     59 
     60 PlatformPathOpenVG& PlatformPathOpenVG::operator=(const PlatformPathOpenVG& other)
     61 {
     62     if (&other != this) {
     63         clear();
     64         // makeCompatibleContextCurrent() is called by clear(), so not necessary here.
     65         vgAppendPath(m_vgPath, other.m_vgPath);
     66         ASSERT_VG_NO_ERROR();
     67     }
     68     return *this;
     69 }
     70 
     71 PlatformPathOpenVG::~PlatformPathOpenVG()
     72 {
     73     makeCompatibleContextCurrent();
     74 
     75     vgDestroyPath(m_vgPath);
     76     ASSERT_VG_NO_ERROR();
     77 }
     78 
     79 void PlatformPathOpenVG::clear()
     80 {
     81     makeCompatibleContextCurrent();
     82 
     83     vgClearPath(m_vgPath, WEBKIT_VG_PATH_CAPABILITIES);
     84     ASSERT_VG_NO_ERROR();
     85 
     86     m_subpathStartPoint.setX(0);
     87     m_subpathStartPoint.setY(0);
     88     m_currentPoint = m_subpathStartPoint;
     89 }
     90 
     91 void PlatformPathOpenVG::createPath()
     92 {
     93     makeSharedContextCurrent();
     94 
     95     m_vgPath = vgCreatePath(
     96         VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F,
     97         1.0 /* scale */, 0.0 /* bias */,
     98         0 /* expected number of segments */,
     99         0 /* expected number of total coordinates */,
    100         WEBKIT_VG_PATH_CAPABILITIES);
    101     ASSERT_VG_NO_ERROR();
    102 }
    103 
    104 
    105 Path::Path()
    106 {
    107     m_path = new PlatformPathOpenVG();
    108 }
    109 
    110 Path::~Path()
    111 {
    112     delete m_path;
    113 }
    114 
    115 Path::Path(const Path& other)
    116 {
    117     m_path = new PlatformPathOpenVG(*(other.m_path));
    118 }
    119 
    120 Path& Path::operator=(const Path& other)
    121 {
    122     *m_path = *(other.m_path);
    123     return *this;
    124 }
    125 
    126 FloatPoint Path::currentPoint() const
    127 {
    128     // FIXME: is this the way to return the current point of the subpath?
    129     return m_currentPoint;
    130 }
    131 
    132 
    133 bool Path::contains(const FloatPoint& point, WindRule rule) const
    134 {
    135     notImplemented();
    136 
    137     // OpenVG has no path-contains function, so for now we approximate by
    138     // using the bounding rect of the path.
    139     return boundingRect().contains(point);
    140 }
    141 
    142 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
    143 {
    144     notImplemented();
    145 
    146     // OpenVG has no path-contains function, so for now we approximate by
    147     // using the stroke bounding rect of the path.
    148     return (const_cast<Path*>(this))->strokeBoundingRect().contains(point);
    149 }
    150 
    151 void Path::translate(const FloatSize& size)
    152 {
    153     AffineTransform transformation;
    154     transformation.translate(size.width(), size.height());
    155     transform(transformation);
    156 }
    157 
    158 FloatRect Path::boundingRect() const
    159 {
    160     VGfloat minX;
    161     VGfloat minY;
    162     VGfloat width;
    163     VGfloat height;
    164 
    165     m_path->makeCompatibleContextCurrent();
    166     vgPathBounds(m_path->vgPath(), &minX, &minY, &width, &height);
    167     ASSERT_VG_NO_ERROR();
    168 
    169     return FloatRect(FloatPoint(minX, minY), FloatSize(width, height));
    170 }
    171 
    172 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const
    173 {
    174     notImplemented();
    175 
    176     // vgPathBounds() ignores stroke parameters, and we don't currently have
    177     // an approximation that takes stroke parameters into account.
    178     return boundingRect();
    179 }
    180 
    181 void Path::moveTo(const FloatPoint& point)
    182 {
    183     static const VGubyte pathSegments[] = { VG_MOVE_TO_ABS };
    184     const VGfloat pathData[] = { point.x(), point.y() };
    185 
    186     m_path->makeCompatibleContextCurrent();
    187     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
    188     ASSERT_VG_NO_ERROR();
    189 
    190     m_path->m_currentPoint = m_path->m_subpathStartPoint = point;
    191 }
    192 
    193 void Path::addLineTo(const FloatPoint& point)
    194 {
    195     static const VGubyte pathSegments[] = { VG_LINE_TO_ABS };
    196     const VGfloat pathData[] = { point.x(), point.y() };
    197 
    198     m_path->makeCompatibleContextCurrent();
    199     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
    200     ASSERT_VG_NO_ERROR();
    201 
    202     m_path->m_currentPoint = point;
    203 }
    204 
    205 void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& endPoint)
    206 {
    207     static const VGubyte pathSegments[] = { VG_QUAD_TO_ABS };
    208     const VGfloat pathData[] = { controlPoint.x(), controlPoint.y(), endPoint.x(), endPoint.y() };
    209 
    210     m_path->makeCompatibleContextCurrent();
    211     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
    212     ASSERT_VG_NO_ERROR();
    213 
    214     m_path->m_currentPoint = endPoint;
    215 }
    216 
    217 void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& endPoint)
    218 {
    219     static const VGubyte pathSegments[] = { VG_CUBIC_TO_ABS };
    220     const VGfloat pathData[] = { controlPoint1.x(), controlPoint1.y(), controlPoint2.x(), controlPoint2.y(), endPoint.x(), endPoint.y() };
    221 
    222     m_path->makeCompatibleContextCurrent();
    223     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
    224     ASSERT_VG_NO_ERROR();
    225 
    226     m_path->m_currentPoint = endPoint;
    227 }
    228 
    229 void Path::addArcTo(const FloatPoint& point1, const FloatPoint& point2, float radius)
    230 {
    231     // See http://philip.html5.org/tests/canvas/suite/tests/spec.html#arcto.
    232 
    233     const FloatPoint& point0 = m_path->m_currentPoint;
    234     if (!radius || point0 == point1 || point1 == point2) {
    235         addLineTo(point1);
    236         return;
    237     }
    238 
    239     FloatSize v01 = point0 - point1;
    240     FloatSize v21 = point2 - point1;
    241 
    242     // sin(A - B) = sin(A) * cos(B) - sin(B) * cos(A)
    243     double cross = v01.width() * v21.height() - v01.height() * v21.width();
    244 
    245     if (fabs(cross) < 1E-10) {
    246         // on one line
    247         addLineTo(point1);
    248         return;
    249     }
    250 
    251     double d01 = hypot(v01.width(), v01.height());
    252     double d21 = hypot(v21.width(), v21.height());
    253     double angle = (piDouble - fabs(asin(cross / (d01 * d21)))) * 0.5;
    254     double span = radius * tan(angle);
    255     double rate = span / d01;
    256     FloatPoint startPoint = FloatPoint(point1.x() + v01.width() * rate,
    257                                        point1.y() + v01.height() * rate);
    258     rate = span / d21;
    259     FloatPoint endPoint = FloatPoint(point1.x() + v21.width() * rate,
    260                                      point1.y() + v21.height() * rate);
    261 
    262     // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO
    263     //     respectively SCCWARC_TO and LCCWARC_TO arcs. We always use small
    264     //     arcs for arcTo(), as the arc is defined as the "shortest arc" of the
    265     //     circle specified in HTML 5.
    266 
    267     // Fs: sweep flag, specifying whether the arc is drawn in increasing (true)
    268     //     or decreasing (0) direction.
    269     const bool anticlockwise = cross < 0;
    270 
    271     // Translate the large arc and sweep flags into an OpenVG segment command.
    272     const VGubyte segmentCommand = anticlockwise ? VG_SCCWARC_TO_ABS : VG_SCWARC_TO_ABS;
    273 
    274     const VGubyte pathSegments[] = {
    275         VG_LINE_TO_ABS,
    276         segmentCommand
    277     };
    278     const VGfloat pathData[] = {
    279         startPoint.x(), startPoint.y(),
    280         radius, radius, 0, endPoint.x(), endPoint.y()
    281     };
    282 
    283     m_path->makeCompatibleContextCurrent();
    284     vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData);
    285     ASSERT_VG_NO_ERROR();
    286 
    287     m_path->m_currentPoint = endPoint;
    288 }
    289 
    290 void Path::closeSubpath()
    291 {
    292     static const VGubyte pathSegments[] = { VG_CLOSE_PATH };
    293     // pathData must not be 0, but certain compilers also don't create
    294     // zero-size arrays. So let's use a random aligned value (sizeof(VGfloat)),
    295     // it won't be accessed anyways as VG_CLOSE_PATH doesn't take coordinates.
    296     static const VGfloat* pathData = reinterpret_cast<VGfloat*>(sizeof(VGfloat));
    297 
    298     m_path->makeCompatibleContextCurrent();
    299     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
    300     ASSERT_VG_NO_ERROR();
    301 
    302     m_path->m_currentPoint = m_path->m_subpathStartPoint;
    303 }
    304 
    305 void Path::addArc(const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise)
    306 {
    307     // The OpenVG spec says nothing about inf as radius or start/end angle.
    308     // WebKit seems to pass those (e.g. https://bugs.webkit.org/show_bug.cgi?id=16449),
    309     // so abort instead of risking undefined behavior.
    310     if (!isfinite(radius) || !isfinite(startAngle) || !isfinite(endAngle))
    311         return;
    312 
    313     // For some reason, the HTML 5 spec defines the angle as going clockwise
    314     // from the positive X axis instead of going standard anticlockwise.
    315     // So let's make it a proper angle in order to keep sanity.
    316     startAngle = fmod((2.0 * piDouble) - startAngle, 2.0 * piDouble);
    317     endAngle = fmod((2.0 * piDouble) - endAngle, 2.0 * piDouble);
    318 
    319     // Make it so that endAngle > startAngle. fmod() above takes care of
    320     // keeping the difference below 360 degrees.
    321     if (endAngle <= startAngle)
    322         endAngle += 2.0 * piDouble;
    323 
    324     const VGfloat angleDelta = anticlockwise
    325         ? (endAngle - startAngle)
    326         : (startAngle - endAngle + (2.0 * piDouble));
    327 
    328     // OpenVG uses endpoint parameterization while this method receives its
    329     // values in center parameterization. It lacks an ellipse rotation
    330     // parameter so we use 0 for that, and also the radius is only a single
    331     // value which makes for rh == rv. In order to convert from endpoint to
    332     // center parameterization, we use the formulas from the OpenVG/SVG specs:
    333 
    334     // (x,y) = (cos rot, -sin rot; sin rot, -cos rot) * (rh * cos angle, rv * sin angle) + (center.x, center.y)
    335     // rot is 0, which simplifies this a bit:
    336     // (x,y) = (1, 0; 0, -1) * (rh * cos angle, rv * sin angle) + (center.x, center.y)
    337     //       = (1 * rh * cos angle + 0 * rv * sin angle, 0 * rh * cos angle + -1 * rv * sin angle) + (center.x, center.y)
    338     //       = (rh * cos angle, -rv * sin angle) + (center.x, center.y)
    339     // (Set angle = {startAngle, endAngle} to retrieve the respective endpoints.)
    340 
    341     const VGfloat startX = radius * cos(startAngle) + center.x();
    342     const VGfloat startY = -radius * sin(startAngle) + center.y();
    343     const VGfloat endX = radius * cos(endAngle) + center.x();
    344     const VGfloat endY = -radius * sin(endAngle) + center.y();
    345 
    346     // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO
    347     //     respectively SCCWARC_TO and LCCWARC_TO arcs.
    348     const bool largeArc = (angleDelta > piDouble);
    349 
    350     // Fs: sweep flag, specifying whether the arc is drawn in increasing (true)
    351     //     or decreasing (0) direction. No need to calculate this value, as it
    352     //     we already get it passed as a parameter (Fs == !anticlockwise).
    353 
    354     // Translate the large arc and sweep flags into an OpenVG segment command.
    355     // As OpenVG thinks of everything upside down, we need to reverse the
    356     // anticlockwise parameter in order to get the specified rotation.
    357     const VGubyte segmentCommand = !anticlockwise
    358         ? (largeArc ? VG_LCCWARC_TO_ABS : VG_SCCWARC_TO_ABS)
    359         : (largeArc ? VG_LCWARC_TO_ABS : VG_SCWARC_TO_ABS);
    360 
    361     // So now, we've got all the parameters in endpoint parameterization format
    362     // as OpenVG requires it. Which means we can just pass it like this.
    363     const VGubyte pathSegments[] = {
    364         hasCurrentPoint() ? VG_LINE_TO_ABS : VG_MOVE_TO_ABS,
    365         segmentCommand
    366     };
    367     const VGfloat pathData[] = {
    368         startX, startY,
    369         radius, radius, 0, endX, endY
    370     };
    371 
    372     m_path->makeCompatibleContextCurrent();
    373     vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData);
    374     ASSERT_VG_NO_ERROR();
    375 
    376     m_path->m_currentPoint.setX(endX);
    377     m_path->m_currentPoint.setY(endY);
    378 }
    379 
    380 void Path::addRect(const FloatRect& rect)
    381 {
    382     static const VGubyte pathSegments[] = {
    383         VG_MOVE_TO_ABS,
    384         VG_HLINE_TO_REL,
    385         VG_VLINE_TO_REL,
    386         VG_HLINE_TO_REL,
    387         VG_CLOSE_PATH
    388     };
    389     const VGfloat pathData[] = {
    390         rect.x(), rect.y(),
    391         rect.width(),
    392         rect.height(),
    393         -rect.width()
    394     };
    395 
    396     m_path->makeCompatibleContextCurrent();
    397     vgAppendPathData(m_path->vgPath(), 5, pathSegments, pathData);
    398     ASSERT_VG_NO_ERROR();
    399 
    400     m_path->m_currentPoint = m_path->m_subpathStartPoint = rect.location();
    401 }
    402 
    403 void Path::addEllipse(const FloatRect& rect)
    404 {
    405     static const VGubyte pathSegments[] = {
    406         VG_MOVE_TO_ABS,
    407         VG_SCCWARC_TO_REL,
    408         VG_SCCWARC_TO_REL,
    409         VG_CLOSE_PATH
    410     };
    411     const VGfloat pathData[] = {
    412         rect.x() + rect.width() / 2.0, rect.y(),
    413         rect.width() / 2.0, rect.height() / 2.0, 0, 0, rect.height(),
    414         rect.width() / 2.0, rect.height() / 2.0, 0, 0, -rect.height()
    415     };
    416 
    417     m_path->makeCompatibleContextCurrent();
    418     vgAppendPathData(m_path->vgPath(), 4, pathSegments, pathData);
    419     ASSERT_VG_NO_ERROR();
    420 }
    421 
    422 void Path::clear()
    423 {
    424     m_path->clear();
    425 }
    426 
    427 bool Path::isEmpty() const
    428 {
    429     m_path->makeCompatibleContextCurrent();
    430     return !vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS);
    431 }
    432 
    433 bool Path::hasCurrentPoint() const
    434 {
    435     m_path->makeCompatibleContextCurrent();
    436     return vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS) > 0;
    437 }
    438 
    439 void Path::apply(void* info, PathApplierFunction function) const
    440 {
    441     // OpenVG provides no means to retrieve path segment information.
    442     // This is *very* unfortunate, we might need to store the segments in
    443     // memory if we want to implement this function properly.
    444     // See http://www.khronos.org/message_boards/viewtopic.php?f=6&t=1887
    445     notImplemented();
    446 }
    447 
    448 void Path::transform(const AffineTransform& transformation)
    449 {
    450     PlatformPathOpenVG* dst = new PlatformPathOpenVG();
    451     // dst->makeCompatibleContextCurrent() is called by the platform path
    452     // constructor, therefore not necessary to call it again here.
    453     PainterOpenVG::transformPath(dst->vgPath(), m_path->vgPath(), transformation);
    454     delete m_path;
    455     m_path = dst;
    456 
    457     m_path->m_currentPoint = transformation.mapPoint(m_path->m_currentPoint);
    458     m_path->m_subpathStartPoint = transformation.mapPoint(m_path->m_subpathStartPoint);
    459 }
    460 
    461 
    462 // Path::length(), Path::pointAtLength() and Path::normalAngleAtLength() are
    463 // reimplemented here instead of in Path.cpp, because OpenVG has its own
    464 // functions and Path::apply() doesn't really work as long as we rely on VGPath
    465 // as primary path storage.
    466 
    467 float Path::length() const
    468 {
    469     m_path->makeCompatibleContextCurrent();
    470     VGfloat length = vgPathLength(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS));
    471     ASSERT_VG_NO_ERROR();
    472     return length;
    473 }
    474 
    475 FloatPoint Path::pointAtLength(float length, bool& ok) const
    476 {
    477     VGfloat x = 0, y = 0;
    478     m_path->makeCompatibleContextCurrent();
    479 
    480     vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS),
    481                      length, &x, &y, 0, 0);
    482     ok = (vgGetError() == VG_NO_ERROR);
    483     return FloatPoint(x, y);
    484 }
    485 
    486 float Path::normalAngleAtLength(float length, bool& ok) const
    487 {
    488     VGfloat tangentX, tangentY;
    489     m_path->makeCompatibleContextCurrent();
    490 
    491     vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS),
    492                      length, 0, 0, &tangentX, &tangentY);
    493     ok = (vgGetError() == VG_NO_ERROR);
    494     return atan2f(tangentY, tangentX) * 180.0 / piFloat; // convert to degrees
    495 }
    496 
    497 }
    498