< Back
< Back
< Back
< Back
Tutorial
How to implement your own format?
This tutorial requires some programming experience and Objective-C knowledge.

At very first you need a proper concept about how to encode the image data. The source is an image, in simple terms, RGBA data. The result is a file containing binary data, in our case NSData.
For instance an 8bit bitmap: The values of the first pixel in the image get casted to Bytes, one for each: Red, green, blue & alpha. 4 bytes for each pixel get added to a NSData object, to get stored to a file on disk. 1 byte is 1 binary character in UTF-8 encodings.
To read the data you simply use the same principle only the other way around. The first binary character of the NSData object must be interpreted as red value for pixel #1, the second char as green value for #1, and so on.

This tutorial doesn't give descriptions to any Objective-C function, it shows only where you have to edit, or better add the new code.

Walk through with the example of USPEC (Uniform Spectrum).

1. Get the source code on GitHub.
2. Open the ExtraFile.xcodeproj with Xcode.

We start with the Resources.

3. Add custom Icon.
Copy your .icns file to the ExtraFile/icons folder and drag it into the Resources group in Xcode.

4. Edit ExtraFile-Info.plist.
Add a new Item including your format properties to the Document types in the ExtraFile-Info.plist.

Add a new Item including your format properties to the Exported Type UTIs in the ExtraFile-Info.plist.

5. Edit XFImageView.h
Add a new NSData* function for your format.
//
//  XFImageView.h
//  ExtraFile
//
//  Created by Kim Asendorf on 06.05.11.
//

#import <Quartz/Quartz.h>

#import "XFImageDocument.h"


@class XFImageDocument;

@interface XFImageView : NSView
{
	IBOutlet XFImageDocument* xfImageDoc;

	NSColor* backgroundColor;
}

- (void)changeBackgroundColor:(NSColor *)color;

- (NSData *)dataInXFF;
- (NSData *)dataInCCI;
- (NSData *)dataInMCF;
- (NSData *)dataIn4BC;
- (NSData *)dataInBASCII;
- (NSData *)dataInBLINX;
- (NSData *)dataInUSPEC;

@end
		


6. Edit XFImageView.m
Add a new NSData* function for your format.
- (NSData *)dataInUSPEC
{
	CGImageRef cgImage = [xfImageDoc currentCGImage];
	if (cgImage==nil)
		return nil;

	NSMutableData* uspecData = [NSMutableData dataWithCapacity:0];

	NSRect bounds = NSMakeRect(0, 0, CGImageGetWidth(cgImage), CGImageGetHeight(cgImage));
	NSImage* image = [[NSImage alloc] initWithSize:bounds.size];

	[image lockFocus];

	CGContextDrawImage([[NSGraphicsContext currentContext] graphicsPort], *(CGRect*)&bounds, cgImage);

	[image unlockFocus];

	NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
	[image release];

	NSMutableArray* pixelArray = [[NSMutableArray alloc] init];

	NSString* indexString = @"index";
	NSString* colorString = @"color";

	NSArray* keys = [NSArray arrayWithObjects:indexString, colorString, nil];

	int imageWidth = CGImageGetWidth(cgImage);
	int imageHeight = CGImageGetHeight(cgImage);

	int imageSize = imageWidth * imageHeight;

	int i;
	for (i=0; i<imageSize; i++) {
		int x = fmod(i, imageWidth);
		int y = i / imageWidth;

		NSColor* color = [bitmap colorAtX:x y:y];

		CGFloat red = [color redComponent]*255.0;
		CGFloat green = [color greenComponent]*255.0;
		CGFloat blue = [color blueComponent]*255.0;

		int colorInt = red + green*256 + blue*256*256;

		NSValue* indexValue = [NSNumber numberWithInt:i];
		NSValue* colorValue = [NSNumber numberWithInt:colorInt];

		NSArray* objects = [NSArray arrayWithObjects:indexValue, colorValue, nil];

		[pixelArray addObject:[NSDictionary dictionaryWithObjects:objects forKeys:keys]];
	}
	NSSortDescriptor * sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"color" ascending:YES] autorelease];
	[pixelArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

	NSMutableData* sortedData = [NSMutableData dataWithCapacity:0];

	for (i=0; i<imageSize; i++) {
		NSDictionary* objects = [pixelArray objectAtIndex:i];
		NSValue* colorValue = [objects valueForKey:@"index"];
		int colorInt;
		[colorValue getValue:&colorInt];		
		[sortedData appendData:[NSData dataWithBytes:&colorInt length:sizeof(colorInt)]];
	}

	//PROPERTIES
	NSString* sizeString = [NSString stringWithFormat:@"%dx%d", CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)];
	const char* utfSizeString = [sizeString UTF8String];

	NSData* size = [NSData dataWithBytes:utfSizeString length:strlen(utfSizeString)+1];

	//SEPARATOR
	const char* utfSeparatorString = [separatorString UTF8String];

	NSData* separator = [NSData dataWithBytes:utfSeparatorString length:strlen(utfSeparatorString)+1];

	[uspecData appendData:size];
	[uspecData appendData:separator];
	[uspecData appendData:sortedData];

	CGImageRelease(cgImage);

	return uspecData;
}
		


