LCOV - code coverage report
Current view: top level - gfx/2d - PathHelpers.h (source / functions) Hit Total Coverage
Test: output.info Lines: 66 133 49.6 %
Date: 2017-07-14 16:53:18 Functions: 16 37 43.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2             :  * This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #ifndef MOZILLA_GFX_PATHHELPERS_H_
       7             : #define MOZILLA_GFX_PATHHELPERS_H_
       8             : 
       9             : #include "2D.h"
      10             : #include "UserData.h"
      11             : 
      12             : #include <cmath>
      13             : 
      14             : namespace mozilla {
      15             : namespace gfx {
      16             : 
      17             : // Kappa constant for 90-degree angle
      18             : const Float kKappaFactor = 0.55191497064665766025f;
      19             : 
      20             : // Calculate kappa constant for partial curve. The sign of angle in the
      21             : // tangent will actually ensure this is negative for a counter clockwise
      22             : // sweep, so changing signs later isn't needed.
      23           0 : inline Float ComputeKappaFactor(Float aAngle)
      24             : {
      25           0 :   return (4.0f / 3.0f) * tanf(aAngle / 4.0f);
      26             : }
      27             : 
      28             : /**
      29             :  * Draws a partial arc <= 90 degrees given exact start and end points.
      30             :  * Assumes that it is continuing from an already specified start point.
      31             :  */
      32             : template <typename T>
      33           0 : inline void PartialArcToBezier(T* aSink,
      34             :                                const Point& aStartOffset, const Point& aEndOffset,
      35             :                                const Matrix& aTransform,
      36             :                                Float aKappaFactor = kKappaFactor)
      37             : {
      38             :   Point cp1 =
      39           0 :     aStartOffset + Point(-aStartOffset.y, aStartOffset.x) * aKappaFactor;
      40             : 
      41             :   Point cp2 =
      42           0 :     aEndOffset + Point(aEndOffset.y, -aEndOffset.x) * aKappaFactor;
      43             : 
      44           0 :   aSink->BezierTo(aTransform.TransformPoint(cp1),
      45           0 :                   aTransform.TransformPoint(cp2),
      46           0 :                   aTransform.TransformPoint(aEndOffset));
      47           0 : }
      48             : 
      49             : /**
      50             :  * Draws an acute arc (<= 90 degrees) given exact start and end points.
      51             :  * Specialized version avoiding kappa calculation.
      52             :  */
      53             : template <typename T>
      54           0 : inline void AcuteArcToBezier(T* aSink,
      55             :                              const Point& aOrigin, const Size& aRadius,
      56             :                              const Point& aStartPoint, const Point& aEndPoint,
      57             :                              Float aKappaFactor = kKappaFactor)
      58             : {
      59           0 :   aSink->LineTo(aStartPoint);
      60           0 :   if (!aRadius.IsEmpty()) {
      61           0 :     Float kappaX = aKappaFactor * aRadius.width / aRadius.height;
      62           0 :     Float kappaY = aKappaFactor * aRadius.height / aRadius.width;
      63           0 :     Point startOffset = aStartPoint - aOrigin;
      64           0 :     Point endOffset = aEndPoint - aOrigin;
      65           0 :     aSink->BezierTo(aStartPoint + Point(-startOffset.y * kappaX, startOffset.x * kappaY),
      66           0 :                     aEndPoint + Point(endOffset.y * kappaX, -endOffset.x * kappaY),
      67             :                     aEndPoint);
      68           0 :   } else if (aEndPoint != aStartPoint) {
      69           0 :     aSink->LineTo(aEndPoint);
      70             :   }
      71           0 : }
      72             : 
      73             : /**
      74             :  * Draws an acute arc (<= 90 degrees) given exact start and end points.
      75             :  */
      76             : template <typename T>
      77           0 : inline void AcuteArcToBezier(T* aSink,
      78             :                              const Point& aOrigin, const Size& aRadius,
      79             :                              const Point& aStartPoint, const Point& aEndPoint,
      80             :                              Float aStartAngle, Float aEndAngle)
      81             : {
      82           0 :   AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
      83             :                    ComputeKappaFactor(aEndAngle - aStartAngle));
      84           0 : }
      85             : 
      86             : template <typename T>
      87           0 : void ArcToBezier(T* aSink, const Point &aOrigin, const Size &aRadius,
      88             :                  float aStartAngle, float aEndAngle, bool aAntiClockwise,
      89             :                  float aRotation = 0.0f)
      90             : {
      91           0 :   Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
      92             : 
      93             :   // Calculate the total arc we're going to sweep.
      94           0 :   Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
      95             : 
      96             :   // Clockwise we always sweep from the smaller to the larger angle, ccw
      97             :   // it's vice versa.
      98           0 :   if (arcSweepLeft < 0) {
      99             :     // Rerverse sweep is modulo'd into range rather than clamped.
     100           0 :     arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI));
     101             :     // Recalculate the start angle to land closer to end angle.
     102           0 :     aStartAngle = aEndAngle - arcSweepLeft * sweepDirection;
     103           0 :   } else if (arcSweepLeft > Float(2.0f * M_PI)) {
     104             :     // Sweeping more than 2 * pi is a full circle.
     105           0 :     arcSweepLeft = Float(2.0f * M_PI);
     106             :   }
     107             : 
     108           0 :   Float currentStartAngle = aStartAngle;
     109           0 :   Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
     110           0 :   Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height);
     111           0 :   if (aRotation != 0.0f) {
     112           0 :     transform *= Matrix::Rotation(aRotation);
     113             :   }
     114           0 :   transform.PostTranslate(aOrigin);
     115           0 :   aSink->LineTo(transform.TransformPoint(currentStartOffset));
     116             : 
     117           0 :   while (arcSweepLeft > 0) {
     118             :     Float currentEndAngle =
     119           0 :       currentStartAngle + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
     120           0 :     Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
     121             : 
     122           0 :     PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform,
     123             :                        ComputeKappaFactor(currentEndAngle - currentStartAngle));
     124             : 
     125             :     // We guarantee here the current point is the start point of the next
     126             :     // curve segment.
     127           0 :     arcSweepLeft -= Float(M_PI / 2.0f);
     128           0 :     currentStartAngle = currentEndAngle;
     129           0 :     currentStartOffset = currentEndOffset;
     130             :   }
     131           0 : }
     132             : 
     133             : /* This is basically the ArcToBezier with the parameters for drawing a circle
     134             :  * inlined which vastly simplifies it and avoids a bunch of transcedental function
     135             :  * calls which should make it faster. */
     136             : template <typename T>
     137           0 : void EllipseToBezier(T* aSink, const Point &aOrigin, const Size &aRadius)
     138             : {
     139           0 :   Matrix transform(aRadius.width, 0, 0, aRadius.height, aOrigin.x, aOrigin.y);
     140           0 :   Point currentStartOffset(1, 0);
     141             : 
     142           0 :   aSink->LineTo(transform.TransformPoint(currentStartOffset));
     143             : 
     144           0 :   for (int i = 0; i < 4; i++) {
     145             :     // cos(x+pi/2) == -sin(x)
     146             :     // sin(x+pi/2) == cos(x)
     147           0 :     Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x);
     148             : 
     149           0 :     PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform);
     150             : 
     151             :     // We guarantee here the current point is the start point of the next
     152             :     // curve segment.
     153           0 :     currentStartOffset = currentEndOffset;
     154             :   }
     155           0 : }
     156             : 
     157             : /**
     158             :  * Appends a path represending a rectangle to the path being built by
     159             :  * aPathBuilder.
     160             :  *
     161             :  * aRect           The rectangle to append.
     162             :  * aDrawClockwise  If set to true, the path will start at the left of the top
     163             :  *                 left edge and draw clockwise. If set to false the path will
     164             :  *                 start at the right of the top left edge and draw counter-
     165             :  *                 clockwise.
     166             :  */
     167             : GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder,
     168             :                                 const Rect& aRect,
     169             :                                 bool aDrawClockwise = true);
     170             : 
     171             : inline already_AddRefed<Path> MakePathForRect(const DrawTarget& aDrawTarget,
     172             :                                           const Rect& aRect,
     173             :                                           bool aDrawClockwise = true)
     174             : {
     175             :   RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
     176             :   AppendRectToPath(builder, aRect, aDrawClockwise);
     177             :   return builder->Finish();
     178             : }
     179             : 
     180             : struct RectCornerRadii {
     181             :   Size radii[eCornerCount];
     182             : 
     183         567 :   RectCornerRadii() {}
     184             : 
     185           9 :   explicit RectCornerRadii(Float radius) {
     186          45 :     NS_FOR_CSS_FULL_CORNERS(i) {
     187          36 :       radii[i].SizeTo(radius, radius);
     188             :     }
     189           9 :   }
     190             : 
     191           4 :   explicit RectCornerRadii(Float radiusX, Float radiusY) {
     192          20 :     NS_FOR_CSS_FULL_CORNERS(i) {
     193          16 :       radii[i].SizeTo(radiusX, radiusY);
     194             :     }
     195           4 :   }
     196             : 
     197             :   RectCornerRadii(Float tl, Float tr, Float br, Float bl) {
     198             :     radii[eCornerTopLeft].SizeTo(tl, tl);
     199             :     radii[eCornerTopRight].SizeTo(tr, tr);
     200             :     radii[eCornerBottomRight].SizeTo(br, br);
     201             :     radii[eCornerBottomLeft].SizeTo(bl, bl);
     202             :   }
     203             : 
     204             :   RectCornerRadii(const Size& tl, const Size& tr,
     205             :                   const Size& br, const Size& bl) {
     206             :     radii[eCornerTopLeft] = tl;
     207             :     radii[eCornerTopRight] = tr;
     208             :     radii[eCornerBottomRight] = br;
     209             :     radii[eCornerBottomLeft] = bl;
     210             :   }
     211             : 
     212        3559 :   const Size& operator[](size_t aCorner) const {
     213        3559 :     return radii[aCorner];
     214             :   }
     215             : 
     216        1223 :   Size& operator[](size_t aCorner) {
     217        1223 :     return radii[aCorner];
     218             :   }
     219             : 
     220           8 :   bool operator==(const RectCornerRadii& aOther) const {
     221          64 :     return TopLeft() == aOther.TopLeft() &&
     222          56 :            TopRight() == aOther.TopRight() &&
     223          88 :            BottomRight() == aOther.BottomRight() &&
     224          40 :            BottomLeft() == aOther.BottomLeft();
     225             :   }
     226             : 
     227           9 :   bool AreRadiiSame() const {
     228          72 :     return TopLeft() == TopRight() &&
     229          90 :            TopLeft() == BottomRight() &&
     230          45 :            TopLeft() == BottomLeft();
     231             :   }
     232             : 
     233           9 :   void Scale(Float aXScale, Float aYScale) {
     234          45 :     NS_FOR_CSS_FULL_CORNERS(i) {
     235          36 :       radii[i].Scale(aXScale, aYScale);
     236             :     }
     237           9 :   }
     238             : 
     239         472 :   const Size TopLeft() const { return radii[eCornerTopLeft]; }
     240           0 :   Size& TopLeft() { return radii[eCornerTopLeft]; }
     241             : 
     242         310 :   const Size TopRight() const { return radii[eCornerTopRight]; }
     243             :   Size& TopRight() { return radii[eCornerTopRight]; }
     244             : 
     245         310 :   const Size BottomRight() const { return radii[eCornerBottomRight]; }
     246             :   Size& BottomRight() { return radii[eCornerBottomRight]; }
     247             : 
     248         310 :   const Size BottomLeft() const { return radii[eCornerBottomLeft]; }
     249             :   Size& BottomLeft() { return radii[eCornerBottomLeft]; }
     250             : 
     251         429 :   bool IsEmpty() const {
     252        2571 :     return TopLeft().IsEmpty() && TopRight().IsEmpty() &&
     253        2571 :            BottomRight().IsEmpty() && BottomLeft().IsEmpty();
     254             :   }
     255             : };
     256             : 
     257             : /**
     258             :  * Appends a path represending a rounded rectangle to the path being built by
     259             :  * aPathBuilder.
     260             :  *
     261             :  * aRect           The rectangle to append.
     262             :  * aCornerRadii    Contains the radii of the top-left, top-right, bottom-right
     263             :  *                 and bottom-left corners, in that order.
     264             :  * aDrawClockwise  If set to true, the path will start at the left of the top
     265             :  *                 left edge and draw clockwise. If set to false the path will
     266             :  *                 start at the right of the top left edge and draw counter-
     267             :  *                 clockwise.
     268             :  */
     269             : GFX2D_API void AppendRoundedRectToPath(PathBuilder* aPathBuilder,
     270             :                                        const Rect& aRect,
     271             :                                        const RectCornerRadii& aRadii,
     272             :                                        bool aDrawClockwise = true);
     273             : 
     274          22 : inline already_AddRefed<Path> MakePathForRoundedRect(const DrawTarget& aDrawTarget,
     275             :                                                  const Rect& aRect,
     276             :                                                  const RectCornerRadii& aRadii,
     277             :                                                  bool aDrawClockwise = true)
     278             : {
     279          44 :   RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
     280          22 :   AppendRoundedRectToPath(builder, aRect, aRadii, aDrawClockwise);
     281          44 :   return builder->Finish();
     282             : }
     283             : 
     284             : /**
     285             :  * Appends a path represending an ellipse to the path being built by
     286             :  * aPathBuilder.
     287             :  *
     288             :  * The ellipse extends aDimensions.width / 2.0 in the horizontal direction
     289             :  * from aCenter, and aDimensions.height / 2.0 in the vertical direction.
     290             :  */
     291             : GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder,
     292             :                                    const Point& aCenter,
     293             :                                    const Size& aDimensions);
     294             : 
     295           0 : inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget,
     296             :                                              const Point& aCenter,
     297             :                                              const Size& aDimensions)
     298             : {
     299           0 :   RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
     300           0 :   AppendEllipseToPath(builder, aCenter, aDimensions);
     301           0 :   return builder->Finish();
     302             : }
     303             : 
     304             : /**
     305             :  * If aDrawTarget's transform only contains a translation, and if this line is
     306             :  * a horizontal or vertical line, this function will snap the line's vertices
     307             :  * to align with the device pixel grid so that stroking the line with a one
     308             :  * pixel wide stroke will result in a crisp line that is not antialiased over
     309             :  * two pixels across its width.
     310             :  *
     311             :  * @return Returns true if this function snaps aRect's vertices, else returns
     312             :  *   false.
     313             :  */
     314             : GFX2D_API bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2,
     315             :                                                  const DrawTarget& aDrawTarget,
     316             :                                                  Float aLineWidth);
     317             : 
     318             : /**
     319             :  * This function paints each edge of aRect separately, snapping the edges using
     320             :  * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
     321             :  * helps ensure not only that the stroke spans a single row of device pixels if
     322             :  * possible, but also that the ends of stroke dashes start and end on device
     323             :  * pixels too.
     324             :  */
     325             : GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect,
     326             :                                         DrawTarget& aDrawTarget,
     327             :                                         const ColorPattern& aColor,
     328             :                                         const StrokeOptions& aStrokeOptions);
     329             : 
     330             : /**
     331             :  * Return the margin, in device space, by which a stroke can extend beyond the
     332             :  * rendered shape.
     333             :  * @param  aStrokeOptions The stroke options that the stroke is drawn with.
     334             :  * @param  aTransform     The user space to device space transform.
     335             :  * @return                The stroke margin.
     336             :  */
     337             : GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
     338             :                                   const Matrix& aTransform);
     339             : 
     340             : extern UserDataKey sDisablePixelSnapping;
     341             : 
     342             : /**
     343             :  * If aDrawTarget's transform only contains a translation or, if
     344             :  * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
     345             :  * function will convert aRect to device space and snap it to device pixels.
     346             :  * This function returns true if aRect is modified, otherwise it returns false.
     347             :  *
     348             :  * Note that the snapping is such that filling the rect using a DrawTarget
     349             :  * which has the identity matrix as its transform will result in crisp edges.
     350             :  * (That is, aRect will have integer values, aligning its edges between pixel
     351             :  * boundaries.)  If on the other hand you stroking the rect with an odd valued
     352             :  * stroke width then the edges of the stroke will be antialiased (assuming an
     353             :  * AntialiasMode that does antialiasing).
     354             :  *
     355             :  * Empty snaps are those which result in a rectangle of 0 area.  If they are
     356             :  * disallowed, an axis is left unsnapped if the rounding process results in a
     357             :  * length of 0.
     358             :  */
     359          59 : inline bool UserToDevicePixelSnapped(Rect& aRect, const DrawTarget& aDrawTarget,
     360             :                                      bool aAllowScaleOr90DegreeRotate = false,
     361             :                                      bool aAllowEmptySnaps = true)
     362             : {
     363          59 :   if (aDrawTarget.GetUserData(&sDisablePixelSnapping)) {
     364           0 :     return false;
     365             :   }
     366             : 
     367          59 :   Matrix mat = aDrawTarget.GetTransform();
     368             : 
     369          59 :   const Float epsilon = 0.0000001f;
     370             : #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
     371          59 :   if (!aAllowScaleOr90DegreeRotate &&
     372           0 :       (!WITHIN_E(mat._11, 1.f) || !WITHIN_E(mat._22, 1.f) ||
     373           0 :        !WITHIN_E(mat._12, 0.f) || !WITHIN_E(mat._21, 0.f))) {
     374             :     // We have non-translation, but only translation is allowed.
     375           0 :     return false;
     376             :   }
     377             : #undef WITHIN_E
     378             : 
     379          59 :   Point p1 = mat.TransformPoint(aRect.TopLeft());
     380          59 :   Point p2 = mat.TransformPoint(aRect.TopRight());
     381          59 :   Point p3 = mat.TransformPoint(aRect.BottomRight());
     382             : 
     383             :   // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
     384             :   // two opposite corners define the entire rectangle. So check if
     385             :   // the axis-aligned rectangle with opposite corners p1 and p3
     386             :   // define an axis-aligned rectangle whose other corners are p2 and p4.
     387             :   // We actually only need to check one of p2 and p4, since an affine
     388             :   // transform maps parallelograms to parallelograms.
     389          59 :   if (p2 == Point(p1.x, p3.y) || p2 == Point(p3.x, p1.y)) {
     390          59 :       Point p1r = p1;
     391          59 :       Point p3r = p3;
     392          59 :       p1r.Round();
     393          59 :       p3r.Round();
     394          59 :       if (aAllowEmptySnaps || p1r.x != p3r.x) {
     395          59 :           p1.x = p1r.x;
     396          59 :           p3.x = p3r.x;
     397             :       }
     398          59 :       if (aAllowEmptySnaps || p1r.y != p3r.y) {
     399          59 :           p1.y = p1r.y;
     400          59 :           p3.y = p3r.y;
     401             :       }
     402             : 
     403          59 :       aRect.MoveTo(Point(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
     404         118 :       aRect.SizeTo(Size(std::max(p1.x, p3.x) - aRect.X(),
     405         118 :                         std::max(p1.y, p3.y) - aRect.Y()));
     406          59 :       return true;
     407             :   }
     408             : 
     409           0 :   return false;
     410             : }
     411             : 
     412             : /**
     413             :  * This function has the same behavior as UserToDevicePixelSnapped except that
     414             :  * aRect is not transformed to device space.
     415             :  */
     416          59 : inline bool MaybeSnapToDevicePixels(Rect& aRect, const DrawTarget& aDrawTarget,
     417             :                                     bool aAllowScaleOr90DegreeRotate = false,
     418             :                                     bool aAllowEmptySnaps = true)
     419             : {
     420          59 :   if (UserToDevicePixelSnapped(aRect, aDrawTarget,
     421             :                                aAllowScaleOr90DegreeRotate, aAllowEmptySnaps)) {
     422             :     // Since UserToDevicePixelSnapped returned true we know there is no
     423             :     // rotation/skew in 'mat', so we can just use TransformBounds() here.
     424          59 :     Matrix mat = aDrawTarget.GetTransform();
     425          59 :     mat.Invert();
     426          59 :     aRect = mat.TransformBounds(aRect);
     427          59 :     return true;
     428             :   }
     429           0 :   return false;
     430             : }
     431             : 
     432             : } // namespace gfx
     433             : } // namespace mozilla
     434             : 
     435             : #endif /* MOZILLA_GFX_PATHHELPERS_H_ */

Generated by: LCOV version 1.13