Crossfire Client, Trunk  R18666
/home/leaf/crossfire/client/trunk/gtk-v2/src/image.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk2_image_c =
00002     "$Id: image.c 12987 2010-04-27 03:50:58Z kbulgrien $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2005-2008,2010 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 /* Do we have new images to display? */
00079 int have_new_image=0;
00080 
00081 /*
00082  * this is used to rescale big images that will be drawn in the inventory/look
00083  * lists.  What the code further below basically does is figure out how big the
00084  * object is (in squares), and this looks at the icon_rescale_factor to figure
00085  * what scale factor it gives.  Not that the icon_rescale_factor values are
00086  * passed directly to the rescale routines.  These represent percentages - so
00087  * even taking into account that the values diminish as the table grows, they
00088  * will still appear larger if the location in the table times the factor is
00089  * greater than 100.  We find the largest dimension that the image has.  The
00090  * values in the comment is the effective scaling compared to the base image
00091  * size that this big image will appear as.  Using a table makes it easier to
00092  * adjust the values so things look right.
00093  */
00094 
00095 #define MAX_ICON_SPACES     10
00096 static const int icon_rescale_factor[MAX_ICON_SPACES] = {
00097 100, 100,           80 /* 2 = 160 */,   60 /* 3 = 180 */,
00098 50 /* 4 = 200 */,   45 /* 5 = 225 */,   40 /* 6 = 240 */,
00099 35 /* 7 = 259 */,   35 /* 8 = 280 */,   33 /* 9 = 300 */
00100 };
00101 
00102 /******************************************************************************
00103  *
00104  * Code related to face caching.
00105  *
00106  *****************************************************************************/
00107 
00108 /* Does not appear to be used anywhere
00109 typedef struct Keys {
00110     uint8       flags;
00111     sint8       direction;
00112     KeySym      keysym;
00113     char        *command;
00114     struct Keys *next;
00115 } Key_Entry;
00116 */
00117 
00118 /* Rotate right from bsd sum. */
00119 #define ROTATE_RIGHT(c) if ((c) & 01) (c) = ((c) >>1) + 0x80000000; else (c) >>= 1;
00120 
00121 /*#define CHECKSUM_DEBUG*/
00122 
00130 static void create_icon_image(uint8 *data, PixmapInfo *pi, int pixmap_num)
00131 {
00132     pi->icon_mask = NULL;
00133     if (rgba_to_gdkpixbuf(data, pi->icon_width, pi->icon_height,
00134                 (GdkPixbuf**)&pi->icon_image))
00135                     LOG (LOG_ERROR,"gtk-v2::create_icon_image","Unable to create scaled image, dest num = %d\n", pixmap_num);
00136 }
00137 
00144 static void create_map_image(uint8 *data, PixmapInfo *pi)
00145 {
00146     pi->map_image = NULL;
00147     pi->map_mask = NULL;
00148 
00149     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00150 #if defined(HAVE_SDL)
00151         int i;
00152         SDL_Surface *fog;
00153         uint32 g,*p;
00154         uint8 *l;
00155 
00156 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
00157         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00158                 pi->map_height, 32, pi->map_width * 4,  0xff,
00159                         0xff00, 0xff0000, 0xff000000);
00160 
00161         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00162                 pi->map_width,  pi->map_height, 32, 0xff,
00163                         0xff00, 0xff0000, 0xff000000);
00164         SDL_LockSurface(fog);
00165 
00166         for (i=0; i < pi->map_width * pi->map_height; i++) {
00167             l = (uint8 *) (data + i*4);
00168 #if 1
00169             g = MAX(*l, *(l+1));
00170             g = MAX(g, *(l+2));
00171 #else
00172             g = ( *l +  *(l+1) + *(l+2)) / 3;
00173 #endif
00174             p = (uint32*) fog->pixels + i;
00175             *p = g | (g << 8) | (g << 16) | (*(l + 3) << 24);
00176         }
00177 
00178         SDL_UnlockSurface(fog);
00179         pi->fog_image = fog;
00180     #else
00181         /* Big endian */
00182         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00183                 pi->map_height, 32, pi->map_width * 4,  0xff000000,
00184                         0xff0000, 0xff00, 0xff);
00185 
00186         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00187                 pi->map_width,  pi->map_height, 32, 0xff000000,
00188                         0xff0000, 0xff00, 0xff);
00189         SDL_LockSurface(fog);
00190 
00191         /*
00192          * I think this works out, but haven't tried it on a big endian machine
00193          * as my recollection is that the png data would be in the same order,
00194          * just the bytes for it to go on the screen are reversed.
00195          */
00196         for (i=0; i < pi->map_width * pi->map_height; i++) {
00197             l = (uint8 *) (data + i*4);
00198 #if 1
00199             g = MAX(*l, *(l+1));
00200             g = MAX(g, *(l+2));
00201 #else
00202             g = ( *l +  *(l+1) + *(l+2)) / 3;
00203 #endif
00204             p = (uint32*) fog->pixels + i;
00205             *p = (g << 8) | (g << 16) | (g << 24) | *(l + 3);
00206         }
00207 
00208         for (i=0; i < pi->map_width * pi->map_height; i+= 4) {
00209             uint32 *tmp;
00210 
00211             /*
00212              * The pointer arithemtic below looks suspicious, but it is a patch
00213              * that is submitted, so just putting it in as submitted.  MSW
00214              * 2004-05-11
00215              */
00216             p = (uint32*) (fog->pixels + i);
00217             g = ( ((*p >> 24) & 0xff)  + ((*p >> 16) & 0xff) + ((*p >> 8) & 0xff)) / 3;
00218             tmp = (uint32*) fog->pixels + i;
00219             *tmp = (g << 24) | (g << 16) | (g << 8) | (*p & 0xff);
00220         }
00221 
00222         SDL_UnlockSurface(fog);
00223         pi->fog_image = fog;
00224     #endif
00225 
00226 #endif
00227     }
00228     else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_OPENGL){
00229 #ifdef HAVE_OPENGL
00230         create_opengl_map_image(data, pi);
00231 #endif
00232     }
00233 
00234     else if (use_config[CONFIG_DISPLAYMODE] == CFG_DM_PIXMAP){
00235         rgba_to_gdkpixmap(window_root->window, data, pi->map_width, pi->map_height,
00236                 (GdkPixmap**)&pi->map_image, (GdkBitmap**)&pi->map_mask,
00237                 gtk_widget_get_colormap(window_root));
00238     }
00239 }
00240 
00246 static void free_pixmap(PixmapInfo *pi)
00247 {
00248     if (pi->icon_image) g_object_unref(pi->icon_image);
00249     if (pi->icon_mask) g_object_unref(pi->icon_mask);
00250     if (pi->map_mask) gdk_pixmap_unref(pi->map_mask);
00251         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00252 #ifdef HAVE_SDL
00253             if (pi->map_image) {
00254                 SDL_FreeSurface(pi->map_image);
00255                 free(((SDL_Surface*)pi->map_image)->pixels);
00256                 SDL_FreeSurface(pi->fog_image);
00257                 /*
00258                  * Minor memory leak here - SDL_FreeSurface() frees the pixel
00259                  * data _unless_ SDL_CreateRGBSurfaceFrom() was used to create
00260                  * the surface.  SDL_CreateRGBSurfaceFrom() is used to create
00261                  * the map data, which is why we need the free there.  The
00262                  * reason this is a minor memory leak is because
00263                  * SDL_CreateRGBSurfaceFrom() is used to create the question
00264                  * mark image, and without this free, that data is not freed.
00265                  * However, with this, client crashes after disconnecting from
00266                  * server with double free.
00267                  */
00268     /*          free(((SDL_Surface*)pi->fog_image)->pixels);*/
00269             }
00270 #endif
00271         }
00272         else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00273 #ifdef HAVE_OPENGL
00274             opengl_free_pixmap(pi);
00275 #endif
00276         }
00277     else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) {
00278         if (pi->map_image) {
00279             gdk_pixmap_unref(pi->map_image);
00280         }
00281     }
00282 }
00283 
00297 int create_and_rescale_image_from_data(Cache_Entry *ce, int pixmap_num, uint8 *rgba_data, int width, int height)
00298 {
00299     int nx, ny, iscale, factor;
00300     uint8 *png_tmp;
00301     PixmapInfo  *pi;
00302 
00303     if (pixmap_num <= 0 || pixmap_num >= MAXPIXMAPNUM)
00304         return 1;
00305 
00306     if (pixmaps[pixmap_num] != pixmaps[0]) {
00307         /* As per bug 2938906, one can see image corruption when switching between
00308          * servers.  The cause is that the cache table stores away
00309          * a pointer to the pixmap[] entry - if we go and free it,
00310          * the cache table can point to garbage, so don't free it.
00311          * This causes some memory leak, but if/when there is good
00312          * cache support for multiple servers, eventually the amount
00313          * of memory consumed will reach a limit (it has every image of
00314          * every server in memory
00315          *
00316          * The cause of image corruption requires a few different things:
00317          * 1) images of the same name have different numbers on the 2 serves.
00318          * 2) the image number is higher on the first than second server
00319          * 3) the image using the high number does not exist/is different
00320          *    on the second server, causing this routine to be called.
00321          */
00322 
00323          if (!use_config[CONFIG_CACHE]) {
00324              free_pixmap(pixmaps[pixmap_num]);
00325              free(pixmaps[pixmap_num]);
00326          }
00327         pixmaps[pixmap_num] = pixmaps[0];
00328     }
00329 
00330     pi = calloc(1, sizeof(PixmapInfo));
00331 
00332     iscale = use_config[CONFIG_ICONSCALE];
00333 
00334     /*
00335      * If the image is big, figure out what we should scale it to so it fits
00336      * better display
00337      */
00338     if (width > DEFAULT_IMAGE_SIZE || height>DEFAULT_IMAGE_SIZE) {
00339         int ts = 100;
00340 
00341         factor = width / DEFAULT_IMAGE_SIZE;
00342         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00343         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00344 
00345         factor = height / DEFAULT_IMAGE_SIZE;
00346         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00347         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00348 
00349         iscale = ts * use_config[CONFIG_ICONSCALE] / 100;
00350     }
00351 
00352     /* In all cases, the icon images are in native form. */
00353     if (iscale != 100) {
00354         nx=width;
00355         ny=height;
00356         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, iscale);
00357         pi->icon_width = nx;
00358         pi->icon_height = ny;
00359         create_icon_image(png_tmp, pi, pixmap_num);
00360         free(png_tmp);
00361     }
00362     else {
00363         pi->icon_width = width;
00364         pi->icon_height = height;
00365         create_icon_image(rgba_data, pi, pixmap_num);
00366     }
00367 
00368     /*
00369      * If icon_scale matched use_config[CONFIG_MAPSCALE], we could try to be
00370      * more intelligent, but this should not be called too often, and this
00371      * keeps the code simpler.
00372      */
00373     if (use_config[CONFIG_MAPSCALE] != 100) {
00374         nx=width;
00375         ny=height;
00376         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, use_config[CONFIG_MAPSCALE]);
00377         pi->map_width = nx;
00378         pi->map_height = ny;
00379         create_map_image(png_tmp, pi);
00380         /*
00381          * pixmap mode and opengl don't need the rgba data after they have
00382          * created the image, so we can free it.  SDL uses the raw rgba data,
00383          * so it can't be freed.
00384          */
00385         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP ||
00386             use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) free(png_tmp);
00387     } else {
00388         pi->map_width = width;
00389         pi->map_height = height;
00390         /*
00391          * If using SDL mode, a copy of the rgba data needs to be stored away.
00392          */
00393         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00394             png_tmp = malloc(width * height * BPP);
00395             memcpy(png_tmp, rgba_data, width * height * BPP);
00396         } else
00397             png_tmp = rgba_data;
00398         create_map_image(png_tmp, pi);
00399     }
00400     /*
00401      * Not ideal, but if it is missing the map or icon image, presume something
00402      * failed.  However, opengl doesn't set the map_image, so if using that
00403      * display mode, don't make this check.
00404      */
00405     if (!pi->icon_image || (!pi->map_image && use_config[CONFIG_DISPLAYMODE]!=CFG_DM_OPENGL)) {
00406         free_pixmap(pi);
00407         free(pi);
00408         return 1;
00409     }
00410     if (ce) {
00411         ce->image_data = pi;
00412     }
00413     pixmaps[pixmap_num] = pi;
00414     if (use_config[CONFIG_CACHE])
00415         have_new_image++;
00416 
00417     return 0;
00418 }
00419 
00426 void addsmooth(uint16 face, uint16 smooth_face)
00427 {
00428     pixmaps[face]->smooth_face = smooth_face;
00429 }
00430 
00439 int associate_cache_entry(Cache_Entry *ce, int pixnum)
00440 {
00441     pixmaps[pixnum] = ce->image_data;
00442     return 0;
00443 }
00444 
00451 void reset_image_data(void)
00452 {
00453     int i;
00454 
00455     reset_image_cache_data();
00456     /*
00457      * The entries in the pixmaps array are also tracked in the image cache in
00458      * the common area.  We will try to recycle those images that we can.
00459      * Thus, if we connect to a new server, we can just re-use the images we
00460      * have already rendered.
00461      */
00462     for (i=1; i<MAXPIXMAPNUM; i++) {
00463         if (!want_config[CONFIG_CACHE] && pixmaps[i] != pixmaps[0]) {
00464             free_pixmap(pixmaps[i]);
00465             free(pixmaps[i]);
00466             pixmaps[i] = pixmaps[0];
00467         }
00468     }
00469 }
00470 
00471 static GtkWidget     *pbar=NULL;
00472 static GtkWidget     *pbar_window=NULL;
00473 static GtkAdjustment *padj=NULL;
00474 
00486 void image_update_download_status(int start, int end, int total)
00487 {
00488     int x, y, wx, wy, w, h;
00489 
00490     if (start == 1) {
00491         padj = (GtkAdjustment*) gtk_adjustment_new (0, 1, total, 0, 0, 0);
00492 
00493         pbar = gtk_progress_bar_new_with_adjustment(padj);
00494         gtk_progress_set_format_string(GTK_PROGRESS(pbar), "Downloading image %v of %u (%p%% complete)");
00495         gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(pbar), GTK_PROGRESS_CONTINUOUS);
00496         gtk_progress_set_show_text(GTK_PROGRESS(pbar), TRUE);
00497         get_window_coord(window_root, &x,&y, &wx,&wy,&w,&h);
00498 
00499         pbar_window = gtk_window_new(GTK_WINDOW_POPUP);
00500         gtk_window_set_policy(GTK_WINDOW(pbar_window), TRUE, TRUE, FALSE);
00501         gtk_window_set_transient_for(GTK_WINDOW(pbar_window), GTK_WINDOW (window_root));
00502         /*
00503          * We more or less want this window centered on the main crossfire
00504          * window, and not necessarily centered on the screen or in the upper
00505          * left corner.
00506          */
00507         gtk_widget_set_uposition(pbar_window, (wx + w)/2, (wy + h) / 2);
00508 
00509         gtk_container_add(GTK_CONTAINER(pbar_window), pbar);
00510         gtk_widget_show(pbar);
00511         gtk_widget_show(pbar_window);
00512     }
00513     if (start == total) {
00514         gtk_widget_destroy(pbar_window);
00515         pbar = NULL;
00516         pbar_window = NULL;
00517         padj = NULL;
00518         return;
00519     }
00520 
00521     gtk_progress_set_value(GTK_PROGRESS(pbar), start);
00522     while ( gtk_events_pending() ) {
00523         gtk_main_iteration();
00524     }
00525 }
00526 
00533 void get_map_image_size(int face, uint8 *w, uint8 *h)
00534 {
00535     /* We want to calculate the number of spaces this image
00536      * uses it.  By adding the image size but substracting one,
00537      * we cover the cases where the image size is not an even
00538      * increment.  EG, if the map_image_size is 32, and an image
00539      * is 33 wide, we want that to register as two spaces.  By
00540      * adding 31, that works out.
00541      */
00542     if ( face < 0 || face >= MAXPIXMAPNUM) {
00543         *w = 1;
00544         *h = 1;
00545     } else {
00546         *w = (pixmaps[face]->map_width + map_image_size - 1)/ map_image_size;
00547         *h = (pixmaps[face]->map_height + map_image_size - 1)/ map_image_size;
00548     }
00549 }
00550 
00551 /******************************************************************************
00552  *
00553  * Code related to face caching.
00554  *
00555  *****************************************************************************/
00556 
00566 void init_image_cache_data(void)
00567 {
00568     int i;
00569     GtkStyle *style;
00570 #include "../../pixmaps/question.xpm"
00571 
00572 
00573     LOG(LOG_INFO,"gtk-v2::init_image_cache_data","Init Image Cache");
00574 
00575     style = gtk_widget_get_style(window_root);
00576     pixmaps[0] = malloc(sizeof(PixmapInfo));
00577     pixmaps[0]->icon_image = gdk_pixmap_create_from_xpm_d(window_root->window,
00578                                                         (GdkBitmap**)&pixmaps[0]->icon_mask,
00579                                                         &style->bg[GTK_STATE_NORMAL],
00580                                                         (gchar **)question_xpm);
00581 #ifdef HAVE_SDL
00582     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00583         /*
00584          * Make a semi-transparent question mark symbol to use for the cached
00585          * images.
00586          */
00587 #include "../../pixmaps/question.sdl"
00588         pixmaps[0]->map_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00589                 32, 32, 1, 4, 1, 1, 1, 1);
00590         SDL_SetAlpha(pixmaps[0]->map_image, SDL_SRCALPHA, 70);
00591         pixmaps[0]->fog_image = SDL_CreateRGBSurfaceFrom(question_sdl,
00592                 32, 32, 1, 4, 1, 1, 1, 1);
00593         SDL_SetAlpha(pixmaps[0]->fog_image, SDL_SRCALPHA, 70);
00594     }
00595     else
00596 #endif
00597     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP)
00598     {
00599         pixmaps[0]->map_image =  pixmaps[0]->icon_image;
00600         pixmaps[0]->fog_image =  pixmaps[0]->icon_image;
00601         pixmaps[0]->map_mask =  pixmaps[0]->icon_mask;
00602     }
00603 #ifdef HAVE_OPENGL
00604     else if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_OPENGL) {
00605         create_opengl_question_mark();
00606     }
00607 #endif
00608 
00609     pixmaps[0]->icon_width = pixmaps[0]->icon_height = pixmaps[0]->map_width = pixmaps[0]->map_height = map_image_size;
00610     pixmaps[0]->smooth_face = 0;
00611 
00612     /* Don't do anything special for SDL image - rather, that drawing
00613      * code will check to see if there is no data
00614      */
00615 
00616     /* Initialize all the images to be of the same value. */
00617     for (i=1; i<MAXPIXMAPNUM; i++)  {
00618         pixmaps[i] = pixmaps[0];
00619     }
00620 
00621     init_common_cache_data();
00622 }