1 /**********************************************************************
2  Freeciv - Copyright (C) 1996-2005 - Freeciv Development Team
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 /* utility */
19 #include "log.h"
20 #include "mem.h"
21 #include "math.h"
22 #include "shared.h"
23 
24 /* client */
25 #include "tilespec.h"
26 
27 /* client/gtk-2.0 */
28 #include "colors.h"
29 
30 #include "sprite.h"
31 
32 #define MAX_FILE_EXTENSIONS 50
33 
34 /****************************************************************************
35   Create a new sprite by cropping and taking only the given portion of
36   the image.
37 
38   source gives the sprite that is to be cropped.
39 
40   x,y, width, height gives the rectangle to be cropped.  The pixel at
41   position of the source sprite will be at (0,0) in the new sprite, and
42   the new sprite will have dimensions (width, height).
43 
44   mask gives an additional mask to be used for clipping the new sprite.
45 
46   mask_offset_x, mask_offset_y is the offset of the mask relative to the
47   origin of the source image.  The pixel at (mask_offset_x,mask_offset_y)
48   in the mask image will be used to clip pixel (0,0) in the source image
49   which is pixel (-x,-y) in the new image.
50 
51   scale gives scale of new tileset
52   smooth means if scaling might be bilinear, if set to false use nearest
53   neighbor
54 ****************************************************************************/
crop_sprite(struct sprite * source,int x,int y,int width,int height,struct sprite * mask,int mask_offset_x,int mask_offset_y,float scale,bool smooth)55 struct sprite *crop_sprite(struct sprite *source,
56 			   int x, int y,
57 			   int width, int height,
58 			   struct sprite *mask, int mask_offset_x, int mask_offset_y,
59                float scale, bool smooth)
60 {
61   GdkPixbuf *mypixbuf, *sub, *mask_pixbuf;
62 
63   /* First just crop the image. */
64   if (x < 0) {
65     width += x;
66     x = 0;
67   }
68   if (y < 0) {
69     height += y;
70     y = 0;
71   }
72   width = CLIP(0, width, source->width - x);
73   height = CLIP(0, height, source->height - y);
74   sub = gdk_pixbuf_new_subpixbuf(sprite_get_pixbuf(source), x, y,
75 				 width, height);
76   if (scale == 1.0f) {
77     mypixbuf = gdk_pixbuf_copy(sub);
78   } else {
79     GdkInterpType interp_type;
80 
81     if (smooth) {
82       interp_type = GDK_INTERP_BILINEAR;
83     } else {
84       interp_type = GDK_INTERP_NEAREST;
85     }
86     int hex = 0;
87     if (tileset_hex_height(tileset) > 0 || tileset_hex_width(tileset) > 0) {
88       hex = 1;
89     }
90     mypixbuf = gdk_pixbuf_scale_simple(sub, ceil(width *scale) + hex,
91                                        ceil(height * scale) + hex,
92                                        interp_type);
93   }
94   g_object_unref(sub);
95 
96   /* Now mask.  This reduces the alpha of the final image proportional to the
97    * alpha of the mask.  Thus if the mask has 50% alpha the final image will
98    * be reduced by 50% alpha.  Note that the mask offset is in coordinates
99    * relative to the clipped image not the final image. */
100   if (mask
101       && (mask_pixbuf = sprite_get_pixbuf(mask))
102       && gdk_pixbuf_get_has_alpha(mask_pixbuf)) {
103     int x1, y1;
104 
105     /* The mask offset is the offset of the mask relative to the origin
106      * of the original source image.  For instance when cropping with
107      * blending sprites the offset is always 0.  Here we convert the
108      * coordinates so that they are relative to the origin of the new
109      * (cropped) image. */
110     mask_offset_x -= x;
111     mask_offset_y -= y;
112 
113     width = CLIP(0, width, mask->width + mask_offset_x);
114     height = CLIP(0, height, mask->height + mask_offset_y);
115 
116     if (!gdk_pixbuf_get_has_alpha(mypixbuf)) {
117       GdkPixbuf *p2 = mypixbuf;
118 
119       mypixbuf = gdk_pixbuf_add_alpha(mypixbuf, FALSE, 0, 0, 0);
120       g_object_unref(p2);
121     }
122 
123     for (x1 = 0; x1 < ceil(width * scale); x1++) {
124       for (y1 = 0; y1 < ceil(height * scale); y1++) {
125 	int mask_x = x1 - mask_offset_x, mask_y = y1 - mask_offset_y;
126 	guchar *alpha = gdk_pixbuf_get_pixels(mypixbuf)
127 	  + y1 * gdk_pixbuf_get_rowstride(mypixbuf)
128 	  + x1 * gdk_pixbuf_get_n_channels(mypixbuf)
129 	  + 3;
130 	guchar *mask_alpha = gdk_pixbuf_get_pixels(mask_pixbuf)
131 	  + mask_y * gdk_pixbuf_get_rowstride(mask_pixbuf)
132 	  + mask_x * gdk_pixbuf_get_n_channels(mask_pixbuf)
133 	  + 3;
134 
135 	*alpha = (*alpha) * (*mask_alpha) / 255;
136       }
137     }
138   }
139 
140   return ctor_sprite(mypixbuf);
141 }
142 
143 /****************************************************************************
144   Create a sprite with the given height, width and color.
145 ****************************************************************************/
create_sprite(int width,int height,struct color * pcolor)146 struct sprite *create_sprite(int width, int height, struct color *pcolor)
147 {
148   GdkPixbuf *mypixbuf = NULL;
149   GdkColor *color = NULL;
150 
151   fc_assert_ret_val(width > 0, NULL);
152   fc_assert_ret_val(height > 0, NULL);
153   fc_assert_ret_val(pcolor != NULL, NULL);
154 
155   color = &pcolor->color;
156   mypixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
157   gdk_pixbuf_fill(mypixbuf, ((guint32)(color->red & 0xff00) << 16)
158                             | ((color->green & 0xff00) << 8)
159                             | (color->blue & 0xff00) | 0xff);
160 
161   return ctor_sprite(mypixbuf);
162 }
163 
164 /****************************************************************************
165   Find the dimensions of the sprite.
166 ****************************************************************************/
get_sprite_dimensions(struct sprite * sprite,int * width,int * height)167 void get_sprite_dimensions(struct sprite *sprite, int *width, int *height)
168 {
169   *width = sprite->width;
170   *height = sprite->height;
171 }
172 
173 /****************************************************************************
174   Create a new sprite with the given pixmap, dimensions, and
175   (optional) mask.
176 
177   FIXME: should be renamed as sprite_new or some such.
178 ****************************************************************************/
ctor_sprite(GdkPixbuf * pixbuf)179 struct sprite *ctor_sprite(GdkPixbuf *pixbuf)
180 {
181   struct sprite *sprite = fc_malloc(sizeof(*sprite));
182   bool has_alpha = FALSE, has_mask = FALSE;
183 
184   sprite->width = gdk_pixbuf_get_width(pixbuf);
185   sprite->height = gdk_pixbuf_get_height(pixbuf);
186 
187   /* Check to see if this pixbuf has an alpha layer. */
188   if (gdk_pixbuf_get_has_alpha(pixbuf)) {
189     guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
190     int x, y, rowstride = gdk_pixbuf_get_rowstride(pixbuf);
191 
192     for (y = 0; y < sprite->height; y++) {
193       for (x = 0; x < sprite->width; x++) {
194 	int i = y * rowstride + 4 * x + 3;
195 	guchar pixel = pixels[i];
196 
197 	if (pixel > 0 && pixel < 255) {
198 	  has_alpha = TRUE;
199 	}
200 	if (pixel == 0) {
201 	  has_mask = TRUE;
202 	}
203       }
204     }
205   }
206 
207   sprite->pixbuf_fogged = NULL;
208   sprite->pixmap_fogged = NULL;
209   if (has_alpha) {
210     sprite->pixbuf = pixbuf;
211     sprite->pixmap = NULL;
212     sprite->mask = NULL;
213   } else {
214     gdk_pixbuf_render_pixmap_and_mask(pixbuf, &sprite->pixmap,
215 				      &sprite->mask, 1);
216     if (!has_mask && sprite->mask) {
217       g_object_unref(sprite->mask);
218       sprite->mask = NULL;
219     }
220     g_object_unref(pixbuf);
221     sprite->pixbuf = NULL;
222   }
223   return sprite;
224 }
225 
226 /****************************************************************************
227   Returns the filename extensions the client supports
228   Order is important.
229 ****************************************************************************/
gfx_fileextensions(void)230 const char **gfx_fileextensions(void)
231 {
232   /* Includes space for hardcoded 'png' and termination NULL */
233   static const char *ext[MAX_FILE_EXTENSIONS + 2] =
234   {
235     NULL
236   };
237 
238   if (ext[0] == NULL) {
239     int count = 0;
240     GSList *formats = gdk_pixbuf_get_formats();
241     GSList *next = formats;
242 
243     while ((next = g_slist_next(next)) != NULL && count < MAX_FILE_EXTENSIONS) {
244       GdkPixbufFormat *format = g_slist_nth_data(next, 0);
245       gchar **mimes = gdk_pixbuf_format_get_mime_types(format);
246       int i;
247 
248       /* Consider .png to be supported even when there's no mime-type called "png" */
249       ext[count++] = fc_strdup("png");
250 
251       for (i = 0; mimes[i] != NULL && count < MAX_FILE_EXTENSIONS; i++) {
252         char *end = strstr(mimes[i], "/");
253 
254         if (end != NULL) {
255           ext[count++] = fc_strdup(end + 1);
256         }
257       }
258 
259       g_strfreev(mimes);
260     }
261 
262     g_slist_free(formats);
263 
264     ext[count] = NULL;
265   }
266 
267   return ext;
268 }
269 
270 /****************************************************************************
271   Load the given graphics file into a sprite.  This function loads an
272   entire image file, which may later be broken up into individual sprites
273   with crop_sprite.
274 ****************************************************************************/
load_gfxfile(const char * filename)275 struct sprite *load_gfxfile(const char *filename)
276 {
277   GdkPixbuf *im;
278   GError *err = NULL;
279 
280   if (!(im = gdk_pixbuf_new_from_file(filename, &err))) {
281     log_fatal("Failed reading graphics file \"%s\": \"%s\"", filename, err->message);
282     exit(EXIT_FAILURE);
283   }
284 
285   return ctor_sprite(im);
286 }
287 
288 /****************************************************************************
289   Free a sprite and all associated image data.
290 ****************************************************************************/
free_sprite(struct sprite * s)291 void free_sprite(struct sprite * s)
292 {
293   if (s->pixmap) {
294     g_object_unref(s->pixmap);
295     s->pixmap = NULL;
296   }
297   if (s->mask) {
298     g_object_unref(s->mask);
299     s->mask = NULL;
300   }
301   if (s->pixbuf) {
302     g_object_unref(s->pixbuf);
303     s->pixbuf = NULL;
304   }
305   if (s->pixmap_fogged) {
306     g_object_unref(s->pixmap_fogged);
307   }
308   if (s->pixbuf_fogged) {
309     g_object_unref(s->pixbuf_fogged);
310   }
311   free(s);
312 }
313 
314 /****************************************************************************
315   Scales a sprite. If the sprite contains a mask, the mask is scaled
316   as as well.
317 ****************************************************************************/
sprite_scale(struct sprite * src,int new_w,int new_h)318 struct sprite *sprite_scale(struct sprite *src, int new_w, int new_h)
319 {
320   return ctor_sprite(gdk_pixbuf_scale_simple(sprite_get_pixbuf(src),
321 					     new_w, new_h,
322 					     GDK_INTERP_BILINEAR));
323 }
324 
325 /****************************************************************************
326   Method returns the bounding box of a sprite. It assumes a rectangular
327   object/mask. The bounding box contains the border (pixel which have
328   unset pixel as neighbours) pixel.
329 ****************************************************************************/
sprite_get_bounding_box(struct sprite * sprite,int * start_x,int * start_y,int * end_x,int * end_y)330 void sprite_get_bounding_box(struct sprite * sprite, int *start_x,
331 			     int *start_y, int *end_x, int *end_y)
332 {
333   GdkImage *mask_image;
334   GdkBitmap *mask = sprite_get_mask(sprite);
335   int i, j;
336 
337   if (!mask) {
338     *start_x = 0;
339     *start_y = 0;
340     *end_x = sprite->width - 1;
341     *end_y = sprite->height - 1;
342     return;
343   }
344 
345   mask_image
346     = gdk_drawable_get_image(mask, 0, 0, sprite->width, sprite->height);
347 
348 
349   /* parses mask image for the first column that contains a visible pixel */
350   *start_x = -1;
351   for (i = 0; i < sprite->width && *start_x == -1; i++) {
352     for (j = 0; j < sprite->height; j++) {
353       if (gdk_image_get_pixel(mask_image, i, j) != 0) {
354 	*start_x = i;
355 	break;
356       }
357     }
358   }
359 
360   /* parses mask image for the last column that contains a visible pixel */
361   *end_x = -1;
362   for (i = sprite->width - 1; i >= *start_x && *end_x == -1; i--) {
363     for (j = 0; j < sprite->height; j++) {
364       if (gdk_image_get_pixel(mask_image, i, j) != 0) {
365 	*end_x = i;
366 	break;
367       }
368     }
369   }
370 
371   /* parses mask image for the first row that contains a visible pixel */
372   *start_y = -1;
373   for (i = 0; i < sprite->height && *start_y == -1; i++) {
374     for (j = *start_x; j <= *end_x; j++) {
375       if (gdk_image_get_pixel(mask_image, j, i) != 0) {
376 	*start_y = i;
377 	break;
378       }
379     }
380   }
381 
382   /* parses mask image for the last row that contains a visible pixel */
383   *end_y = -1;
384   for (i = sprite->height - 1; i >= *end_y && *end_y == -1; i--) {
385     for (j = *start_x; j <= *end_x; j++) {
386       if (gdk_image_get_pixel(mask_image, j, i) != 0) {
387 	*end_y = i;
388 	break;
389       }
390     }
391   }
392 
393   g_object_unref(mask_image);
394 }
395 
396 /****************************************************************************
397   Crops all blankspace from a sprite (insofar as is possible as a rectangle)
398 ****************************************************************************/
crop_blankspace(struct sprite * s)399 struct sprite *crop_blankspace(struct sprite *s)
400 {
401   int x1, y1, x2, y2;
402 
403   sprite_get_bounding_box(s, &x1, &y1, &x2, &y2);
404 
405   return crop_sprite(s, x1, y1, x2 - x1 + 1, y2 - y1 + 1, NULL, -1, -1,
406                      1.0, FALSE);
407 }
408 
409 /****************************************************************************
410   Converts a pixmap/mask sprite to a GdkPixbuf.
411 
412   This is just a helper function for sprite_get_pixbuf().  Most callers
413   should use that function instead.
414 ****************************************************************************/
gdk_pixbuf_new_from_pixmap_sprite(struct sprite * src)415 static GdkPixbuf *gdk_pixbuf_new_from_pixmap_sprite(struct sprite *src)
416 {
417   GdkPixbuf *dst;
418   int w, h;
419 
420   w = src->width;
421   h = src->height;
422 
423   /* convert pixmap */
424   dst = gdk_pixbuf_new(GDK_COLORSPACE_RGB, src->mask != NULL, 8, w, h);
425   gdk_pixbuf_get_from_drawable(dst, src->pixmap, NULL, 0, 0, 0, 0, w, h);
426 
427   /* convert mask */
428   if (src->mask) {
429     GdkImage *img;
430     int x, y, rowstride;
431     guchar *pixels;
432 
433     img = gdk_drawable_get_image(src->mask, 0, 0, w, h);
434 
435     pixels = gdk_pixbuf_get_pixels(dst);
436     rowstride = gdk_pixbuf_get_rowstride(dst);
437 
438     for (y = 0; y < h; y++) {
439       for (x = 0; x < w; x++) {
440 	guchar *pixel = pixels + y * rowstride + x * 4 + 3;
441 
442 	if (gdk_image_get_pixel(img, x, y)) {
443 	  *pixel = 255;
444 	} else {
445 	  *pixel = 0;
446 	}
447       }
448     }
449     g_object_unref(img);
450   }
451 
452   return dst;
453 }
454 
455 /********************************************************************
456   Render a pixbuf from the sprite.
457 
458   NOTE: the pixmap and mask of a sprite must not change after this
459         function is called!
460 ********************************************************************/
sprite_get_pixbuf(struct sprite * sprite)461 GdkPixbuf *sprite_get_pixbuf(struct sprite *sprite)
462 {
463   if (!sprite) {
464     return NULL;
465   }
466 
467   if (!sprite->pixbuf) {
468     sprite->pixbuf = gdk_pixbuf_new_from_pixmap_sprite(sprite);
469   }
470   return sprite->pixbuf;
471 }
472 
473 /****************************************************************************
474   Render a mask from the sprite.
475 
476   NOTE: the pixbuf of a sprite must not change after this function is called!
477 ****************************************************************************/
sprite_get_mask(struct sprite * sprite)478 GdkBitmap *sprite_get_mask(struct sprite *sprite)
479 {
480   if (!sprite->pixmap && !sprite->mask) {
481     /* If we're not in pixmap mode and we don't yet have a mask, render
482      * the pixbuf to a mask. */
483     gdk_pixbuf_render_pixmap_and_mask(sprite->pixbuf, NULL,
484 				      &sprite->mask, 1);
485   }
486   return sprite->mask;
487 }
488