1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/2d/PathHelpers.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,167 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "PathHelpers.h" 1.10 + 1.11 +namespace mozilla { 1.12 +namespace gfx { 1.13 + 1.14 +void 1.15 +AppendRoundedRectToPath(PathBuilder* aPathBuilder, 1.16 + const Rect& aRect, 1.17 + // paren's needed due to operator precedence: 1.18 + const Size(& aCornerRadii)[4], 1.19 + bool aDrawClockwise) 1.20 +{ 1.21 + // For CW drawing, this looks like: 1.22 + // 1.23 + // ...******0** 1 C 1.24 + // **** 1.25 + // *** 2 1.26 + // ** 1.27 + // * 1.28 + // * 1.29 + // 3 1.30 + // * 1.31 + // * 1.32 + // 1.33 + // Where 0, 1, 2, 3 are the control points of the Bezier curve for 1.34 + // the corner, and C is the actual corner point. 1.35 + // 1.36 + // At the start of the loop, the current point is assumed to be 1.37 + // the point adjacent to the top left corner on the top 1.38 + // horizontal. Note that corner indices start at the top left and 1.39 + // continue clockwise, whereas in our loop i = 0 refers to the top 1.40 + // right corner. 1.41 + // 1.42 + // When going CCW, the control points are swapped, and the first 1.43 + // corner that's drawn is the top left (along with the top segment). 1.44 + // 1.45 + // There is considerable latitude in how one chooses the four 1.46 + // control points for a Bezier curve approximation to an ellipse. 1.47 + // For the overall path to be continuous and show no corner at the 1.48 + // endpoints of the arc, points 0 and 3 must be at the ends of the 1.49 + // straight segments of the rectangle; points 0, 1, and C must be 1.50 + // collinear; and points 3, 2, and C must also be collinear. This 1.51 + // leaves only two free parameters: the ratio of the line segments 1.52 + // 01 and 0C, and the ratio of the line segments 32 and 3C. See 1.53 + // the following papers for extensive discussion of how to choose 1.54 + // these ratios: 1.55 + // 1.56 + // Dokken, Tor, et al. "Good approximation of circles by 1.57 + // curvature-continuous Bezier curves." Computer-Aided 1.58 + // Geometric Design 7(1990) 33--41. 1.59 + // Goldapp, Michael. "Approximation of circular arcs by cubic 1.60 + // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. 1.61 + // Maisonobe, Luc. "Drawing an elliptical arc using polylines, 1.62 + // quadratic, or cubic Bezier curves." 1.63 + // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf 1.64 + // 1.65 + // We follow the approach in section 2 of Goldapp (least-error, 1.66 + // Hermite-type approximation) and make both ratios equal to 1.67 + // 1.68 + // 2 2 + n - sqrt(2n + 28) 1.69 + // alpha = - * --------------------- 1.70 + // 3 n - 4 1.71 + // 1.72 + // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). 1.73 + // 1.74 + // This is the result of Goldapp's equation (10b) when the angle 1.75 + // swept out by the arc is pi/2, and the parameter "a-bar" is the 1.76 + // expression given immediately below equation (21). 1.77 + // 1.78 + // Using this value, the maximum radial error for a circle, as a 1.79 + // fraction of the radius, is on the order of 0.2 x 10^-3. 1.80 + // Neither Dokken nor Goldapp discusses error for a general 1.81 + // ellipse; Maisonobe does, but his choice of control points 1.82 + // follows different constraints, and Goldapp's expression for 1.83 + // 'alpha' gives much smaller radial error, even for very flat 1.84 + // ellipses, than Maisonobe's equivalent. 1.85 + // 1.86 + // For the various corners and for each axis, the sign of this 1.87 + // constant changes, or it might be 0 -- it's multiplied by the 1.88 + // appropriate multiplier from the list before using. 1.89 + 1.90 + const Float alpha = Float(0.55191497064665766025); 1.91 + 1.92 + typedef struct { Float a, b; } twoFloats; 1.93 + 1.94 + twoFloats cwCornerMults[4] = { { -1, 0 }, // cc == clockwise 1.95 + { 0, -1 }, 1.96 + { +1, 0 }, 1.97 + { 0, +1 } }; 1.98 + twoFloats ccwCornerMults[4] = { { +1, 0 }, // ccw == counter-clockwise 1.99 + { 0, -1 }, 1.100 + { -1, 0 }, 1.101 + { 0, +1 } }; 1.102 + 1.103 + twoFloats *cornerMults = aDrawClockwise ? cwCornerMults : ccwCornerMults; 1.104 + 1.105 + Point cornerCoords[] = { aRect.TopLeft(), aRect.TopRight(), 1.106 + aRect.BottomRight(), aRect.BottomLeft() }; 1.107 + 1.108 + Point pc, p0, p1, p2, p3; 1.109 + 1.110 + // The indexes of the corners: 1.111 + const int kTopLeft = 0, kTopRight = 1; 1.112 + 1.113 + if (aDrawClockwise) { 1.114 + aPathBuilder->MoveTo(Point(aRect.X() + aCornerRadii[kTopLeft].width, 1.115 + aRect.Y())); 1.116 + } else { 1.117 + aPathBuilder->MoveTo(Point(aRect.X() + aRect.Width() - aCornerRadii[kTopRight].width, 1.118 + aRect.Y())); 1.119 + } 1.120 + 1.121 + for (int i = 0; i < 4; ++i) { 1.122 + // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) 1.123 + int c = aDrawClockwise ? ((i+1) % 4) : ((4-i) % 4); 1.124 + 1.125 + // i+2 and i+3 respectively. These are used to index into the corner 1.126 + // multiplier table, and were deduced by calculating out the long form 1.127 + // of each corner and finding a pattern in the signs and values. 1.128 + int i2 = (i+2) % 4; 1.129 + int i3 = (i+3) % 4; 1.130 + 1.131 + pc = cornerCoords[c]; 1.132 + 1.133 + if (aCornerRadii[c].width > 0.0 && aCornerRadii[c].height > 0.0) { 1.134 + p0.x = pc.x + cornerMults[i].a * aCornerRadii[c].width; 1.135 + p0.y = pc.y + cornerMults[i].b * aCornerRadii[c].height; 1.136 + 1.137 + p3.x = pc.x + cornerMults[i3].a * aCornerRadii[c].width; 1.138 + p3.y = pc.y + cornerMults[i3].b * aCornerRadii[c].height; 1.139 + 1.140 + p1.x = p0.x + alpha * cornerMults[i2].a * aCornerRadii[c].width; 1.141 + p1.y = p0.y + alpha * cornerMults[i2].b * aCornerRadii[c].height; 1.142 + 1.143 + p2.x = p3.x - alpha * cornerMults[i3].a * aCornerRadii[c].width; 1.144 + p2.y = p3.y - alpha * cornerMults[i3].b * aCornerRadii[c].height; 1.145 + 1.146 + aPathBuilder->LineTo(p0); 1.147 + aPathBuilder->BezierTo(p1, p2, p3); 1.148 + } else { 1.149 + aPathBuilder->LineTo(pc); 1.150 + } 1.151 + } 1.152 + 1.153 + aPathBuilder->Close(); 1.154 +} 1.155 + 1.156 +void 1.157 +AppendEllipseToPath(PathBuilder* aPathBuilder, 1.158 + const Point& aCenter, 1.159 + const Size& aDimensions) 1.160 +{ 1.161 + Size halfDim = aDimensions / 2.0; 1.162 + Rect rect(aCenter - Point(halfDim.width, halfDim.height), aDimensions); 1.163 + Size radii[] = { halfDim, halfDim, halfDim, halfDim }; 1.164 + 1.165 + AppendRoundedRectToPath(aPathBuilder, rect, radii); 1.166 +} 1.167 + 1.168 +} // namespace gfx 1.169 +} // namespace mozilla 1.170 +