7. Edit XFDocument.m
Add a static NSString* as bundle identifier for your format, and add a new else if clause to XFIOLocalizedString.
static NSString* XFAppString = @"EXTRAFILE";
static NSString* XFTypeNameRoot = @"org.extrafile";
static NSString* XFTypeNameXFF = @"org.extrafile.xff";
static NSString* XFTypeNameCCI = @"org.extrafile.cci";
static NSString* XFTypeNameMCF = @"org.extrafile.mcf";
static NSString* XFTypeName4BC = @"org.extrafile.4bc";
static NSString* XFTypeNameBASCII = @"org.extrafile.bascii";
static NSString* XFTypeNameBLINX = @"org.extrafile.blinx";
static NSString* XFTypeNameUSPEC = @"org.extrafile.uspec";


static NSString* XFIOLocalizedString(NSString* key)
{
	static NSString* type = nil;

	if ([key isEqualToString:XFTypeNameXFF]) {
		type = @"XFF";
	} else if ([key isEqualToString:XFTypeNameCCI]) {
		type = @"CCI";
	} else if ([key isEqualToString:XFTypeNameMCF]) {
		type = @"MCF";
	} else if ([key isEqualToString:XFTypeName4BC]) {
		type = @"4BC";
	} else if ([key isEqualToString:XFTypeNameBASCII]) {
		type = @"BASCII";
	} else if ([key isEqualToString:XFTypeNameBLINX]) {
		type = @"BLINX";
	} else if ([key isEqualToString:XFTypeNameUSPEC]) {
		type = @"USPEC";
	}
	return type;
}
		

Go to #pragma mark #### Getter and add your format to readableTypes and writableTypes.
#pragma mark #### Getter

+ (NSArray *)readableTypes
{
	NSMutableArray* allTypes = [NSMutableArray arrayWithArray:[(NSArray *)CGImageSourceCopyTypeIdentifiers() autorelease]];

	//ADD CUSTOM FORMATS
	[allTypes addObject:XFTypeNameXFF];
	[allTypes addObject:XFTypeNameCCI];
	[allTypes addObject:XFTypeNameMCF];
	[allTypes addObject:XFTypeName4BC];
	[allTypes addObject:XFTypeNameBASCII];
	[allTypes addObject:XFTypeNameBLINX];
	[allTypes addObject:XFTypeNameUSPEC];

    return allTypes;
}


