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