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