+ (NSArray *)writableTypes
{
	NSMutableArray* allTypes = [NSMutableArray arrayWithArray:[(NSArray *)CGImageDestinationCopyTypeIdentifiers() autorelease]];

	//ADD CUSTOM FORMATS
	[allTypes addObject:XFTypeNameXFF];
	[allTypes addObject:XFTypeNameCCI];
	[allTypes addObject:XFTypeNameMCF];
	[allTypes addObject:XFTypeName4BC];
	[allTypes addObject:XFTypeNameBASCII];
	[allTypes addObject:XFTypeNameBLINX];
	[allTypes addObject:XFTypeNameUSPEC];

	return allTypes;
}
		

Go to #pragma mark #### Read/Write to edit the readFromURL function.
#pragma mark #### Read/Write

- (BOOL)readFromURL:(NSURL *)absURL ofType:(NSString *)typeName error:(NSError **)outError
		

After the first if clause you'll find all data properties that are read from the file. You only have to care about imageWidth, imageHeight and imageData.
	if ([typeName rangeOfString:XFTypeNameRoot].location != NSNotFound) {

			NSData* xfData = [NSData dataWithContentsOfURL:absURL];

			NSRange startUpRange = NSMakeRange(0, MIN([xfData length], 256));
			NSData* headData = [xfData subdataWithRange:startUpRange];
			unsigned char* xfBuffer[[headData length]];
			[headData getBytes:xfBuffer];

			NSString* xfString = [[NSString alloc] initWithBytes:xfBuffer length:sizeof(xfBuffer) encoding:NSASCIIStringEncoding];
			NSRange headEnd = [xfString rangeOfString:separatorString];
			NSUInteger headLength = headEnd.location+headEnd.length+1;
			NSRange headRange = NSMakeRange(0, headLength);
			NSString* headString = [xfString substringWithRange:headRange];
			NSArray* headElements = [headString componentsSeparatedByCharactersInSet:[NSCharacterSet controlCharacterSet]];

			NSString* applicationName = [headElements objectAtIndex:0];
			NSString* fileType = [headElements objectAtIndex:1];
			NSString* imageSize = [headElements objectAtIndex:2];
			NSArray* imageSizeElements = [imageSize componentsSeparatedByString:@"x"];

			int imageWidth = [[imageSizeElements objectAtIndex:0] intValue];
			int imageHeight = [[imageSizeElements objectAtIndex:1] intValue];

			NSRange imageRange = NSMakeRange(headLength, [xfData length]-headLength);
			NSData* imageData = [xfData subdataWithRange:imageRange];
		

Add an other else if clause for your format. This code example shows the clause of USPEC.
		} else if ([fileType rangeOfString:XFTypeNameUSPEC].location != NSNotFound) {

			[xfProgressStatus setStringValue:@"USPEC Type Found"];
			[xfProgressStatus display];

			int imageSize = imageWidth * imageHeight;
			int dataSize = [imageData length] / 4;
			int colorStep = (255 + 255*256 + 255*256*256) / dataSize;

			unsigned char* rBuffer = (unsigned char *)malloc(imageSize);
			unsigned char* gBuffer = (unsigned char *)malloc(imageSize);
			unsigned char* bBuffer = (unsigned char *)malloc(imageSize);

			int i;
			if (cleanBuffer) {
				for (i=0; i<imageSize; i++) {
					rBuffer[i] = 0;
					gBuffer[i] = 0;
					bBuffer[i] = 0;
				}
			}

			for (i=0; i<dataSize; i++) {

				NSRange fourByteRange = NSMakeRange(i*4, 4);
				NSData* fourByteData = [imageData subdataWithRange:fourByteRange];

				int index;
				[fourByteData getBytes:&index length:sizeof(index)];
				index = fmod(abs(index), imageSize);

				int colorIndex = colorStep * i;

				int bRest = fmod(colorIndex, 256.0f*256.0f);
				int rRest = fmod(bRest, 256.0f);


				rBuffer[index] = (unsigned char)(rRest);
				gBuffer[index] = (unsigned char)(bRest/256.0f);
				bBuffer[index] = (unsigned char)(colorIndex/(256.0f*256.0f));
			}

			unsigned char* rgba = (unsigned char *)malloc(imageSize*4);
			for (i=0; i<imageSize; i++) {
				rgba[4*i] = rBuffer[i];
				rgba[4*i+1] = gBuffer[i];
				rgba[4*i+2] = bBuffer[i];
				rgba[4*i+3] = 0;
			}

			free(rBuffer);
			free(gBuffer);
			free(bBuffer);

			CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
			CGContextRef bitmapContext = CGBitmapContextCreate(
															   rgba,
															   imageWidth,
															   imageHeight,
															   8, // bitsPerComponent
															   4*imageWidth, // bytesPerRow
															   colorSpace,
															   kCGImageAlphaNoneSkipLast);

			CFRelease(colorSpace);

			CGImageRef image = CGBitmapContextCreateImage(bitmapContext);

			free(rgba);

			xfImage = CGImageCreateCopyWithDefaultSpace(image);

			xfFilteredImage = [(XFImageFilter*)[XFImageFilter alloc] initWithImage:xfImage];

			CGImageRelease(image);
		}
		

