views:

294

answers:

5

I have a 32-bit NSBitmapImageRep which has an alpha channel with essentially 1-bit values (the pixels are either on or off).

I want to save this bitmap to an 8-bit PNG file with transparency. If I use the -representationUsingType:properties: method of NSBitmapImageRep and pass in NSPNGFileType, a 32-bit PNG is created, which is not what I want.

I know that 8-bit PNGs can be read, they open in Preview with no problems, but is it possible to write this type of PNG file using any built-in Mac OS X APIs? I'm happy to drop down to Core Image or even QuickTime if necessary. A cursory examination of the CGImage docs didn't reveal anything obvious.

EDIT: I've started a bounty on this question, if someone can provide working source code that takes a 32-bit NSBitmapImageRep and writes a 256-color PNG with 1-bit transparency, it's yours.

A: 

One thing to try would be creating a NSBitmapImageRep with 8 bits, then copying the data to it.

This would actually be a lot of work, as you would have to compute the color index table yourself.

cobbal
Correct. Peter Hosey's suggestion of using **pngnq** solves this palette creation problem quite nicely, albeit with the need to spawn a task.
Rob Keniger
+1  A: 

How about pnglib? It's really lightweight and easy to use.

Carl Norum
This is definitely an option but since I've not worked with `pnglib` before there's a lot of learning involved, I was really hoping for something higher-level.
Rob Keniger
@Rob, there's not a lot of learning for pnglib, it's really pretty straightforward as far as C libraries go. You might be stuck for something else, most of the higher-level APIs assume more general cases, which usually means 24 or 32 bpp images.
Carl Norum
A: 

CGImageDestination is your man for low-level image writing, but I don't know if it supports that specific ability.

Mike Abdullah
Yeah, it looks like it should be the answer but so far I've been unable to find a way to specify the type of PNG to create, everything I try writes a 32-bit PNG.
Rob Keniger
A: 

A great reference for working with lower level APIs is Programming With Quartz

Some of the code below is based on examples from that book.

Note: This is un-tested code meant to be a starting point only....

- (NSBitmapImageRep*)convertImageRep:(NSBitmapImageRep*)startingImage{

    CGImageRef anImage = [startingImage CGImage];

    CGContextRef    bitmapContext;
    CGRect ctxRect;
    size_t  bytesPerRow, width, height;

    width = CGImageGetWidth(anImage);
    height = CGImageGetHeight(anImage);
    ctxRect = CGRectMake(0.0, 0.0, width, height);
    bytesPerRow = (width * 4 + 63) & ~63;
    bitmapData = calloc(bytesPerRow * height, 1);
    bitmapContext = createRGBBitmapContext(width, height, TRUE);
    CGContextDrawImage (bitmapContext, ctxRect, anImage);

    //Now extract the image from the context
    CGImageRef      bitmapImage = nil;
    bitmapImage = CGBitmapContextCreateImage(bitmapContext);
    if(!bitmapImage){
        fprintf(stderr, "Couldn't create the image!\n");
        return nil;
    }

    NSBitmapImageRep *newImage = [[NSBitmapImageRep alloc] initWithCGImage:bitmapImage];
    return newImage;
}

Context Creation Function:

CGContextRef createRGBBitmapContext(size_t width, size_t height, Boolean needsTransparentBitmap)
{
    CGContextRef context;
    size_t bytesPerRow;
    unsigned char *rasterData;

    //minimum bytes per row is 4 bytes per sample * number of samples
    bytesPerRow = width*4;
    //round up to nearest multiple of 16.
    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);

    int bitsPerComponent = 2;  // to get 256 colors (2xRGBA)

    //use function 'calloc' so memory is initialized to 0.
    rasterData = calloc(1, bytesPerRow * height);
    if(rasterData == NULL){
        fprintf(stderr, "Couldn't allocate the needed amount of memory!\n");
        return NULL;
    }

    // uses the generic calibrated RGB color space.
    context = CGBitmapContextCreate(rasterData, width, height, bitsPerComponent, bytesPerRow,
                                    CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB),
                                    (needsTransparentBitmap ? kCGImageAlphaPremultipliedFirst :
                                     kCGImageAlphaNoneSkipFirst)
                                    );
    if(context == NULL){
        free(rasterData);
        fprintf(stderr, "Couldn't create the context!\n");
        return NULL;
    }

    //Either clear the rect or paint with opaque white,
    if(needsTransparentBitmap){
        CGContextClearRect(context, CGRectMake(0, 0, width, height));
    }else{
        CGContextSaveGState(context);
        CGContextSetFillColorWithColor(context, getRGBOpaqueWhiteColor());
        CGContextFillRect(context, CGRectMake(0, 0, width, height));
        CGContextRestoreGState(context);
    }
    return context;
}

Usage would be:

NSBitmapImageRep *startingImage;  // assumed to be previously set.
NSBitmapImageRep *endingImageRep = [self convertImageRep:startingImage];
// Write out as data
NSData *outputData = [endingImageRep representationUsingType:NSPNGFileType properties:nil];
// somePath is set elsewhere
[outputData writeToFile:somePath atomically:YES];
Chip Coons
Thanks, this will work nicely to create the bitmap but it doesn't actually solve the problem of writing a 256-color 8-bit PNG file.
Rob Keniger
Sorry, I left that step out. I'll edit my answer to include the two calls needed.
Chip Coons
I've looked into this, and according to the "Supported Pixel Formats" in the Quartz 2D Programming Guide, it's not possible to create an 8-bit RGB context, so this code can't work. If you try running it, it fails to create the context due to an "invalid parameter combination". http://developer.apple.com/mac/library/DOCUMENTATION/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB
Rob Keniger
A: 

pngnq has BSD-style license, so you can just include it in your program. No need to spawn as separate task.

porneL
This does actually work, and fixes my problem, thanks.
Rob Keniger