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/. */
6 #ifndef MOZILLA_GFX_PATHHELPERS_H_
7 #define MOZILLA_GFX_PATHHELPERS_H_
10 #include "mozilla/Constants.h"
11 #include "mozilla/TypedEnum.h"
18 void ArcToBezier(T
* aSink
, const Point
&aOrigin
, const Size
&aRadius
,
19 float aStartAngle
, float aEndAngle
, bool aAntiClockwise
)
21 Point
startPoint(aOrigin
.x
+ cosf(aStartAngle
) * aRadius
.width
,
22 aOrigin
.y
+ sinf(aStartAngle
) * aRadius
.height
);
24 aSink
->LineTo(startPoint
);
26 // Clockwise we always sweep from the smaller to the larger angle, ccw
28 if (!aAntiClockwise
&& (aEndAngle
< aStartAngle
)) {
29 Float correction
= Float(ceil((aStartAngle
- aEndAngle
) / (2.0f
* M_PI
)));
30 aEndAngle
+= float(correction
* 2.0f
* M_PI
);
31 } else if (aAntiClockwise
&& (aStartAngle
< aEndAngle
)) {
32 Float correction
= (Float
)ceil((aEndAngle
- aStartAngle
) / (2.0f
* M_PI
));
33 aStartAngle
+= float(correction
* 2.0f
* M_PI
);
36 // Sweeping more than 2 * pi is a full circle.
37 if (!aAntiClockwise
&& (aEndAngle
- aStartAngle
> 2 * M_PI
)) {
38 aEndAngle
= float(aStartAngle
+ 2.0f
* M_PI
);
39 } else if (aAntiClockwise
&& (aStartAngle
- aEndAngle
> 2.0f
* M_PI
)) {
40 aEndAngle
= float(aStartAngle
- 2.0f
* M_PI
);
43 // Calculate the total arc we're going to sweep.
44 Float arcSweepLeft
= fabs(aEndAngle
- aStartAngle
);
46 Float sweepDirection
= aAntiClockwise
? -1.0f
: 1.0f
;
48 Float currentStartAngle
= aStartAngle
;
50 while (arcSweepLeft
> 0) {
51 // We guarantee here the current point is the start point of the next
53 Float currentEndAngle
;
55 if (arcSweepLeft
> M_PI
/ 2.0f
) {
56 currentEndAngle
= Float(currentStartAngle
+ M_PI
/ 2.0f
* sweepDirection
);
58 currentEndAngle
= currentStartAngle
+ arcSweepLeft
* sweepDirection
;
61 Point
currentStartPoint(aOrigin
.x
+ cosf(currentStartAngle
) * aRadius
.width
,
62 aOrigin
.y
+ sinf(currentStartAngle
) * aRadius
.height
);
63 Point
currentEndPoint(aOrigin
.x
+ cosf(currentEndAngle
) * aRadius
.width
,
64 aOrigin
.y
+ sinf(currentEndAngle
) * aRadius
.height
);
66 // Calculate kappa constant for partial curve. The sign of angle in the
67 // tangent will actually ensure this is negative for a counter clockwise
68 // sweep, so changing signs later isn't needed.
69 Float kappaFactor
= (4.0f
/ 3.0f
) * tan((currentEndAngle
- currentStartAngle
) / 4.0f
);
70 Float kappaX
= kappaFactor
* aRadius
.width
;
71 Float kappaY
= kappaFactor
* aRadius
.height
;
73 Point
tangentStart(-sin(currentStartAngle
), cos(currentStartAngle
));
74 Point cp1
= currentStartPoint
;
75 cp1
+= Point(tangentStart
.x
* kappaX
, tangentStart
.y
* kappaY
);
77 Point
revTangentEnd(sin(currentEndAngle
), -cos(currentEndAngle
));
78 Point cp2
= currentEndPoint
;
79 cp2
+= Point(revTangentEnd
.x
* kappaX
, revTangentEnd
.y
* kappaY
);
81 aSink
->BezierTo(cp1
, cp2
, currentEndPoint
);
83 arcSweepLeft
-= Float(M_PI
/ 2.0f
);
84 currentStartAngle
= currentEndAngle
;
88 /* This is basically the ArcToBezier with the parameters for drawing a circle
89 * inlined which vastly simplifies it and avoids a bunch of transcedental function
90 * calls which should make it faster. */
92 void EllipseToBezier(T
* aSink
, const Point
&aOrigin
, const Size
&aRadius
)
94 Point
startPoint(aOrigin
.x
+ aRadius
.width
,
97 aSink
->LineTo(startPoint
);
99 // Calculate kappa constant for partial curve. The sign of angle in the
100 // tangent will actually ensure this is negative for a counter clockwise
101 // sweep, so changing signs later isn't needed.
102 Float kappaFactor
= (4.0f
/ 3.0f
) * tan((M_PI
/2.0f
) / 4.0f
);
103 Float kappaX
= kappaFactor
* aRadius
.width
;
104 Float kappaY
= kappaFactor
* aRadius
.height
;
105 Float cosStartAngle
= 1;
106 Float sinStartAngle
= 0;
107 for (int i
= 0; i
< 4; i
++) {
108 // We guarantee here the current point is the start point of the next
110 Point
currentStartPoint(aOrigin
.x
+ cosStartAngle
* aRadius
.width
,
111 aOrigin
.y
+ sinStartAngle
* aRadius
.height
);
112 Point
currentEndPoint(aOrigin
.x
+ -sinStartAngle
* aRadius
.width
,
113 aOrigin
.y
+ cosStartAngle
* aRadius
.height
);
115 Point
tangentStart(-sinStartAngle
, cosStartAngle
);
116 Point cp1
= currentStartPoint
;
117 cp1
+= Point(tangentStart
.x
* kappaX
, tangentStart
.y
* kappaY
);
119 Point
revTangentEnd(cosStartAngle
, sinStartAngle
);
120 Point cp2
= currentEndPoint
;
121 cp2
+= Point(revTangentEnd
.x
* kappaX
, revTangentEnd
.y
* kappaY
);
123 aSink
->BezierTo(cp1
, cp2
, currentEndPoint
);
125 // cos(x+pi/2) == -sin(x)
126 // sin(x+pi/2) == cos(x)
127 Float tmp
= cosStartAngle
;
128 cosStartAngle
= -sinStartAngle
;
134 * Appends a path represending a rectangle to the path being built by
137 * aRect The rectangle to append.
138 * aDrawClockwise If set to true, the path will start at the left of the top
139 * left edge and draw clockwise. If set to false the path will
140 * start at the right of the top left edge and draw counter-
143 GFX2D_API
void AppendRectToPath(PathBuilder
* aPathBuilder
,
145 bool aDrawClockwise
= true);
147 inline TemporaryRef
<Path
> MakePathForRect(const DrawTarget
& aDrawTarget
,
149 bool aDrawClockwise
= true)
151 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
152 AppendRectToPath(builder
, aRect
, aDrawClockwise
);
153 return builder
->Finish();
156 struct RectCornerRadii
{
157 Size radii
[RectCorner::Count
];
161 explicit RectCornerRadii(Float radius
) {
162 for (int i
= 0; i
< RectCorner::Count
; i
++) {
163 radii
[i
].SizeTo(radius
, radius
);
167 explicit RectCornerRadii(Float radiusX
, Float radiusY
) {
168 for (int i
= 0; i
< RectCorner::Count
; i
++) {
169 radii
[i
].SizeTo(radiusX
, radiusY
);
173 RectCornerRadii(Float tl
, Float tr
, Float br
, Float bl
) {
174 radii
[RectCorner::TopLeft
].SizeTo(tl
, tl
);
175 radii
[RectCorner::TopRight
].SizeTo(tr
, tr
);
176 radii
[RectCorner::BottomRight
].SizeTo(br
, br
);
177 radii
[RectCorner::BottomLeft
].SizeTo(bl
, bl
);
180 RectCornerRadii(const Size
& tl
, const Size
& tr
,
181 const Size
& br
, const Size
& bl
) {
182 radii
[RectCorner::TopLeft
] = tl
;
183 radii
[RectCorner::TopRight
] = tr
;
184 radii
[RectCorner::BottomRight
] = br
;
185 radii
[RectCorner::BottomLeft
] = bl
;
188 const Size
& operator[](size_t aCorner
) const {
189 return radii
[aCorner
];
192 Size
& operator[](size_t aCorner
) {
193 return radii
[aCorner
];
196 void Scale(Float aXScale
, Float aYScale
) {
197 for (int i
= 0; i
< RectCorner::Count
; i
++) {
198 radii
[i
].Scale(aXScale
, aYScale
);
202 const Size
TopLeft() const { return radii
[RectCorner::TopLeft
]; }
203 Size
& TopLeft() { return radii
[RectCorner::TopLeft
]; }
205 const Size
TopRight() const { return radii
[RectCorner::TopRight
]; }
206 Size
& TopRight() { return radii
[RectCorner::TopRight
]; }
208 const Size
BottomRight() const { return radii
[RectCorner::BottomRight
]; }
209 Size
& BottomRight() { return radii
[RectCorner::BottomRight
]; }
211 const Size
BottomLeft() const { return radii
[RectCorner::BottomLeft
]; }
212 Size
& BottomLeft() { return radii
[RectCorner::BottomLeft
]; }
216 * Appends a path represending a rounded rectangle to the path being built by
219 * aRect The rectangle to append.
220 * aCornerRadii Contains the radii of the top-left, top-right, bottom-right
221 * and bottom-left corners, in that order.
222 * aDrawClockwise If set to true, the path will start at the left of the top
223 * left edge and draw clockwise. If set to false the path will
224 * start at the right of the top left edge and draw counter-
227 GFX2D_API
void AppendRoundedRectToPath(PathBuilder
* aPathBuilder
,
229 const RectCornerRadii
& aRadii
,
230 bool aDrawClockwise
= true);
232 inline TemporaryRef
<Path
> MakePathForRoundedRect(const DrawTarget
& aDrawTarget
,
234 const RectCornerRadii
& aRadii
,
235 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 TemporaryRef
<Path
> MakePathForEllipse(const DrawTarget
& aDrawTarget
,
254 const Point
& aCenter
,
255 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
);
276 * This function paints each edge of aRect separately, snapping the edges using
277 * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
278 * helps ensure not only that the stroke spans a single row of device pixels if
279 * possible, but also that the ends of stroke dashes start and end on device
282 GFX2D_API
void StrokeSnappedEdgesOfRect(const Rect
& aRect
,
283 DrawTarget
& aDrawTarget
,
284 const ColorPattern
& aColor
,
285 const StrokeOptions
& aStrokeOptions
);
287 extern UserDataKey sDisablePixelSnapping
;
290 * If aDrawTarget's transform only contains a translation or, if
291 * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
292 * function will convert aRect to device space and snap it to device pixels.
293 * This function returns true if aRect is modified, otherwise it returns false.
295 * Note that the snapping is such that filling the rect using a DrawTarget
296 * which has the identity matrix as its transform will result in crisp edges.
297 * (That is, aRect will have integer values, aligning its edges between pixel
298 * boundaries.) If on the other hand you stroking the rect with an odd valued
299 * stroke width then the edges of the stroke will be antialiased (assuming an
300 * AntialiasMode that does antialiasing).
302 inline bool UserToDevicePixelSnapped(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
303 bool aAllowScaleOr90DegreeRotate
= false)
305 if (aDrawTarget
.GetUserData(&sDisablePixelSnapping
)) {
309 Matrix mat
= aDrawTarget
.GetTransform();
311 const Float epsilon
= 0.0000001f
;
312 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
313 if (!aAllowScaleOr90DegreeRotate
&&
314 (!WITHIN_E(mat
._11
, 1.f
) || !WITHIN_E(mat
._22
, 1.f
) ||
315 !WITHIN_E(mat
._12
, 0.f
) || !WITHIN_E(mat
._21
, 0.f
))) {
316 // We have non-translation, but only translation is allowed.
321 Point p1
= mat
* aRect
.TopLeft();
322 Point p2
= mat
* aRect
.TopRight();
323 Point p3
= mat
* aRect
.BottomRight();
325 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
326 // two opposite corners define the entire rectangle. So check if
327 // the axis-aligned rectangle with opposite corners p1 and p3
328 // define an axis-aligned rectangle whose other corners are p2 and p4.
329 // We actually only need to check one of p2 and p4, since an affine
330 // transform maps parallelograms to parallelograms.
331 if (p2
== Point(p1
.x
, p3
.y
) || p2
== Point(p3
.x
, p1
.y
)) {
335 aRect
.MoveTo(Point(std::min(p1
.x
, p3
.x
), std::min(p1
.y
, p3
.y
)));
336 aRect
.SizeTo(Size(std::max(p1
.x
, p3
.x
) - aRect
.X(),
337 std::max(p1
.y
, p3
.y
) - aRect
.Y()));
345 * This function has the same behavior as UserToDevicePixelSnapped except that
346 * aRect is not transformed to device space.
348 inline void MaybeSnapToDevicePixels(Rect
& aRect
, const DrawTarget
& aDrawTarget
,
349 bool aIgnoreScale
= false)
351 if (UserToDevicePixelSnapped(aRect
, aDrawTarget
, aIgnoreScale
)) {
352 // Since UserToDevicePixelSnapped returned true we know there is no
353 // rotation/skew in 'mat', so we can just use TransformBounds() here.
354 Matrix mat
= aDrawTarget
.GetTransform();
356 aRect
= mat
.TransformBounds(aRect
);
361 } // namespace mozilla
363 #endif /* MOZILLA_GFX_PATHHELPERS_H_ */