Go to the writeToURL function.
- (BOOL)writeToURL:(NSURL *)absURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOp originalContentsURL:(NSURL *)absOrigURL error:(NSError **)outError
		

Add an other else if clause for your format.
		//ADD XF DATA
		NSData* imgBytes;
		if ([typeName rangeOfString:XFTypeNameXFF].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode XFF"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInXFF];
		} else if ([typeName rangeOfString:XFTypeNameCCI].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode CCI"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInCCI];
		} else if ([typeName rangeOfString:XFTypeNameMCF].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode MCF"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInMCF];
		} else if ([typeName rangeOfString:XFTypeName4BC].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode 4BC"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataIn4BC];
		} else if ([typeName rangeOfString:XFTypeNameBASCII].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode BASCII"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInBASCII];
		} else if ([typeName rangeOfString:XFTypeNameBLINX].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode BLINX"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInBLINX];
		} else if ([typeName rangeOfString:XFTypeNameUSPEC].location != NSNotFound) {
			[xfProgressStatus setStringValue:@"Decode USPEC"];
			[xfProgressStatus display];
			imgBytes = [xfImageView dataInUSPEC];
		}
		


8. Edit XFDocumentController.m
Add a static NSString* as bundle identifier for your format, and add a new else if clause to XFIOLocalizedString. (Yes, exactly the same like in XFDocument.m.)
static NSString* XFAppString = @"EXTRAFILE";
static NSString* XFTypeNameRoot = @"org.extrafile";
static NSString* XFTypeNameXFF = @"org.extrafile.xff";
static NSString* XFTypeNameCCI = @"org.extrafile.cci";
static NSString* XFTypeNameMCF = @"org.extrafile.mcf";
static NSString* XFTypeName4BC = @"org.extrafile.4bc";
static NSString* XFTypeNameBASCII = @"org.extrafile.bascii";
static NSString* XFTypeNameBLINX = @"org.extrafile.blinx";
static NSString* XFTypeNameUSPEC = @"org.extrafile.uspec";


static NSString* XFIOLocalizedString(NSString* key)
{
	static NSString* type = nil;

	if ([key isEqualToString:XFTypeNameXFF]) {
		type = @"XFF";
	} else if ([key isEqualToString:XFTypeNameCCI]) {
		type = @"CCI";
	} else if ([key isEqualToString:XFTypeNameMCF]) {
		type = @"MCF";
	} else if ([key isEqualToString:XFTypeName4BC]) {
		type = @"4BC";
	} else if ([key isEqualToString:XFTypeNameBASCII]) {
		type = @"BASCII";
	} else if ([key isEqualToString:XFTypeNameBLINX]) {
		type = @"BLINX";
	} else if ([key isEqualToString:XFTypeNameUSPEC]) {
		type = @"USPEC";
	}
	return type;
}
		


You are done! build and enj0y!

=)

Please fork your custom file format to our GitHub repository, we would love to include it to the official source code and download.