Skip to content

Instantly share code, notes, and snippets.

@KyleLeneau
Forked from ahayman/CGPathBevel.h
Created December 12, 2012 15:57
Show Gist options
  • Save KyleLeneau/4268942 to your computer and use it in GitHub Desktop.
Save KyleLeneau/4268942 to your computer and use it in GitHub Desktop.
//
// 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);
//
// 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