/* Copyright (c) 2007 Christopher J. W. Lloyd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import #import #import // ellipse to 4 spline bezier, http://www.tinaja.com/glib/ellipse4.pdf void O2MutablePathEllipseToBezier(O2Point *cp, O2Float x, O2Float y, O2Float xrad, O2Float yrad) { O2Float magic = 0.551784; O2Float xmag = xrad * magic; O2Float ymag = yrad * magic; size_t i = 0; cp[i++] = O2PointMake(-xrad, 0); cp[i++] = O2PointMake(-xrad, -ymag); cp[i++] = O2PointMake(-xmag, -yrad); cp[i++] = O2PointMake(0, -yrad); cp[i++] = O2PointMake(xmag, -yrad); cp[i++] = O2PointMake(xrad, -ymag); cp[i++] = O2PointMake(xrad, 0); cp[i++] = O2PointMake(xrad, ymag); cp[i++] = O2PointMake(xmag, yrad); cp[i++] = O2PointMake(0, yrad); cp[i++] = O2PointMake(-xmag, yrad); cp[i++] = O2PointMake(-xrad, ymag); cp[i++] = O2PointMake(-xrad, 0); for (i = 0; i < 13; i++) { cp[i].x += x; cp[i].y += y; } } @implementation O2MutablePath // Bezier and arc to bezier algorithms from: Windows Graphics Programming by // Feng Yuan #if 0 static void bezier(O2GState *self,double x1,double y1,double x2, double y2,double x3,double y3,double x4,double y4){ // Ax+By+C=0 is the line (x1,y1) (x4,y4); double A=y4-y1; double B=x1-x4; double C=y1*(x4-x1)-x1*(y4-y1); double AB=A*A+B*B; if((A*x2+B*y2+C)*(A*x2+B*y2+C)_numberOfElements = 0; self->_numberOfPoints = 0; } static inline void expandOperatorCapacity(O2MutablePath *self, size_t delta) { if (self->_numberOfElements + delta > self->_capacityOfElements) { self->_capacityOfElements = MAX(1, self->_capacityOfElements); while (self->_numberOfElements + delta > self->_capacityOfElements) self->_capacityOfElements *= 2; self->_elements = NSZoneRealloc(NULL, self->_elements, self->_capacityOfElements); } } static inline void expandPointCapacity(O2MutablePath *self, size_t delta) { if (self->_numberOfPoints + delta > self->_capacityOfPoints) { self->_capacityOfPoints = MAX(1, self->_capacityOfPoints); while (self->_numberOfPoints + delta > self->_capacityOfPoints) self->_capacityOfPoints *= 2; self->_points = NSZoneRealloc( NULL, self->_points, self->_capacityOfPoints * sizeof(O2Point)); } } void O2PathMoveToPoint(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float x, O2Float y) { O2Point point = O2PointMake(x, y); if (matrix != NULL) point = O2PointApplyAffineTransform(point, *matrix); expandOperatorCapacity(self, 1); expandPointCapacity(self, 1); self->_elements[self->_numberOfElements++] = kO2PathElementMoveToPoint; self->_points[self->_numberOfPoints++] = point; } void O2PathAddLineToPoint(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float x, O2Float y) { O2Point point = O2PointMake(x, y); if (matrix != NULL) point = O2PointApplyAffineTransform(point, *matrix); expandOperatorCapacity(self, 1); expandPointCapacity(self, 1); self->_elements[self->_numberOfElements++] = kO2PathElementAddLineToPoint; self->_points[self->_numberOfPoints++] = point; } void O2PathAddCurveToPoint(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float cp1x, O2Float cp1y, O2Float cp2x, O2Float cp2y, O2Float x, O2Float y) { O2Point cp1 = O2PointMake(cp1x, cp1y); O2Point cp2 = O2PointMake(cp2x, cp2y); O2Point endPoint = O2PointMake(x, y); if (matrix != NULL) { cp1 = O2PointApplyAffineTransform(cp1, *matrix); cp2 = O2PointApplyAffineTransform(cp2, *matrix); endPoint = O2PointApplyAffineTransform(endPoint, *matrix); } expandOperatorCapacity(self, 1); expandPointCapacity(self, 3); self->_elements[self->_numberOfElements++] = kO2PathElementAddCurveToPoint; self->_points[self->_numberOfPoints++] = cp1; self->_points[self->_numberOfPoints++] = cp2; self->_points[self->_numberOfPoints++] = endPoint; } void O2PathAddQuadCurveToPoint(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float cpx, O2Float cpy, O2Float x, O2Float y) { O2Point cp1 = O2PointMake(cpx, cpy); O2Point endPoint = O2PointMake(x, y); if (matrix != NULL) { cp1 = O2PointApplyAffineTransform(cp1, *matrix); endPoint = O2PointApplyAffineTransform(endPoint, *matrix); } expandOperatorCapacity(self, 1); expandPointCapacity(self, 2); self->_elements[self->_numberOfElements++] = kO2PathElementAddQuadCurveToPoint; self->_points[self->_numberOfPoints++] = cp1; self->_points[self->_numberOfPoints++] = endPoint; } void O2PathCloseSubpath(O2MutablePathRef self) { expandOperatorCapacity(self, 1); self->_elements[self->_numberOfElements++] = kO2PathElementCloseSubpath; } void O2PathAddLines(O2MutablePathRef self, const O2AffineTransform *matrix, const O2Point *points, size_t count) { size_t i; if (count == 0) return; O2PathMoveToPoint(self, matrix, points[0].x, points[0].y); for (i = 1; i < count; i++) O2PathAddLineToPoint(self, matrix, points[i].x, points[i].y); } void O2PathAddRect(O2MutablePathRef self, const O2AffineTransform *matrix, O2Rect rect) { // The line order is correct per documentation, do not change. O2PathMoveToPoint(self, matrix, O2RectGetMinX(rect), O2RectGetMinY(rect)); O2PathAddLineToPoint(self, matrix, O2RectGetMaxX(rect), O2RectGetMinY(rect)); O2PathAddLineToPoint(self, matrix, O2RectGetMaxX(rect), O2RectGetMaxY(rect)); O2PathAddLineToPoint(self, matrix, O2RectGetMinX(rect), O2RectGetMaxY(rect)); O2PathCloseSubpath(self); } void O2PathAddRects(O2MutablePathRef self, const O2AffineTransform *matrix, const O2Rect *rects, size_t count) { size_t i; for (i = 0; i < count; i++) O2PathAddRect(self, matrix, rects[i]); } void O2PathAddArc(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float x, O2Float y, O2Float radius, O2Float startRadian, O2Float endRadian, BOOL clockwise) { startRadian = fmod(startRadian, M_PI * 2); endRadian = fmod(endRadian, M_PI * 2); if (clockwise) { O2Float tmp = startRadian; startRadian = endRadian; endRadian = tmp; } if (endRadian < startRadian) { endRadian += M_PI * 2; } O2Float radiusx = radius, radiusy = radius; double remainder = ABS(endRadian - startRadian); double delta = M_PI_2; // 90 degrees size_t i; O2Point points[4 * ((int) ceil(remainder / delta) + 1)]; size_t pointsIndex = 0; for (; remainder > 0; startRadian += delta, remainder -= delta) { double sweepangle = (remainder > delta) ? delta : remainder; double XY[8]; double B = sin(sweepangle / 2); double C = cos(sweepangle / 2); double A = 1 - C; double X = A * 4 / 3; double Y = B - X * (1 - A) / B; double s = sin(startRadian + sweepangle / 2); double c = cos(startRadian + sweepangle / 2); XY[0] = C; XY[1] = -B; XY[2] = C + X; XY[3] = -Y; XY[4] = C + X; XY[5] = Y; XY[6] = C; XY[7] = B; for (i = 0; i < 4; i++) { points[pointsIndex].x = x + (XY[i * 2] * c - XY[i * 2 + 1] * s) * radiusx; points[pointsIndex].y = y + (XY[i * 2] * s + XY[i * 2 + 1] * c) * radiusy; pointsIndex++; } } if (clockwise) { // just reverse points for (i = 0; i < pointsIndex / 2; i++) { O2Point tmp; tmp = points[i]; points[i] = points[(pointsIndex - 1) - i]; points[(pointsIndex - 1) - i] = tmp; } } if (pointsIndex > 0) { if (O2PathIsEmpty(self)) { O2PathMoveToPoint(self, matrix, points[0].x, points[0].y); } else { O2PathAddLineToPoint(self, matrix, points[0].x, points[0].y); } } for (i = 0; i < pointsIndex; i += 4) { O2PathAddCurveToPoint(self, matrix, points[i + 1].x, points[i + 1].y, points[i + 2].x, points[i + 2].y, points[i + 3].x, points[i + 3].y); } } void O2PathAddArcToPoint(O2MutablePathRef self, const O2AffineTransform *matrix, O2Float tx1, O2Float ty1, O2Float tx2, O2Float ty2, O2Float radius) { #if 0 // ignores arc and draws a sharp corner O2PathAddLineToPoint(self,matrix,tx1,ty1); #else if (self->_numberOfPoints == 0) return; O2Point start = self->_points[self->_numberOfPoints - 1]; O2Point mid = O2PointMake(tx1, ty1); O2Point end = O2PointMake(tx2, ty2); if (matrix != NULL) { // If a matrix is specified, either the start point needs to be // transformed, or the arc needs to be transformed. Since we really want // the result curve transformed to get non-uniform scale/skew results // to come out correctly, we just back-transform the start point. But // the assumption here is that the start point was transformed using the // same matrix as is being passed in, otherwise we have to track the // last point and the matrix that goes with it which doesn't seem tidy. // Further testing required for this case. start = O2PointApplyAffineTransform(start, O2AffineTransformInvert(*matrix)); } double x1 = start.x - mid.x; double y1 = start.y - mid.y; double x2 = end.x - mid.x; double y2 = end.y - mid.y; double n1 = sqrt(x1 * x1 + y1 * y1); double n2 = sqrt(x2 * x2 + y2 * y2); O2Float startAngle = acos(x1 / n1); if (y1 < 0) startAngle = M_PI * 2 - startAngle; O2Float endAngle = acos(x2 / n2); if (y2 < 0) endAngle = M_PI * 2 - endAngle; O2Float angleBetweenLines = (endAngle - startAngle); int clockwise = 1; O2Float clockwiseRotate = 0; if (angleBetweenLines < 0) angleBetweenLines = M_PI * 2 + angleBetweenLines; CGFloat arcAngle = M_PI - angleBetweenLines; if (angleBetweenLines > M_PI) { arcAngle = M_PI * 2 - angleBetweenLines; clockwise = 0; clockwiseRotate = M_PI; } /* The triangle formed by the center of the arc, the midpoint and where the radius and tangent line meet Using the law of sines we can figure the other two sides of the triangle using the radius and the angles. The radius meets the tangent at 90 degrees. The tangent meets the mid->center line at half the angle between the tangents The radius meets the mid->center line at 180 - the other angles */ CGFloat angleOfRadiusAndTangent = M_PI / 2; CGFloat angleOfTangentAndLineToCenter = angleBetweenLines / 2.0; CGFloat midToCenterLength = (radius * sin(angleOfRadiusAndTangent)) / sin(angleOfTangentAndLineToCenter); CGPoint center; center.x = cos(startAngle + angleBetweenLines / 2 + clockwiseRotate) * midToCenterLength; center.y = sin(startAngle + angleBetweenLines / 2 + clockwiseRotate) * midToCenterLength; center.x += mid.x; center.y += mid.y; CGFloat arcStartAngle; CGFloat arcEndAngle; if (clockwise) { arcStartAngle = (M_PI * 2 - (M_PI / 2 - startAngle)); arcEndAngle = (arcStartAngle - arcAngle); } else { arcStartAngle = (M_PI * 2 - (M_PI / 2 - startAngle)) + clockwiseRotate; arcEndAngle = (arcStartAngle - arcAngle) + clockwiseRotate; } O2PathAddArc(self, matrix, center.x, center.y, radius, arcStartAngle, arcEndAngle, clockwise); #endif } void O2PathAddEllipseInRect(O2MutablePathRef self, const O2AffineTransform *matrix, O2Rect rect) { O2Float xradius = rect.size.width / 2; O2Float yradius = rect.size.height / 2; O2Float x = rect.origin.x + xradius; O2Float y = rect.origin.y + yradius; O2Point cp[13]; size_t i; O2MutablePathEllipseToBezier(cp, x, y, xradius, yradius); O2PathMoveToPoint(self, matrix, cp[0].x, cp[0].y); for (i = 1; i < 13; i += 3) O2PathAddCurveToPoint(self, matrix, cp[i].x, cp[i].y, cp[i + 1].x, cp[i + 1].y, cp[i + 2].x, cp[i + 2].y); O2PathCloseSubpath(self); } void O2PathAddPath(O2MutablePathRef self, const O2AffineTransform *matrix, O2PathRef path) { size_t opsCount = O2PathNumberOfElements(path); const unsigned char *ops = O2PathElements(path); size_t pointCount = O2PathNumberOfPoints(path); const O2Point *points = O2PathPoints(path); size_t i; expandOperatorCapacity(self, opsCount); expandPointCapacity(self, pointCount); for (i = 0; i < opsCount; i++) self->_elements[self->_numberOfElements++] = ops[i]; if (matrix == NULL) { for (i = 0; i < pointCount; i++) self->_points[self->_numberOfPoints++] = points[i]; } else { for (i = 0; i < pointCount; i++) self->_points[self->_numberOfPoints++] = O2PointApplyAffineTransform(points[i], *matrix); } } void O2PathApplyTransform(O2MutablePathRef self, const O2AffineTransform matrix) { size_t i; for (i = 0; i < self->_numberOfPoints; i++) self->_points[i] = O2PointApplyAffineTransform(self->_points[i], matrix); } @end