-
-
Save KyleLeneau/4268942 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CGPathBevel.h | |
// | |
// Created by Aaron Hayman on 6/21/12. | |
// Copyright (c) 2012 FlexileSoft, LLC. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
void bevelPath(CGPathRef path, CGContextRef context, CGFloat bevelDepth, CGColorRef highlight, CGColorRef shadow, BOOL eofFill); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CGPathBevel.m | |
// | |
// | |
// Created by Aaron Hayman on 6/21/12. | |
// Copyright (c) 2012 FlexileSoft, LLC. All rights reserved. | |
// | |
#import "CGPathBevel.h" | |
#import <QuartzCore/QuartzCore.h> | |
#define lowerTheta 2.35619449019f | |
#define upperTheta 5.49778714378f | |
#define ThetaTolerance .01f | |
#define BevelGradSpread .2f | |
typedef struct{ | |
CGFloat a; | |
CGFloat b; | |
CGFloat c; | |
} LineDef; | |
typedef struct{ | |
CGPoint point; | |
CGPoint ctrlPoint1; | |
CGPoint ctrlPoint2; | |
CGPathElementType type; | |
CGPoint bevPoint; | |
CGFloat incidentTheta; | |
CGFloat bisectingTheta; | |
CGFloat focalTheta; | |
} PathElement; | |
typedef struct{ | |
PathElement **elements; | |
NSUInteger count; | |
CGPoint lastMove; | |
CGPoint lastPoint; | |
NSUInteger size; | |
} PathElements; | |
typedef struct{ | |
void **objects; | |
NSUInteger count; | |
} PointerArray; | |
typedef struct{ | |
CGPoint *points; | |
NSUInteger count; | |
} PointArray; | |
CGColorSpaceRef defaultColorSpace(void); | |
CGFloat PointTheta(CGPoint); | |
PathElement * NewPathElement(CGPathElementType type); | |
PathElements * NewPathElementArray(void); | |
PointerArray * NewPointerArray(void); | |
void AddToArray(PointerArray *array, void * object); | |
void AddPathElement(PathElement *element, PathElements *elements); | |
void GetPathElements(void *info, const CGPathElement *element); | |
void FindBoundingRect (void *info, const CGPathElement *element); | |
void SetBisectorsAndFocalPointsForPathElements(PathElements *elements, CGPathRef path, BOOL eofFill); | |
void BevelSubpath(PathElements *subPathElements, CGContextRef context, CGFloat bevelSize, CGColorRef highlight, CGColorRef shadow); | |
CGColorSpaceRef defaultColorSpace(){ | |
static CGColorSpaceRef space = NULL; | |
if (space == NULL){ | |
space = CGColorSpaceCreateDeviceRGB(); | |
} | |
return space; | |
} | |
CGFloat PointTheta(CGPoint point){ | |
//This assumes an origin of {0, 0} and returns a theta for the given point | |
CGFloat theta = atanf(point.y / point.x); | |
if (point.x == 0.0f) theta = (point.y >= 0.0f) ? M_PI_2 : M_PI + M_PI_2; | |
else if (point.x < 0.0f) theta += M_PI; | |
else if (point.x > 0.0f && point.y < 0.0f) theta += (M_PI * 2); | |
return theta; | |
} | |
PathElement * NewPathElement(CGPathElementType type){ | |
PathElement *pathElement = malloc(sizeof(PathElement)); | |
pathElement->type = type; | |
pathElement->point = pathElement->bevPoint = pathElement->ctrlPoint1 = pathElement->ctrlPoint2 = CGPointZero; | |
pathElement->incidentTheta = pathElement->bisectingTheta = pathElement->focalTheta = CGFLOAT_MAX; | |
return pathElement; | |
} | |
PathElements * NewPathElementArray(){ | |
PathElements *elements = malloc(sizeof(PathElements)); | |
elements->elements = NULL; | |
elements->count = elements->size = 0; | |
elements->lastMove = elements->lastPoint = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX); | |
return elements; | |
} | |
PointerArray * NewPointerArray(){ | |
PointerArray *array = malloc(sizeof(PointerArray)); | |
array->count = 0; | |
array->objects = NULL; | |
return array; | |
} | |
void AddToArray(PointerArray *array, void *object){ | |
array->count++; | |
array->objects = realloc(array->objects, array->count * sizeof(void *)); | |
array->objects[array->count - 1] = object; | |
} | |
void AddPathElement(PathElement *element, PathElements *elements){ | |
elements->count++; | |
if (elements->count > elements->size){ | |
elements->elements = realloc(elements->elements, elements->count * sizeof(PathElement *)); | |
elements->size = elements->count; | |
} | |
elements->elements[elements->count - 1] = element; | |
} | |
void GetPathElements(void *info, const CGPathElement *element){ | |
PathElements *elements = (PathElements *)info; | |
CGPoint *points = element->points; | |
PathElement *newElement = NewPathElement(element->type); | |
BOOL (^CGPointsEqual)(CGPoint, CGPoint) = ^BOOL(CGPoint p1, CGPoint p2){ | |
//Using a tolerance to determine point equality. Some default CG shapes include supurflous path elements that I don't care to replicate | |
CGFloat tolerance = 100; | |
NSUInteger p1X = p1.x * tolerance; | |
NSUInteger p1Y = p1.y * tolerance; | |
NSUInteger p2X = p2.x * tolerance; | |
NSUInteger p2Y = p2.y * tolerance; | |
return (p1X > p2X - tolerance && p1X < p2X + tolerance && p1Y > p2Y - tolerance && p1Y < p2Y + tolerance); | |
}; | |
switch (newElement->type) { | |
case kCGPathElementMoveToPoint: | |
newElement->point = points[0]; | |
elements->lastMove = points[0]; | |
break; | |
case kCGPathElementAddLineToPoint: | |
newElement->point = points[0]; | |
break; | |
case kCGPathElementAddCurveToPoint: | |
newElement->point = points[2]; | |
newElement->ctrlPoint1 = points[0]; | |
newElement->ctrlPoint2 = points[1]; | |
break; | |
case kCGPathElementAddQuadCurveToPoint: | |
newElement->point = points[1]; | |
newElement->ctrlPoint1 = points[0]; | |
break; | |
case kCGPathElementCloseSubpath: | |
newElement->point = elements->lastMove; | |
break; | |
} | |
//Remove superfluous elements | |
if (elements->count > 0 && CGPointsEqual(newElement->point, elements->lastPoint)){ | |
free(newElement); | |
return; | |
} | |
//If a path doesn't begin with moveToPoint, it begins at {0,0}, so we create a 'moveToPoint' element representing this | |
if (elements->count == 0 && element->type != kCGPathElementMoveToPoint){ | |
PathElement *firstElement = NewPathElement(kCGPathElementMoveToPoint); | |
firstElement->point = CGPointZero; | |
elements->lastMove = CGPointZero; | |
AddPathElement(firstElement, elements); | |
AddPathElement(newElement, elements); | |
elements->lastPoint = newElement->point; | |
} else { | |
elements->lastPoint = newElement->point; | |
AddPathElement(newElement, elements); | |
} | |
} | |
void SetBisectorsAndFocalPointsForPathElements(PathElements *elements, CGPathRef path, BOOL eofFill){ | |
if (elements->count < 3) return; | |
PointArray *focalPoints = malloc(sizeof(PointArray)); | |
focalPoints->count = 0; | |
focalPoints->points = NULL; | |
LineDef (^LineDefForPoints)(CGPoint, CGPoint) = ^LineDef(CGPoint p1, CGPoint p2){ | |
LineDef line = {0,0,0}; | |
line.a = p2.y - p1.y; | |
line.b = p1.x - p2.x; | |
line.c = line.a*p1.x + line.b*p1.y; | |
return line; | |
}; | |
CGFloat (^CalculateTheta) (CGPoint, CGPoint, CGPoint, CGFloat *) = ^CGFloat (CGPoint endPoint1, CGPoint centerPoint, CGPoint endPoint2, CGFloat *biTheta){ | |
//normalize end points | |
endPoint1 = CGPointMake(endPoint1.x - centerPoint.x, endPoint1.y - centerPoint.y); | |
endPoint2 = CGPointMake(endPoint2.x - centerPoint.x, endPoint2.y - centerPoint.y); | |
//grab our line thetas | |
CGFloat theta1 = PointTheta(endPoint1); | |
CGFloat theta2 = PointTheta(endPoint2); | |
//grab bisecting angle | |
*biTheta = (fmaxf(theta1, theta2) - fminf(theta1, theta2)) / 2 + fminf(theta1, theta2); | |
//Determine if angle is pointing into the shape, it not, rotate is 180° | |
if (!CGPathContainsPoint(path, NULL, CGPointMake(centerPoint.x - cosf(*biTheta), centerPoint.y - sinf(*biTheta)), eofFill)) | |
*biTheta += (*biTheta < M_PI) ? M_PI : -M_PI; | |
//take the least difference in thetas for the angle of incidence | |
theta1 = (fmaxf(theta1, theta2) - fminf(theta1, theta2)); | |
if (theta1 > M_PI) theta1 = fabsf((M_PI * 2) - theta1); | |
return theta1; | |
}; | |
LineDef (^CalculateBisector)(CGPoint, CGFloat) = ^(CGPoint centerPoint, CGFloat bisectingTheta){ | |
//Calculate a another point on the line | |
CGPoint biPoint = CGPointMake(cosf(bisectingTheta) * 10, sinf(bisectingTheta) * 10); | |
//de-normalize biPoint | |
biPoint.x += centerPoint.x; | |
biPoint.y += centerPoint.y; | |
return LineDefForPoints(biPoint, centerPoint); | |
}; | |
//Takes two LineDef's and returns the intersection of the lines | |
CGPoint (^CalculateFocalPoint)(LineDef, LineDef) = ^(LineDef l1, LineDef l2){ | |
//m is essentially the diff in line slopes | |
CGFloat m = (l1.a * l2.b) - (l2.a * l1.b); | |
if (m == 0) | |
return CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX); | |
else { | |
CGPoint rPoint = CGPointMake(((l2.b * l1.c) - (l1.b * l2.c)) / m, ((l1.a * l2.c) - (l2.a * l1.c)) / m); | |
//Round to the nearest tenth. | |
rPoint.x *= 10; | |
rPoint.y *= 10; | |
rPoint.x = roundf(rPoint.x); | |
rPoint.y = roundf(rPoint.y); | |
rPoint.x /= 10; | |
rPoint.y /= 10; | |
return rPoint; | |
} | |
}; | |
BOOL (^CGPointsEqual)(CGPoint, CGPoint) = ^BOOL(CGPoint p1, CGPoint p2){ | |
CGFloat tolerance = 100; | |
NSUInteger p1X = p1.x * tolerance; | |
NSUInteger p1Y = p1.y * tolerance; | |
NSUInteger p2X = p2.x * tolerance; | |
NSUInteger p2Y = p2.y * tolerance; | |
return (p1X > p2X - tolerance && p1X < p2X + tolerance && p1Y > p2Y - tolerance && p1Y < p2Y + tolerance); | |
}; | |
void (^AddFocalPoint)(CGPoint) = ^(CGPoint focalPoint){ | |
if (focalPoint.x == CGFLOAT_MAX) return; | |
if (!CGPathContainsPoint(path, NULL, focalPoint, eofFill)) return; | |
focalPoints->count++; | |
focalPoints->points = realloc(focalPoints->points, focalPoints->count * sizeof(CGPoint)); | |
focalPoints->points[focalPoints->count - 1] = focalPoint; | |
}; | |
PathElement *cElement = nil; | |
PathElement *pElement = nil; | |
PathElement *p2Element = nil; | |
PathElement *poppedElement = nil; | |
LineDef prevBisector = {CGFLOAT_MAX, CGFLOAT_MAX, CGFLOAT_MAX}; | |
LineDef curBisector = prevBisector; | |
LineDef firstBisector = prevBisector; | |
PathElements *updateQueue = NewPathElementArray(); | |
CGPoint cPoint, pPoint, p2Point; | |
cPoint = pPoint = p2Point = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX); | |
CGFloat theta = CGFLOAT_MAX; | |
int totalCount, escapeCount, cIndex; | |
cIndex = totalCount = 0; | |
escapeCount = elements->count * 2; | |
while (theta == CGFLOAT_MAX) { | |
totalCount++; | |
if (pElement) | |
p2Element = pElement; | |
if (cElement) | |
pElement = cElement; | |
cElement = elements->elements[cIndex]; | |
theta = cElement->bisectingTheta; | |
if (cElement && pElement && p2Element){ | |
//While cPoint == cElement.point, there is no path progression, queue all elements at the same point to update when we do move | |
if (!CGPointsEqual(cElement->point, cPoint)){ | |
cPoint = cElement->point; | |
if (updateQueue->count < 1){ | |
pPoint = pElement->point; | |
p2Point = p2Element->point; | |
} | |
pElement->incidentTheta = CalculateTheta(p2Point, pPoint, cPoint, &(pElement->bisectingTheta)); | |
//If there are elements in queue, update them with the current theta | |
for (int i = 0; i < updateQueue->count; i++){ | |
poppedElement = updateQueue->elements[i]; | |
poppedElement->incidentTheta = pElement->incidentTheta; | |
poppedElement->bisectingTheta = pElement->bisectingTheta; | |
} | |
updateQueue->count = 0; | |
//Check previous element and calculate bisector/focalpoint | |
curBisector = CalculateBisector(pPoint, pElement->bisectingTheta); | |
if (prevBisector.a != CGFLOAT_MAX) | |
AddFocalPoint(CalculateFocalPoint(prevBisector, curBisector)); | |
prevBisector = curBisector; | |
if (firstBisector.a == CGFLOAT_MAX) firstBisector = curBisector; | |
} else { | |
//On the first queued item, we need to progress the point cache so that it's accurate | |
if (theta == CGFLOAT_MAX){ | |
if (updateQueue->count < 1){ | |
p2Point = pPoint; | |
pPoint = cPoint; | |
} | |
AddPathElement(pElement, updateQueue); | |
} else { | |
for (int i = 0; i < updateQueue->count; i++){ | |
poppedElement = updateQueue->elements[i]; | |
poppedElement->incidentTheta = pElement->incidentTheta; | |
poppedElement->bisectingTheta = pElement->bisectingTheta; | |
} | |
} | |
} | |
} | |
cIndex++; | |
if (cIndex >= elements->count) | |
cIndex = 0; | |
//If the path is too small (points too close together) this loop could run forever | |
if (totalCount > escapeCount) | |
break; | |
} | |
//The above will miss checking the intersection of the first and last bisectors | |
AddFocalPoint(CalculateFocalPoint(curBisector, firstBisector)); | |
CGFloat (^ThetaToNearestFocalPoint)(CGPoint, PointArray *) = ^CGFloat (CGPoint refPoint, PointArray *focalPoints){ | |
CGFloat hyp = CGFLOAT_MAX; | |
CGPoint focalPoint = CGPointZero; | |
CGPoint cPoint; | |
CGFloat cDist; | |
//Find which focalPoint is closest | |
for (int i = 0; i < focalPoints->count; i++){ | |
cPoint = focalPoints->points[i]; | |
cDist = hypotf(cPoint.x - refPoint.x, cPoint.y - refPoint.y); | |
if (cDist < hyp){ | |
focalPoint = cPoint; | |
hyp = cDist; | |
} | |
} | |
//Normalize the reference point and return theta | |
CGPoint stdPoint = CGPointMake(refPoint.x - focalPoint.x, refPoint.y - focalPoint.y); | |
return PointTheta(stdPoint); | |
}; | |
for (int i = 0; i < elements->count; i++){ | |
cElement = elements->elements[i]; | |
cElement->focalTheta = ThetaToNearestFocalPoint(cElement->point, focalPoints); | |
} | |
free(updateQueue->elements); | |
free(updateQueue); | |
free(focalPoints->points); | |
free(focalPoints); | |
} | |
void BevelSubpath(PathElements *subPathElements, CGContextRef context, CGFloat bevelSize, CGColorRef highlight, CGColorRef shadow){ | |
CGPoint (^ScaledCtrlPoint)(CGPoint, CGPoint, CGPoint, CGPoint, CGPoint) = ^CGPoint (CGPoint refPoint1, CGPoint refPoint2, CGPoint bevPoint1, CGPoint bevPoint2, CGPoint ctrlPoint){ | |
CGPoint translation = CGPointMake(bevPoint1.x - refPoint1.x, bevPoint1.y - refPoint1.y); | |
//Normalize points to refPoint1 | |
refPoint2.x -= refPoint1.x; refPoint2.y -= refPoint1.y; | |
ctrlPoint.x -= refPoint1.x; ctrlPoint.y -= refPoint1.y; | |
//Normalize bevPoints to bevPoint1 | |
bevPoint2.x -= bevPoint1.x; bevPoint2.y -= bevPoint1.y; | |
//Get the distances between reference points and bevelled points and calculate the scale | |
CGFloat theta = PointTheta(refPoint2); | |
CGFloat refHyp = (refPoint2.y != 0.0f) ? refPoint2.y / sinf(theta) : refPoint2.x / cosf(theta); | |
theta = PointTheta(bevPoint2); | |
CGFloat bevHyp = (bevPoint2.y != 0.0f) ? bevPoint2.y / sinf(theta) : bevPoint2.x / cosf(theta); | |
CGFloat scale = (bevHyp / refHyp); | |
CGAffineTransform txfm = CGAffineTransformMakeScale(scale, scale); | |
txfm = CGAffineTransformTranslate(txfm, translation.x, translation.y); | |
ctrlPoint = CGPointApplyAffineTransform(ctrlPoint, txfm); | |
ctrlPoint.x += refPoint1.x; | |
ctrlPoint.y += refPoint1.y; | |
return ctrlPoint; | |
}; | |
//This will calculate the hypotenuse size based on the incident angle, and then generate the bevelled point | |
CGPoint (^BevelPoint)(CGPoint, CGFloat, CGFloat) = ^CGPoint (CGPoint refPoint, CGFloat bisectingTheta, CGFloat incidentTheta){ | |
CGFloat hypBevel = bevelSize / sinf(incidentTheta / 2); | |
return CGPointMake((refPoint.x - (cosf(bisectingTheta) * hypBevel)), (refPoint.y - (sinf(bisectingTheta) * hypBevel))); | |
}; | |
void (^CalculateOffsets)(CGPoint *fromOffset, CGPoint *toOffset, CGPoint from, CGPoint to) = ^(CGPoint *fromOffset, CGPoint *toOffset, CGPoint from, CGPoint to){ | |
//This calculates an offset to remove seams that occur when two shapes are diagonally butted against each other | |
//This isn't perfect, and on larger bevel depths with semi-transparent fills a seam may still occur due to the overlap | |
CGFloat offset = .015f; | |
if (to.x < from.x){ | |
toOffset->x = -offset; | |
fromOffset->x = offset; | |
} | |
if (to.x > from.x){ | |
toOffset->x = offset; | |
fromOffset->x = -offset; | |
} | |
if (to.y < from.y){ | |
toOffset->y = -offset; | |
fromOffset->y = offset; | |
} | |
if (to.y > from.y){ | |
toOffset->y = offset; | |
fromOffset->y = -offset; | |
} | |
}; | |
void (^FillPath)(CGPathRef, CGPoint, CGFloat, CGPoint, CGFloat) = ^(CGPathRef fillPath, CGPoint point1, CGFloat theta1, CGPoint point2, CGFloat theta2){ | |
//I hate this algorithm | |
BOOL point1Highlight = NO; | |
BOOL point2Highlight = NO; | |
//Determine where point1 lies: | |
if (theta1 < upperTheta && theta1 > lowerTheta) | |
point1Highlight = YES; | |
if (theta2 < upperTheta && theta2 > lowerTheta) | |
point2Highlight = YES; | |
//Check if a theta is within tolerance, if it is, set it to the other theta value (prevents edge cases) | |
if ((theta1 > upperTheta - ThetaTolerance && theta1 < upperTheta + ThetaTolerance) || (theta1 > lowerTheta - ThetaTolerance && theta1 < lowerTheta + ThetaTolerance)) | |
point1Highlight = point2Highlight; | |
if ((theta2 > upperTheta - ThetaTolerance && theta2 < upperTheta + ThetaTolerance) || (theta2 > lowerTheta - ThetaTolerance && theta2 < lowerTheta + ThetaTolerance)) | |
point2Highlight = point1Highlight; | |
//Calculate the exactly where the transition boundary occurs on the shape | |
//Yes, this was a PITA to figure out and write | |
CGContextSaveGState(context); | |
CGContextAddPath(context, fillPath); | |
if (point1Highlight != point2Highlight){ | |
CGContextClip(context); | |
CGFloat boundary = .5f; | |
if (point1Highlight){ | |
if (theta1 < theta2){ | |
boundary = (theta2 - upperTheta) / (theta2 - theta1); | |
} else { | |
if (theta1 - theta2 > M_PI){ | |
CGFloat shift = M_PI * 2 - theta1; | |
theta2 += shift; | |
theta1 = upperTheta - theta1; | |
boundary = theta1 / theta2; | |
} else { | |
boundary = (theta1 - lowerTheta) / (theta1 - theta2); | |
} | |
} | |
} else { | |
if (theta2 < theta1){ | |
boundary = (theta1 - upperTheta) / (theta1 - theta2); | |
} else { | |
if (theta2 - theta1 > M_PI){ | |
CGFloat shift = M_PI * 2 - theta2; | |
theta1 += shift; | |
theta2 = upperTheta - theta2; | |
boundary = 1 - (theta2 / theta1); | |
} else { | |
boundary = (theta2 - lowerTheta) / (theta2 - theta1); | |
} | |
} | |
} | |
//Use Boundary to figure out where the gradient starts/ends | |
CGFloat gStart = fmaxf(0.0f, boundary - BevelGradSpread); | |
CGFloat gEnd = fminf(1.0f, boundary + BevelGradSpread); | |
//Draw the gradient | |
CGFloat locations[4] = {0.0f, gStart, gEnd, 1.0f}; | |
CGColorRef p1Color = (point1Highlight) ? highlight : shadow; | |
CGColorRef p2Color = (point2Highlight) ? highlight : shadow; | |
CGColorRef colorRefs[4] = {p1Color, p1Color, p2Color, p2Color}; | |
CFArrayRef colors = CFArrayCreate(NULL, (const void**)colorRefs, 4, &kCFTypeArrayCallBacks); | |
CGGradientRef grad = CGGradientCreateWithColors(defaultColorSpace(), colors, locations); | |
CGContextDrawLinearGradient(context, grad, point1, point2, kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation); | |
CGGradientRelease(grad); | |
CFRelease(colors); | |
} else { | |
CGContextSetFillColorWithColor(context, (point1Highlight) ? highlight : shadow); | |
CGContextFillPath(context); | |
} | |
CGContextRestoreGState(context); | |
}; | |
PathElement *prevElement, *originalElement, *pathElement; | |
prevElement = originalElement = pathElement = subPathElements->elements[0]; | |
CGPoint curPoint, bevPoint, curCtrlPoint, curBevCtrlPoint, curCtrlPoint2, curBevCtrlPoint2, prevPoint, prevBevPoint, fOff, tOff; | |
CGMutablePathRef path; | |
for (int i = 0; i < subPathElements->count; i++){ | |
pathElement = subPathElements->elements[i]; | |
switch (pathElement->type) { | |
case kCGPathElementMoveToPoint:{ | |
pathElement->bevPoint = BevelPoint(pathElement->point, pathElement->bisectingTheta, pathElement->incidentTheta); | |
originalElement = pathElement; | |
break; | |
} | |
case kCGPathElementAddLineToPoint: | |
//calculate the bevel | |
curPoint = pathElement->point; | |
prevPoint = prevElement->point; | |
prevBevPoint = prevElement->bevPoint; | |
pathElement->bevPoint = bevPoint = BevelPoint(pathElement->point, pathElement->bisectingTheta, pathElement->incidentTheta); | |
//determine offset to Cover seams | |
fOff = CGPointZero; | |
tOff = CGPointZero; | |
CalculateOffsets(&fOff, &tOff, prevElement->point, pathElement->point); | |
//create path for bevelled side | |
path = CGPathCreateMutable(); | |
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y); | |
CGPathAddLineToPoint(path, NULL, prevBevPoint.x + fOff.x, prevBevPoint.y + fOff.y); | |
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y); | |
CGPathAddLineToPoint(path, NULL, curPoint.x + tOff.x, curPoint.y + tOff.y); | |
CGPathCloseSubpath(path); | |
//compute mid hypotenuse and use to determine fill/gradient color | |
FillPath(path, curPoint, pathElement->focalTheta, prevPoint, prevElement->focalTheta); | |
CGPathRelease(path); | |
break; | |
case kCGPathElementAddCurveToPoint:{ | |
//get prev vars | |
prevPoint = prevElement->point; | |
prevBevPoint = prevElement->bevPoint; | |
//calculate the bevel | |
curPoint = pathElement->point; | |
pathElement->bevPoint = bevPoint = BevelPoint(curPoint, pathElement->bisectingTheta, pathElement->incidentTheta); | |
//scale control points | |
curCtrlPoint = pathElement->ctrlPoint1; | |
curCtrlPoint2 = pathElement->ctrlPoint2; | |
//Note: ctrlPoints are reversed, cause we're going in the other direction | |
curBevCtrlPoint = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint2); | |
curBevCtrlPoint2 = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint); | |
//create the path | |
fOff = CGPointZero; | |
tOff = CGPointZero; | |
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint); | |
path = CGPathCreateMutable(); | |
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y); | |
CGPathAddCurveToPoint(path, NULL, curCtrlPoint.x, curCtrlPoint.y, curCtrlPoint2.x, curCtrlPoint2.y, curPoint.x + tOff.x, curPoint.y + tOff.y); | |
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y); | |
CGPathAddCurveToPoint(path, NULL, curBevCtrlPoint.x + fOff.x, curBevCtrlPoint.y + fOff.y, curBevCtrlPoint2.x + tOff.x, curBevCtrlPoint2.y + tOff.y, prevBevPoint.x +fOff.x, prevBevPoint.y + fOff.y); | |
CGPathCloseSubpath(path); | |
//fill the path | |
FillPath(path, curPoint, pathElement->focalTheta, prevPoint, prevElement->focalTheta); | |
CGPathRelease(path); | |
break; | |
} | |
case kCGPathElementAddQuadCurveToPoint: | |
//get prev vars | |
prevPoint = prevElement->point; | |
prevBevPoint = prevElement->bevPoint; | |
//calculate the bevel | |
curPoint = pathElement->point; | |
pathElement->bevPoint = bevPoint = BevelPoint(curPoint, pathElement->bisectingTheta, pathElement->incidentTheta); | |
//scale control point | |
curCtrlPoint = pathElement->ctrlPoint1; | |
curBevCtrlPoint = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint); | |
//create path | |
fOff = CGPointZero; | |
tOff = CGPointZero; | |
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint); | |
path = CGPathCreateMutable(); | |
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y); | |
CGPathAddQuadCurveToPoint(path, NULL, curCtrlPoint.x, curCtrlPoint.y, curPoint.x + tOff.x, curPoint.y + tOff.y); | |
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y); | |
CGPathAddQuadCurveToPoint(path, NULL, curBevCtrlPoint.x, curBevCtrlPoint.y, prevBevPoint.x +fOff.x, prevBevPoint.y + fOff.y); | |
CGPathCloseSubpath(path); | |
//fill the path | |
FillPath(path, curPoint, pathElement->focalTheta, prevPoint, prevElement->focalTheta); | |
CGPathRelease(path); | |
break; | |
case kCGPathElementCloseSubpath: | |
//Get theta | |
curPoint = originalElement->point; | |
bevPoint = originalElement->bevPoint; | |
prevPoint = prevElement->point; | |
prevBevPoint = prevElement->bevPoint; | |
//create path for bevelled side | |
fOff = CGPointZero; | |
tOff = CGPointZero; | |
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint); | |
path = CGPathCreateMutable(); | |
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y); | |
CGPathAddLineToPoint(path, NULL, prevBevPoint.x + fOff.x, prevBevPoint.y + fOff.y); | |
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y); | |
CGPathAddLineToPoint(path, NULL, curPoint.x + tOff.x, curPoint.y + tOff.y); | |
CGPathCloseSubpath(path); | |
//compute mid hypotenuse and use to determine fill color | |
FillPath(path, curPoint, originalElement->focalTheta, prevPoint, prevElement->focalTheta); | |
CGPathRelease(path); | |
break; | |
} | |
prevElement = pathElement; | |
} | |
} | |
void bevelPath(CGPathRef path, CGContextRef context, CGFloat bevelDepth, CGColorRef highlight, CGColorRef shadow, BOOL eofFill){ | |
if (bevelDepth <= 0 || !highlight || !shadow) return; | |
//Grab the all the elements in the path | |
PathElements *pathElements = NewPathElementArray(); | |
CGPathApply(path, pathElements, GetPathElements); | |
//Split out the subpaths into separate linked lists, stored in an array | |
PathElement *element; | |
PathElements *subPath; | |
PointerArray *subPaths = NewPointerArray(); | |
for (int i = 0; i < pathElements->count; i++){ | |
element = pathElements->elements[i]; | |
//Sepearete out subpaths into individual stacks | |
if (i == 0 || element->type == kCGPathElementMoveToPoint){ | |
subPath = NewPathElementArray(); | |
AddToArray(subPaths, subPath); | |
} | |
AddPathElement(element, subPath); | |
} | |
//Perform the actual bevelling | |
for (int i = 0; i < subPaths->count; i++){ | |
subPath = subPaths->objects[i]; | |
SetBisectorsAndFocalPointsForPathElements(subPath, path, eofFill); | |
BevelSubpath(subPath, context, bevelDepth, highlight, shadow); | |
} | |
//Free memory | |
for (int i = 0; i < subPaths->count; i++){ | |
subPath = subPaths->objects[i]; | |
free(subPath->elements); | |
free(subPath); | |
} | |
free(subPaths->objects); | |
free(subPaths); | |
for (int i = 0; i < pathElements->size; i++){ | |
free(pathElements->elements[i]); | |
} | |
free(pathElements->elements); | |
free(pathElements); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment