Crossfire Client, Branch  R11627
image.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk2_image_c =
00002     "$Id: image.c 11043 2008-12-21 04:44:14Z kbulgrien $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2005 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@metalforge.org
00023 */
00024 
00032 #include <config.h>
00033 #include <stdlib.h>
00034 #include <sys/stat.h>
00035 #ifndef WIN32
00036 #include <unistd.h>
00037 #endif
00038 #include <png.h>
00039 
00040 /* Pick up the gtk headers we need */
00041 #include <gtk/gtk.h>
00042 #include <glade/glade.h>
00043 #ifndef WIN32
00044 #include <gdk/gdkx.h>
00045 #else
00046 #include <gdk/gdkwin32.h>
00047 #endif
00048 #include <gdk/gdkkeysyms.h>
00049 
00050 #ifdef HAVE_SDL
00051 #include <SDL.h>
00052 #include <SDL_image.h>
00053 #endif
00054 
00055 #include "client-types.h"
00056 #include "client.h"
00057 #include "image.h"
00058 #include "main.h"
00059 #include "mapdata.h"
00060 #include "gtk2proto.h"
00061 
00062 extern GtkWidget *window_root; 
00063 int image_size=DEFAULT_IMAGE_SIZE;
00064 
00065 struct {
00066     char    *name;
00067     uint32  checksum;
00068     uint8   *png_data;
00069     uint32  width, height;
00070 } private_cache[MAXPIXMAPNUM];
00071 
00072 #define BPP 4
00073 
00074 PixmapInfo *pixmaps[MAXPIXMAPNUM];
00075 
00076 int last_face_num=0;
00077 
00078 /*
00079  * this is used to rescale big images that will be drawn in the inventory/look
00080  * lists.  What the code further below basically does is figure out how big the
00081  * object is (in squares), and this looks at the icon_rescale_factor to figure
00082  * what scale factor it gives.  Not that the icon_rescale_factor values are
00083  * passed directly to the rescale routines.  These represent percentages - so
00084  * even taking into account that the values diminish as the table grows, they
00085  * will still appear larger if the location in the table times the factor is
00086  * greater than 100.  We find the largest dimension that the image has.  The
00087  * values in the comment is the effective scaling compared to the base image
00088  * size that this big image will appear as.  Using a table makes it easier to
00089  * adjust the values so things look right.
00090  */
00091 
00092 #define MAX_ICON_SPACES     10
00093 static const int icon_rescale_factor[MAX_ICON_SPACES] = {
00094 100, 100,           80 /* 2 = 160 */,   60 /* 3 = 180 */,
00095 50 /* 4 = 200 */,   45 /* 5 = 225 */,   40 /* 6 = 240 */,
00096 35 /* 7 = 259 */,   35 /* 8 = 280 */,   33 /* 9 = 300 */
00097 };
00098 
00099 /******************************************************************************
00100  *
00101  * Code related to face caching.
00102  *
00103  *****************************************************************************/
00104 
00105 /* Does not appear to be used anywhere
00106 typedef struct Keys {
00107     uint8       flags;
00108     sint8       direction;
00109     KeySym      keysym;
00110     char        *command;
00111     struct Keys *next;
00112 } Key_Entry;
00113 */
00114 
00115 /* Rotate right from bsd sum. */
00116 #define ROTATE_RIGHT(c) if ((c) & 01) (c) = ((c) >>1) + 0x80000000; else (c) >>= 1;
00117 
00118 /*#define CHECKSUM_DEBUG*/
00119 
00127 static void create_icon_image(uint8 *data, PixmapInfo *pi, int pixmap_num)
00128 {
00129     pi->icon_mask = NULL;
00130     if (rgba_to_gdkpixbuf(data, pi->icon_width, pi->icon_height,
00131                 (GdkPixbuf**)&pi->icon_image))
00132                     LOG (LOG_ERROR,"gtk::create_icon_image","Unable to create scaled image, dest num = %d\n", pixmap_num);
00133 }
00134 
00141 static void create_map_image(uint8 *data, PixmapInfo *pi)
00142 {
00143     pi->map_image = NULL;
00144     pi->map_mask = NULL;
00145 
00146     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00147 #if defined(HAVE_SDL)
00148         int i;
00149         SDL_Surface *fog;
00150         uint32 g,*p;
00151         uint8 *l;
00152 
00153 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
00154         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00155                 pi->map_height, 32, pi->map_width * 4,  0xff,
00156                         0xff00, 0xff0000, 0xff000000);
00157 
00158         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00159                 pi->map_width,  pi->map_height, 32, 0xff,
00160                         0xff00, 0xff0000, 0xff000000);
00161         SDL_LockSurface(fog);
00162 
00163         for (i=0; i < pi->map_width * pi->map_height; i++) {
00164             l = (uint8 *) (data + i*4);
00165 #if 1
00166             g = MAX(*l, *(l+1));
00167             g = MAX(g, *(l+2));
00168 #else
00169             g = ( *l +  *(l+1) + *(l+2)) / 3;
00170 #endif
00171             p = (uint32*) fog->pixels + i;
00172             *p = g | (g << 8) | (g << 16) | (*(l + 3) << 24);
00173         }
00174 
00175         SDL_UnlockSurface(fog);
00176         pi->fog_image = fog;
00177     #else
00178         /* Big endian */
00179         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00180                 pi->map_height, 32, pi->map_width * 4,  0xff000000,
00181                         0xff0000, 0xff00, 0xff);
00182 
00183         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00184                 pi->map_width,  pi->map_height, 32, 0xff000000,
00185                         0xff0000, 0xff00, 0xff);
00186         SDL_LockSurface(fog);
00187 
00188         /*
00189          * I think this works out, but haven't tried it on a big endian machine
00190          * as my recollection is that the png data would be in the same order,
00191          * just the bytes for it to go on the screen are reversed.
00192          */
00193         for (i=0; i < pi->map_width * pi->map_height; i++) {
00194             l = (uint8 *) (data + i*4);
00195 #if 1
00196             g = MAX(*l, *(l+1));
00197             g = MAX(g, *(l+2));
00198 #else
00199             g = ( *l +  *(l+1) + *(l+2)) / 3;
00200 #endif
00201             p = (uint32*) fog->pixels + i;
00202             *p = (g << 8) | (g << 16) | (g << 24) | *(l + 3);
00203         }
00204 
00205         for (i=0; i < pi->map_width * pi->map_height; i+= 4) {
00206             uint32 *tmp;
00207 
00208             /*
00209              * The pointer arithemtic below looks suspicious, but it is a patch
00210              * that is submitted, so just putting it in as submitted.  MSW
00211              * 2004-05-11
00212              */
00213             p = (uint32*) (fog->pixels + i);
00214             g = ( ((*p >> 24) & 0xff)  + ((*p >> 16) & 0xff) + ((*p >> 8) & 0xff)) / 3;
00215             tmp = (uint32*) fog->pixels + i;
00216             *tmp = (g << 24) | (g << 16) | (g << 8) | (*p & 0xff);
00217         }
00218 
00219         SDL_UnlockSurface(fog);
00220         pi->fog_image = fog;
00221     #endif
00222 
00223 #endif
00224     }
00225     else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_OPENGL){
00226 #ifdef HAVE_OPENGL
00227         create_opengl_map_image(data, pi);
00228 #endif
00229     }
00230 
00231     else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_PIXMAP){
00232         rgba_to_gdkpixmap(window_root->window, data, pi->map_width, pi->map_height,
00233                 (GdkPixmap**)&pi->map_image, (GdkBitmap**)&pi->map_mask,
00234                 gtk_widget_get_colormap(window_root));
00235     }
00236 }
00237 
00243 static void free_pixmap(PixmapInfo *pi)
00244 {
00245     if (pi->icon_image) g_object_unref(pi->icon_image);
00246     if (pi->icon_mask) g_object_unref(pi->icon_mask);
00247     if (pi->map_mask) gdk_pixmap_unref(pi->map_mask);
00248         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00249 #ifdef HAVE_SDL
00250             if (pi->map_image) {
00251                 SDL_FreeSurface(pi->map_image);
00252                 free(((SDL_Surface*)pi->map_image)->pixels);
00253                 SDL_FreeSurface(pi->fog_image);
00254                 /*
00255                  * Minor memory leak here - SDL_FreeSurface() frees the pixel
00256                  * data _unless_ SDL_CreateRGBSurfaceFrom() was used to create
00257                  * the surface.  SDL_CreateRGBSurfaceFrom() is used to create
00258                  * the map data, which is why we need the free there.  The
00259                  * reason this is a minor memory leak is because
00260                  * SDL_CreateRGBSurfaceFrom() is used to create the question
00261                  * mark image, and without this free, that data is not freed.
00262                  * However, with this, client crashes after disconnecting from
00263                  * server with double free.
00264                  */
00265     /*          free(((SDL_Surface*)pi->fog_image)->pixels);*/
00266             }
00267 #endif
00268         }
00269         else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00270 #ifdef HAVE_OPENGL
00271             opengl_free_pixmap(pi);
00272 #endif
00273         }
00274     else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) {
00275         if (pi->map_image) {
00276             gdk_pixmap_unref(pi->map_image);
00277         }
00278     }
00279 }
00280 
00294 int create_and_rescale_image_from_data(Cache_Entry *ce, int pixmap_num, uint8 *rgba_data, int width, int height)
00295 {
00296     int nx, ny, iscale, factor;
00297     uint8 *png_tmp;
00298     PixmapInfo  *pi;
00299 
00300     if (pixmap_num <= 0 || pixmap_num >= MAXPIXMAPNUM)
00301         return 1;
00302 
00303     if (pixmaps[pixmap_num] != pixmaps[0]) {
00304         free_pixmap(pixmaps[pixmap_num]);
00305         free(pixmaps[pixmap_num]);
00306         pixmaps[pixmap_num] = pixmaps[0];
00307     }
00308 
00309     pi = calloc(1, sizeof(PixmapInfo));
00310 
00311     iscale = use_config[CONFIG_ICONSCALE];
00312 
00313     /*
00314      * If the image is big, figure out what we should scale it to so it fits
00315      * better display
00316      */
00317     if (width > DEFAULT_IMAGE_SIZE || height>DEFAULT_IMAGE_SIZE) {
00318         int ts = 100;
00319 
00320         factor = width / DEFAULT_IMAGE_SIZE;
00321         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00322         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00323 
00324         factor = height / DEFAULT_IMAGE_SIZE;
00325         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00326         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00327 
00328         iscale = ts * use_config[CONFIG_ICONSCALE] / 100;
00329     }
00330 
00331     /* In all cases, the icon images are in native form. */
00332     if (iscale != 100) {
00333         nx=width;
00334         ny=height;
00335         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, iscale);
00336         pi->icon_width = nx;
00337         pi->icon_height = ny;
00338         create_icon_image(png_tmp, pi, pixmap_num);
00339         free(png_tmp);
00340     }
00341     else {
00342         pi->icon_width = width;
00343         pi->icon_height = height;
00344         create_icon_image(rgba_data, pi, pixmap_num);
00345     }
00346 
00347     /*
00348      * If icon_scale matched use_config[CONFIG_MAPSCALE], we could try to be
00349      * more intelligent, but this should not be called too often, and this
00350      * keeps the code simpler.
00351      */
00352     if (use_config[CONFIG_MAPSCALE] != 100) {
00353         nx=width;
00354         ny=height;
00355         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, use_config[CONFIG_MAPSCALE]);
00356         pi->map_width = nx;
00357         pi->map_height = ny;
00358         create_map_image(png_tmp, pi);
00359         /*
00360          * pixmap mode and opengl don't need the rgba data after they have
00361          * created the image, so we can free it.  SDL uses the raw rgba data,
00362          * so it can't be freed.
00363          */
00364         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP ||
00365             use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) free(png_tmp);
00366     } else {
00367         pi->map_width = width;
00368         pi->map_height = height;
00369         /*
00370          * If using SDL mode, a copy of the rgba data needs to be stored away.
00371          */
00372         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00373             png_tmp = malloc(width * height * BPP);
00374             memcpy(png_tmp, rgba_data, width * height * BPP);
00375         } else
00376             png_tmp = rgba_data;
00377         create_map_image(png_tmp, pi);
00378     }
00379     /*
00380      * Not ideal, but if it is missing the map or icon image, presume something
00381      * failed.  However, opengl doesn't set the map_image, so if using that
00382      * display mode, don't make this check.
00383      */
00384     if (!pi->icon_image || (!pi->map_image && use_config[CONFIG_DISPLAYMODE]!=CFG_DM_OPENGL)) {
00385         free_pixmap(pi);
00386         free(pi);
00387         return 1;
00388     }
00389     if (ce) {
00390         ce->image_data = pi;
00391     }
00392     pixmaps[pixmap_num] = pi;
00393     return 0;
00394 }
00395 
00402 void addsmooth(uint16 face, uint16 smooth_face)
00403 {
00404     pixmaps[face]->smooth_face = smooth_face;
00405 }
00406 
00415 int associate_cache_entry(Cache_Entry *ce, int pixnum)
00416 {
00417     pixmaps[pixnum] = ce->image_data;
00418     return 0;
00419 }
00420 
00427 void reset_image_data(void)
00428 {
00429     int i;
00430 
00431     reset_image_cache_data();
00432     /*
00433      * The entries in the pixmaps array are also tracked in the image cache in
00434      * the common area.  We will try to recycle those images that we can.
00435      * Thus, if we connect to a new server, we can just re-use the images we
00436      * have already rendered.
00437      */
00438     for (i=1; i<MAXPIXMAPNUM; i++) {
00439         if (!want_config[CONFIG_CACHE] && pixmaps[i] != pixmaps[0]) {
00440             free_pixmap(pixmaps[i]);
00441             free(pixmaps[i]);
00442             pixmaps[i] = pixmaps[0];
00443         }
00444     }
00445 }
00446 
00447 static GtkWidget     *pbar=NULL;
00448 static GtkWidget     *pbar_window=NULL;
00449 static GtkAdjustment *padj=NULL;
00450 
00462 void image_update_download_status(int start, int end, int total)
00463 {
00464     int x, y, wx, wy, w, h;
00465 
00466     if (start == 1) {
00467         padj = (GtkAdjustment*) gtk_adjustment_new (0, 1, total, 0, 0, 0);
00468 
00469         pbar = gtk_progress_bar_new_with_adjustment(padj);
00470         gtk_progress_set_format_string(GTK_PROGRESS(pbar), "Downloading image %v of %u (%p%% complete)");
00471         gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(pbar), GTK_PROGRESS_CONTINUOUS);
00472         gtk_progress_set_show_text(GTK_PROGRESS(pbar), TRUE);
00473         get_window_coord(window_root, &x,&y, &wx,&wy,&w,&h);
00474 
00475         pbar_window = gtk_window_new(GTK_WINDOW_POPUP);
00476         gtk_window_set_policy(GTK_WINDOW(pbar_window), TRUE, TRUE, FALSE);
00477         gtk_window_set_transient_for(GTK_WINDOW(pbar_window), GTK_WINDOW (window_root));
00478         /*
00479          * We more or less want this window centered on the main crossfire
00480          * window, and not necessarily centered on the screen or in the upper
00481          * left corner.
00482          */
00483         gtk_widget_set_uposition(pbar_window, (wx + w)/2, (wy + h) / 2);
00484 
00485         gtk_container_add(GTK_CONTAINER(pbar_window), pbar);
00486         gtk_widget_show(pbar);
00487         gtk_widget_show(pbar_window);
00488     }
00489     if (start == total) {
00490         gtk_widget_destroy(pbar_window);
00491         pbar = NULL;
00492         pbar_window = NULL;
00493         padj = NULL;
00494         return;
00495     }
00496 
00497     gtk_progress_set_value(GTK_PROGRESS(pbar), start);
00498     while ( gtk_events_pending() ) {
00499         gtk_main_iteration();
00500     }
00501 }
00502 
00509 void get_map_image_size(int face, uint8 *w, uint8 *h)
00510 {
00511     /* We want to calculate the number of spaces this image
00512      * uses it.  By adding the image size but substracting one,
00513      * we cover the cases where the image size is not an even
00514      * increment.  EG, if the map_image_size is 32, and an image
00515      * is 33 wide, we want that to register as two spaces.  By
00516      * adding 31, that works out.
00517      */
00518     if ( face < 0 || face >= MAXPIXMAPNUM) {
00519         *w = 1;
00520         *h = 1;
00521     } else {
00522         *w = (pixmaps[face]->map_width + map_image_size - 1)/ map_image_size;
00523         *h = (pixmaps[face]->map_height + map_image_size - 1)/ map_image_size;
00524     }
00525 }
00526 
00527 /******************************************************************************
00528  *
00529  * Code related to face caching.
00530  *
00531  *****************************************************************************/
00532 
00542 void init_cache_data(void)
00543 {
00544     int i;
00545     GtkStyle *style;
00546 #include "../../pixmaps/question.xpm"
00547 
00548 
00549     LOG(LOG_INFO,"gtk::init_cache_data","Init Cache");
00550 
00551     style = gtk_widget_get_style(window_root);
00552     pixmaps[0] = malloc(sizeof(PixmapInfo));
00553     pixmaps[0]->icon_image = gdk_pixmap_create_from_xpm_d(window_root->window,
00554                                                         (GdkBitmap**)&pixmaps[0]->icon_mask,
00555                                                         &style->bg[GTK_STATE_NORMAL],
00556                                                         (gchar **)question_xpm);
00557 #ifdef HAVE_SDL
00558     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00559         /*
00560          * Make a semi-transparent question mark symbol to use for the cached
00561          * images.
00562          */
00563 #include "../../pixmaps/question.sdl"
00564         pixmaps[0]->map_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00565                 32, 32, 1, 4, 1, 1, 1, 1);
00566         SDL_SetAlpha(pixmaps[0]->map_image, SDL_SRCALPHA, 70);
00567         pixmaps[0]->fog_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00568                 32, 32, 1, 4, 1, 1, 1, 1);
00569         SDL_SetAlpha(pixmaps[0]->fog_image, SDL_SRCALPHA, 70);
00570     }
00571     else
00572 #endif
00573     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP)
00574     {
00575         pixmaps[0]->map_image =  pixmaps[0]->icon_image;
00576         pixmaps[0]->fog_image =  pixmaps[0]->icon_image;
00577         pixmaps[0]->map_mask =  pixmaps[0]->icon_mask;
00578     }
00579 #ifdef HAVE_OPENGL
00580     else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00581         create_opengl_question_mark();
00582     }
00583 #endif
00584 
00585     pixmaps[0]->icon_width = pixmaps[0]->icon_height = pixmaps[0]->map_width = pixmaps[0]->map_height = map_image_size;
00586     pixmaps[0]->smooth_face = 0;
00587 
00588     /* Don't do anything special for SDL image - rather, that drawing
00589      * code will check to see if there is no data
00590      */
00591 
00592     /* Initialize all the images to be of the same value. */
00593     for (i=1; i<MAXPIXMAPNUM; i++)  {
00594         pixmaps[i] = pixmaps[0];
00595     }
00596 
00597     init_common_cache_data();
00598 }