Crossfire Client, Branch  R11627
image.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk_image_c =
00002     "$Id: image.c 9201 2008-06-01 17:32:45Z 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  * This file contains image related functions - this is a higher level up -
00027  * it mostly deals with the caching of the images, processing the image commands
00028  * from the server, etc.  This file is gtk specific - at least it returns
00029  * gtk pixmaps.
00030  */
00031 
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 #ifndef WIN32
00043 #include <gdk/gdkx.h>
00044 #else
00045 #include <gdk/gdkwin32.h>
00046 #endif
00047 #include <gdk/gdkkeysyms.h>
00048 
00049 #ifdef HAVE_SDL
00050 #include <SDL.h>
00051 #include <SDL_image.h>
00052 #endif
00053 
00054 #include "client-types.h"
00055 #include "gx11.h"
00056 #include "client.h"
00057 
00058 #include "gtkproto.h"
00059 
00060 struct {
00061     char    *name;
00062     uint32  checksum;
00063     uint8   *png_data;
00064     uint32  width, height;
00065 } private_cache[MAXPIXMAPNUM];
00066 
00067 #define BPP 4
00068 
00069 int last_face_num=0;
00070 
00071 /* this is used to rescale big images that will be drawn in the inventory/look
00072  * lists.  What the code further below basically does is figure out how big
00073  * the object is (in squares), and this looks at the icon_rescale_factor
00074  * to figure what scale factor it gives.  Not that the icon_rescale_factor
00075  * values are passed directly to the rescale routines.  These represent
00076  * percentages - so even taking into account that the values diminish
00077  * as the table grows, they will still appear larger if the location
00078  * in the table times the factor is greater than 100.  We find
00079  * the largest dimension that the image has.  The values in the comment
00080  * is the effective scaling compared to the base image size that this
00081  * big image will appear as.
00082  * Using a table makes it easier to adjust the values so things
00083  * look right.
00084  */
00085 
00086 #define MAX_ICON_SPACES     10
00087 static int icon_rescale_factor[MAX_ICON_SPACES] = {
00088 100, 100,           80 /* 2 = 160 */,   60 /* 3 = 180 */,
00089 50 /* 4 = 200 */,   45 /* 5 = 225 */,   40 /* 6 = 240 */,
00090 35 /* 7 = 259 */,   35 /* 8 = 280 */,   33 /* 9 = 300 */
00091 };
00092 
00093 /******************************************************************************
00094  *
00095  * Code related to face caching.
00096  *
00097  *****************************************************************************/
00098 
00099 typedef struct Keys {
00100     uint8       flags;
00101     sint8       direction;
00102     KeySym      keysym;
00103     char        *command;
00104     struct Keys *next;
00105 } Key_Entry;
00106 
00107 
00108 /* Rotate right from bsd sum. */
00109 #define ROTATE_RIGHT(c) if ((c) & 01) (c) = ((c) >>1) + 0x80000000; else (c) >>= 1;
00110 
00111 /*#define CHECKSUM_DEBUG*/
00112 
00113 /* These little helper functions just make the code below much more readable */
00114 static void create_icon_image(uint8 *data, PixmapInfo *pi, int pixmap_num)
00115 {
00116     if (rgba_to_gdkpixmap(gtkwin_root->window, data, pi->icon_width, pi->icon_height,
00117                 (GdkPixmap**)&pi->icon_image, (GdkBitmap**)&pi->icon_mask,
00118                 gtk_widget_get_colormap(gtkwin_root)))
00119                     LOG (LOG_ERROR,"gtk::create_icon_image","Unable to create scaled image, dest num = %d\n", pixmap_num);
00120 }
00121 
00122 /* These little helper functions just make the code below much more readable */
00123 static void create_map_image(uint8 *data, PixmapInfo *pi)
00124 {
00125     pi->map_image = NULL;
00126     pi->map_mask = NULL;
00127 
00128     if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00129 #if defined(HAVE_SDL)
00130         int i;
00131         SDL_Surface *fog;
00132         uint32 g,*p;
00133         uint8 *l;
00134 
00135     #if SDL_BYTEORDER == SDL_LIL_ENDIAN
00136         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00137                 pi->map_height, 32, pi->map_width * 4,  0xff,
00138                         0xff00, 0xff0000, 0xff000000);
00139 
00140         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00141                 pi->map_width,  pi->map_height, 32, 0xff,
00142                         0xff00, 0xff0000, 0xff000000);
00143         SDL_LockSurface(fog);
00144 
00145         for (i=0; i < pi->map_width * pi->map_height; i++) {
00146             l = (uint8 *) (data + i*4);
00147             /* The effective luminance of a pixel is calculated with the following formula:
00148              *      Y=0.3RED+0.59GREEN+0.11Blue
00149              * This is the way you are supposed to convert color to grayscale
00150              * for best 'natural' results. Using maxes, mins or simple mean is
00151              * gives awfull results with darkness code.
00152              * For speed reason i work here in integers
00153              *      (77RED+151GREEN+28BLUE)>>8
00154              *
00155              *                          Tchize 22 May 2004
00156              */
00157             g=(77*(*l)+151*(*(l+1))+28*(*(l+2)))>>8;
00158             p = (uint32*) fog->pixels + i;
00159             *p = g | (g << 8) | (g << 16) | (*(l + 3) << 24);
00160         }
00161 
00162         SDL_UnlockSurface(fog);
00163         pi->fog_image = fog;
00164     #else
00165         /* Big endian */
00166         pi->map_image = SDL_CreateRGBSurfaceFrom(data, pi->map_width,
00167                 pi->map_height, 32, pi->map_width * 4,  0xff000000,
00168                         0xff0000, 0xff00, 0xff);
00169 
00170         fog = SDL_CreateRGBSurface(SDL_SRCALPHA | SDL_HWSURFACE,
00171                 pi->map_width,  pi->map_height, 32, 0xff000000,
00172                         0xff0000, 0xff00, 0xff);
00173         SDL_LockSurface(fog);
00174 
00175         /* I think this works out, but haven't tried it on a big
00176          * endian machine - my recollection is that the png data
00177          * would be in the same order, just the bytes for it to go
00178          * on teh screen are reversed.
00179          */
00180         for (i=0; i < pi->map_width * pi->map_height; i++) {
00181             l = (uint8 *) (data + i*4);
00182             /* The effective luminance of a pixel is calculated with the following formula:
00183              *      Y=0.3RED+0.59GREEN+0.11Blue
00184              * This is the way you are supposed to convert color to grayscale
00185              * for best 'natural' results. Using maxes, mins or simple mean is
00186              * gives awfull results with darkness code.
00187              * For speed reason i work here in integers
00188              *      (77RED+151GREEN+28BLUE)>>8
00189              *
00190              *                          Tchize 22 May 2004
00191              */
00192             g=(77*(*l)+151*(*(l+1))+28*(*(l+2)))>>8;
00193             p = (uint32*) fog->pixels + i;
00194             *p = (g << 8) | (g << 16) | (g << 24) | *(l + 3);
00195         }
00196 
00197         for (i=0; i < pi->map_width * pi->map_height; i+= 4) {
00198             uint32 *tmp;
00199 
00200             /* The pointer arithemtic below looks suspicious, but it is a patch that
00201              * is submitted, so just putting it in as submitted.
00202              * MSW 2004-05-11
00203              */
00204             p = (uint32*) (fog->pixels + i);
00205             g = ( ((*p >> 24) & 0xff)  + ((*p >> 16) & 0xff) + ((*p >> 8) & 0xff)) / 3;
00206             tmp = (uint32*) fog->pixels + i;
00207             *tmp = (g << 24) | (g << 16) | (g << 8) | (*p & 0xff);
00208 
00209         }
00210 
00211         SDL_UnlockSurface(fog);
00212         pi->fog_image = fog;
00213     #endif
00214 
00215 #endif
00216     }
00217     else {
00218         rgba_to_gdkpixmap(gtkwin_root->window, data, pi->map_width, pi->map_height,
00219                 (GdkPixmap**)&pi->map_image, (GdkBitmap**)&pi->map_mask,
00220                 gtk_widget_get_colormap(gtkwin_root));
00221     }
00222 }
00223 
00224 static void free_pixmap(PixmapInfo *pi)
00225 {
00226     if (pi->icon_image) gdk_pixmap_unref(pi->icon_image);
00227     if (pi->icon_mask) gdk_pixmap_unref(pi->icon_mask);
00228     if (pi->map_mask) gdk_pixmap_unref(pi->map_mask);
00229     if (pi->map_image) {
00230 #ifdef HAVE_SDL
00231         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00232             free(((SDL_Surface*)pi->map_image)->pixels);
00233             SDL_FreeSurface(pi->map_image);
00234             SDL_FreeSurface(pi->fog_image);
00235 /*          free(((SDL_Surface*)pi->fog_image)->pixels);*/
00236         }
00237         else
00238 #endif
00239         {
00240             gdk_pixmap_unref(pi->map_image);
00241         }
00242     }
00243 }
00244 
00245 /* Takes the pixmap to put the data into, as well as the rgba
00246  * data (ie, already loaded with png_to_data).  Scales and
00247  * stores the relevant data into the pixmap structure.
00248  * returns 1 on failure.
00249  * ce can be NULL
00250  */
00251 
00252 int create_and_rescale_image_from_data(Cache_Entry *ce, int pixmap_num, uint8 *rgba_data, int width, int height)
00253 {
00254     int nx, ny, iscale, factor;
00255     uint8 *png_tmp;
00256     PixmapInfo  *pi;
00257 
00258     if (pixmap_num <= 0 || pixmap_num >= MAXPIXMAPNUM)
00259         return 1;
00260 
00261     if (pixmaps[pixmap_num] != pixmaps[0]) {
00262         free_pixmap(pixmaps[pixmap_num]);
00263         free(pixmaps[pixmap_num]);
00264         pixmaps[pixmap_num] = pixmaps[0];
00265     }
00266 
00267     pi = calloc(1, sizeof(PixmapInfo));
00268 
00269     iscale = use_config[CONFIG_ICONSCALE];
00270 
00271     /* If the image is big, figure out what we should scale it to so it fits better display */
00272     if (width > DEFAULT_IMAGE_SIZE || height>DEFAULT_IMAGE_SIZE) {
00273         int ts = 100;
00274 
00275         factor = width / DEFAULT_IMAGE_SIZE;
00276         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00277         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00278 
00279         factor = height / DEFAULT_IMAGE_SIZE;
00280         if (factor >= MAX_ICON_SPACES) factor = MAX_ICON_SPACES - 1;
00281         if (icon_rescale_factor[factor] < ts) ts = icon_rescale_factor[factor];
00282 
00283         iscale = ts * use_config[CONFIG_ICONSCALE] / 100;
00284     }
00285 
00286 
00287     /* In all cases, the icon images are in native form. */
00288     if (iscale != 100) {
00289         nx=width;
00290         ny=height;
00291         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, iscale);
00292         pi->icon_width = nx;
00293         pi->icon_height = ny;
00294         create_icon_image(png_tmp, pi, pixmap_num);
00295         free(png_tmp);
00296     }
00297     else {
00298         pi->icon_width = width;
00299         pi->icon_height = height;
00300         create_icon_image(rgba_data, pi, pixmap_num);
00301     }
00302 
00303     /* We could try to be more intelligent if icon_scale matched use_config[CONFIG_MAPSCALE],
00304      * but this shouldn't be called too often, and this keeps the code
00305      * simpler.
00306      *
00307      *  maybe, but it uses graphical resources, and takes time (don't laugh, some people use
00308      *   slow old comps! :p)
00309      *  Ryo 2005-09-05
00310      *
00311      * Maybe, but only if were using PIXMAP mode - otherwise, SDL doesn't know how to
00312      * draw GDK pixmaps.
00313      * MSW 2006-11-05
00314      */
00315     if (iscale == use_config[CONFIG_MAPSCALE] && use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) {
00316         pi->map_height = pi->icon_height;
00317         pi->map_width = pi->icon_width;
00318         pi->map_mask = pi->icon_mask;
00319         pi->map_image = pi->icon_image;
00320         gdk_pixmap_ref(pi->map_image);
00321         if (pi->map_mask)
00322             gdk_pixmap_ref(pi->map_mask);
00323     } else if (use_config[CONFIG_MAPSCALE] != 100) {
00324         nx=width;
00325         ny=height;
00326         png_tmp = rescale_rgba_data(rgba_data, &nx, &ny, use_config[CONFIG_MAPSCALE]);
00327         pi->map_width = nx;
00328         pi->map_height = ny;
00329         create_map_image(png_tmp, pi);
00330         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_PIXMAP) free(png_tmp);
00331     } else {
00332         pi->map_width = width;
00333         pi->map_height = height;
00334         /* if using SDL mode, a copy of the rgba data needs to be
00335          * stored away.
00336          */
00337         if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) {
00338             png_tmp = malloc(width * height * BPP);
00339             memcpy(png_tmp, rgba_data, width * height * BPP);
00340         } else
00341             png_tmp = rgba_data;
00342         create_map_image(png_tmp, pi);
00343     }
00344     /* Not ideal, but basically, if it is missing the map or icon image, presume
00345      * something failed.
00346      */
00347     if (!pi->icon_image || !pi->map_image) {
00348         free_pixmap(pi);
00349         free(pi);
00350         return 1;
00351     }
00352     if (ce) {
00353         ce->image_data = pi;
00354         redraw_needed=1;
00355     }
00356     pixmaps[pixmap_num] = pi;
00357     return 0;
00358 }
00359 
00360 void addsmooth(uint16 face, uint16 smooth_face)
00361 {
00362     pixmaps[face]->smooth_face = smooth_face;
00363 }
00364 
00365 /* This functions associates the image_data in the cache entry
00366  * with the specific pixmap number.  Returns 0 on success, -1
00367  * on failure.  Currently, there is no failure condition, but
00368  * there is the potential that in the future, we want to more
00369  * closely look at the data and if it isn't valid, return
00370  * the failure code.
00371  */
00372 int associate_cache_entry(Cache_Entry *ce, int pixnum)
00373 {
00374 
00375     pixmaps[pixnum] = ce->image_data;
00376     return 0;
00377 }
00378 
00379 /* We can now connect to different servers, so we need to clear out
00380  * any old images.  We try to free the data also to prevent memory
00381  * leaks.
00382  * This could be more clever, ie, if we're caching images and go to
00383  * a new server and get a name, we should try to re-arrange our cache
00384  * or the like.
00385  */
00386 
00387 void reset_image_data(void)
00388 {
00389     int i;
00390     reset_image_cache_data();
00391 
00392     /* The entries in the pixmaps array are also tracked in the image cache in
00393      * the common area.  We will try to recyle those images that we can - thus, if
00394      * we connect to a new server, we can just re-use the images we have already
00395      * rendered.
00396      */
00397     for (i=1; i<MAXPIXMAPNUM; i++) {
00398         if (!want_config[CONFIG_CACHE] && pixmaps[i] != pixmaps[0]) {
00399             free_pixmap(pixmaps[i]);
00400             free(pixmaps[i]);
00401         }
00402         pixmaps[i] = pixmaps[0];
00403     }
00404 }
00405 
00406 
00407 /* This function draws a little status bar showing where we our
00408  * in terms of downloading all the image data.
00409  * start is the start value just sent to the server, end is the end
00410  * value.  total is the total number of images.
00411  * A few hacks:
00412  * If start is 1, this is the first batch, so it means we need to
00413  * create the appropriate status window.
00414  * if start = end = total, it means were finished, so destroy
00415  * the gui element.
00416  */
00417 static GtkWidget        *pbar=NULL, *pbar_window=NULL;
00418 static GtkAdjustment *padj=NULL;
00419 void image_update_download_status(int start, int end, int total)
00420 {
00421     int x, y, wx, wy, w, h;
00422 
00423     if (start == 1) {
00424         padj = (GtkAdjustment*) gtk_adjustment_new (0, 1, total, 0, 0, 0);
00425 
00426         pbar = gtk_progress_bar_new_with_adjustment(padj);
00427         gtk_progress_set_format_string(GTK_PROGRESS(pbar), "Downloading image %v of %u (%p%% complete)");
00428         gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(pbar), GTK_PROGRESS_CONTINUOUS);
00429         gtk_progress_set_show_text(GTK_PROGRESS(pbar), TRUE);
00430         get_window_coord(gtkwin_root, &x,&y, &wx,&wy,&w,&h);
00431 
00432         pbar_window = gtk_window_new(GTK_WINDOW_POPUP);
00433         gtk_window_set_policy(GTK_WINDOW(pbar_window), TRUE, TRUE, FALSE);
00434         gtk_window_set_transient_for(GTK_WINDOW(pbar_window), GTK_WINDOW (gtkwin_root));
00435         /* we more or less want this window centered on the main crossfire window,
00436          * and not necessarily centered on the screen or in the upper left corner.
00437          */
00438         gtk_widget_set_uposition(pbar_window, (wx + w)/2, (wy + h) / 2);
00439 
00440         gtk_container_add(GTK_CONTAINER(pbar_window), pbar);
00441         gtk_widget_show(pbar);
00442         gtk_widget_show(pbar_window);
00443     }
00444     if (start == total) {
00445         gtk_widget_destroy(pbar_window);
00446         pbar = NULL;
00447         pbar_window = NULL;
00448         padj = NULL;
00449         return;
00450     }
00451 
00452     gtk_progress_set_value(GTK_PROGRESS(pbar), start);
00453     while ( gtk_events_pending() ) {
00454         gtk_main_iteration();
00455     }
00456 
00457 }
00458 
00459 void get_map_image_size(int face, uint8 *w, uint8 *h)
00460 {
00461     /* We want to calculate the number of spaces this image
00462      * uses it.  By adding the image size but substracting one,
00463      * we cover the cases where the image size is not an even
00464      * increment.  EG, if the map_image_size is 32, and an image
00465      * is 33 wide, we want that to register as two spaces.  By
00466      * adding 31, that works out.
00467      */
00468     if (face < 0 || face >= MAXPIXMAPNUM) {
00469         *w = 1;
00470         *h = 1;
00471     } else {
00472         *w = (pixmaps[face]->map_width + map_image_size - 1)/ map_image_size;
00473         *h = (pixmaps[face]->map_height + map_image_size - 1)/ map_image_size;
00474     }
00475 }