Manipulating raw png data

2.6k Views Asked by At

I want to read a PNG file such that I can:

a) Access the raw bitmap data of the file, with no color space adjustment or alpha premultiply.

b) Based on that bitmap, display bit slices (any single bit of R, G, B, or A, across the whole image) in an image in the window. If I have the bitmap I can find the right bits, but what can I stuff them into to get them onscreen?

c) After some modification of the bitplanes, write a new PNG file, again with no adjustments.

This is only for certain specific images. The PNG is not expected to have any data other than simply RGBA-32.

From reading some similar questions here, I'm suspecting NSBitmapImageRep for the file read/write, and drawing in an NSView for the onscreen part. Does this sound right?

2

There are 2 best solutions below

1
On

I haven't ever worked in this area, but you may be able to use Image IO for this.

4
On

1.) You can use NSBitmapImageRep's -bitmapData to get the raw pixel data. Unfortunately, CG (NSBitmapImageRep's backend) does not support native unpremultiplication so you would have to unpremultiply yourself. The colorspace used in this will be the same as present in the file. Here is how to unpremultiply the image data:

NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:data];
    NSInteger width = [imageRep pixelsWide];
    NSInteger height = [imageRep pixelsHigh];

    unsigned char *bytes = [imageRep bitmapData];
    for (NSUInteger y = 0; y < width * height * 4; y += 4) { // bgra little endian + alpha first
        uint8_t a, r, g, b;

        if (imageRep.bitmapFormat & NSAlphaFirstBitmapFormat) {
            a = bytes[y];
            r = bytes[y+1];
            g = bytes[y+2];
            b = bytes[y+3];
        } else {
            r = bytes[y+0];
            g = bytes[y+1];
            b = bytes[y+2];
            a = bytes[y+3];
        }

        // unpremultiply alpha if there is any
        if (a > 0) {
            if (!(imageRep.bitmapFormat & NSAlphaNonpremultipliedBitmapFormat)) {
                float factor = 255.0f/a;
                b *= factor;
                g *= factor;
                r *= factor;
            }
        } else {
            b = 0;
            g = 0;
            r = 0;
        }
        bytes[y]=a; // for argb
        bytes[y+1]=r;
        bytes[y+2]=g;
        bytes[y+3]=b;
    }

2.) I couldn't think of a simple way to do this. You could make your own image drawing method that loops through the raw image data and generates a new image based on the values. Refer above to see how to start doing it.

3.) Here is a method to get a CGImage from raw data places (you can write the png to a file using native CG functions or convert it to NSBitmapImageRep if CG makes you uncomfortable)

static CGImageRef cgImageFrom(NSData *data, uint16_t width, uint16_t height) {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaFirst;

CGImageRef cgImage = CGImageCreate(width, height, 8, 32, 4 * width, colorSpace, bitmapInfo, provider, NULL, NO, kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return cgImage;
}

You can create the NSData object out of the raw data object with +dataWithBytes:length: