Crossfire Client, Branch
R11627
|
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 }