"same than" -> "same as".
[freeciv.git] / client / gui-gtk-4.0 / sprite.c
blobcafd2ef0a6f9f94a5efb95dc940d8b23bae98ec3
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/gui-gtk-4.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.
47 scale gives scale of new tileset
48 smooth means if scaling might be bilinear, if set to false use nearest
49 neighbor
50 ****************************************************************************/
51 struct sprite *crop_sprite(struct sprite *source,
52 int x, int y,
53 int width, int height,
54 struct sprite *mask, int mask_offset_x, int mask_offset_y,
55 float scale, bool smooth)
57 struct sprite *new = fc_malloc(sizeof(*new));
58 cairo_t *cr;
60 fc_assert_ret_val(source, NULL);
62 new->surface = cairo_surface_create_similar(source->surface,
63 CAIRO_CONTENT_COLOR_ALPHA, width, height);
64 cr = cairo_create(new->surface);
65 cairo_rectangle(cr, 0, 0, width, height);
66 cairo_clip(cr);
68 cairo_set_source_surface(cr, source->surface, -x, -y);
69 cairo_paint(cr);
70 if (mask) {
71 cairo_set_operator(cr, CAIRO_OPERATOR_DEST_IN);
72 cairo_set_source_surface(cr, mask->surface, mask_offset_x-x, mask_offset_y-y);
73 cairo_paint(cr);
75 cairo_destroy(cr);
77 return new;
80 /****************************************************************************
81 Create a sprite with the given height, width and color.
82 ****************************************************************************/
83 struct sprite *create_sprite(int width, int height, struct color *pcolor)
85 struct sprite *sprite = fc_malloc(sizeof(*sprite));
86 cairo_t *cr;
88 fc_assert_ret_val(width > 0, NULL);
89 fc_assert_ret_val(height > 0, NULL);
90 fc_assert_ret_val(pcolor != NULL, NULL);
92 sprite->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
93 width, height);
95 cr = cairo_create(sprite->surface);
96 gdk_cairo_set_source_rgba(cr, &pcolor->color);
97 cairo_paint(cr);
98 cairo_destroy(cr);
100 return sprite;
103 /****************************************************************************
104 Find the dimensions of the sprite.
105 ****************************************************************************/
106 void get_sprite_dimensions(struct sprite *sprite, int *width, int *height)
108 *width = cairo_image_surface_get_width(sprite->surface);
109 *height = cairo_image_surface_get_height(sprite->surface);
112 /****************************************************************************
113 Returns the filename extensions the client supports
114 Order is important.
115 ****************************************************************************/
116 const char **gfx_fileextensions(void)
118 /* Includes space for hardcoded 'png' and termination NULL */
119 static const char *ext[MAX_FILE_EXTENSIONS + 2] =
121 NULL
124 if (ext[0] == NULL) {
125 int count = 0;
126 GSList *formats = gdk_pixbuf_get_formats();
127 GSList *next = formats;
129 while ((next = g_slist_next(next)) != NULL && count < MAX_FILE_EXTENSIONS) {
130 GdkPixbufFormat *format = g_slist_nth_data(next, 0);
131 gchar **mimes = gdk_pixbuf_format_get_mime_types(format);
132 int i;
134 /* Consider .png to be supported even when there's no mime-type called "png" */
135 ext[count++] = fc_strdup("png");
137 for (i = 0; mimes[i] != NULL && count < MAX_FILE_EXTENSIONS; i++) {
138 char *end = strstr(mimes[i], "/");
140 if (end != NULL) {
141 ext[count++] = fc_strdup(end + 1);
145 g_strfreev(mimes);
148 g_slist_free(formats);
150 ext[count] = NULL;
153 return ext;
156 /****************************************************************************
157 Called when the cairo surface with freeciv allocated data is destroyed.
158 ****************************************************************************/
159 static void surf_destroy_callback(void *data)
161 free(data);
164 /****************************************************************************
165 Load the given graphics file into a sprite. This function loads an
166 entire image file, which may later be broken up into individual sprites
167 with crop_sprite.
168 ****************************************************************************/
169 struct sprite *load_gfxfile(const char *filename)
171 struct sprite *spr;
172 GError *err = NULL;;
173 GdkPixbuf *pb = gdk_pixbuf_new_from_file(filename, &err);
174 int width;
175 int height;
176 unsigned char *pbdata;
177 int rs;
178 unsigned char *cairo_data;
179 unsigned char *data;
180 int i, j;
181 int cairo_stride;
182 bool has_alpha;
183 int channels;
185 if (pb == NULL) {
186 log_error(_("Can't load %s: %s"), filename, err->message);
187 return NULL;
190 spr = fc_malloc(sizeof(*spr));
191 width = gdk_pixbuf_get_width(pb);
192 height = gdk_pixbuf_get_height(pb);
193 pbdata = gdk_pixbuf_get_pixels(pb);
194 rs = gdk_pixbuf_get_rowstride(pb);
195 has_alpha = gdk_pixbuf_get_has_alpha(pb);
196 channels = gdk_pixbuf_get_n_channels(pb);
198 cairo_stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
199 if (cairo_stride <= 0) {
200 log_error("Cairo does not give stride for width %d", width);
201 free(spr);
202 return NULL;
205 cairo_data = fc_malloc(height * cairo_stride * 4);
206 data = cairo_data;
208 for (i = 0; i < height; i++) {
209 for (j = 0; j < width; j++) {
210 if (has_alpha) {
211 unsigned char tmp;
213 #define MULTI_UNc(a,b) ((a * b - (b / 2)) / 0xFF)
215 if (is_bigendian()) {
216 tmp = pbdata[j * channels + 3];
217 data[j * 4 + 3] = MULTI_UNc(pbdata[j * channels + 2], tmp);
218 data[j * 4 + 2] = MULTI_UNc(pbdata[j * channels + 1], tmp);
219 data[j * 4 + 1] = MULTI_UNc(pbdata[j * channels + 0], tmp);
220 data[j * 4 + 0] = tmp;
221 } else {
222 tmp = MULTI_UNc(pbdata[j * channels + 2], pbdata[j * channels + 3]);
223 data[j * 4 + 1] = MULTI_UNc(pbdata[j * channels + 1], pbdata[j * channels + 3]);
224 data[j * 4 + 2] = MULTI_UNc(pbdata[j * channels + 0], pbdata[j * channels + 3]);
225 data[j * 4 + 0] = tmp;
226 data[j * 4 + 3] = pbdata[j * channels + 3];
229 #undef MULTI_UNc
231 } else {
232 data[j * 4 + 3] = 255;
233 data[j * 4 + 0] = pbdata[j * channels + 2];
234 data[j * 4 + 1] = pbdata[j * channels + 1];
235 data[j * 4 + 2] = pbdata[j * channels + 0];
239 data += cairo_stride;
240 pbdata += rs;
243 g_object_unref(pb);
245 spr->surface = cairo_image_surface_create_for_data(cairo_data, CAIRO_FORMAT_ARGB32,
246 width, height, cairo_stride);
247 if (spr->surface == NULL || cairo_surface_status(spr->surface) != CAIRO_STATUS_SUCCESS) {
248 log_error("Cairo image surface creation error");
249 free(spr);
250 free(cairo_data);
252 return NULL;
255 cairo_surface_set_user_data(spr->surface, NULL, cairo_data, surf_destroy_callback);
257 fc_assert(cairo_image_surface_get_format(spr->surface) == CAIRO_FORMAT_ARGB32);
259 if (cairo_surface_status(spr->surface) != CAIRO_STATUS_SUCCESS) {
260 log_fatal("Failed reading graphics file: \"%s\"", filename);
262 exit(EXIT_FAILURE);
265 return spr;
268 /****************************************************************************
269 Free a sprite and all associated image data.
270 ****************************************************************************/
271 void free_sprite(struct sprite * s)
273 cairo_surface_destroy(s->surface);
274 free(s);
277 /****************************************************************************
278 Scales a sprite. If the sprite contains a mask, the mask is scaled
279 as as well.
280 ****************************************************************************/
281 struct sprite *sprite_scale(struct sprite *src, int new_w, int new_h)
283 cairo_t *cr;
284 struct sprite *new = fc_malloc(sizeof(*new));
285 int width, height;
287 get_sprite_dimensions(src, &width, &height);
289 new->surface = cairo_surface_create_similar(src->surface,
290 CAIRO_CONTENT_COLOR_ALPHA, new_w, new_h);
292 cr = cairo_create(new->surface);
293 cairo_save(cr);
294 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
295 cairo_paint(cr);
296 cairo_restore(cr);
297 cairo_scale(cr, (double) new_w / (double) width, (double) new_h / (double) height);
298 cairo_set_source_surface(cr, src->surface, 0, 0);
299 cairo_paint(cr);
301 cairo_destroy(cr);
303 return new;
306 /****************************************************************************
307 Method returns the bounding box of a sprite. It assumes a rectangular
308 object/mask. The bounding box contains the border (pixel which have
309 unset pixel as neighbours) pixel.
310 ****************************************************************************/
311 void sprite_get_bounding_box(struct sprite * sprite, int *start_x,
312 int *start_y, int *end_x, int *end_y)
314 unsigned char *data = cairo_image_surface_get_data(sprite->surface);
315 int width = cairo_image_surface_get_width(sprite->surface);
316 int height = cairo_image_surface_get_height(sprite->surface);
317 int i, j;
318 int endian;
320 if (is_bigendian()) {
321 endian = 0;
322 } else {
323 endian = 3;
326 fc_assert(cairo_image_surface_get_format(sprite->surface) == CAIRO_FORMAT_ARGB32);
328 /* parses mask image for the first column that contains a visible pixel */
329 *start_x = -1;
330 for (i = 0; i < width && *start_x == -1; i++) {
331 for (j = 0; j < height; j++) {
332 if (data[(j * width + i) * 4 + endian]) {
333 *start_x = i;
334 break;
339 /* parses mask image for the last column that contains a visible pixel */
340 *end_x = -1;
341 for (i = width - 1; i >= *start_x && *end_x == -1; i--) {
342 for (j = 0; j < height; j++) {
343 if (data[(j * width + i) * 4 + endian]) {
344 *end_x = i;
345 break;
350 /* parses mask image for the first row that contains a visible pixel */
351 *start_y = -1;
352 for (i = 0; i < height && *start_y == -1; i++) {
353 for (j = *start_x; j <= *end_x; j++) {
354 if (data[(i * width + j) * 4 + endian]) {
355 *start_y = i;
356 break;
361 /* parses mask image for the last row that contains a visible pixel */
362 *end_y = -1;
363 for (i = height - 1; i >= *end_y && *end_y == -1; i--) {
364 for (j = *start_x; j <= *end_x; j++) {
365 if (data[(i * width + j) * 4 + endian]) {
366 *end_y = i;
367 break;
373 /****************************************************************************
374 Crops all blankspace from a sprite (insofar as is possible as a rectangle)
375 ****************************************************************************/
376 struct sprite *crop_blankspace(struct sprite *s)
378 int x1, y1, x2, y2;
380 sprite_get_bounding_box(s, &x1, &y1, &x2, &y2);
382 return crop_sprite(s, x1, y1, x2 - x1 + 1, y2 - y1 + 1, NULL, -1, -1,
383 1.0, FALSE);
386 /********************************************************************
387 Render a pixbuf from the sprite.
389 NOTE: the pixmap and mask of a sprite must not change after this
390 function is called!
391 ********************************************************************/
392 GdkPixbuf *sprite_get_pixbuf(struct sprite *sprite)
394 int width, height;
396 if (!sprite) {
397 return NULL;
400 get_sprite_dimensions(sprite, &width, &height);
402 return surface_get_pixbuf(sprite->surface, width, height);
405 /********************************************************************
406 Render a pixbuf from the cairo surface
407 ********************************************************************/
408 GdkPixbuf *surface_get_pixbuf(cairo_surface_t *surf, int width, int height)
410 cairo_t *cr;
411 cairo_surface_t *tmpsurf;
412 GdkPixbuf *pb;
413 unsigned char *pixels;
414 int rowstride;
415 int i;
417 pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
418 pixels = gdk_pixbuf_get_pixels(pb);
419 rowstride = gdk_pixbuf_get_rowstride(pb);
421 tmpsurf = cairo_image_surface_create_for_data(pixels, CAIRO_FORMAT_ARGB32,
422 width, height, rowstride);
424 cr = cairo_create(tmpsurf);
425 cairo_save(cr);
426 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
427 cairo_paint(cr);
428 cairo_restore(cr);
429 cairo_set_source_surface(cr, surf, 0, 0);
430 cairo_paint(cr);
431 cairo_destroy(cr);
433 for (i = height; i > 0; i--) {
434 unsigned char *p = pixels;
435 unsigned char *end = p + 4 * width;
436 unsigned char tmp;
438 #define DIV_UNc(a,b) (((guint16) (a) * 0xFF + ((b) / 2)) / (b))
440 while (p < end) {
441 tmp = p[0];
443 if (is_bigendian()) {
444 if (tmp != 0) {
445 p[0] = DIV_UNc(p[1], tmp);
446 p[1] = DIV_UNc(p[2], tmp);
447 p[2] = DIV_UNc(p[3], tmp);
448 p[3] = tmp;
449 } else {
450 p[1] = p[2] = p[3] = 0;
452 } else {
453 if (p[3] != 0) {
454 p[0] = DIV_UNc(p[2], p[3]);
455 p[1] = DIV_UNc(p[1], p[3]);
456 p[2] = DIV_UNc(tmp, p[3]);
457 } else {
458 p[0] = p[1] = p[2] = 0;
462 p += 4;
465 #undef DIV_UNc
467 pixels += rowstride;
470 cairo_surface_destroy(tmpsurf);
472 return pb;