4 votes

Comment enregistrer une surface SDL au format PNG et obtenir des couleurs correctes ?

J'ai écrit un simple patch pour ajouter des capacités d'enregistrement de PNG à l'application Bibliothèque SDL_Image . Cela fonctionne presque aussi. Le problème, c'est que les couleurs sont brouillées et que je ne connais pas assez le langage C pour comprendre ce qui ne va pas. Quelqu'un peut-il jeter un coup d'œil et m'aider à le réparer ?

Cas d'utilisation :

  1. Charger une image PNG 256 couleurs avec IMG_LoadPNG_RW .
  2. Sauvegardez-le avec IMG_SavePNG_RW.
  3. Voyez s'ils sont identiques ou non.

Rustine :

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

3voto

Mason Wheeler Points 52022

J'ai réussi à le faire fonctionner. J'ai eu besoin d'appeler png_set_bgr() pour fixer la palette.

2voto

blahdiblah Points 17382

L'enregistrement des surfaces SDL en tant qu'images PNG est un problème qui a été résolu à plusieurs reprises sur le web :
http://www.bishoujo.us/svn/renpy/trunk/module/
http://lists.libsdl.org/pipermail/sdl-libsdl.org/2006-May/055936.html
http://www.os4depot.net/index.php?function=showfile&file=development/example/sdlpngsavesurf.lha
http://encelo.netsons.org/programming/sdl

Plutôt que de demander de l'aide pour déboguer une grande partie du code, il serait probablement plus productif de comparer votre code à ces autres implémentations et de voir où elles diffèrent, ou de demander laquelle de ces autres implémentations est la meilleure à utiliser.

Par ailleurs, Parcheando la bibliothèque SDL_image pourrait être une solution moins robuste que d'écrire des bibliothèques dépendantes séparées pour mettre en œuvre l'enregistrement PNG. Ainsi, la prochaine mise à jour de SDL ne cassera pas votre code. Si vous pensiez apporter le correctif à SDL, toutes les autres implémentations mentionnées remontent à quelques années. Je ne pense pas que SDL_image n'inclue pas l'enregistrement PNG par manque de volonté ou de capacité à le coder.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X