Bumping manifests a=b2g-bump
[gecko.git] / gfx / 2d / PathHelpers.h
blobe198b2f2df084f213ba3a487ccf83eb3ab993534
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_
9 #include "2D.h"
10 #include "mozilla/Constants.h"
11 #include "mozilla/TypedEnum.h"
12 #include "UserData.h"
14 namespace mozilla {
15 namespace gfx {
17 template <typename T>
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
27 // it's vice versa.
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
52 // curve segment.
53 Float currentEndAngle;
55 if (arcSweepLeft > M_PI / 2.0f) {
56 currentEndAngle = Float(currentStartAngle + M_PI / 2.0f * sweepDirection);
57 } else {
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. */
91 template <typename T>
92 void EllipseToBezier(T* aSink, const Point &aOrigin, const Size &aRadius)
94 Point startPoint(aOrigin.x + aRadius.width,
95 aOrigin.y);
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
109 // curve segment.
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;
129 sinStartAngle = tmp;
134 * Appends a path represending a rectangle to the path being built by
135 * aPathBuilder.
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-
141 * clockwise.
143 GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder,
144 const Rect& aRect,
145 bool aDrawClockwise = true);
147 inline TemporaryRef<Path> MakePathForRect(const DrawTarget& aDrawTarget,
148 const Rect& aRect,
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];
159 RectCornerRadii() {}
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
217 * aPathBuilder.
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-
225 * clockwise.
227 GFX2D_API void AppendRoundedRectToPath(PathBuilder* aPathBuilder,
228 const Rect& aRect,
229 const RectCornerRadii& aRadii,
230 bool aDrawClockwise = true);
232 inline TemporaryRef<Path> MakePathForRoundedRect(const DrawTarget& aDrawTarget,
233 const Rect& aRect,
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
244 * aPathBuilder.
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
270 * false.
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
280 * pixels too.
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)) {
306 return false;
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.
317 return false;
319 #undef WITHIN_E
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)) {
332 p1.Round();
333 p3.Round();
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()));
338 return true;
341 return false;
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();
355 mat.Invert();
356 aRect = mat.TransformBounds(aRect);
360 } // namespace gfx
361 } // namespace mozilla
363 #endif /* MOZILLA_GFX_PATHHELPERS_H_ */