1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef MOZILLA_GFX_PATHHELPERS_H_
8 #define MOZILLA_GFX_PATHHELPERS_H_
32 #if (!defined(__GNUC__) || __GNUC__ >= 7) && defined(__clang__)
59 const int32_t sPointCount
[] = {1, 1, 3, 2, 0, 0};
61 // Kappa constant for 90-degree angle
62 const Float kKappaFactor
= 0.55191497064665766025f
;
64 // Calculate kappa constant for partial curve. The sign of angle in the
65 // tangent will actually ensure this is negative for a counter clockwise
66 // sweep, so changing signs later isn't needed.
67 inline Float
ComputeKappaFactor(Float aAngle
) {
68 return (4.0f
/ 3.0f
) * tanf(aAngle
/ 4.0f
);
72 * Draws a partial arc <= 90 degrees given exact start and end points.
73 * Assumes that it is continuing from an already specified start point.
76 inline void PartialArcToBezier(T
* aSink
, const Point
& aStartOffset
,
77 const Point
& aEndOffset
,
78 const Matrix
& aTransform
,
79 Float aKappaFactor
= kKappaFactor
) {
81 aStartOffset
+ Point(-aStartOffset
.y
, aStartOffset
.x
) * aKappaFactor
;
83 Point cp2
= aEndOffset
+ Point(aEndOffset
.y
, -aEndOffset
.x
) * aKappaFactor
;
85 aSink
->BezierTo(aTransform
.TransformPoint(cp1
),
86 aTransform
.TransformPoint(cp2
),
87 aTransform
.TransformPoint(aEndOffset
));
91 * Draws an acute arc (<= 90 degrees) given exact start and end points.
92 * Specialized version avoiding kappa calculation.
95 inline void AcuteArcToBezier(T
* aSink
, const Point
& aOrigin
,
96 const Size
& aRadius
, const Point
& aStartPoint
,
97 const Point
& aEndPoint
,
98 Float aKappaFactor
= kKappaFactor
) {
99 aSink
->LineTo(aStartPoint
);
100 if (!aRadius
.IsEmpty()) {
101 Float kappaX
= aKappaFactor
* aRadius
.width
/ aRadius
.height
;
102 Float kappaY
= aKappaFactor
* aRadius
.height
/ aRadius
.width
;
103 Point startOffset
= aStartPoint
- aOrigin
;
104 Point endOffset
= aEndPoint
- aOrigin
;
106 aStartPoint
+ Point(-startOffset
.y
* kappaX
, startOffset
.x
* kappaY
),
107 aEndPoint
+ Point(endOffset
.y
* kappaX
, -endOffset
.x
* kappaY
),
109 } else if (aEndPoint
!= aStartPoint
) {
110 aSink
->LineTo(aEndPoint
);
115 * Draws an acute arc (<= 90 degrees) given exact start and end points.
117 template <typename T
>
118 inline void AcuteArcToBezier(T
* aSink
, const Point
& aOrigin
,
119 const Size
& aRadius
, const Point
& aStartPoint
,
120 const Point
& aEndPoint
, Float aStartAngle
,
122 AcuteArcToBezier(aSink
, aOrigin
, aRadius
, aStartPoint
, aEndPoint
,
123 ComputeKappaFactor(aEndAngle
- aStartAngle
));
126 template <typename T
>
127 void ArcToBezier(T
* aSink
, const Point
& aOrigin
, const Size
& aRadius
,
128 float aStartAngle
, float aEndAngle
, bool aAntiClockwise
,
129 float aRotation
= 0.0f
, const Matrix
& aTransform
= Matrix()) {
130 Float sweepDirection
= aAntiClockwise
? -1.0f
: 1.0f
;
132 // Calculate the total arc we're going to sweep.
133 Float arcSweepLeft
= (aEndAngle
- aStartAngle
) * sweepDirection
;
135 // Clockwise we always sweep from the smaller to the larger angle, ccw
137 if (arcSweepLeft
< 0) {
138 // Rerverse sweep is modulo'd into range rather than clamped.
139 arcSweepLeft
= Float(2.0f
* M_PI
) + fmodf(arcSweepLeft
, Float(2.0f
* M_PI
));
140 // Recalculate the start angle to land closer to end angle.
141 aStartAngle
= aEndAngle
- arcSweepLeft
* sweepDirection
;
142 } else if (arcSweepLeft
> Float(2.0f
* M_PI
)) {
143 // Sweeping more than 2 * pi is a full circle.
144 arcSweepLeft
= Float(2.0f
* M_PI
);
147 Float currentStartAngle
= aStartAngle
;
148 Point
currentStartOffset(cosf(aStartAngle
), sinf(aStartAngle
));
149 Matrix transform
= Matrix::Scaling(aRadius
.width
, aRadius
.height
);
150 if (aRotation
!= 0.0f
) {
151 transform
*= Matrix::Rotation(aRotation
);
153 transform
.PostTranslate(aOrigin
);
154 transform
*= aTransform
;
155 aSink
->LineTo(transform
.TransformPoint(currentStartOffset
));
157 while (arcSweepLeft
> 0) {
158 Float currentEndAngle
=
160 std::min(arcSweepLeft
, Float(M_PI
/ 2.0f
)) * sweepDirection
;
161 Point
currentEndOffset(cosf(currentEndAngle
), sinf(currentEndAngle
));
163 PartialArcToBezier(aSink
, currentStartOffset
, currentEndOffset
, transform
,
164 ComputeKappaFactor(currentEndAngle
- currentStartAngle
));
166 // We guarantee here the current point is the start point of the next
168 arcSweepLeft
-= Float(M_PI
/ 2.0f
);
169 currentStartAngle
= currentEndAngle
;
170 currentStartOffset
= currentEndOffset
;
174 /* This is basically the ArcToBezier with the parameters for drawing a circle
175 * inlined which vastly simplifies it and avoids a bunch of transcedental
176 * function calls which should make it faster. */
177 template <typename T
>
178 void EllipseToBezier(T
* aSink
, const Point
& aOrigin
, const Size
& aRadius
) {
179 Matrix
transform(aRadius
.width
, 0, 0, aRadius
.height
, aOrigin
.x
, aOrigin
.y
);
180 Point
currentStartOffset(1, 0);
182 aSink
->LineTo(transform
.TransformPoint(currentStartOffset
));
184 for (int i
= 0; i
< 4; i
++) {
185 // cos(x+pi/2) == -sin(x)
186 // sin(x+pi/2) == cos(x)
187 Point
currentEndOffset(-currentStartOffset
.y
, currentStartOffset
.x
);
189 PartialArcToBezier(aSink
, currentStartOffset
, currentEndOffset
, transform
);
191 // We guarantee here the current point is the start point of the next
193 currentStartOffset
= currentEndOffset
;
198 * Appends a path represending a rectangle to the path being built by
201 * aRect The rectangle to append.
202 * aDrawClockwise If set to true, the path will start at the left of the top
203 * left edge and draw clockwise. If set to false the path will
204 * start at the right of the top left edge and draw counter-
207 GFX2D_API
void AppendRectToPath(PathBuilder
* aPathBuilder
, const Rect
& aRect
,
208 bool aDrawClockwise
= true);
210 inline already_AddRefed
<Path
> MakePathForRect(const DrawTarget
& aDrawTarget
,
212 bool aDrawClockwise
= true) {
213 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
214 AppendRectToPath(builder
, aRect
, aDrawClockwise
);
215 return builder
->Finish();
219 * Appends a path represending a rounded rectangle to the path being built by
222 * aRect The rectangle to append.
223 * aCornerRadii Contains the radii of the top-left, top-right, bottom-right
224 * and bottom-left corners, in that order.
225 * aDrawClockwise If set to true, the path will start at the left of the top
226 * left edge and draw clockwise. If set to false the path will
227 * start at the right of the top left edge and draw counter-
230 GFX2D_API
void AppendRoundedRectToPath(
231 PathBuilder
* aPathBuilder
, const Rect
& aRect
, const RectCornerRadii
& aRadii
,
232 bool aDrawClockwise
= true, const Maybe
<Matrix
>& aTransform
= Nothing());
234 inline already_AddRefed
<Path
> MakePathForRoundedRect(
235 const DrawTarget
& aDrawTarget
, const Rect
& aRect
,
236 const RectCornerRadii
& aRadii
, bool aDrawClockwise
= true) {
237 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
238 AppendRoundedRectToPath(builder
, aRect
, aRadii
, aDrawClockwise
);
239 return builder
->Finish();
243 * Appends a path represending an ellipse to the path being built by
246 * The ellipse extends aDimensions.width / 2.0 in the horizontal direction
247 * from aCenter, and aDimensions.height / 2.0 in the vertical direction.
249 GFX2D_API
void AppendEllipseToPath(PathBuilder
* aPathBuilder
,
250 const Point
& aCenter
,
251 const Size
& aDimensions
);
253 inline already_AddRefed
<Path
> MakePathForEllipse(const DrawTarget
& aDrawTarget
,
254 const Point
& aCenter
,
255 const Size
& aDimensions
) {
256 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
257 AppendEllipseToPath(builder
, aCenter
, aDimensions
);
258 return builder
->Finish();
261 inline already_AddRefed
<Path
> MakePathForCircle(const DrawTarget
& aDrawTarget
,
262 const Point
& aCenter
,
264 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
265 builder
->Arc(aCenter
, aRadius
, 0.0f
, Float(2.0 * M_PI
));
267 return builder
->Finish();
271 * If aDrawTarget's transform only contains a translation, and if this line is
272 * a horizontal or vertical line, this function will snap the line's vertices
273 * to align with the device pixel grid so that stroking the line with a one
274 * pixel wide stroke will result in a crisp line that is not antialiased over
275 * two pixels across its width.
277 * @return Returns true if this function snaps aRect's vertices, else returns
280 GFX2D_API
bool SnapLineToDevicePixelsForStroking(Point
& aP1
, Point
& aP2
,
281 const DrawTarget
& aDrawTarget
,
285 * This function paints each edge of aRect separately, snapping the edges using
286 * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
287 * helps ensure not only that the stroke spans a single row of device pixels if
288 * possible, but also that the ends of stroke dashes start and end on device
291 GFX2D_API
void StrokeSnappedEdgesOfRect(const Rect
& aRect
,
292 DrawTarget
& aDrawTarget
,
293 const ColorPattern
& aColor
,
294 const StrokeOptions
& aStrokeOptions
);
297 * Return the margin, in device space, by which a stroke can extend beyond the
299 * @param aStrokeOptions The stroke options that the stroke is drawn with.
300 * @param aTransform The user space to device space transform.
301 * @return The stroke margin.
303 GFX2D_API Margin
MaxStrokeExtents(const StrokeOptions
& aStrokeOptions
,
304 const Matrix
& aTransform
);
306 extern UserDataKey sDisablePixelSnapping
;
309 * If aDrawTarget's transform only contains a translation or, if
310 * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
311 * function will convert aRect to device space and snap it to device pixels.
312 * This function returns true if aRect is modified, otherwise it returns false.
314 * Note that the snapping is such that filling the rect using a DrawTarget
315 * which has the identity matrix as its transform will result in crisp edges.
316 * (That is, aRect will have integer values, aligning its edges between pixel
317 * boundaries.) If on the other hand you stroking the rect with an odd valued
318 * stroke width then the edges of the stroke will be antialiased (assuming an
319 * AntialiasMode that does antialiasing).
321 * Empty snaps are those which result in a rectangle of 0 area. If they are
322 * disallowed, an axis is left unsnapped if the rounding process results in a
325 inline bool UserToDevicePixelSnapped(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
326 bool aAllowScaleOr90DegreeRotate
= false,
327 bool aAllowEmptySnaps
= true) {
328 if (aDrawTarget
.GetUserData(&sDisablePixelSnapping
)) {
332 Matrix mat
= aDrawTarget
.GetTransform();
334 const Float epsilon
= 0.0000001f
;
335 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
336 if (!aAllowScaleOr90DegreeRotate
&&
337 (!WITHIN_E(mat
._11
, 1.f
) || !WITHIN_E(mat
._22
, 1.f
) ||
338 !WITHIN_E(mat
._12
, 0.f
) || !WITHIN_E(mat
._21
, 0.f
))) {
339 // We have non-translation, but only translation is allowed.
344 Point p1
= mat
.TransformPoint(aRect
.TopLeft());
345 Point p2
= mat
.TransformPoint(aRect
.TopRight());
346 Point p3
= mat
.TransformPoint(aRect
.BottomRight());
348 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
349 // two opposite corners define the entire rectangle. So check if
350 // the axis-aligned rectangle with opposite corners p1 and p3
351 // define an axis-aligned rectangle whose other corners are p2 and p4.
352 // We actually only need to check one of p2 and p4, since an affine
353 // transform maps parallelograms to parallelograms.
354 if (p2
== Point(p1
.x
, p3
.y
) || p2
== Point(p3
.x
, p1
.y
)) {
359 if (aAllowEmptySnaps
|| p1r
.x
!= p3r
.x
) {
363 if (aAllowEmptySnaps
|| p1r
.y
!= p3r
.y
) {
368 aRect
.MoveTo(Point(std::min(p1
.x
, p3
.x
), std::min(p1
.y
, p3
.y
)));
369 aRect
.SizeTo(Size(std::max(p1
.x
, p3
.x
) - aRect
.X(),
370 std::max(p1
.y
, p3
.y
) - aRect
.Y()));
378 * This function has the same behavior as UserToDevicePixelSnapped except that
379 * aRect is not transformed to device space.
381 inline bool MaybeSnapToDevicePixels(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
382 bool aAllowScaleOr90DegreeRotate
= false,
383 bool aAllowEmptySnaps
= true) {
384 if (UserToDevicePixelSnapped(aRect
, aDrawTarget
, aAllowScaleOr90DegreeRotate
,
386 // Since UserToDevicePixelSnapped returned true we know there is no
387 // rotation/skew in 'mat', so we can just use TransformBounds() here.
388 Matrix mat
= aDrawTarget
.GetTransform();
390 aRect
= mat
.TransformBounds(aRect
);
397 } // namespace mozilla
399 #endif /* MOZILLA_GFX_PATHHELPERS_H_ */