Skip to content

Instantly share code, notes, and snippets.

@joericioppo
Created February 12, 2012 00:28
Show Gist options
  • Save joericioppo/1805302 to your computer and use it in GitHub Desktop.
Save joericioppo/1805302 to your computer and use it in GitHub Desktop.
An Objective-C PSD writer that got shelved. Nick Paulson and Gus Mueller helped me out with this.
#import <Foundation/Foundation.h>
@interface FOOPSDWriter : NSObject {
NSMutableData *data_;
NSUInteger location_;
}
- (void) writeInt64:(UInt64)value;
- (void) writeInt32:(UInt32)value;
- (void) writeInt32:(UInt32)value atLocation:(NSUInteger)location;
- (void) writeInt16:(UInt16)value;
- (void) writeData:(NSData *)data;
- (void) writeChars:(char *)chars length:(NSUInteger)length;
- (void) writeUnsignedChar:(unsigned char)value;
- (void) writePascalStringWithString:(NSString *)string padding:(NSUInteger)padding;
- (void) skipLength:(NSUInteger)length;
- (NSUInteger) location;
- (NSData *) data;
@end
@implementation FOOPSDWriter
- (id) init
{
self = [super init];
if (!self) {
return nil;
}
data_ = [[NSMutableData alloc] init];
location_ = 0;
return self;
}
- (void) dealloc
{
[data_ release], data_ = nil;
[super dealloc];
}
#pragma mark -
- (void) writeInt64:(UInt64)value
{
value = NSSwapHostLongLongToBig(value);
[data_ appendBytes:&value length:sizeof(value)];
location_ += sizeof(value);
}
- (void) writeInt32:(UInt32)value
{
value = NSSwapHostIntToBig(value);
[data_ appendBytes:&value length:sizeof(value)];
location_ += sizeof(value);
}
- (void) writeInt32:(UInt32)value atLocation:(NSUInteger)location
{
[data_ replaceBytesInRange:NSMakeRange(location, 4) withBytes:&value];
}
- (void) writeInt16:(UInt16)value
{
value = NSSwapHostShortToBig(value);
[data_ appendBytes:&value length:sizeof(value)];
location_ += sizeof(value);
}
- (void) writeData:(NSData *)data
{
[data_ appendData:data];
location_ += data.length;
}
- (void) writeChars:(char *)chars length:(NSUInteger)length
{
[data_ appendBytes:chars length:length];
location_ += length;
}
- (void) writeUnsignedChar:(unsigned char)value
{
[data_ appendBytes:&value length:1];
location_ += 1;
}
- (void) writePascalStringWithString:(NSString *)string padding:(NSUInteger)padding
{
NSUInteger bufferLength = string.length + 1;
while (bufferLength % padding != 0) {
bufferLength ++;
}
NSMutableData *stringData = [NSMutableData dataWithLength:bufferLength];
Boolean success = CFStringGetPascalString((CFStringRef)string, [stringData mutableBytes], (CFIndex)stringData.length, kCFStringEncodingMacRoman);
if (success) {
[data_ appendBytes:[stringData mutableBytes] length:stringData.length];
location_ += stringData.length;
}
}
- (void) skipLength:(NSUInteger)length
{
location_ += length;
}
#pragma mark -
- (NSUInteger) location
{
return location_;
}
- (NSData *)data
{
return data_;
}
@end
// -------
#import <Foundation/Foundation.h>
@class FOOPSDWriter;
@interface FOOPSDLayer : NSObject {
NSString *name_;
NSData *imageData_;
UInt16 numberOfChannels_;
UInt32 top_;
UInt32 left_;
UInt32 bottom_;
UInt32 right_;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) NSData *imageData;
@property (nonatomic, assign) UInt16 numberOfChannels;
@property (nonatomic, assign) UInt32 top;
@property (nonatomic, assign) UInt32 left;
@property (nonatomic, assign) UInt32 bottom;
@property (nonatomic, assign) UInt32 right;
+ (id) layerWithImage:(UIImage *)image;
- (id) initWithImage:(UIImage *)image;
+ (id) layerWithImage:(UIImage *)image name:(NSString *)layerName;
- (id) initWithImage:(UIImage *)image name:(NSString *)layerName;
- (void) writeLayerInfoWithWriter:(FOOPSDWriter *)writer;
- (void) writeImageDataWithWriter:(FOOPSDWriter *)writer;
- (CGSize)size;
@end
#import "FOOPSDLayer.h"
#import "FOOPSDWriter.h"
@implementation FOOPSDLayer
@synthesize top = top_;
@synthesize left = left_;
@synthesize bottom = bottom_;
@synthesize right = right_;
@synthesize numberOfChannels = numberOfChannels_;
@synthesize imageData = imageData_;
@synthesize name = name_;
+ (id) layerWithImage:(UIImage *)image
{
return [[[self alloc] initWithImage:image name:nil] autorelease];
}
- (id) initWithImage:(UIImage *)image
{
return [self initWithImage:image name:nil];
}
+ (id) layerWithImage:(UIImage *)image name:(NSString *)layerName
{
return [[[self alloc] initWithImage:image name:layerName] autorelease];
}
- (id) initWithImage:(UIImage *)image name:(NSString *)layerName
{
self = [super init];
if (!self) {
return nil;
}
CGSize size = image.size;
top_ = 0;
left_ = 0;
bottom_ = size.height;
right_ = size.width;
imageData_ = [[NSData alloc] initWithData:UIImagePNGRepresentation(image)];
name_ = [layerName copy];
return self;
}
#pragma mark -
- (void) writeLayerInfoWithWriter:(FOOPSDWriter *)writer
{
// rectangle
[writer writeInt32:self.top];
[writer writeInt32:self.left];
[writer writeInt32:self.bottom];
[writer writeInt32:self.right];
// channels
[writer writeInt16:self.numberOfChannels];
for (int ix = 0; ix < self.numberOfChannels; ix++) {
/* "6 bytes per channel:
- 2 for Channel ID: 0 = red, 1 = green, etc.; -1 = transparency mask; -2 = user supplied layer mask, -3 real user supplied layer mask (when both a user mask and a vector mask are present)
- 4 for length of corresponding channel data" */
}
// blend mode
char *blendModeSignature = "8BIM";
char *blendModeKey = "norm";
[writer writeChars:blendModeSignature length:4];
[writer writeChars:blendModeKey length:4];
// opacity
unsigned char opacity = 255;
[writer writeUnsignedChar:opacity];
// clipping
unsigned char clipping = 0;
[writer writeUnsignedChar:clipping];
// flags
unsigned char flags = 0;
flags |= (1 << 1); // visible
[writer writeUnsignedChar:flags];
// filler
unsigned char filler = 0;
[writer writeUnsignedChar:filler];
// extra data fields - start
[writer skipLength:4];
NSUInteger extraDataStartLocation = writer.location;
// layer mask data
UInt32 zeroLayerMask = 0;
[writer writeInt32:zeroLayerMask];
// blending ranges data
UInt32 blendingRangesDataLength = 4 + 4 + (self.numberOfChannels * 4 * 2);
[writer writeInt32:blendingRangesDataLength];
for (int ix = 0; ix < blendingRangesDataLength; ix++) {
// 4 bytes - Composite gray blend source. Contains 2 black values followed by 2 white values. Present but irrelevant for Lab & Grayscale.
// 4 bytes - Composite gray blend destination range
// 4 bytes channel1 source
// 4 bytes channel1 destination
// channel2, channel3, ...
unsigned char startBlack = 0;
unsigned char endBlack = 0;
unsigned char startWhite = 255;
unsigned char endWhite = 255;
[writer writeUnsignedChar:startBlack];
[writer writeUnsignedChar:endBlack];
[writer writeUnsignedChar:startWhite];
[writer writeUnsignedChar:endWhite];
}
// layer name
[writer writePascalStringWithString:self.name padding:4];
// extra data fields - end
UInt32 extraDataFieldsLength = (UInt32)(writer.location - extraDataStartLocation);
[writer writeInt32:extraDataFieldsLength atLocation:extraDataStartLocation - 4];
}
- (void) writeImageDataWithWriter:(FOOPSDWriter *)writer
{
for (int ix =0; ix < self.numberOfChannels; ix++) {
// 2 bytes - Compression. 0 = Raw Data, 1 = RLE compressed, 2 = ZIP without prediction, 3 = ZIP with prediction.
UInt16 compression = 0;
[writer writeInt16:compression];
// write image data for each channel
/* "If the compression code is 0, the image data is just the raw image data, whose size is calculated as (LayerBottom-LayerTop)* (LayerRight-LayerLeft) (from the first field in See Layer records).
If the compression code is 1, the image data starts with the byte counts for all the scan lines in the channel (LayerBottom-LayerTop) , with each count stored as a two-byte value.(**PSB** each count stored as a four-byte value.) The RLE compressed data follows, with each scan line compressed separately. The RLE compression is the same compression algorithm used by the Macintosh ROM routine PackBits, and the TIFF standard.
If the layer's size, and therefore the data, is odd, a pad byte will be inserted at the end of the row.
If the layer is an adjustment layer, the channel data is undefined (probably all white.)" */
}
}
#pragma mark -
- (NSString *)name
{
return name_ ? : @"Layer";
}
- (CGSize)size
{
return CGSizeMake(right_ - left_, bottom_ - top_);
}
#pragma mark -
- (void) dealloc
{
[name_ release];
[imageData_ release], imageData_ = nil;
[super dealloc];
}
@end
// -------
#import <Foundation/Foundation.h>
@class FOOPSDWriter;
@interface FOOPSD : NSObject {
FOOPSDWriter *writer_;
NSMutableArray *layers_;
}
@property (nonatomic, retain) NSMutableArray *layers;
+ (id) PSDWithLayeredImages:(NSArray *)images;
- (id) initWithLayeredImages:(NSArray *)images;
+ (id) PSDWithLayeredImages:(NSArray *)images names:(NSArray *)names;
- (id) initWithLayeredImages:(NSArray *)images names:(NSArray *)names;
- (void) writeToURL:(NSURL *)url;
@end
#import "FOOPSD.h"
#import "FOOPSDLayer.h"
#import "FOOPSDWriter.h"
typedef enum {
WDPSDColorModeBitmap = 0,
WDPSDColorModeGreyscale = 1,
WDPSDColorModeIndexed = 2,
WDPSDColorModeRGB = 3,
WDPSDColorModeCYMK = 4,
WDPSDColorModeMultichannel = 7,
WDPSDColorModeDuotone = 8,
WDPSDColorModeModelab = 9
} WDPSDColorMode;
@interface FOOPSD ()
// imageSize: 1-30,000 channels: 1-56 depth(bits per channel): 1, 8, 16 or 32
- (void) writeHeaderDataWithImageSize:(CGSize)imageSize channels:(UInt16)numberOfChannels depth:(UInt16)bitsPerChannel colorMode:(WDPSDColorMode)colorMode;
- (void) writeColorModeSection_;
- (void) writeImageResourceSection_;
- (void) writeLayerSection_;
- (void) writeDataToURL_:(NSURL *)url;
@end
static const NSUInteger kFourBytes = 4;
@implementation FOOPSD
@synthesize layers = layers_;
+ (id) PSDWithLayeredImages:(NSArray *)images
{
return [[[self alloc] initWithLayeredImages:images] autorelease];
}
- (id) initWithLayeredImages:(NSArray *)images
{
return [self initWithLayeredImages:images names:nil];
}
+ (id) PSDWithLayeredImages:(NSArray *)images names:(NSArray *)names
{
return [[[self alloc] initWithLayeredImages:images names:names] autorelease];
}
- (id) initWithLayeredImages:(NSArray *)images names:(NSArray *)names
{
self = [self init];
if (!self) {
return nil;
}
[images enumerateObjectsUsingBlock:^(id image, NSUInteger idx, BOOL *stop) {
FOOPSDLayer *layer = (names.count <= idx) ? [FOOPSDLayer layerWithImage:image] : [FOOPSDLayer layerWithImage:image name:[names objectAtIndex:idx]];
if (layer) {
[layers_ addObject:layer];
}
}];
return self;
}
- (id) init
{
self = [super init];
if (!self) {
return nil;
}
writer_ = [[FOOPSDWriter alloc] init];
layers_ = [[NSMutableArray alloc] init];
return self;
}
#pragma mark -
- (void) writeToURL:(NSURL *)url
{
if (!layers_.count > 0) {
return;
}
[self writeHeaderDataWithImageSize:[[layers_ objectAtIndex:0] size] channels:4 depth:8 colorMode:WDPSDColorModeRGB];
[self writeColorModeSection_];
[self writeImageResourceSection_];
[self writeLayerSection_];
[self writeDataToURL_:url];
}
#pragma mark -
- (void) writeHeaderDataWithImageSize:(CGSize)imageSize channels:(UInt16)numberOfChannels depth:(UInt16)bitsPerChannel colorMode:(WDPSDColorMode)colorMode
{
// Signature
char *signature = "8BPS";
[writer_ writeChars:signature length:4];
// Version
UInt16 version = 1;
[writer_ writeInt16:version];
// Reserved data
[writer_ skipLength:6];
// Number of channels
if (numberOfChannels < 1 || numberOfChannels > 56) {
NSLog(@"Number of channels must be in the range of 1 and 56 - numberOfChannels: %hu", (unsigned short)numberOfChannels);
return;
}
[writer_ writeInt16:numberOfChannels];
// Image height
UInt32 imageHeight = (UInt32)imageSize.height;
if (imageHeight < 1 || imageHeight > 30000) {
NSLog(@"Image height must be in the range of 1 and 30,000 - imageSize.height: %u", (unsigned int)imageHeight);
return;
}
[writer_ writeInt32:imageHeight];
// Image width
UInt32 imageWidth = (UInt32)imageSize.width;
if (imageWidth < 1 || imageWidth > 30000) {
NSLog(@"Image width must be in the range of 1 and 30,000 - imageSize.width: %u", (unsigned int)imageWidth);
return;
}
[writer_ writeInt32:imageWidth];
// Depth - Bits per channel
if (!(bitsPerChannel == 1 || bitsPerChannel == 8 || bitsPerChannel == 16 || bitsPerChannel == 32)) {
NSLog(@"Bits per channel (depth) must be equal to 1, 8, 16 or 32 - depth %hu", (unsigned short)bitsPerChannel);
return;
}
[writer_ writeInt16:bitsPerChannel];
// Color mode
UInt16 mode = colorMode;
[writer_ writeInt16:mode];
}
- (void) writeColorModeSection_
{
UInt32 zero = 0;
[writer_ writeInt32:zero];
}
- (void) writeImageResourceSection_
{
// write thumbnail resource
UInt32 zero = 0;
[writer_ writeInt32:zero];
}
- (void) writeLayerSection_
{
// layer & mask section
// length of layer and mask section
// layer info
// length of info section
// number of layers
// layer reconds
// image data records
// global layer mask
// layer & mask section - start
[writer_ skipLength:kFourBytes];
NSUInteger layerAndMaskStartLocation = writer_.location;
// layer info section - start
[writer_ skipLength:kFourBytes];
NSUInteger layerInfoStartLocation = writer_.location;
UInt16 layerCount = (UInt16)layers_.count;
[writer_ writeInt16:layerCount];
// layer info records
for (FOOPSDLayer *layer in layers_) {
[layer writeLayerInfoWithWriter:writer_];
}
// channel image data
for (FOOPSDLayer *layer in layers_) {
[layer writeImageDataWithWriter:writer_];
}
UInt32 layerInfoSectionLength = (UInt32)(writer_.location - layerInfoStartLocation);
[writer_ writeInt32:layerInfoSectionLength atLocation:layerInfoStartLocation - kFourBytes];
// global layer mask info
UInt32 globalMaskInfoLength = 14;
[writer_ writeInt32:globalMaskInfoLength];
UInt16 overlayColorSpace = 0; // spec says 'undocumented'?
[writer_ writeInt16:overlayColorSpace];
UInt16 colorComponent1 = 0; // '4 * 2 byte color components'
UInt16 colorComponent2 = 0;
UInt16 colorComponent3 = 0;
UInt16 colorComponent4 = 0;
[writer_ writeInt16:colorComponent1];
[writer_ writeInt16:colorComponent2];
[writer_ writeInt16:colorComponent3];
[writer_ writeInt16:colorComponent4];
UInt16 opacity = 100;
[writer_ writeInt16:opacity];
unsigned char kind = 128;
[writer_ writeUnsignedChar:kind];
NSMutableData *zeros = [NSMutableData dataWithLength:3]; // 'Variable Filler: zeros' padded to even
[writer_ writeData:zeros];
// layer & mask section - end
UInt32 layerAndMaskSectionLength = (UInt32)(writer_.location - layerAndMaskStartLocation);
[writer_ writeInt32:layerAndMaskSectionLength atLocation:layerAndMaskStartLocation - kFourBytes];
}
- (void) writeDataToURL_:(NSURL *)url
{
[writer_.data writeToURL:url atomically:YES];
}
#pragma mark -
- (void) dealloc
{
[layers_ release];
[writer_ release];
[super dealloc];
}
@end
@bengotow
Copy link

I recently published a library for iOS / Mac OS that allows you to write layered PSDs—if this got shelved, it might do what you need: https://github.com/bengotow/PSDWriter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment