Saving CGPaths

Let’s say you’ve created a CGPath in your code, and you’d like to save it to disk. It wasn’t immediately obvious to me how one might do this, but it turns out to be pretty easy with the use of the CGPathApply function.

CGPathApply

The CGPathApply function invokes a user-supplied callback for each “element” of a specified path. These “elements” are CGPathElement structures; each contains an operator/type (one of 5) and an array of CGPoints that modify the operator. Each operator maps intuitively to a CGPath… function, which makes reconstitution easy.

Demo

Here’s some code for you. This class wraps a CGPath, and provides basic load/save functionality. It’s been, ah, “lightly” tested, so proceed with caution. Whatever the particulars, the general approach seems sound.

Path.h:

#import <Foundation/Foundation.h>

@interface Path : NSObject
{
	CGPathRef	path;
}

- (CGPathRef)path;
- (void)setPath:(CGPathRef)newPath;

- (BOOL)saveToFile:(NSString*)fileName;
- (BOOL)loadFromFile:(NSString*)fileName;

@end

Path.m:

#import "Path.h"

static void saveApplier(void* info, const CGPathElement* element)
{
	NSMutableArray* a = (NSMutableArray*) info;

	int nPoints;
	switch (element->type)
	{
		case kCGPathElementMoveToPoint:
			nPoints = 1;
			break;
		case kCGPathElementAddLineToPoint:
			nPoints = 1;
			break;
		case kCGPathElementAddQuadCurveToPoint:
			nPoints = 2;
			break;
		case kCGPathElementAddCurveToPoint:
			nPoints = 3;
			break;
		case kCGPathElementCloseSubpath:
			nPoints = 0;
			break;
		default:
			[a replaceObjectAtIndex:0 withObject:[NSNumber numberWithBool:NO]];
			return;
	}

	NSNumber* type = [NSNumber numberWithInt:element->type];
	NSData* points = [NSData dataWithBytes:element->points length:nPoints*sizeof(CGPoint)];
	[a addObject:[NSDictionary dictionaryWithObjectsAndKeys:type,@"type",points,@"points",nil]];
}

@implementation Path

- (CGPathRef)path
{
	return path;
}

- (void)setPath:(CGPathRef)newPath
{
	if (path != newPath)
	{
		CGPathRelease(path);
		path = CGPathRetain(newPath);
	}
}

- (BOOL)saveToFile:(NSString*)fileName
{
	// Convert path to an array
	NSMutableArray* a = [NSMutableArray arrayWithObject:[NSNumber numberWithBool:YES]];
	CGPathApply(path, a, saveApplier);
	if (![[a objectAtIndex:0] boolValue])
	{
		return NO;
	}

	// Write path
	return [a writeToFile:fileName atomically:YES];
}

- (BOOL)loadFromFile:(NSString*)fileName
{
	// Read path
	NSArray* a = [NSArray arrayWithContentsOfFile:fileName];
	if (!a) return NO;

	// Recreate (and store) path
	CGMutablePathRef p = CGPathCreateMutable();
	for (NSInteger i = 1, l = [a count]; i < l; i++)
	{
		NSDictionary* d = [a objectAtIndex:i];

		CGPoint* points = (CGPoint*) [[d objectForKey:@"points"] bytes];
		switch ([[d objectForKey:@"type"] intValue])
		{
			case kCGPathElementMoveToPoint:
				CGPathMoveToPoint(p, NULL, points[0].x, points[0].y);
				break;
			case kCGPathElementAddLineToPoint:
				CGPathAddLineToPoint(p, NULL, points[0].x, points[0].y);
				break;
			case kCGPathElementAddQuadCurveToPoint:
				CGPathAddQuadCurveToPoint(p, NULL, points[0].x, points[0].y, points[1].x, points[1].y);
				break;
			case kCGPathElementAddCurveToPoint:
				CGPathAddCurveToPoint(p, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
				break;
			case kCGPathElementCloseSubpath:
				CGPathCloseSubpath(p);
				break;
			default:
				CGPathRelease(p);
				return NO;
		}
	}
	self.path = p;
	CGPathRelease(p);

	// Signal success
	return YES;
}

- (void)dealloc
{
	CGPathRelease(path);
	[super dealloc];
}

@end

Format

The thing that bothers me the most about this code is the disk format of the CGPoint arrays. I chose to stuff them into NSData objects for simplicity and speed of implementation, but this yields an opaque file format. It would be better to store the CGPoints as, say, arrays of dictionaries, or any equivalent arrangement which would render them human-readable and -editable.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

Comments are closed.