Rulesave saves trade.type and trade.bonus correctly.
[freeciv.git] / client / gui-gtk-2.0 / sprite.c
blob7f96094eca877c2f7956a49041380fefb3175c18
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.
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 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 /* utility */
19 #include "log.h"
20 #include "mem.h"
21 #include "shared.h"
23 /* client/gtk-2.0 */
24 #include "colors.h"
26 #include "sprite.h"
28 #define MAX_FILE_EXTENSIONS 50
30 /****************************************************************************
31 Create a new sprite by cropping and taking only the given portion of
32 the image.
34 source gives the sprite that is to be cropped.
36 x,y, width, height gives the rectangle to be cropped. The pixel at
37 position of the source sprite will be at (0,0) in the new sprite, and
38 the new sprite will have dimensions (width, height).
40 mask gives an additional mask to be used for clipping the new sprite.
42 mask_offset_x, mask_offset_y is the offset of the mask relative to the
43 origin of the source image. The pixel at (mask_offset_x,mask_offset_y)
44 in the mask image will be used to clip pixel (0,0) in the source image
45 which is pixel (-x,-y) in the new image.
46 ****************************************************************************/
47 struct sprite *crop_sprite(struct sprite *source,
48 int x, int y,
49 int width, int height,
50 struct sprite *mask,
51 int mask_offset_x, int mask_offset_y)
53 GdkPixbuf *mypixbuf, *sub, *mask_pixbuf;
55 /* First just crop the image. */
56 if (x < 0) {
57 width += x;
58 x = 0;
60 if (y < 0) {
61 height += y;
62 y = 0;
64 width = CLIP(0, width, source->width - x);
65 height = CLIP(0, height, source->height - y);
66 sub = gdk_pixbuf_new_subpixbuf(sprite_get_pixbuf(source), x, y,
67 width, height);
68 mypixbuf = gdk_pixbuf_copy(sub);
69 g_object_unref(sub);
71 /* Now mask. This reduces the alpha of the final image proportional to the
72 * alpha of the mask. Thus if the mask has 50% alpha the final image will
73 * be reduced by 50% alpha. Note that the mask offset is in coordinates
74 * relative to the clipped image not the final image. */
75 if (mask
76 && (mask_pixbuf = sprite_get_pixbuf(mask))
77 && gdk_pixbuf_get_has_alpha(mask_pixbuf)) {
78 int x1, y1;
80 /* The mask offset is the offset of the mask relative to the origin
81 * of the original source image. For instance when cropping with
82 * blending sprites the offset is always 0. Here we convert the
83 * coordinates so that they are relative to the origin of the new
84 * (cropped) image. */
85 mask_offset_x -= x;
86 mask_offset_y -= y;
88 width = CLIP(0, width, mask->width + mask_offset_x);
89 height = CLIP(0, height, mask->height + mask_offset_y);
91 if (!gdk_pixbuf_get_has_alpha(mypixbuf)) {
92 GdkPixbuf *p2 = mypixbuf;
94 mypixbuf = gdk_pixbuf_add_alpha(mypixbuf, FALSE, 0, 0, 0);
95 g_object_unref(p2);
98 for (x1 = 0; x1 < width; x1++) {
99 for (y1 = 0; y1 < height; y1++) {
100 int mask_x = x1 - mask_offset_x, mask_y = y1 - mask_offset_y;
101 guchar *alpha = gdk_pixbuf_get_pixels(mypixbuf)
102 + y1 * gdk_pixbuf_get_rowstride(mypixbuf)
103 + x1 * gdk_pixbuf_get_n_channels(mypixbuf)
104 + 3;
105 guchar *mask_alpha = gdk_pixbuf_get_pixels(mask_pixbuf)
106 + mask_y * gdk_pixbuf_get_rowstride(mask_pixbuf)
107 + mask_x * gdk_pixbuf_get_n_channels(mask_pixbuf)
108 + 3;
110 *alpha = (*alpha) * (*mask_alpha) / 255;
115 return ctor_sprite(mypixbuf);
118 /****************************************************************************
119 Create a sprite with the given height, width and color.
120 ****************************************************************************/
121 struct sprite *create_sprite(int width, int height, struct color *pcolor)
123 GdkPixbuf *mypixbuf = NULL;
124 GdkColor *color = NULL;
126 fc_assert_ret_val(width > 0, NULL);
127 fc_assert_ret_val(height > 0, NULL);
128 fc_assert_ret_val(pcolor != NULL, NULL);
130 color = &pcolor->color;
131 mypixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
132 gdk_pixbuf_fill(mypixbuf, ((guint32)(color->red & 0xff00) << 16)
133 | ((color->green & 0xff00) << 8)
134 | (color->blue & 0xff00) | 0xff);
136 return ctor_sprite(mypixbuf);
139 /****************************************************************************
140 Find the dimensions of the sprite.
141 ****************************************************************************/
142 void get_sprite_dimensions(struct sprite *sprite, int *width, int *height)
144 *width = sprite->width;
145 *height = sprite->height;
148 /****************************************************************************
149 Create a new sprite with the given pixmap, dimensions, and
150 (optional) mask.
152 FIXME: should be renamed as sprite_new or some such.
153 ****************************************************************************/
154 struct sprite *ctor_sprite(GdkPixbuf *pixbuf)
156 struct sprite *sprite = fc_malloc(sizeof(*sprite));
157 bool has_alpha = FALSE, has_mask = FALSE;
159 sprite->width = gdk_pixbuf_get_width(pixbuf);
160 sprite->height = gdk_pixbuf_get_height(pixbuf);
162 /* Check to see if this pixbuf has an alpha layer. */
163 if (gdk_pixbuf_get_has_alpha(pixbuf)) {
164 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
165 int x, y, rowstride = gdk_pixbuf_get_rowstride(pixbuf);
167 for (y = 0; y < sprite->height; y++) {
168 for (x = 0; x < sprite->width; x++) {
169 int i = y * rowstride + 4 * x + 3;
170 guchar pixel = pixels[i];
172 if (pixel > 0 && pixel < 255) {
173 has_alpha = TRUE;
175 if (pixel == 0) {
176 has_mask = TRUE;
182 sprite->pixbuf_fogged = NULL;
183 sprite->pixmap_fogged = NULL;
184 if (has_alpha) {
185 sprite->pixbuf = pixbuf;
186 sprite->pixmap = NULL;
187 sprite->mask = NULL;
188 } else {
189 gdk_pixbuf_render_pixmap_and_mask(pixbuf, &sprite->pixmap,
190 &sprite->mask, 1);
191 if (!has_mask && sprite->mask) {
192 g_object_unref(sprite->mask);
193 sprite->mask = NULL;
195 g_object_unref(pixbuf);
196 sprite->pixbuf = NULL;
198 return sprite;
201 /****************************************************************************
202 Returns the filename extensions the client supports
203 Order is important.
204 ****************************************************************************/
205 const char **gfx_fileextensions(void)
207 /* Includes space for termination NULL */
208 static const char *ext[MAX_FILE_EXTENSIONS + 1] =
210 NULL
213 if (ext[0] == NULL) {
214 int count = 0;
215 GSList *formats = gdk_pixbuf_get_formats();
216 GSList *next = formats;
218 while ((next = g_slist_next(next)) != NULL && count < MAX_FILE_EXTENSIONS) {
219 GdkPixbufFormat *format = g_slist_nth_data(next, 0);
220 gchar **mimes = gdk_pixbuf_format_get_mime_types(format);
221 int i;
223 for (i = 0; mimes[i] != NULL && count < MAX_FILE_EXTENSIONS; i++) {
224 char *end = strstr(mimes[i], "/");
226 if (end != NULL) {
227 ext[count++] = fc_strdup(end + 1);
231 g_strfreev(mimes);
234 g_slist_free(formats);
236 ext[count] = NULL;
239 return ext;
242 /****************************************************************************
243 Load the given graphics file into a sprite. This function loads an
244 entire image file, which may later be broken up into individual sprites
245 with crop_sprite.
246 ****************************************************************************/
247 struct sprite *load_gfxfile(const char *filename)
249 GdkPixbuf *im;
250 GError *err = NULL;
252 if (!(im = gdk_pixbuf_new_from_file(filename, &err))) {
253 log_fatal("Failed reading graphics file \"%s\": \"%s\"", filename, err->message);
254 exit(EXIT_FAILURE);
257 return ctor_sprite(im);
260 /****************************************************************************
261 Free a sprite and all associated image data.
262 ****************************************************************************/
263 void free_sprite(struct sprite * s)
265 if (s->pixmap) {
266 g_object_unref(s->pixmap);
267 s->pixmap = NULL;
269 if (s->mask) {
270 g_object_unref(s->mask);
271 s->mask = NULL;
273 if (s->pixbuf) {
274 g_object_unref(s->pixbuf);
275 s->pixbuf = NULL;
277 if (s->pixmap_fogged) {
278 g_object_unref(s->pixmap_fogged);
280 if (s->pixbuf_fogged) {
281 g_object_unref(s->pixbuf_fogged);
283 free(s);
286 /****************************************************************************
287 Scales a sprite. If the sprite contains a mask, the mask is scaled
288 as as well.
289 ****************************************************************************/
290 struct sprite *sprite_scale(struct sprite *src, int new_w, int new_h)
292 return ctor_sprite(gdk_pixbuf_scale_simple(sprite_get_pixbuf(src),
293 new_w, new_h,
294 GDK_INTERP_BILINEAR));
297 /****************************************************************************
298 Method returns the bounding box of a sprite. It assumes a rectangular
299 object/mask. The bounding box contains the border (pixel which have
300 unset pixel as neighbours) pixel.
301 ****************************************************************************/
302 void sprite_get_bounding_box(struct sprite * sprite, int *start_x,
303 int *start_y, int *end_x, int *end_y)
305 GdkImage *mask_image;
306 GdkBitmap *mask = sprite_get_mask(sprite);
307 int i, j;
309 if (!mask) {
310 *start_x = 0;
311 *start_y = 0;
312 *end_x = sprite->width - 1;
313 *end_y = sprite->height - 1;
314 return;
317 mask_image
318 = gdk_drawable_get_image(mask, 0, 0, sprite->width, sprite->height);
321 /* parses mask image for the first column that contains a visible pixel */
322 *start_x = -1;
323 for (i = 0; i < sprite->width && *start_x == -1; i++) {
324 for (j = 0; j < sprite->height; j++) {
325 if (gdk_image_get_pixel(mask_image, i, j) != 0) {
326 *start_x = i;
327 break;
332 /* parses mask image for the last column that contains a visible pixel */
333 *end_x = -1;
334 for (i = sprite->width - 1; i >= *start_x && *end_x == -1; i--) {
335 for (j = 0; j < sprite->height; j++) {
336 if (gdk_image_get_pixel(mask_image, i, j) != 0) {
337 *end_x = i;
338 break;
343 /* parses mask image for the first row that contains a visible pixel */
344 *start_y = -1;
345 for (i = 0; i < sprite->height && *start_y == -1; i++) {
346 for (j = *start_x; j <= *end_x; j++) {
347 if (gdk_image_get_pixel(mask_image, j, i) != 0) {
348 *start_y = i;
349 break;
354 /* parses mask image for the last row that contains a visible pixel */
355 *end_y = -1;
356 for (i = sprite->height - 1; i >= *end_y && *end_y == -1; i--) {
357 for (j = *start_x; j <= *end_x; j++) {
358 if (gdk_image_get_pixel(mask_image, j, i) != 0) {
359 *end_y = i;
360 break;
365 g_object_unref(mask_image);
368 /****************************************************************************
369 Crops all blankspace from a sprite (insofar as is possible as a rectangle)
370 ****************************************************************************/
371 struct sprite *crop_blankspace(struct sprite *s)
373 int x1, y1, x2, y2;
375 sprite_get_bounding_box(s, &x1, &y1, &x2, &y2);
377 return crop_sprite(s, x1, y1, x2 - x1 + 1, y2 - y1 + 1, NULL, -1, -1);
380 /****************************************************************************
381 Converts a pixmap/mask sprite to a GdkPixbuf.
383 This is just a helper function for sprite_get_pixbuf(). Most callers
384 should use that function instead.
385 ****************************************************************************/
386 static GdkPixbuf *gdk_pixbuf_new_from_pixmap_sprite(struct sprite *src)
388 GdkPixbuf *dst;
389 int w, h;
391 w = src->width;
392 h = src->height;
394 /* convert pixmap */
395 dst = gdk_pixbuf_new(GDK_COLORSPACE_RGB, src->mask != NULL, 8, w, h);
396 gdk_pixbuf_get_from_drawable(dst, src->pixmap, NULL, 0, 0, 0, 0, w, h);
398 /* convert mask */
399 if (src->mask) {
400 GdkImage *img;
401 int x, y, rowstride;
402 guchar *pixels;
404 img = gdk_drawable_get_image(src->mask, 0, 0, w, h);
406 pixels = gdk_pixbuf_get_pixels(dst);
407 rowstride = gdk_pixbuf_get_rowstride(dst);
409 for (y = 0; y < h; y++) {
410 for (x = 0; x < w; x++) {
411 guchar *pixel = pixels + y * rowstride + x * 4 + 3;
413 if (gdk_image_get_pixel(img, x, y)) {
414 *pixel = 255;
415 } else {
416 *pixel = 0;
420 g_object_unref(img);
423 return dst;
426 /********************************************************************
427 Render a pixbuf from the sprite.
429 NOTE: the pixmap and mask of a sprite must not change after this
430 function is called!
431 ********************************************************************/
432 GdkPixbuf *sprite_get_pixbuf(struct sprite *sprite)
434 if (!sprite) {
435 return NULL;
438 if (!sprite->pixbuf) {
439 sprite->pixbuf = gdk_pixbuf_new_from_pixmap_sprite(sprite);
441 return sprite->pixbuf;
444 /****************************************************************************
445 Render a mask from the sprite.
447 NOTE: the pixbuf of a sprite must not change after this function is called!
448 ****************************************************************************/
449 GdkBitmap *sprite_get_mask(struct sprite *sprite)
451 if (!sprite->pixmap && !sprite->mask) {
452 /* If we're not in pixmap mode and we don't yet have a mask, render
453 * the pixbuf to a mask. */
454 gdk_pixbuf_render_pixmap_and_mask(sprite->pixbuf, NULL,
455 &sprite->mask, 1);
457 return sprite->mask;