Skip to content

Instantly share code, notes, and snippets.

@joshcodes
Created May 30, 2013 21:44
Show Gist options
  • Save joshcodes/5681512 to your computer and use it in GitHub Desktop.
Save joshcodes/5681512 to your computer and use it in GitHub Desktop.
//
// NSData+PngRgba.h
// Josh Wingstrom <josh@joshcodes.com>
//
// Created by Josh Wingstrom on 5/30/13.
//
#import <Foundation/Foundation.h>
@interface NSData (PngRgba)
/*! @function +rgbaWithPng:
@discussion This method returns an NSData object. The NSData object is initialized with the
the byte array extracted from the provided PNG byte data.
@param self An NSData object that contains a raw 8-bit PNG file.
@param format An reference to a GLint which will contain the OpenGL texture format (GL_RGB, GL_RGBA, etc) of the PNG file.
@param width An reference to a NSUInteger which will contain the width of the PNG file.
@param height An reference to a NSUInteger which will contain the height of the PNG file.
@param height If YES, the image is inverted. OpenGL y-coords are reverse of PNG.
@param error An reference to a NSError object which will be set to nil on success or contain
the appropriate error message on failure.
@result The RGB(A) byte array which can be used by OpenGL. */
- (NSData *) pngToFormat:(GLint*)format width:(NSUInteger*)width height:(NSUInteger*)height invert:(BOOL)invert error:(NSError**)error;
@end
----------------------------------------------- [FILE BREAK] -----------------------------------------------
//
// NSData+PngRgba.m
// Josh Wingstrom <josh@joshcodes.com>
//
// Created by Josh Wingstrom on 5/30/13.
//
#import "NSData+PngRgba.h"
#import "png.h"
#define PNG_HEADER_SIZE (8)
typedef struct{
const void *source;
uint index;
} ReadStream;
@implementation NSData (PngRgba)
void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
void *nsDataPtr = png_get_io_ptr(png_ptr);
ReadStream *readStream = (ReadStream*)nsDataPtr;
memcpy(data, readStream->source + readStream->index, length);
readStream->index += length;
}
- (NSData *) pngToFormat:(GLint*)format width:(NSUInteger*)width height:(NSUInteger*)height invert:(BOOL)invert error:(NSError**)error
{
ReadStream readStream;
readStream.source = self.bytes;
readStream.index = PNG_HEADER_SIZE;
png_byte header[PNG_HEADER_SIZE];
memcpy(header, self.bytes, sizeof(header)*sizeof(header[0]));
int is_png = png_sig_cmp(header, 0, PNG_HEADER_SIZE);
if (is_png != 0)
{
*error = [NSError errorWithDomain:@"File is not a PNG" code:44 userInfo:nil];
// Assume it's square RGBA
NSUInteger size = (NSUInteger)sqrt(self.length / 4);
*height = size;
*width = size;
*format = GL_RGBA;
return self;
}
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
*error = [NSError errorWithDomain:@"png_create_read_struct returned 0" code:44 userInfo:nil];
return nil;
}
// create png info struct
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
*error = [NSError errorWithDomain:@"png_create_info_struct returned 0" code:44 userInfo:nil];
return nil;
}
// create png info struct
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info)
{
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
*error = [NSError errorWithDomain:@"png_create_info_struct returned 0" code:44 userInfo:nil];
return nil;
}
// the code in this if statement gets called if libpng encounters an error
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
*error = [NSError errorWithDomain:@"error from libpng while decompressing" code:44 userInfo:nil];
return nil;
}
// init png reading
png_set_read_fn(png_ptr, &readStream, user_read_data);
// let libpng know you already read the first 8 bytes
png_set_sig_bytes(png_ptr, PNG_HEADER_SIZE);
// read all the info up to the image data
png_read_info(png_ptr, info_ptr);
// variables to pass to get info
int bit_depth, color_type;
png_uint_32 temp_width, temp_height;
// get info about png file and populate the reference parameters
// Get IHDR png info
png_get_IHDR(png_ptr, info_ptr, &temp_width, &temp_height, &bit_depth, &color_type,
NULL, NULL, NULL);
// validate that the bit depth is supported
if (bit_depth != 8)
{
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
*error = [NSError errorWithDomain:[NSString stringWithFormat:@"Unsupported bit depth %d. Must be 8", bit_depth] code:44 userInfo:nil];
return nil;
}
// Populate reference parameters
if (width) { *width = temp_width; }
if (height) { *height = temp_height; }
switch(color_type)
{
case PNG_COLOR_TYPE_RGB:
*format = GL_RGB;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
*format = GL_RGBA;
break;
default:
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
*error = [NSError errorWithDomain:[NSString stringWithFormat:@"Unknown libpng color type %d.", color_type] code:44 userInfo:nil];
return nil;
}
// Update the png info struct.
png_read_update_info(png_ptr, info_ptr);
// Row size in bytes.
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
// glTexImage2d requires rows to be 4-byte aligned
rowbytes += 3 - ((rowbytes-1) % 4);
// Allocate the image_data as a big block, to be given to OpenGL
png_byte * image_data;
image_data = malloc(rowbytes * temp_height * sizeof(png_byte)+15); // ???: Does anyone know why +15?
if (image_data == NULL)
{
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
*error = [NSError errorWithDomain:@"could not allocate memory for PNG image data" code:44 userInfo:nil];
return nil;
}
// libPng needs an array of pointers to the arrays into which it writes the RGB(A) data. Therefore, create
// row_pointers and initialize it pointing to the appropriate indexes of image_data
// Create row_pointers
png_byte ** row_pointers = (png_byte **)malloc(temp_height * sizeof(png_byte *));
if (row_pointers == NULL)
{
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
*error = [NSError errorWithDomain:@"could not allocate memory for PNG row pointers" code:44 userInfo:nil];
return nil;
}
// set the individual row_pointers to point at the correct offsets of image_data
// ???: Compiler does optimize out the if invert branch
for (unsigned int i = 0; i < temp_height; i++)
{
if (invert)
{
row_pointers[temp_height - 1 - i] = image_data + i * rowbytes;
continue;
}
row_pointers[i] = image_data + i * rowbytes;
}
// read the png into image_data through row_pointers
png_read_image(png_ptr, row_pointers);
// convert image to NSData object to return
NSData *rgbaData = [NSData dataWithBytes:image_data length:rowbytes * temp_height * sizeof(png_byte)];
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
free(image_data);
free(row_pointers);
return rgbaData;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment