Crossfire Client, Trunk  R19307
image.c
Go to the documentation of this file.
00001 /*
00002  * Crossfire -- cooperative multi-player graphical RPG and adventure game
00003  *
00004  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
00005  * Copyright (c) 1992 Frank Tore Johansen
00006  *
00007  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
00008  * welcome to redistribute it under certain conditions. For details, see the
00009  * 'LICENSE' and 'COPYING' files.
00010  *
00011  * The authors can be reached via e-mail to crossfire-devel@real-time.com
00012  */
00013 
00021 #ifdef HAVE_CONFIG_H
00022 #include <config.h>
00023 #endif
00024 
00025 #include <png.h>
00026 #include <stdlib.h>
00027 #include <sys/stat.h>
00028 
00029 #ifndef WIN32
00030 #include <unistd.h>
00031 #endif
00032 
00033 /* Pick up the gtk headers we need */
00034 #include <gtk/gtk.h>
00035 #ifndef WIN32
00036 #include <gdk/gdkx.h>
00037 #else
00038 #include <gdk/gdkwin32.h>
00039 #endif
00040 #include <gdk/gdkkeysyms.h>
00041 
00042 #ifdef HAVE_SDL
00043 #include <SDL.h>
00044 #include <SDL_image.h>
00045 #endif
00046 
00047 #include "client-types.h"
00048 #include "client.h"
00049 #include "image.h"
00050 #include "main.h"
00051 #include "mapdata.h"
00052 #include "gtk2proto.h"
00053 
00054 extern GtkWidget *window_root; 
00055 int image_size=DEFAULT_IMAGE_SIZE;
00056 
00057 struct {
00058     char    *name;
00059     uint32  checksum;
00060     uint8   *png_data;
00061     uint32  width, height;
00062 } private_cache[MAXPIXMAPNUM];
00063 
00064 #define BPP 4
00065 
00066 PixmapInfo *pixmaps[MAXPIXMAPNUM];
00067 
00068 int last_face_num=0;
00069 
00070 /* Do we have new images to display? */
00071 int have_new_image=0;
00072 
00073 /*
00074  * this is used to rescale big images that will be drawn in the inventory/look
00075  * lists.  What the code further below basically does is figure out how big the
00076  * object is (in squares), and this looks at the icon_rescale_factor to figure
00077  * what scale factor it gives.  Not that the icon_rescale_factor values are
00078  * passed directly to the rescale routines.  These represent percentages - so
00079  * even taking into account that the values diminish as the table grows, they
00080  * will still appear larger if the location in the table times the factor is
00081  * greater than 100.  We find the largest dimension that the image has.  The
00082  * values in the comment is the effective scaling compared to the base image
00083  * size that this big image will appear as.  Using a table makes it easier to
00084  * adjust the values so things look right.
00085  */
00086 
00087 #define MAX_ICON_SPACES     10
00088 static const int icon_rescale_factor[MAX_ICON_SPACES] = {
00089     100, 100,           80 /* 2 = 160 */,   60 /* 3 = 180 */,
00090     50 /* 4 = 200 */,   45 /* 5 = 225 */,   40 /* 6 = 240 */,
00091     35 /* 7 = 259 */,   35 /* 8 = 280 */,   33 /* 9 = 300 */
00092 };
00093 
00094 /******************************************************************************
00095  *
00096  * Code related to face caching.
00097  *
00098  *****************************************************************************/
00099 
00100 /* Does not appear to be used anywhere
00101 typedef struct Keys {
00102     uint8       flags;
00103     sint8       direction;
00104     KeySym      keysym;
00105     char        *command;
00106     struct Keys *next;
00107 } Key_Entry;
00108 */
00109 
00110 /* Rotate right from bsd sum. */
00111 #define ROTATE_RIGHT(c) if ((c) & 01) (c) = ((c) >>1) + 0x80000000; else (c) >>= 1;
00112 
00113 /*#define CHECKSUM_DEBUG*/
00114 
00122 static void create_icon_image(uint8 *data, PixmapInfo *pi, int pixmap_num)
00123 {
00124     pi->icon_mask = NULL;
00125     if (rgba_to_gdkpixbuf(data, pi->icon_width, pi->icon_height,
00126                           (GdkPixbuf**)&pi->icon_image)) {
00127         LOG (LOG_ERROR,"gtk-v2::create_icon_image","Unable to create scaled image, dest num = %d\n", pixmap_num);
00128     }
00129 }
00130 
00137 static void create_map_image(uint8 *data, PixmapInfo *pi)
00138 {
00139     pi->map_image = NULL;
00140     pi->map_mask = NULL;
00141 
00142     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00143 #if defined(HAVE_SDL)
00144         int i;
00145         SDL_Surface *fog;
00146         uint32 g,*p;
00147         uint8 *l;
00148 
00149 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
00150         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00151                         pi->map_height, 32, pi->map_width * 4,  0xff,
00152                         0xff00, 0xff0000, 0xff000000);
00153 
00154         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00155                                    pi->map_width,  pi->map_height, 32, 0xff,
00156                                    0xff00, 0xff0000, 0xff000000);
00157         SDL_LockSurface(fog);
00158 
00159         for (i=0; i < pi->map_width * pi->map_height; i++) {
00160             l = (uint8 *) (data + i*4);
00161 #if 1
00162             g = MAX(*l, *(l+1));
00163             g = MAX(g, *(l+2));
00164 #else
00165             g = ( *l +  *(l+1) + *(l+2)) / 3;
00166 #endif
00167             p = (uint32*) fog->pixels + i;
00168             *p = g | (g << 8) | (g << 16) | (*(l + 3) << 24);
00169         }
00170 
00171         SDL_UnlockSurface(fog);
00172         pi->fog_image = fog;
00173 #else
00174         /* Big endian */
00175         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00176                         pi->map_height, 32, pi->map_width * 4,  0xff000000,
00177                         0xff0000, 0xff00, 0xff);
00178 
00179         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00180                                    pi->map_width,  pi->map_height, 32, 0xff000000,
00181                                    0xff0000, 0xff00, 0xff);
00182         SDL_LockSurface(fog);
00183 
00184         /*
00185          * I think this works out, but haven't tried it on a big endian machine
00186          * as my recollection is that the png data would be in the same order,
00187          * just the bytes for it to go on the screen are reversed.
00188          */
00189         for (i=0; i < pi->map_width * pi->map_height; i++) {
00190             l = (uint8 *) (data + i*4);
00191 #if 1
00192             g = MAX(*l, *(l+1));
00193             g = MAX(g, *(l+2));
00194 #else
00195             g = ( *l +  *(l+1) + *(l+2)) / 3;
00196 #endif
00197             p = (uint32*) fog->pixels + i;
00198             *p = (g << 8) | (g << 16) | (g << 24) | *(l + 3);
00199         }
00200 
00201         for (i=0; i < pi->map_width * pi->map_height; i+= 4) {
00202             uint32 *tmp;
00203 
00204             /*
00205              * The pointer arithemtic below looks suspicious, but it is a patch
00206              * that is submitted, so just putting it in as submitted.  MSW
00207              * 2004-05-11
00208              */
00209             p = (uint32*) (fog->pixels + i);
00210             g = ( ((*p >> 24) & 0xff)  + ((*p >> 16) & 0xff) + ((*p >> 8) & 0xff)) / 3;
00211             tmp = (uint32*) fog->pixels + i;
00212             *tmp = (g << 24) | (g << 16) | (g << 8) | (*p & 0xff);
00213         }
00214 
00215         SDL_UnlockSurface(fog);
00216         pi->fog_image = fog;
00217 #endif
00218 
00219 #endif
00220     } else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_OPENGL) {
00221 #ifdef HAVE_OPENGL
00222         create_opengl_map_image(data, pi);
00223 #endif
00224     }
00225 
00226     else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_PIXMAP) {
00227         rgba_to_gdkpixmap(window_root->window, data, pi->map_width, pi->map_height,
00228                           (GdkPixmap**)&pi->map_image, (GdkBitmap**)&pi->map_mask,
00229                           gtk_widget_get_colormap(window_root));
00230     }
00231 }
00232 
00238 static void free_pixmap(PixmapInfo *pi)
00239 {
00240     if (pi->icon_image) {
00241         g_object_unref(pi->icon_image);
00242     }
00243     if (pi->icon_mask) {
00244         g_object_unref(pi->icon_mask);
00245     }
00246     if (pi->map_mask) {
00247         gdk_pixmap_unref(pi->map_mask);
00248     }
00249     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00250 #ifdef HAVE_SDL
00251         if (pi->map_image) {
00252             SDL_FreeSurface(pi->map_image);
00253             free(((SDL_Surface*)pi->map_image)->pixels);
00254             SDL_FreeSurface(pi->fog_image);
00255             /*
00256              * Minor memory leak here - SDL_FreeSurface() frees the pixel
00257              * data _unless_ SDL_CreateRGBSurfaceFrom() was used to create
00258              * the surface.  SDL_CreateRGBSurfaceFrom() is used to create
00259              * the map data, which is why we need the free there.  The
00260              * reason this is a minor memory leak is because
00261              * SDL_CreateRGBSurfaceFrom() is used to create the question
00262              * mark image, and without this free, that data is not freed.
00263              * However, with this, client crashes after disconnecting from
00264              * server with double free.
00265              */
00266             /*          free(((SDL_Surface*)pi->fog_image)->pixels);*/
00267         }
00268 #endif
00269     } else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00270 #ifdef HAVE_OPENGL
00271         opengl_free_pixmap(pi);
00272 #endif
00273     } else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) {
00274         if (pi->map_image) {
00275             gdk_pixmap_unref(pi->map_image);
00276         }
00277     }
00278 }
00279 
00293 int create_and_rescale_image_from_data(Cache_Entry *ce, int pixmap_num, uint8 *rgba_data, int width, int height)
00294 {
00295     int nx, ny, iscale, factor;
00296     uint8 *png_tmp;
00297     PixmapInfo  *pi;
00298 
00299     if (pixmap_num <= 0 || pixmap_num >= MAXPIXMAPNUM) {
00300         return 1;
00301     }
00302 
00303     if (pixmaps[pixmap_num] != pixmaps[0]) {
00304         /* As per bug 2938906, one can see image corruption when switching between
00305          * servers.  The cause is that the cache table stores away
00306          * a pointer to the pixmap[] entry - if we go and free it,
00307          * the cache table can point to garbage, so don't free it.
00308          * This causes some memory leak, but if/when there is good
00309          * cache support for multiple servers, eventually the amount
00310          * of memory consumed will reach a limit (it has every image of
00311          * every server in memory
00312          *
00313          * The cause of image corruption requires a few different things:
00314          * 1) images of the same name have different numbers on the 2 serves.
00315          * 2) the image number is higher on the first than second server
00316          * 3) the image using the high number does not exist/is different
00317          *    on the second server, causing this routine to be called.
00318          */
00319 
00320         if (!use_config[CONFIG_CACHE]) {
00321             free_pixmap(pixmaps[pixmap_num]);
00322             free(pixmaps[pixmap_num]);
00323         }
00324         pixmaps[pixmap_num] = pixmaps[0];
00325     }
00326 
00327     pi = calloc(1, sizeof(PixmapInfo));
00328 
00329     iscale = use_config[CONFIG_ICONSCALE];
00330 
00331     /*
00332      * If the image is big, figure out what we should scale it to so it fits
00333      * better display
00334      */
00335     if (width > DEFAULT_IMAGE_SIZE || height>DEFAULT_IMAGE_SIZE) {
00336         int ts = 100;
00337 
00338         factor = width / DEFAULT_IMAGE_SIZE;
00339         if (factor >= MAX_ICON_SPACES) {
00340             factor = MAX_ICON_SPACES - 1;
00341         }
00342         if (icon_rescale_factor[factor] < ts) {
00343             ts = icon_rescale_factor[factor];
00344         }
00345 
00346         factor = height / DEFAULT_IMAGE_SIZE;
00347         if (factor >= MAX_ICON_SPACES) {
00348             factor = MAX_ICON_SPACES - 1;
00349         }
00350         if (icon_rescale_factor[factor] < ts) {
00351             ts = icon_rescale_factor[factor];
00352         }
00353 
00354         iscale = ts * use_config[CONFIG_ICONSCALE] / 100;
00355     }
00356 
00357     /* In all cases, the icon images are in native form. */
00358     if (iscale != 100) {
00359         nx=width;
00360         ny=height;
00361         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, iscale);
00362         pi->icon_width = nx;
00363         pi->icon_height = ny;
00364         create_icon_image(png_tmp, pi, pixmap_num);
00365         free(png_tmp);
00366     } else {
00367         pi->icon_width = width;
00368         pi->icon_height = height;
00369         create_icon_image(rgba_data, pi, pixmap_num);
00370     }
00371 
00372     /*
00373      * If icon_scale matched use_config[CONFIG_MAPSCALE], we could try to be
00374      * more intelligent, but this should not be called too often, and this
00375      * keeps the code simpler.
00376      */
00377     if (use_config[CONFIG_MAPSCALE] != 100) {
00378         nx=width;
00379         ny=height;
00380         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, use_config[CONFIG_MAPSCALE]);
00381         pi->map_width = nx;
00382         pi->map_height = ny;
00383         create_map_image(png_tmp, pi);
00384         /*
00385          * pixmap mode and opengl don't need the rgba data after they have
00386          * created the image, so we can free it.  SDL uses the raw rgba data,
00387          * so it can't be freed.
00388          */
00389         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP ||
00390                 use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00391             free(png_tmp);
00392         }
00393     } else {
00394         pi->map_width = width;
00395         pi->map_height = height;
00396         /*
00397          * If using SDL mode, a copy of the rgba data needs to be stored away.
00398          */
00399         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00400             png_tmp = malloc(width * height * BPP);
00401             memcpy(png_tmp, rgba_data, width * height * BPP);
00402         } else {
00403             png_tmp = rgba_data;
00404         }
00405         create_map_image(png_tmp, pi);
00406     }
00407     /*
00408      * Not ideal, but if it is missing the map or icon image, presume something
00409      * failed.  However, opengl doesn't set the map_image, so if using that
00410      * display mode, don't make this check.
00411      */
00412     if (!pi->icon_image || (!pi->map_image && use_config[CONFIG_DISPLAYMODE]!=CFG_DM_OPENGL)) {
00413         free_pixmap(pi);
00414         free(pi);
00415         return 1;
00416     }
00417     if (ce) {
00418         ce->image_data = pi;
00419     }
00420     pixmaps[pixmap_num] = pi;
00421     if (use_config[CONFIG_CACHE]) {
00422         have_new_image++;
00423     }
00424 
00425     return 0;
00426 }
00427 
00434 void addsmooth(uint16 face, uint16 smooth_face)
00435 {
00436     pixmaps[face]->smooth_face = smooth_face;
00437 }
00438 
00447 int associate_cache_entry(Cache_Entry *ce, int pixnum)
00448 {
00449     pixmaps[pixnum] = ce->image_data;
00450     return 0;
00451 }
00452 
00459 void reset_image_data(void)
00460 {
00461     int i;
00462 
00463     reset_image_cache_data();
00464     /*
00465      * The entries in the pixmaps array are also tracked in the image cache in
00466      * the common area.  We will try to recycle those images that we can.
00467      * Thus, if we connect to a new server, we can just re-use the images we
00468      * have already rendered.
00469      */
00470     for (i=1; i<MAXPIXMAPNUM; i++) {
00471         if (!want_config[CONFIG_CACHE] && pixmaps[i] != pixmaps[0]) {
00472             free_pixmap(pixmaps[i]);
00473             free(pixmaps[i]);
00474             pixmaps[i] = pixmaps[0];
00475         }
00476     }
00477 }
00478 
00479 static GtkWidget     *pbar=NULL;
00480 static GtkWidget     *pbar_window=NULL;
00481 static GtkAdjustment *padj=NULL;
00482 
00494 void image_update_download_status(int start, int end, int total)
00495 {
00496     int x, y, wx, wy, w, h;
00497 
00498     if (start == 1) {
00499         padj = (GtkAdjustment*) gtk_adjustment_new (0, 1, total, 0, 0, 0);
00500 
00501         pbar = gtk_progress_bar_new_with_adjustment(padj);
00502         gtk_progress_set_format_string(GTK_PROGRESS(pbar), "Downloading image %v of %u (%p%% complete)");
00503         gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(pbar), GTK_PROGRESS_CONTINUOUS);
00504         gtk_progress_set_show_text(GTK_PROGRESS(pbar), TRUE);
00505         get_window_coord(window_root, &x,&y, &wx,&wy,&w,&h);
00506 
00507         pbar_window = gtk_window_new(GTK_WINDOW_POPUP);
00508         gtk_window_set_policy(GTK_WINDOW(pbar_window), TRUE, TRUE, FALSE);
00509         gtk_window_set_transient_for(GTK_WINDOW(pbar_window), GTK_WINDOW (window_root));
00510         /*
00511          * We more or less want this window centered on the main crossfire
00512          * window, and not necessarily centered on the screen or in the upper
00513          * left corner.
00514          */
00515         gtk_widget_set_uposition(pbar_window, (wx + w)/2, (wy + h) / 2);
00516 
00517         gtk_container_add(GTK_CONTAINER(pbar_window), pbar);
00518         gtk_widget_show(pbar);
00519         gtk_widget_show(pbar_window);
00520     }
00521     if (start == total) {
00522         gtk_widget_destroy(pbar_window);
00523         pbar = NULL;
00524         pbar_window = NULL;
00525         padj = NULL;
00526         return;
00527     }
00528 
00529     gtk_progress_set_value(GTK_PROGRESS(pbar), start);
00530     while ( gtk_events_pending() ) {
00531         gtk_main_iteration();
00532     }
00533 }
00534 
00541 void get_map_image_size(int face, uint8 *w, uint8 *h)
00542 {
00543     /* We want to calculate the number of spaces this image
00544      * uses it.  By adding the image size but substracting one,
00545      * we cover the cases where the image size is not an even
00546      * increment.  EG, if the map_image_size is 32, and an image
00547      * is 33 wide, we want that to register as two spaces.  By
00548      * adding 31, that works out.
00549      */
00550     if ( face < 0 || face >= MAXPIXMAPNUM) {
00551         *w = 1;
00552         *h = 1;
00553     } else {
00554         *w = (pixmaps[face]->map_width + map_image_size - 1)/ map_image_size;
00555         *h = (pixmaps[face]->map_height + map_image_size - 1)/ map_image_size;
00556     }
00557 }
00558 
00559 /******************************************************************************
00560  *
00561  * Code related to face caching.
00562  *
00563  *****************************************************************************/
00564 
00574 void init_image_cache_data(void)
00575 {
00576     int i;
00577     GtkStyle *style;
00578 #include "../../pixmaps/question.xpm"
00579 
00580 
00581     LOG(LOG_INFO,"gtk-v2::init_image_cache_data","Init Image Cache");
00582 
00583     style = gtk_widget_get_style(window_root);
00584     pixmaps[0] = malloc(sizeof(PixmapInfo));
00585     pixmaps[0]->icon_image = gdk_pixmap_create_from_xpm_d(window_root->window,
00586                              (GdkBitmap**)&pixmaps[0]->icon_mask,
00587                              &style->bg[GTK_STATE_NORMAL],
00588                              (gchar **)question_xpm);
00589 #ifdef HAVE_SDL
00590     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00591         /*
00592          * Make a semi-transparent question mark symbol to use for the cached
00593          * images.
00594          */
00595 #include "../../pixmaps/question.sdl"
00596         pixmaps[0]->map_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00597                                 32, 32, 1, 4, 1, 1, 1, 1);
00598         SDL_SetAlpha(pixmaps[0]->map_image, SDL_SRCALPHA, 70);
00599         pixmaps[0]->fog_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00600                                 32, 32, 1, 4, 1, 1, 1, 1);
00601         SDL_SetAlpha(pixmaps[0]->fog_image, SDL_SRCALPHA, 70);
00602     } else
00603 #endif
00604         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) {
00605             pixmaps[0]->map_image =  pixmaps[0]->icon_image;
00606             pixmaps[0]->fog_image =  pixmaps[0]->icon_image;
00607             pixmaps[0]->map_mask =  pixmaps[0]->icon_mask;
00608         }
00609 #ifdef HAVE_OPENGL
00610         else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00611             create_opengl_question_mark();
00612         }
00613 #endif
00614 
00615     pixmaps[0]->icon_width = pixmaps[0]->icon_height = pixmaps[0]->map_width = pixmaps[0]->map_height = map_image_size;
00616     pixmaps[0]->smooth_face = 0;
00617 
00618     /* Don't do anything special for SDL image - rather, that drawing
00619      * code will check to see if there is no data
00620      */
00621 
00622     /* Initialize all the images to be of the same value. */
00623     for (i=1; i<MAXPIXMAPNUM; i++)  {
00624         pixmaps[i] = pixmaps[0];
00625     }
00626 
00627     init_common_cache_data();
00628 }