I wrote up a simple patch to add PNG-saving capabilities to the SDL_Image library. It almost works, too. Problem is, the colors come out all scrambled, and I don't know enough C to figure out what's wrong. Can anyone take a look at this and help me fix it up?
Use case:
- Load a 256-color PNG image with
IMG_LoadPNG_RW
. - Save it with
IMG_SavePNG_RW.
- See if they're identical or not.
Patch:
Index: IMG_png.c
===================================================================
--- IMG_png.c (revision 4475)
+++ IMG_png.c (working copy)
@@ -76,17 +76,25 @@
png_infop (*png_create_info_struct) (png_structp png_ptr);
png_structp (*png_create_read_struct) (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn);
void (*png_destroy_read_struct) (png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr);
+ png_structp (*png_create_write_struct) (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn);
+ void (*png_destroy_write_struct) (png_structpp png_ptr_ptr, png_infopp info_ptr_ptr);
png_uint_32 (*png_get_IHDR) (png_structp png_ptr, png_infop info_ptr, png_uint_32 *width, png_uint_32 *height, int *bit_depth, int *color_type, int *interlace_method, int *compression_method, int *filter_method);
+ void (*png_set_IHDR) (png_structp png_ptr, png_infop info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, int color_type, int interlace_method, int compression_method, int filter_method);
png_voidp (*png_get_io_ptr) (png_structp png_ptr);
png_uint_32 (*png_get_tRNS) (png_structp png_ptr, png_infop info_ptr, png_bytep *trans, int *num_trans, png_color_16p *trans_values);
png_uint_32 (*png_get_valid) (png_structp png_ptr, png_infop info_ptr, png_uint_32 flag);
void (*png_read_image) (png_structp png_ptr, png_bytepp image);
+ void (*png_write_image) (png_structp png_ptr, png_bytepp image);
void (*png_read_info) (png_structp png_ptr, png_infop info_ptr);
+ void (*png_write_info) (png_structp png_ptr, png_infop info_ptr);
+ void (*png_set_PLTE) (png_structp png_ptr, png_infop info_ptr, png_colorp palette, int num_palette);
+ void (*png_write_end) (png_structp png_ptr, png_infop info_ptr);
void (*png_read_update_info) (png_structp png_ptr, png_infop info_ptr);
void (*png_set_expand) (png_structp png_ptr);
void (*png_set_gray_to_rgb) (png_structp png_ptr);
void (*png_set_packing) (png_structp png_ptr);
void (*png_set_read_fn) (png_structp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn);
+ void (*png_set_write_fn) (png_structp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn);
void (*png_set_strip_16) (png_structp png_ptr);
int (*png_sig_cmp) (png_bytep sig, png_size_t start, png_size_t num_to_check);
} lib;
@@ -120,6 +128,20 @@
SDL_UnloadObject(lib.handle);
return -1;
}
+ lib.png_create_write_struct =
+ (png_structp (*) (png_const_charp, png_voidp, png_error_ptr, png_error_ptr))
+ SDL_LoadFunction(lib.handle, "png_create_write_struct");
+ if ( lib.png_create_write_struct == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
+ lib.png_destroy_write_struct =
+ (void (*) (png_structpp, png_infopp))
+ SDL_LoadFunction(lib.handle, "png_destroy_write_struct");
+ if ( lib.png_destroy_write_struct == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
lib.png_get_IHDR =
(png_uint_32 (*) (png_structp, png_infop, png_uint_32 *, png_uint_32 *, int *, int *, int *, int *, int *))
SDL_LoadFunction(lib.handle, "png_get_IHDR");
@@ -127,6 +149,13 @@
SDL_UnloadObject(lib.handle);
return -1;
}
+ lib.png_set_IHDR =
+ (void (*) (png_structp, png_infop, png_uint_32, png_uint_32, int, int, int, int, int))
+ SDL_LoadFunction(lib.handle, "png_set_IHDR");
+ if ( lib.png_set_IHDR == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
lib.png_get_io_ptr =
(png_voidp (*) (png_structp))
SDL_LoadFunction(lib.handle, "png_get_io_ptr");
@@ -155,6 +184,13 @@
SDL_UnloadObject(lib.handle);
return -1;
}
+ lib.png_write_image =
+ (void (*) (png_structp, png_bytepp))
+ SDL_LoadFunction(lib.handle, "png_write_image");
+ if ( lib.png_write_image == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
lib.png_read_info =
(void (*) (png_structp, png_infop))
SDL_LoadFunction(lib.handle, "png_read_info");
@@ -162,6 +198,27 @@
SDL_UnloadObject(lib.handle);
return -1;
}
+ lib.png_write_info =
+ (void (*) (png_structp, png_infop))
+ SDL_LoadFunction(lib.handle, "png_write_info");
+ if ( lib.png_write_info == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
+ lib.png_set_PLTE =
+ (void (*) (png_structp, png_infop, png_colorp, int))
+ SDL_LoadFunction(lib.handle, "png_set_PLTE");
+ if ( lib.png_set_PLTE == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
+ lib.png_write_end =
+ (void (*) (png_structp, png_infop))
+ SDL_LoadFunction(lib.handle, "png_write_end");
+ if ( lib.png_write_end == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
lib.png_read_update_info =
(void (*) (png_structp, png_infop))
SDL_LoadFunction(lib.handle, "png_read_update_info");
@@ -197,6 +254,13 @@
SDL_UnloadObject(lib.handle);
return -1;
}
+ lib.png_set_write_fn =
+ (void (*) (png_structp, png_voidp, png_rw_ptr))
+ SDL_LoadFunction(lib.handle, "png_set_write_fn");
+ if ( lib.png_set_write_fn == NULL ) {
+ SDL_UnloadObject(lib.handle);
+ return -1;
+ }
lib.png_set_strip_16 =
(void (*) (png_structp))
SDL_LoadFunction(lib.handle, "png_set_strip_16");
@@ -472,7 +536,7 @@
palette->colors[i].g = i;
palette->colors[i].b = i;
}
- } else if (info_ptr->num_palette > 0 ) {
+ } else if (info_ptr->num_palette > 0 ) {
palette->ncolors = info_ptr->num_palette;
for( i=0; i<info_ptr->num_palette; ++i ) {
palette->colors[i].b = info_ptr->palette[i].blue;
@@ -505,18 +569,167 @@
return(surface);
}
-#else
+static void png_write_data(png_structp ctx, png_bytep area, png_size_t size)
+{
+ SDL_RWops *src;
-/* See if an image is contained in a data source */
-int IMG_isPNG(SDL_RWops *src)
+ src = (SDL_RWops *)lib.png_get_io_ptr(ctx);
+ SDL_RWwrite(src, area, size, 1);
+}
+/* write a png file */
+int IMG_SavePNG_RW(SDL_Surface *surface, SDL_RWops *src)
{
- return(0);
-}
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_colorp palette = NULL;
+ int start;
+ int colorType;
+ int i;
+ const char *error;
+ SDL_Palette *sdlPalette;
+ png_uint_32 height = surface->h;
+ png_uint_32 width = surface->w;
+ png_bytep *volatile row_pointers;
+ int row;
-/* Load a PNG type image from an SDL datasource */
-SDL_Surface *IMG_LoadPNG_RW(SDL_RWops *src)
-{
- return(NULL);
+ if ( !src ) {
+ /* The error message has been set in SDL_RWFromFile */
+ return -1;
+ }
+ start = SDL_RWtell(src);
+
+ if ( IMG_InitPNG() < 0 ) {
+ return -1;
+ }
+
+ /* Create and initialize the png_struct with the desired error handler
+ * functions. If you want to use the default stderr and longjump method,
+ * you can supply NULL for the last three parameters. We also check that
+ * the library version is compatible with the one used at compile time,
+ * in case we are using dynamically linked libraries. REQUIRED.
+ */
+ png_ptr = NULL; info_ptr = NULL;
+
+ /* Create the PNG loading context structure */
+ png_ptr = lib.png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ NULL,NULL,NULL);
+ if (png_ptr == NULL){
+ error = "Couldn't allocate memory for PNG file or incompatible PNG dll";
+ goto done;
+ }
+
+ /* Allocate/initialize the memory for image information. REQUIRED. */
+ info_ptr = lib.png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ error = "Couldn't create image information for PNG file";
+ goto done;
+ }
+
+ /* Set error handling if you are using setjmp/longjmp method (this is
+ * the normal method of doing things with libpng). REQUIRED unless you
+ * set up your own error handlers in png_create_read_struct() earlier.
+ */
+ if ( setjmp(png_ptr->jmpbuf) ) {
+ error = "Error reading the PNG file.";
+ goto done;
+ }
+
+ /* Set up the output control */
+ lib.png_set_write_fn(png_ptr, src, png_write_data);
+
+ /* Set the image information here. Width and height are up to 2^31,
+ * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
+ * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
+ * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
+ * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
+ * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
+ * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
+ */
+ sdlPalette = surface->format->palette;
+ if (sdlPalette)
+ {
+ colorType = PNG_COLOR_TYPE_PALETTE;
+ } else if (surface->format->Amask)
+ {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ } else
+ {
+ colorType = PNG_COLOR_TYPE_RGB;
+ }
+ lib.png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, surface->format->BitsPerPixel, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ /* set the palette if there is one. REQUIRED for indexed-color images */
+ if (colorType == PNG_COLOR_TYPE_PALETTE)
+ {
+ palette = (png_colorp) malloc(sdlPalette->ncolors * sizeof(png_color));
+
+ for( i=0; i < sdlPalette->ncolors; ++i ) {
+ palette[i].blue = sdlPalette->colors[i].b;
+ palette[i].green = sdlPalette->colors[i].g;
+ palette[i].red = sdlPalette->colors[i].r;
+ }
+ lib.png_set_PLTE(png_ptr, info_ptr, palette, sdlPalette->ncolors);
+ }
+ else
+ { //not sure how to handle this
+// sig_bit.red = true_red_bit_depth;
+// sig_bit.green = true_green_bit_depth;
+// sig_bit.blue = true_blue_bit_depth;
+ /* if the image has an alpha channel then */
+// sig_bit.alpha = true_alpha_bit_depth;
+// png_set_sBIT(png_ptr, info_ptr, sig_bit);
+ }
+
+ /* Write the file header information. REQUIRED */
+ lib.png_write_info(png_ptr, info_ptr);
+
+ /* The easiest way to write the image (you may have a different memory
+ * layout, however, so choose what fits your needs best). You need to
+ * use the first method if you aren't handling interlacing yourself.
+ */
+
+ row_pointers = (png_bytep*) malloc(sizeof(png_bytep)*height);
+ if ( (row_pointers == NULL) ) {
+ error = "Out of memory";
+ goto done;
+ }
+ for (row = 0; row < (int)height; row++) {
+ row_pointers[row] = (png_bytep)
+ (Uint8 *)surface->pixels + row*surface->pitch;
+ }
+
+ if (height > PNG_UINT_32_MAX/sizeof(png_bytep))
+ {
+ error = "Image is too tall to process in memory";
+ goto done;
+ }
+
+ /* Read the entire image in one go */
+ lib.png_write_image(png_ptr, row_pointers);
+
+ /* It is REQUIRED to call this to finish writing the rest of the file */
+ lib.png_write_end(png_ptr, info_ptr);
+
+done: /* Clean up and return */
+ if ( png_ptr ) {
+ lib.png_destroy_write_struct(&png_ptr, &info_ptr);
+ }
+ if ( row_pointers ) {
+ free(row_pointers);
+ }
+ if (palette)
+ {
+ free(palette);
+ }
+ if ( error ) {
+ IMG_QuitPNG();
+ IMG_SetError(error);
+ return(-1);
+ } else {
+ IMG_QuitPNG();
+ }
+ return(0);
}
#endif /* LOAD_PNG */
Index: SDL_image.h
===================================================================
--- SDL_image.h (revision 4475)
+++ SDL_image.h (working copy)
@@ -107,6 +107,8 @@
extern DECLSPEC SDL_Surface * SDLCALL IMG_ReadXPMFromArray(char **xpm);
+extern DECLSPEC int SDLCALL IMG_SavePNG_RW(SDL_Surface *surface, SDL_RWops *src);
+
/* We'll use SDL for reporting errors */
#define IMG_SetError SDL_SetError
#define IMG_GetError SDL_GetError