Crossfire Client, Branch  R11627
png.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk_png_c =
00002     "$Id: png.c 9190 2008-06-01 08:53:05Z anmaster $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2001 Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire-devel@real-time.com
00023 */
00024 
00025 
00026 #include <config.h>
00027 #include <stdlib.h>
00028 #include <sys/stat.h>
00029 #ifndef WIN32
00030 #include <unistd.h>
00031 #endif
00032 #include <png.h>
00033 #include <client-types.h>
00034 #include <client.h>
00035 
00036 /* Pick up the gtk headers we need */
00037 #include <gtk/gtk.h>
00038 #ifndef WIN32
00039 #include <gdk/gdkx.h>
00040 #else
00041 #include <gdk/gdkwin32.h>
00042 #endif
00043 #include <gdk/gdkkeysyms.h>
00044 
00045 
00046 /* Defines for PNG return values */
00047 /* These should be in a header file, but currently our calling functions
00048  * routines just check for nonzero return status and don't really care
00049  * why the load failed.
00050  */
00051 #define PNGX_NOFILE     1
00052 #define PNGX_OUTOFMEM   2
00053 #define PNGX_DATA       3
00054 
00055 static uint8 *data_cp;
00056 static int data_len, data_start;
00057 
00058 static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
00059     memcpy(data, data_cp + data_start, length);
00060     data_start += length;
00061 }
00062 
00063 uint8 *png_to_data(uint8 *data, int len, uint32 *width, uint32 *height)
00064 {
00065     uint8 *pixels=NULL;
00066     static png_bytepp   rows=NULL;
00067     static int rows_byte=0;
00068 
00069     png_structp png_ptr;
00070     png_infop   info_ptr;
00071     int bit_depth, color_type, interlace_type, compression_type, y;
00072 
00073     data_len=len;
00074     data_cp = data;
00075     data_start=0;
00076 
00077     png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
00078                                      NULL, NULL, NULL);
00079 
00080     if (!png_ptr) {
00081         return NULL;
00082     }
00083     info_ptr = png_create_info_struct (png_ptr);
00084 
00085     if (!info_ptr) {
00086         png_destroy_read_struct (&png_ptr, NULL, NULL);
00087         return NULL;
00088     }
00089     if (setjmp (png_ptr->jmpbuf)) {
00090         png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
00091         return NULL;
00092     }
00093 
00094     png_set_read_fn(png_ptr, NULL, user_read_data);
00095     png_read_info (png_ptr, info_ptr);
00096     /*
00097      * This seems to bug on at least one system (other than mine)
00098      * http://www.metalforge.net/cfmb/viewtopic.php?t=1085
00099      *
00100      * I think its actually a bug in libpng. This function dies with an
00101      * error based on image width. However I've produced a work around
00102      * using the indivial functions. Repeated below.
00103      *
00104     png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)width, (png_uint_32*)height, &bit_depth,
00105                  &color_type, &interlace_type, &compression_type, &filter_type);
00106     */
00107     *width = png_get_image_width(png_ptr, info_ptr);
00108     *   height = png_get_image_height(png_ptr, info_ptr);
00109     bit_depth = png_get_bit_depth(png_ptr, info_ptr);
00110     color_type = png_get_color_type(png_ptr, info_ptr);
00111     interlace_type = png_get_interlace_type(png_ptr, info_ptr);
00112     compression_type = png_get_compression_type(png_ptr, info_ptr);
00113     if (color_type == PNG_COLOR_TYPE_PALETTE &&
00114             bit_depth <= 8) {
00115 
00116                 /* Convert indexed images to RGB */
00117                 png_set_expand (png_ptr);
00118 
00119     } else if (color_type == PNG_COLOR_TYPE_GRAY &&
00120                    bit_depth < 8) {
00121 
00122                 /* Convert grayscale to RGB */
00123                 png_set_expand (png_ptr);
00124 
00125     } else if (png_get_valid (png_ptr,
00126                                   info_ptr, PNG_INFO_tRNS)) {
00127 
00128                 /* If we have transparency header, convert it to alpha
00129                    channel */
00130                 png_set_expand(png_ptr);
00131 
00132     } else if (bit_depth < 8) {
00133 
00134                 /* If we have < 8 scale it up to 8 */
00135                 png_set_expand(png_ptr);
00136 
00137 
00138                 /* Conceivably, png_set_packing() is a better idea;
00139                  * God only knows how libpng works
00140                  */
00141     }
00142         /* If we are 16-bit, convert to 8-bit */
00143     if (bit_depth == 16) {
00144                 png_set_strip_16(png_ptr);
00145     }
00146 
00147         /* If gray scale, convert to RGB */
00148     if (color_type == PNG_COLOR_TYPE_GRAY ||
00149             color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
00150                 png_set_gray_to_rgb(png_ptr);
00151     }
00152 
00153         /* If interlaced, handle that */
00154     if (interlace_type != PNG_INTERLACE_NONE) {
00155                 png_set_interlace_handling(png_ptr);
00156     }
00157 
00158     /* pad it to 4 bytes to make processing easier */
00159     if (!(color_type & PNG_COLOR_MASK_ALPHA))
00160         png_set_filler(png_ptr, 255, PNG_FILLER_AFTER);
00161 
00162     /* Update the info the reflect our transformations */
00163     png_read_update_info(png_ptr, info_ptr);
00164     /* re-read due to transformations just made */
00165     /*
00166      * See above for error description
00167     png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)width, (png_uint_32*)height, &bit_depth,
00168                  &color_type, &interlace_type, &compression_type, &filter_type);
00169     */
00170     *width = png_get_image_width(png_ptr, info_ptr);
00171     *height = png_get_image_height(png_ptr, info_ptr);
00172     bit_depth = png_get_bit_depth(png_ptr, info_ptr);
00173     color_type = png_get_color_type(png_ptr, info_ptr);
00174     interlace_type = png_get_interlace_type(png_ptr, info_ptr);
00175     compression_type = png_get_compression_type(png_ptr, info_ptr);
00176 
00177     pixels = (uint8*)malloc(*width * *height * 4);
00178 
00179     if (!pixels) {
00180         png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
00181         LOG(LOG_CRITICAL,"gtk::png_to_data","Out of memory - exiting");
00182         exit(1);
00183     }
00184 
00185     /* the png library needs the rows, but we will just return the raw data */
00186     if (rows_byte == 0) {
00187         rows =(png_bytepp) malloc(sizeof(char*) * *height);
00188         rows_byte=*height;
00189     } else if (*height > rows_byte) {
00190         rows =(png_bytepp) realloc(rows, sizeof(char*) * *height);
00191         rows_byte=*height;
00192     }
00193     if (!rows) {
00194         png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
00195         return NULL;
00196     }
00197 
00198     for (y=0; y<*height; y++)
00199         rows[y] = pixels + y * *width * 4;
00200 
00201     png_read_image(png_ptr, rows);
00202     png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
00203 
00204     return pixels;
00205 }
00206 
00207 
00208 /* rescale_png_image takes png data and scales it accordingly.
00209  * This function is based on pnmscale, but has been modified to support alpha
00210  * channel - instead of blending the alpha channel, it takes the most opaque
00211  * value - blending it is not likely to give sane results IMO - for any image
00212  * that has transparent information, if we blended the alpha, the result would
00213  * be the edges of that region being partially transparent.
00214  * This function has also been re-written to use more static data - in the
00215  * case of the client, it will be called thousands of times, so it doesn't make
00216  * sense to free the data and then re-allocate it.
00217  *
00218  * For pixels that are fully transparent, the end result after scaling is they
00219  * will be tranparent black.  This is a needed effect for blending to work properly.
00220  *
00221  * This function returns a new pointer to the scaled image data.  This is
00222  * malloc'd data, so should be freed at some point to prevent leaks.
00223  * This function does not modify the data passed to it - the caller is responsible
00224  * for freeing it if it is no longer needed.
00225  *
00226  * function arguments:
00227  * data: PNG data - really, this is any 4 byte per pixel data, in RGBA format.
00228  * *width, *height: The source width and height.  These values are modified
00229  *   to contain the new image size.
00230  * scale: percentage size that new image should be.  100 is a same size
00231  *    image - values larger than 100 will result in zoom, values less than
00232  *    100 will result in a shrinkage.
00233  */
00234 
00235 /* RATIO is used to know what units scale is - in this case, a percentage, so
00236  * it is set to 100
00237  */
00238 #define RATIO   100
00239 
00240 #define MAX_IMAGE_WIDTH         1024
00241 #define MAX_IMAGE_HEIGHT        1024
00242 #define BPP 4
00243 
00244 uint8 *rescale_rgba_data(uint8 *data, int *width, int *height, int scale)
00245 {
00246     static int xrow[BPP * MAX_IMAGE_WIDTH], yrow[BPP*MAX_IMAGE_HEIGHT];
00247     static uint8 *nrows[MAX_IMAGE_HEIGHT];
00248 
00249     /* Figure out new height/width */
00250     int new_width = *width  * scale / RATIO, new_height = *height * scale / RATIO;
00251 
00252     int sourcerow=0, ytoleft, ytofill, xtoleft, xtofill, dest_column=0, source_column=0, needcol,
00253         destrow=0;
00254     int x,y;
00255     uint8 *ndata;
00256     uint8 r,g,b,a;
00257 
00258     if (*width > MAX_IMAGE_WIDTH || new_width > MAX_IMAGE_WIDTH
00259     || *height > MAX_IMAGE_HEIGHT || new_height > MAX_IMAGE_HEIGHT)
00260     {
00261         LOG(LOG_CRITICAL,"gtk::rescale_rgba_data","Image too big");
00262         exit(0);
00263     }
00264 
00265     /* clear old values these may have */
00266     memset(yrow, 0, sizeof(int) * *height * BPP);
00267 
00268     ndata = (uint8*)malloc(new_width * new_height * BPP);
00269 
00270     for (y=0; y<new_height; y++)
00271         nrows[y] = (png_bytep) (ndata + y * new_width * BPP);
00272 
00273     ytoleft = scale;
00274     ytofill = RATIO;
00275 
00276     for (y=0,sourcerow=0; y < new_height; y++) {
00277         memset(xrow, 0, sizeof(int) * *width * BPP);
00278         while (ytoleft < ytofill) {
00279             for (x=0; x< *width; ++x) {
00280                 /* Only want to copy the data if this is not a transperent pixel.
00281                  * If it is transparent, the color information is has is probably
00282                  * bogus, and blending that makes the results look worse.
00283                  */
00284                 if (data[(sourcerow * *width + x)*BPP+3] > 0 ) {
00285                     yrow[x*BPP] += ytoleft * data[(sourcerow * *width + x)*BPP]/RATIO;
00286                     yrow[x*BPP+1] += ytoleft * data[(sourcerow * *width + x)*BPP+1]/RATIO;
00287                     yrow[x*BPP+2] += ytoleft * data[(sourcerow * *width + x)*BPP+2]/RATIO;
00288                 }
00289                 /* Alpha is a bit special - we don't want to blend it -
00290                  * we want to take whatever is the more opaque value.
00291                  */
00292                 if (data[(sourcerow * *width + x)*BPP+3] > yrow[x*BPP+3])
00293                     yrow[x*BPP+3] = data[(sourcerow * *width + x)*BPP+3];
00294             }
00295             ytofill -= ytoleft;
00296             ytoleft = scale;
00297             if (sourcerow < *height)
00298                 sourcerow++;
00299         }
00300 
00301         for (x=0; x < *width; ++x) {
00302             if (data[(sourcerow * *width + x)*BPP+3] > 0 ) {
00303                 xrow[x*BPP] = yrow[x*BPP] + ytofill * data[(sourcerow * *width + x)*BPP] / RATIO;
00304                 xrow[x*BPP+1] = yrow[x*BPP+1] + ytofill * data[(sourcerow * *width + x)*BPP+1] / RATIO;
00305                 xrow[x*BPP+2] = yrow[x*BPP+2] + ytofill * data[(sourcerow * *width + x)*BPP+2] / RATIO;
00306             }
00307             if (data[(sourcerow * *width + x)*BPP+3] > xrow[x*BPP+3])
00308                 xrow[x*BPP+3] = data[(sourcerow * *width + x)*BPP+3];
00309             yrow[x*BPP]=0; yrow[x*BPP+1]=0; yrow[x*BPP+2]=0; yrow[x*BPP+3]=0;
00310         }
00311 
00312         ytoleft -= ytofill;
00313         if (ytoleft <= 0) {
00314             ytoleft = scale;
00315             if (sourcerow < *height)
00316                 sourcerow++;
00317         }
00318 
00319         ytofill = RATIO;
00320         xtofill = RATIO;
00321         dest_column = 0;
00322         source_column=0;
00323         needcol=0;
00324         r=0; g=0; b=0; a=0;
00325 
00326         for (x=0; x< *width; x++) {
00327             xtoleft = scale;
00328 
00329             while (xtoleft >= xtofill) {
00330                 if (needcol) {
00331                     dest_column++;
00332                     r=0; g=0; b=0; a=0;
00333                 }
00334 
00335                 if (xrow[source_column*BPP+3] > 0) {
00336                     r += xtofill * xrow[source_column*BPP] / RATIO;
00337                     g += xtofill * xrow[1+source_column*BPP] / RATIO;
00338                     b += xtofill * xrow[2+source_column*BPP] / RATIO;
00339                 }
00340                 if (xrow[3+source_column*BPP] > a)
00341                     a = xrow[3+source_column*BPP];
00342 
00343                 nrows[destrow][dest_column * BPP] = r;
00344                 nrows[destrow][1+dest_column * BPP] = g;
00345                 nrows[destrow][2+dest_column * BPP] = b;
00346                 nrows[destrow][3+dest_column * BPP] = a;
00347                 xtoleft -= xtofill;
00348                 xtofill = RATIO;
00349                 needcol=1;
00350             }
00351 
00352             if (xtoleft > 0 ){
00353                 if (needcol) {
00354                     dest_column++;
00355                     r=0; g=0; b=0; a=0;
00356                     needcol=0;
00357                 }
00358 
00359                 if (xrow[3+source_column*BPP] > 0) {
00360                     r += xtoleft * xrow[source_column*BPP] / RATIO;
00361                     g += xtoleft * xrow[1+source_column*BPP] / RATIO;
00362                     b += xtoleft * xrow[2+source_column*BPP] / RATIO;
00363                 }
00364                 if (xrow[3+source_column*BPP] > a)
00365                     a = xrow[3+source_column*BPP];
00366 
00367                 xtofill -= xtoleft;
00368             }
00369             source_column++;
00370         }
00371 
00372         if (xtofill > 0 ) {
00373             source_column--;
00374             if (xrow[3+source_column*BPP] > 0) {
00375                 r += xtofill * xrow[source_column*BPP] / RATIO;
00376                 g += xtofill * xrow[1+source_column*BPP] / RATIO;
00377                 b += xtofill * xrow[2+source_column*BPP] / RATIO;
00378             }
00379             if (xrow[3+source_column*BPP] > a)
00380                 a = xrow[3+source_column*BPP];
00381         }
00382 
00383         /* Not positve, but without the bound checking for dest_column,
00384          * we were overrunning the buffer.  My guess is this only really
00385          * showed up if when the images are being scaled - there is probably
00386          * something like half a pixel left over.
00387          */
00388         if (!needcol && (dest_column < new_width)) {
00389             nrows[destrow][dest_column * BPP] = r;
00390             nrows[destrow][1+dest_column * BPP] = g;
00391             nrows[destrow][2+dest_column * BPP] = b;
00392             nrows[destrow][3+dest_column * BPP] = a;
00393         }
00394         destrow++;
00395     }
00396     *width = new_width;
00397     *height = new_height;
00398     return ndata;
00399 }
00400 
00401 
00402 /* This takes data that has already been converted into RGBA format (via
00403  * png_to_data above perhaps) and creates a GdkPixmap and GdkBitmap out
00404  * of it.
00405  * Return non zero on error (currently, no checks for error conditions is done
00406  */
00407 int rgba_to_gdkpixmap(GdkWindow *window, uint8 *data,int width, int height,
00408                    GdkPixmap **pix, GdkBitmap **mask, GdkColormap *colormap)
00409 {
00410     GdkGC       *gc, *gc_alpha;
00411     int         has_alpha=0, alpha;
00412     GdkColor  scolor;
00413     int x,y;
00414 
00415     *pix = gdk_pixmap_new(window, width, height, -1);
00416 
00417     gc=gdk_gc_new(*pix);
00418     gdk_gc_set_function(gc, GDK_COPY);
00419 
00420     *mask=gdk_pixmap_new(window, width, height,1);
00421     gc_alpha=gdk_gc_new(*mask);
00422 
00423     scolor.pixel=1;
00424     gdk_gc_set_foreground(gc_alpha, &scolor);
00425     gdk_draw_rectangle(*mask, gc_alpha, 1, 0, 0, width, height);
00426 
00427     scolor.pixel=0;
00428     gdk_gc_set_foreground(gc_alpha, &scolor);
00429 
00430     /* we need to draw the alpha channel.  The image may not in fact
00431      * have alpha, but no way to know at this point other than to try
00432      * and draw it.
00433      */
00434     for (y=0; y<height; y++) {
00435         for (x=0; x<width; x++) {
00436             alpha = data[(y * width + x) * 4 +3];
00437             /* Transparent bit */
00438             if (alpha==0) {
00439                 gdk_draw_point(*mask, gc_alpha, x, y);
00440                 has_alpha=1;
00441             }
00442         }
00443     }
00444 
00445     gdk_draw_rgb_32_image(*pix, gc,  0, 0, width, height, GDK_RGB_DITHER_NONE, data, width*4);
00446     if (!has_alpha) {
00447         gdk_pixmap_unref(*mask);
00448         *mask = NULL;
00449     }
00450 
00451     gdk_gc_destroy(gc_alpha);
00452     gdk_gc_destroy(gc);
00453     return 0;
00454 }