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(PathBuilder
* aPathBuilder
,
232 const RectCornerRadii
& aRadii
,
233 bool aDrawClockwise
= true);
235 inline already_AddRefed
<Path
> MakePathForRoundedRect(
236 const DrawTarget
& aDrawTarget
, const Rect
& aRect
,
237 const RectCornerRadii
& aRadii
, bool aDrawClockwise
= true) {
238 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
239 AppendRoundedRectToPath(builder
, aRect
, aRadii
, aDrawClockwise
);
240 return builder
->Finish();
244 * Appends a path represending an ellipse to the path being built by
247 * The ellipse extends aDimensions.width / 2.0 in the horizontal direction
248 * from aCenter, and aDimensions.height / 2.0 in the vertical direction.
250 GFX2D_API
void AppendEllipseToPath(PathBuilder
* aPathBuilder
,
251 const Point
& aCenter
,
252 const Size
& aDimensions
);
254 inline already_AddRefed
<Path
> MakePathForEllipse(const DrawTarget
& aDrawTarget
,
255 const Point
& aCenter
,
256 const Size
& aDimensions
) {
257 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
258 AppendEllipseToPath(builder
, aCenter
, aDimensions
);
259 return builder
->Finish();
263 * If aDrawTarget's transform only contains a translation, and if this line is
264 * a horizontal or vertical line, this function will snap the line's vertices
265 * to align with the device pixel grid so that stroking the line with a one
266 * pixel wide stroke will result in a crisp line that is not antialiased over
267 * two pixels across its width.
269 * @return Returns true if this function snaps aRect's vertices, else returns
272 GFX2D_API
bool SnapLineToDevicePixelsForStroking(Point
& aP1
, Point
& aP2
,
273 const DrawTarget
& aDrawTarget
,
277 * This function paints each edge of aRect separately, snapping the edges using
278 * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
279 * helps ensure not only that the stroke spans a single row of device pixels if
280 * possible, but also that the ends of stroke dashes start and end on device
283 GFX2D_API
void StrokeSnappedEdgesOfRect(const Rect
& aRect
,
284 DrawTarget
& aDrawTarget
,
285 const ColorPattern
& aColor
,
286 const StrokeOptions
& aStrokeOptions
);
289 * Return the margin, in device space, by which a stroke can extend beyond the
291 * @param aStrokeOptions The stroke options that the stroke is drawn with.
292 * @param aTransform The user space to device space transform.
293 * @return The stroke margin.
295 GFX2D_API Margin
MaxStrokeExtents(const StrokeOptions
& aStrokeOptions
,
296 const Matrix
& aTransform
);
298 extern UserDataKey sDisablePixelSnapping
;
301 * If aDrawTarget's transform only contains a translation or, if
302 * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
303 * function will convert aRect to device space and snap it to device pixels.
304 * This function returns true if aRect is modified, otherwise it returns false.
306 * Note that the snapping is such that filling the rect using a DrawTarget
307 * which has the identity matrix as its transform will result in crisp edges.
308 * (That is, aRect will have integer values, aligning its edges between pixel
309 * boundaries.) If on the other hand you stroking the rect with an odd valued
310 * stroke width then the edges of the stroke will be antialiased (assuming an
311 * AntialiasMode that does antialiasing).
313 * Empty snaps are those which result in a rectangle of 0 area. If they are
314 * disallowed, an axis is left unsnapped if the rounding process results in a
317 inline bool UserToDevicePixelSnapped(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
318 bool aAllowScaleOr90DegreeRotate
= false,
319 bool aAllowEmptySnaps
= true) {
320 if (aDrawTarget
.GetUserData(&sDisablePixelSnapping
)) {
324 Matrix mat
= aDrawTarget
.GetTransform();
326 const Float epsilon
= 0.0000001f
;
327 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
328 if (!aAllowScaleOr90DegreeRotate
&&
329 (!WITHIN_E(mat
._11
, 1.f
) || !WITHIN_E(mat
._22
, 1.f
) ||
330 !WITHIN_E(mat
._12
, 0.f
) || !WITHIN_E(mat
._21
, 0.f
))) {
331 // We have non-translation, but only translation is allowed.
336 Point p1
= mat
.TransformPoint(aRect
.TopLeft());
337 Point p2
= mat
.TransformPoint(aRect
.TopRight());
338 Point p3
= mat
.TransformPoint(aRect
.BottomRight());
340 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
341 // two opposite corners define the entire rectangle. So check if
342 // the axis-aligned rectangle with opposite corners p1 and p3
343 // define an axis-aligned rectangle whose other corners are p2 and p4.
344 // We actually only need to check one of p2 and p4, since an affine
345 // transform maps parallelograms to parallelograms.
346 if (p2
== Point(p1
.x
, p3
.y
) || p2
== Point(p3
.x
, p1
.y
)) {
351 if (aAllowEmptySnaps
|| p1r
.x
!= p3r
.x
) {
355 if (aAllowEmptySnaps
|| p1r
.y
!= p3r
.y
) {
360 aRect
.MoveTo(Point(std::min(p1
.x
, p3
.x
), std::min(p1
.y
, p3
.y
)));
361 aRect
.SizeTo(Size(std::max(p1
.x
, p3
.x
) - aRect
.X(),
362 std::max(p1
.y
, p3
.y
) - aRect
.Y()));
370 * This function has the same behavior as UserToDevicePixelSnapped except that
371 * aRect is not transformed to device space.
373 inline bool MaybeSnapToDevicePixels(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
374 bool aAllowScaleOr90DegreeRotate
= false,
375 bool aAllowEmptySnaps
= true) {
376 if (UserToDevicePixelSnapped(aRect
, aDrawTarget
, aAllowScaleOr90DegreeRotate
,
378 // Since UserToDevicePixelSnapped returned true we know there is no
379 // rotation/skew in 'mat', so we can just use TransformBounds() here.
380 Matrix mat
= aDrawTarget
.GetTransform();
382 aRect
= mat
.TransformBounds(aRect
);
389 } // namespace mozilla
391 #endif /* MOZILLA_GFX_PATHHELPERS_H_ */