Created
May 30, 2013 21:44
-
-
Save joshcodes/5681512 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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