20100212
[gdash.git] / src / gtkgfx.c
blob2bd8cb01a98d5ce6cc4827aeec036aa6f8b29c52
1 /*
2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <gtk/gtk.h>
17 #include <glib.h>
18 #include <glib/gi18n.h>
19 #include <glib/gstdio.h>
20 #include "gfxutil.h"
21 #include "colors.h"
22 #include "cave.h"
23 #include "cavedb.h"
24 #include "caveset.h"
25 #include "caveobject.h"
26 #include "gtkgfx.h"
27 #include "c64_gfx.h" /* char c64_gfx[] with (almost) original graphics */
28 #include "settings.h"
29 #include "util.h"
31 #include "c64_png_colors.h"
33 static GdkPixbuf *cells_pb[NUM_OF_CELLS];
34 static GdkPixbuf *combo_pb[NUM_OF_CELLS];
36 static GdkPixmap *cells_game[NUM_OF_CELLS*3], *cells_editor[NUM_OF_CELLS*3];
37 int gd_cell_size_game, gd_cell_size_editor;
39 GdkPixbuf *gd_pixbuf_for_builtin_theme; /* this stores a player image, which is the pixbuf for the settings window */
44 static GdColor color0, color1, color2, color3, color4, color5; /* currently used cell colors */
45 static guint8 *c64_custom_gfx=NULL;
46 static gboolean using_png_gfx;
48 /* to be called at application start. creates a pixbuf,
49 * which represents the builtin theme in the preferences window.
51 void gd_create_pixbuf_for_builtin_theme()
53 /* use gdash palette */
54 gd_select_pixbuf_colors(GD_GDASH_BLACK, GD_GDASH_MIDDLEBLUE, GD_GDASH_LIGHTRED, GD_GDASH_WHITE, GD_GDASH_WHITE, GD_GDASH_WHITE);
55 gd_pixbuf_for_builtin_theme=gdk_pixbuf_copy(cells_pb[ABS(gd_elements[O_PLAYER].image_game)]);
59 /* used by the editor for the element pick dialog */
60 GdColor
61 gd_current_background_color()
63 if (color0!=GD_COLOR_INVALID)
64 return color0;
65 return GD_GDASH_BLACK;
69 /* wrapper around scale2x defined in gfxutil.c */
70 static GdkPixbuf *
71 scale2x(GdkPixbuf *src)
73 GdkPixbuf *dst;
75 g_assert(gdk_pixbuf_get_colorspace(src)==GDK_COLORSPACE_RGB);
76 g_assert(gdk_pixbuf_get_has_alpha(src));
77 g_assert(gdk_pixbuf_get_n_channels(src)==4);
78 g_assert(gdk_pixbuf_get_bits_per_sample(src)==8);
80 dst=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 2*gdk_pixbuf_get_width(src), 2*gdk_pixbuf_get_height(src));
82 gd_scale2x_raw(gdk_pixbuf_get_pixels(src), gdk_pixbuf_get_width(src), gdk_pixbuf_get_height(src), gdk_pixbuf_get_rowstride(src), gdk_pixbuf_get_pixels(dst), gdk_pixbuf_get_rowstride(dst));
84 return dst;
87 /* wrapper around scale3x defined in gfxutil.c */
88 static GdkPixbuf *
89 scale3x(GdkPixbuf *src)
91 GdkPixbuf *dst;
93 g_assert(gdk_pixbuf_get_colorspace(src)==GDK_COLORSPACE_RGB);
94 g_assert(gdk_pixbuf_get_has_alpha(src));
95 g_assert(gdk_pixbuf_get_n_channels(src)==4);
96 g_assert(gdk_pixbuf_get_bits_per_sample(src)==8);
98 dst=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 3*gdk_pixbuf_get_width(src), 3*gdk_pixbuf_get_height(src));
100 gd_scale3x_raw(gdk_pixbuf_get_pixels(src), gdk_pixbuf_get_width(src), gdk_pixbuf_get_height(src), gdk_pixbuf_get_rowstride(src), gdk_pixbuf_get_pixels(dst), gdk_pixbuf_get_rowstride(dst));
102 return dst;
105 /* scale4x is essentially a scale2x applied twice */
106 static GdkPixbuf *
107 scale4x(GdkPixbuf *src)
109 GdkPixbuf *dst2x, *dst;
111 g_assert(gdk_pixbuf_get_colorspace(src)==GDK_COLORSPACE_RGB);
112 g_assert(gdk_pixbuf_get_has_alpha(src));
113 g_assert(gdk_pixbuf_get_n_channels(src)==4);
114 g_assert(gdk_pixbuf_get_bits_per_sample(src)==8);
116 dst2x=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 2*gdk_pixbuf_get_width(src), 2*gdk_pixbuf_get_height(src));
117 dst=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 4*gdk_pixbuf_get_width(src), 4*gdk_pixbuf_get_height(src));
119 gd_scale2x_raw(gdk_pixbuf_get_pixels(src), gdk_pixbuf_get_width(src), gdk_pixbuf_get_height(src), gdk_pixbuf_get_rowstride(src), gdk_pixbuf_get_pixels(dst2x), gdk_pixbuf_get_rowstride(dst2x));
121 gd_scale2x_raw(gdk_pixbuf_get_pixels(dst2x), gdk_pixbuf_get_width(dst2x), gdk_pixbuf_get_height(dst2x), gdk_pixbuf_get_rowstride(dst2x), gdk_pixbuf_get_pixels(dst), gdk_pixbuf_get_rowstride(dst));
123 g_object_unref(dst2x);
125 return dst;
131 /* scales a pixbuf with the appropriate scaling type. */
132 GdkPixbuf *
133 gd_pixbuf_scale(GdkPixbuf *orig, GdScalingType type)
135 GdkPixbuf *pixbuf=NULL;
137 switch (type) {
138 case GD_SCALING_ORIGINAL:
139 pixbuf=gdk_pixbuf_copy(orig);
140 break;
142 case GD_SCALING_2X:
143 pixbuf=gdk_pixbuf_scale_simple(orig, 2*gdk_pixbuf_get_width(orig), 2*gdk_pixbuf_get_height(orig), GDK_INTERP_NEAREST);
144 break;
146 case GD_SCALING_2X_BILINEAR:
147 pixbuf=gdk_pixbuf_scale_simple(orig, 2*gdk_pixbuf_get_width(orig), 2*gdk_pixbuf_get_height(orig), GDK_INTERP_BILINEAR);
148 break;
150 case GD_SCALING_2X_SCALE2X:
151 pixbuf=scale2x(orig);
152 break;
154 case GD_SCALING_3X:
155 pixbuf=gdk_pixbuf_scale_simple(orig, 3*gdk_pixbuf_get_width(orig), 3*gdk_pixbuf_get_height(orig), GDK_INTERP_NEAREST);
156 break;
158 case GD_SCALING_3X_BILINEAR:
159 pixbuf=gdk_pixbuf_scale_simple(orig, 3*gdk_pixbuf_get_width(orig), 3*gdk_pixbuf_get_height(orig), GDK_INTERP_BILINEAR);
160 break;
162 case GD_SCALING_3X_SCALE3X:
163 pixbuf=scale3x(orig);
164 break;
166 case GD_SCALING_4X:
167 pixbuf=gdk_pixbuf_scale_simple(orig, 4*gdk_pixbuf_get_width(orig), 4*gdk_pixbuf_get_height(orig), GDK_INTERP_NEAREST);
168 break;
170 case GD_SCALING_4X_BILINEAR:
171 pixbuf=gdk_pixbuf_scale_simple(orig, 4*gdk_pixbuf_get_width(orig), 4*gdk_pixbuf_get_height(orig), GDK_INTERP_BILINEAR);
172 break;
174 case GD_SCALING_4X_SCALE4X:
175 pixbuf=scale4x(orig);
176 break;
178 case GD_SCALING_MAX:
179 /* to avoid compiler warning */
180 g_assert_not_reached();
181 break;
184 return pixbuf;
188 draw an element - usually an arrow or something like that
189 over another one.
191 the destination element's editor drawing will be used.
192 the source element will be a game element.
194 static void
195 add_arrow_to_cell(GdElement dest, GdElement src, GdElement arrow, GdkPixbufRotation rotation)
197 int pixbuf_cell_size=gdk_pixbuf_get_height(cells_pb[0]);
198 GdkPixbuf *arrow_pb=gdk_pixbuf_rotate_simple(cells_pb[gd_elements[arrow].image], rotation); /* arrow */
200 if (gd_elements[dest].image<NUM_OF_CELLS_X*NUM_OF_CELLS_Y) {
201 g_critical("destination index %d<NUM_OF_CELLS_X*NUM_OF_CELLS_Y, element %s", dest, gd_elements[dest].name);
202 g_assert_not_reached();
205 if (gd_elements[dest].image>=NUM_OF_CELLS) {
206 g_critical("destination index %d>=NUM_OF_CELLS, element %s", dest, gd_elements[dest].name);
207 g_assert_not_reached();
209 if (cells_pb[gd_elements[dest].image]!=NULL) {
210 g_critical("destination index %d!=NULL, element %s", dest, gd_elements[dest].name);
211 g_assert_not_reached();
214 /* editor image <- game image */
215 cells_pb[gd_elements[dest].image]=gdk_pixbuf_copy(cells_pb[ABS(gd_elements[src].image_game)]);
216 /* composite arrow to copy */
217 gdk_pixbuf_composite (arrow_pb, cells_pb[gd_elements[dest].image], 0, 0, pixbuf_cell_size, pixbuf_cell_size, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
218 g_object_unref (arrow_pb);
221 static void
222 copy_cell(int dest, int src)
224 g_assert(cells_pb[dest]==NULL);
225 g_assert(src<NUM_OF_CELLS);
226 cells_pb[dest]=gdk_pixbuf_copy(cells_pb[src]);
230 composite two elements.
232 static void
233 create_composite_cell_pixbuf(GdElement dest, GdElement src1, GdElement src2)
235 int pixbuf_cell_size=gdk_pixbuf_get_height (cells_pb[0]);
237 g_assert(gd_elements[dest].image<NUM_OF_CELLS);
238 g_assert(cells_pb[gd_elements[dest].image]==NULL);
240 /* destination image=source1 */
241 cells_pb[gd_elements[dest].image]=gdk_pixbuf_copy(cells_pb[gd_elements[src1].image]);
242 /* composite source2 to destination */
243 gdk_pixbuf_composite(cells_pb[gd_elements[src2].image], cells_pb[gd_elements[dest].image], 0, 0, pixbuf_cell_size, pixbuf_cell_size, 0, 0, 1, 1, GDK_INTERP_NEAREST, 85);
246 /* check if pixbuf is suitable for a theme.
247 report_error=TRUE, then it will send warning messages if it isn't.
249 const char *
250 gd_is_pixbuf_ok_for_theme(GdkPixbuf *pixbuf)
252 static char *error=NULL;
253 int width, height;
255 g_assert(pixbuf!=NULL);
257 g_free(error);
258 error=NULL;
260 width=gdk_pixbuf_get_width(pixbuf);
261 height=gdk_pixbuf_get_height(pixbuf);
263 if ((width % NUM_OF_CELLS_X != 0) || (height % NUM_OF_CELLS_Y != 0) || (width / NUM_OF_CELLS_X != height / NUM_OF_CELLS_Y)) {
264 error=g_strdup_printf("Image should contain %d cells in a row and %d in a column!", NUM_OF_CELLS_X, NUM_OF_CELLS_Y);
265 return error;
267 if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
268 error=g_strdup_printf("Image should have an alpha channel!");
269 return error;
272 /* passes tests */
273 return NULL;
276 /* check if file is suitable for a theme.
277 report_error=TRUE, then it will send warning messages if it isn't.
279 const char *
280 gd_is_image_ok_for_theme(const char *filename)
282 /* load from file */
283 GdkPixbuf *pixbuf;
284 GError *error=NULL;
285 static char *error_msg=NULL;
286 const char *error_from_pixbuf;
288 g_assert(filename!=NULL);
290 g_free(error_msg);
291 error_msg=NULL;
293 /* open file */
294 pixbuf=gdk_pixbuf_new_from_file (filename, &error);
295 if (error) {
296 error_msg=g_strdup(error->message);
297 g_error_free(error);
298 return error_msg;
300 error_from_pixbuf=gd_is_pixbuf_ok_for_theme(pixbuf);
301 g_object_unref(pixbuf);
303 if (error_from_pixbuf) {
304 error_msg=g_strdup(error_from_pixbuf);
305 return error_msg;
308 return NULL; /* passed tests */
311 /* remove pixmaps from x server */
312 static void
313 free_pixmaps()
315 int i;
317 /* if cells already loaded, unref them */
318 for (i=0; i<G_N_ELEMENTS(cells_game); i++) {
319 if (cells_game[i])
320 g_object_unref(cells_game[i]);
321 cells_game[i]=NULL;
323 /* if cells already loaded, unref them */
324 for (i=0; i<G_N_ELEMENTS(cells_editor); i++) {
325 if (cells_editor[i])
326 g_object_unref(cells_editor[i]);
327 cells_editor[i]=NULL;
331 /* returns true, if the given pixbuf seems to be a c64 imported image. */
332 static gboolean
333 check_if_pixbuf_c64_png (GdkPixbuf *pixbuf)
335 int width, height, rowstride, n_channels;
336 guchar *pixels, *p;
337 int x, y;
339 n_channels=gdk_pixbuf_get_n_channels (pixbuf);
341 g_assert(gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
342 g_assert(gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
343 g_assert(gdk_pixbuf_get_has_alpha (pixbuf));
344 g_assert(n_channels == 4);
346 width=gdk_pixbuf_get_width (pixbuf);
347 height=gdk_pixbuf_get_height (pixbuf);
349 rowstride=gdk_pixbuf_get_rowstride (pixbuf);
350 pixels=gdk_pixbuf_get_pixels (pixbuf);
352 for (y=0; y<height; y++) {
353 p=pixels + y * rowstride;
354 for (x=0; x<width*n_channels; x++)
355 if (p[x]!=0 && p[x]!=255)
356 return FALSE;
358 return TRUE;
361 static void
362 check_pixbuf(int i, const char *name)
364 if (i>=0) {
365 if (cells_pb[i]==NULL)
366 g_critical("no pixbuf for %s", name);
367 } else {
368 int x;
370 for (x=0; x<8; x++)
371 if (cells_pb[ABS(i)+x]==NULL)
372 g_critical("no pixbuf for %s", name);
377 /* load cells, eg. create cells_pb and combo_pb
378 from a big pixbuf.
380 static void
381 loadcells_from_pixbuf(GdkPixbuf *cells_pixbuf)
383 static gboolean checked_cells=FALSE;
384 int i;
385 int pixbuf_cell_size;
387 /* now that we have the pixbuf, we can start freeing old graphics. */
388 for (i=0; i<G_N_ELEMENTS(cells_pb); i++) {
389 if (cells_pb[i]) {
390 g_object_unref (cells_pb[i]);
391 cells_pb[i]=NULL;
393 /* scaled cells for editor combo boxes. created by editor, but we free them if we load a new theme */
394 if (combo_pb[i]) {
395 g_object_unref (combo_pb[i]);
396 combo_pb[i]=NULL;
399 /* if we have scaled pixmaps, remove them */
400 free_pixmaps();
402 /* 8 (NUM_OF_CELLS_X) cells in a row, so divide by it and we get the size of a cell in pixels */
403 pixbuf_cell_size=gdk_pixbuf_get_width(cells_pixbuf) / NUM_OF_CELLS_X;
405 /* make individual cell pixbufs */
406 for (i=0; i<NUM_OF_CELLS_Y*NUM_OF_CELLS_X; i++)
407 /* copy one cell */
408 cells_pb[i]=gdk_pixbuf_new_subpixbuf(cells_pixbuf, (i%NUM_OF_CELLS_X) * pixbuf_cell_size, (i/NUM_OF_CELLS_X) * pixbuf_cell_size, pixbuf_cell_size, pixbuf_cell_size);
410 /* set cell sizes */
411 gd_cell_size_game=pixbuf_cell_size*gd_scaling_scale[gd_cell_scale_game];
412 gd_cell_size_editor=pixbuf_cell_size*gd_scaling_scale[gd_cell_scale_editor];
414 /* draw some elements, combining them with arrows and the like */
415 add_arrow_to_cell(O_STEEL_EATABLE, O_STEEL, O_EATABLE, GDK_PIXBUF_ROTATE_NONE);
416 add_arrow_to_cell(O_BRICK_EATABLE, O_BRICK, O_EATABLE, GDK_PIXBUF_ROTATE_NONE);
417 create_composite_cell_pixbuf(O_BRICK_NON_SLOPED, O_STEEL, O_BRICK);
419 create_composite_cell_pixbuf(O_WALLED_KEY_1, O_KEY_1, O_BRICK);
420 create_composite_cell_pixbuf(O_WALLED_KEY_2, O_KEY_2, O_BRICK);
421 create_composite_cell_pixbuf(O_WALLED_KEY_3, O_KEY_3, O_BRICK);
422 create_composite_cell_pixbuf(O_WALLED_DIAMOND, O_DIAMOND, O_BRICK);
424 add_arrow_to_cell(O_FIREFLY_1, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
425 add_arrow_to_cell(O_FIREFLY_2, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
426 add_arrow_to_cell(O_FIREFLY_3, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
427 add_arrow_to_cell(O_FIREFLY_4, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
429 add_arrow_to_cell(O_ALT_FIREFLY_1, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
430 add_arrow_to_cell(O_ALT_FIREFLY_2, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
431 add_arrow_to_cell(O_ALT_FIREFLY_3, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
432 add_arrow_to_cell(O_ALT_FIREFLY_4, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
434 add_arrow_to_cell(O_H_EXPANDING_WALL, O_H_EXPANDING_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_NONE);
435 add_arrow_to_cell(O_V_EXPANDING_WALL, O_V_EXPANDING_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
436 add_arrow_to_cell(O_EXPANDING_WALL, O_EXPANDING_WALL, O_EVERYDIR_ARROW, GDK_PIXBUF_ROTATE_NONE);
438 add_arrow_to_cell(O_H_EXPANDING_STEEL_WALL, O_H_EXPANDING_STEEL_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_NONE);
439 add_arrow_to_cell(O_V_EXPANDING_STEEL_WALL, O_V_EXPANDING_STEEL_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
440 add_arrow_to_cell(O_EXPANDING_STEEL_WALL, O_EXPANDING_STEEL_WALL, O_EVERYDIR_ARROW, GDK_PIXBUF_ROTATE_NONE);
442 add_arrow_to_cell(O_BUTTER_1, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
443 add_arrow_to_cell(O_BUTTER_2, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
444 add_arrow_to_cell(O_BUTTER_3, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
445 add_arrow_to_cell(O_BUTTER_4, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
447 add_arrow_to_cell(O_DRAGONFLY_1, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
448 add_arrow_to_cell(O_DRAGONFLY_2, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
449 add_arrow_to_cell(O_DRAGONFLY_3, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
450 add_arrow_to_cell(O_DRAGONFLY_4, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
452 add_arrow_to_cell(O_COW_1, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
453 add_arrow_to_cell(O_COW_2, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
454 add_arrow_to_cell(O_COW_3, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
455 add_arrow_to_cell(O_COW_4, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
456 add_arrow_to_cell(O_COW_ENCLOSED_1, O_COW_1, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
458 add_arrow_to_cell(O_ALT_BUTTER_1, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
459 add_arrow_to_cell(O_ALT_BUTTER_2, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
460 add_arrow_to_cell(O_ALT_BUTTER_3, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
461 add_arrow_to_cell(O_ALT_BUTTER_4, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
463 add_arrow_to_cell(O_PLAYER_GLUED, O_PLAYER, O_GLUED, 0);
464 add_arrow_to_cell(O_PLAYER, O_PLAYER, O_EXCLAMATION_MARK, 0);
465 add_arrow_to_cell(O_STONE_GLUED, O_STONE, O_GLUED, 0);
466 add_arrow_to_cell(O_DIAMOND_GLUED, O_DIAMOND, O_GLUED, 0);
467 add_arrow_to_cell(O_DIRT_GLUED, O_DIRT, O_GLUED, 0);
468 add_arrow_to_cell(O_STONE_F, O_STONE, O_DOWN_ARROW, 0);
469 add_arrow_to_cell(O_FLYING_STONE_F, O_FLYING_STONE, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
470 add_arrow_to_cell(O_MEGA_STONE_F, O_MEGA_STONE, O_DOWN_ARROW, 0);
471 add_arrow_to_cell(O_DIAMOND_F, O_DIAMOND, O_DOWN_ARROW, 0);
472 add_arrow_to_cell(O_FLYING_DIAMOND_F, O_FLYING_DIAMOND, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
473 add_arrow_to_cell(O_NUT_F, O_NUT, O_DOWN_ARROW, 0);
474 add_arrow_to_cell(O_FALLING_WALL, O_BRICK, O_EXCLAMATION_MARK, 0);
475 add_arrow_to_cell(O_FALLING_WALL_F, O_BRICK, O_DOWN_ARROW, 0);
476 add_arrow_to_cell(O_TIME_PENALTY, O_GRAVESTONE, O_EXCLAMATION_MARK, 0);
477 add_arrow_to_cell(O_NITRO_PACK_F, O_NITRO_PACK, O_DOWN_ARROW, 0);
478 add_arrow_to_cell(O_NITRO_PACK_EXPLODE, O_NITRO_PACK, O_EXCLAMATION_MARK, 0);
479 add_arrow_to_cell(O_CONVEYOR_LEFT, O_CONVEYOR_LEFT, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
480 add_arrow_to_cell(O_CONVEYOR_RIGHT, O_CONVEYOR_RIGHT, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
482 add_arrow_to_cell(O_STONEFLY_1, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
483 add_arrow_to_cell(O_STONEFLY_2, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
484 add_arrow_to_cell(O_STONEFLY_3, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
485 add_arrow_to_cell(O_STONEFLY_4, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
487 add_arrow_to_cell(O_BITER_1, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
488 add_arrow_to_cell(O_BITER_2, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
489 add_arrow_to_cell(O_BITER_3, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
490 add_arrow_to_cell(O_BITER_4, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
492 add_arrow_to_cell(O_PRE_INVIS_OUTBOX, O_OUTBOX_CLOSED, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
493 add_arrow_to_cell(O_PRE_OUTBOX, O_OUTBOX_OPEN, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
494 add_arrow_to_cell(O_INVIS_OUTBOX, O_OUTBOX_CLOSED, O_OUT, GDK_PIXBUF_ROTATE_NONE);
495 add_arrow_to_cell(O_OUTBOX, O_OUTBOX_OPEN, O_OUT, GDK_PIXBUF_ROTATE_NONE);
497 add_arrow_to_cell(O_UNKNOWN, O_STEEL, O_QUESTION_MARK, GDK_PIXBUF_ROTATE_NONE);
498 add_arrow_to_cell(O_WAITING_STONE, O_STONE, O_EXCLAMATION_MARK, GDK_PIXBUF_ROTATE_NONE);
500 /* blinking outbox: helps editor, drawing the cave is more simple */
501 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+0, gd_elements[O_OUTBOX_OPEN].image_game);
502 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+1, gd_elements[O_OUTBOX_OPEN].image_game);
503 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+2, gd_elements[O_OUTBOX_OPEN].image_game);
504 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+3, gd_elements[O_OUTBOX_OPEN].image_game);
505 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+4, gd_elements[O_OUTBOX_CLOSED].image_game);
506 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+5, gd_elements[O_OUTBOX_CLOSED].image_game);
507 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+6, gd_elements[O_OUTBOX_CLOSED].image_game);
508 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+7, gd_elements[O_OUTBOX_CLOSED].image_game);
510 /* check if all cells have been generated; if not,
511 some add_arrows or copy_cells are missing for a
512 newly added element. do this only once per game run. */
513 if (!checked_cells) {
514 checked_cells=TRUE;
515 for (i=0; gd_elements[i].element!=-1; i++) {
516 check_pixbuf(gd_elements[i].image, gd_elements[i].name);
517 check_pixbuf(gd_elements[i].image_simple, gd_elements[i].name);
518 check_pixbuf(gd_elements[i].image_game, gd_elements[i].name);
523 static guint32
524 rgba_pixel_from_color(GdColor col, guint8 a)
526 guint8 r=gd_color_get_r(col);
527 guint8 g=gd_color_get_g(col);
528 guint8 b=gd_color_get_b(col);
529 #if G_BYTE_ORDER==G_LITTLE_ENDIAN
530 return r+(g<<8)+(b<<16)+(a<<24);
531 #else
532 return (r<<24)+(g<<16)+(b<<8)+a;
533 #endif
536 static void
537 loadcells_c64_with_colors(GdColor c0, GdColor c1, GdColor c2, GdColor c3, GdColor c4, GdColor c5)
539 const guchar *gfx; /* currently used graphics, will point to c64_gfx or c64_custom_gfx */
540 GdkPixbuf *cells_pixbuf;
541 guint32 cols[9]; /* holds rgba for color indexes internally used */
542 int rowstride, n_channels;
543 guchar *pixels;
544 int pos, x, y;
546 gfx=c64_custom_gfx?c64_custom_gfx:c64_gfx;
548 cols[0]=rgba_pixel_from_color(0, 0);
549 cols[1]=rgba_pixel_from_color(c0, 0xff); /* c64 background */
550 cols[2]=rgba_pixel_from_color(c1, 0xff); /* foreg1 */
551 cols[3]=rgba_pixel_from_color(c2, 0xff); /* foreg2 */
552 cols[4]=rgba_pixel_from_color(c3, 0xff); /* foreg3 */
553 cols[5]=rgba_pixel_from_color(c4, 0xff); /* amoeba */
554 cols[6]=rgba_pixel_from_color(c5, 0xff); /* slime */
555 cols[7]=rgba_pixel_from_color(0, 0xff); /* black, opaque*/
556 cols[8]=rgba_pixel_from_color(0xffffff, 0xff); /* white, opaque*/
558 cells_pixbuf=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, NUM_OF_CELLS_X*gfx[0], NUM_OF_CELLS_Y*gfx[0]);
559 n_channels=gdk_pixbuf_get_n_channels (cells_pixbuf);
560 rowstride=gdk_pixbuf_get_rowstride (cells_pixbuf); /* bytes / row */
561 pixels=gdk_pixbuf_get_pixels (cells_pixbuf); /* pointer to pixbuf memory */
563 pos=1; /* index to gfx array */
564 /* create colored pixbuf from c64 graphics, using c0, c1, c2, c3 */
565 for (y=0; y<NUM_OF_CELLS_Y*gfx[0]; y++) {
566 guint32 *p=(guint32*) (pixels + y * rowstride); /* write 32bits at once - faster than writing bytes */
568 for (x=0; x<NUM_OF_CELLS_X*gfx[0]; x++)
569 p[x]=cols[(int) gfx[pos++]];
572 /* from here, same as any other png */
573 loadcells_from_pixbuf(cells_pixbuf);
574 g_object_unref(cells_pixbuf);
579 static guchar *
580 c64_gfx_data_from_pixbuf(GdkPixbuf *pixbuf)
582 int width, height, rowstride, n_channels;
583 guchar *pixels;
584 int x, y;
585 guchar *data;
586 int out;
588 g_assert(gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB);
589 g_assert(gdk_pixbuf_get_bits_per_sample(pixbuf) == 8);
590 g_assert(gdk_pixbuf_get_has_alpha(pixbuf));
592 n_channels=gdk_pixbuf_get_n_channels (pixbuf);
593 width=gdk_pixbuf_get_width (pixbuf);
594 height=gdk_pixbuf_get_height (pixbuf);
595 rowstride=gdk_pixbuf_get_rowstride (pixbuf);
596 pixels=gdk_pixbuf_get_pixels (pixbuf);
598 g_assert (n_channels == 4);
600 data=g_new(guchar, width*height+1);
601 out=0;
602 data[out++]=width/NUM_OF_CELLS_X;
604 for (y=0; y<height; y++)
605 for (x=0; x<width; x++) {
606 int r, g, b, a;
608 guchar *p=pixels + y * rowstride + x * n_channels;
609 r=p[0];
610 g=p[1];
611 b=p[2];
612 a=p[3];
613 data[out++]=c64_png_colors(r, g, b, a);
615 return data;
620 gboolean
621 gd_loadcells_file(const char *filename)
623 GdkPixbuf *cells_pixbuf;
624 GError *error=NULL;
625 const char *error_msg;
627 /* load cell graphics */
628 /* load from file */
629 cells_pixbuf=gdk_pixbuf_new_from_file (filename, &error);
630 if (error) {
631 g_warning("%s", error->message);
632 g_error_free(error);
633 return FALSE;
635 /* check if file has the properties which we need for a theme */
636 error_msg=gd_is_pixbuf_ok_for_theme(cells_pixbuf);
637 if (error_msg) {
638 g_object_unref(cells_pixbuf);
639 g_warning("%s", error_msg);
640 return FALSE;
643 if (check_if_pixbuf_c64_png(cells_pixbuf)) {
644 /* c64 pixbuf with a small number of colors which can be changed */
645 g_free(c64_custom_gfx);
646 c64_custom_gfx=c64_gfx_data_from_pixbuf(cells_pixbuf);
647 using_png_gfx=FALSE;
648 } else {
649 /* normal, "truecolor" pixbuf */
650 g_free(c64_custom_gfx);
651 c64_custom_gfx=NULL;
652 loadcells_from_pixbuf(cells_pixbuf);
653 using_png_gfx=TRUE;
655 g_object_unref(cells_pixbuf);
656 color0=GD_COLOR_INVALID; /* so that pixbufs will be recreated */
658 return TRUE;
661 void
662 gd_loadcells_default()
664 g_free(c64_custom_gfx);
665 c64_custom_gfx=NULL;
666 using_png_gfx=FALSE;
667 color0=GD_COLOR_INVALID; /* so that pixbufs will be recreated */
669 /* size of array (in bytes), -1 which is the cell size */
670 g_assert(sizeof(c64_gfx)-1 == NUM_OF_CELLS_X*NUM_OF_CELLS_Y*c64_gfx[0]*c64_gfx[0]);
673 /* wrapper */
674 /* exported for settings window */
675 void
676 gd_pal_emulate_pixbuf(GdkPixbuf *pixbuf)
678 #if G_BYTE_ORDER == G_BIG_ENDIAN
679 guint32 rshift=24;
680 guint32 gshift=16;
681 guint32 bshift=8;
682 guint32 ashift=0;
683 #else
684 guint32 rshift=0;
685 guint32 gshift=8;
686 guint32 bshift=16;
687 guint32 ashift=24;
688 #endif
689 g_assert(gdk_pixbuf_get_has_alpha(pixbuf));
691 gd_pal_emulate_raw(gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_rowstride(pixbuf), rshift, gshift, bshift, ashift);
694 /* create pixmap for image number 'index', using cell size. */
695 /* a pixmap * array is to be given; that may be cells_game or cells_editor */
696 /* pal emulation does just that. */
697 /* if render_selected is true, it will render the blue cell, too - that is used only in the editor. */
698 static void
699 create_pixmap(GdkPixmap **cells, int index, int cell_size, GdScalingType type, gboolean render_selected, gboolean pal_emul)
701 GdkPixbuf *selected, *normal, *element;
702 GdkWindow *window;
704 g_assert(index>=0 && index<NUM_OF_CELLS);
705 g_assert(cells_pb[index]!=NULL);
707 window=gdk_get_default_root_window();
709 /* scale the cell.
710 * scale every cell on its own, or else some pixels might be merged on borders */
711 normal=gd_pixbuf_scale(cells_pb[index], type);
712 if (pal_emul)
713 gd_pal_emulate_pixbuf(normal);
714 /* create colored cells. */
715 /* here no scaling is done, so interp_nearest is ok. */
717 /* create pixmap containing pixbuf */
718 /* draw the pixbufs to the new pixmaps */
719 cells[index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
720 gdk_draw_pixbuf(cells[index], NULL, normal, 0, 0, 0, 0, cell_size, cell_size, GDK_RGB_DITHER_MAX, 0, 0);
722 element=gdk_pixbuf_composite_color_simple(normal, cell_size, cell_size, GDK_INTERP_NEAREST, 128, 1, gd_flash_color, gd_flash_color);
723 cells[NUM_OF_CELLS + index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
724 gdk_draw_pixbuf(cells[NUM_OF_CELLS + index], NULL, element, 0, 0, 0, 0, cell_size, cell_size, GDK_RGB_DITHER_MAX, 0, 0);
725 g_object_unref(element);
727 if (render_selected) {
728 selected=gdk_pixbuf_composite_color_simple(normal, cell_size, cell_size, GDK_INTERP_NEAREST, 128, 1, gd_select_color, gd_select_color);
729 cells[2 * NUM_OF_CELLS + index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
730 gdk_draw_pixbuf(cells[2 * NUM_OF_CELLS + index], NULL, selected, 0, 0, 0, 0, cell_size, cell_size, GDK_RGB_DITHER_MAX, 0, 0);
731 g_object_unref(selected);
734 /* forget scaled pixbufs, as only the pixmaps are needed */
735 g_object_unref(normal);
738 /* return a pixmap scaled for the game */
739 GdkPixmap *
740 gd_game_pixmap(int index)
742 int i;
744 g_assert(index<2*NUM_OF_CELLS); /* make sure that no blue/"selected" pixbuf is requested for a game */
745 i=index%NUM_OF_CELLS; /* might request a colored, so we do a % NUM_OF_CELLS */
746 if (cells_game[i]==NULL) /* check if pixmap already exists */
747 create_pixmap(cells_game, i, gd_cell_size_game, gd_cell_scale_game, FALSE, gd_pal_emulation_game);
748 return cells_game[index];
751 /* return a pixmap scaled for the editor */
752 GdkPixmap *
753 gd_editor_pixmap(int index)
755 int i;
757 i=index%NUM_OF_CELLS; /* might request a colored, so we do a % NUM_OF_CELLS */
758 if (cells_editor[i]==NULL) /* check if pixmap already exists */
759 create_pixmap(cells_editor, i, gd_cell_size_editor, gd_cell_scale_editor, TRUE, gd_pal_emulation_editor);
760 return cells_editor[index];
763 /* set pixbuf colors. */
764 /* (if png graphics is used, it does nothing. */
765 void
766 gd_select_pixbuf_colors(GdColor c0, GdColor c1, GdColor c2, GdColor c3, GdColor c4, GdColor c5)
768 /* if non-c64 gfx, nothing to do */
769 if (using_png_gfx)
770 return;
772 /* convert to rgb value */
773 c0=gd_color_get_rgb(c0);
774 c1=gd_color_get_rgb(c1);
775 c2=gd_color_get_rgb(c2);
776 c3=gd_color_get_rgb(c3);
777 c4=gd_color_get_rgb(c4);
778 c5=gd_color_get_rgb(c5);
780 /* and compare rgb values! */
781 if (c0!=color0 || c1!=color1 || c2!=color2 || c3!=color3 || c4!=color4 || c5!=color5) {
782 /* if not the same colors as requested before */
783 color0=c0;
784 color1=c1;
785 color2=c2;
786 color3=c3;
787 color4=c4;
788 color5=c5;
790 loadcells_c64_with_colors(c0, c1, c2, c3, c4, c5);
795 static GdkPixbuf *
796 get_element_pixbuf_with_border(int index)
798 if (!combo_pb[index]) {
799 /* create small size pixbuf if needed. */
800 int x, y;
801 GdkPixbuf *pixbuf, *pixbuf_border;
803 /* scale pixbuf to that specified by gtk */
804 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &x, &y);
805 pixbuf=gdk_pixbuf_scale_simple(cells_pb[index], x, y, GDK_INTERP_BILINEAR);
806 /* draw a little black border around image, makes the icons look much better */
807 pixbuf_border=gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (pixbuf), 8, x+2, y+2);
808 gdk_pixbuf_fill(pixbuf_border, 0x000000ff); /* RGBA: opaque black */
809 gdk_pixbuf_copy_area(pixbuf, 0, 0, x, y, pixbuf_border, 1, 1);
810 g_object_unref(pixbuf);
811 combo_pb[index]=pixbuf_border;
813 return combo_pb[index];
817 returns a cell pixbuf, scaled to gtk icon size.
818 it also adds a little black border, which makes them look much better
820 GdkPixbuf *
821 gd_get_element_pixbuf_with_border(GdElement element)
823 int index;
824 /* which pixbuf to show? */
825 index=ABS(gd_elements[element].image);
826 return get_element_pixbuf_with_border(index);
830 returns a cell pixbuf, scaled to gtk icon size.
831 it also adds a little black border, which makes them look much better
833 GdkPixbuf *
834 gd_get_element_pixbuf_simple_with_border (GdElement element)
836 int index;
837 /* which pixbuf to show? */
838 index=ABS(gd_elements[element].image_simple);
839 return get_element_pixbuf_with_border(index);
843 creates a pixbuf, which shows the cave.
844 if width and height are given (nonzero),
845 scale pixbuf proportionally, so it fits in width*height
846 pixels. otherwise return in original size.
847 up to the caller to unref the returned pixbuf.
848 also up to the caller to call this function only for rendered caves.
850 GdkPixbuf *
851 gd_drawcave_to_pixbuf(const GdCave *cave, const int width, const int height, const gboolean game_view, const gboolean border)
853 int x, y;
854 int cell_size;
855 GdkPixbuf *pixbuf, *scaled;
856 float scale;
857 int x1, y1, x2, y2;
858 int borderadd=border?4:0, borderpos=border?2:0;
860 g_assert(cave->map!=NULL);
861 if (game_view) {
862 /* if showing the visible part only */
863 x1=cave->x1;
864 y1=cave->y1;
865 x2=cave->x2;
866 y2=cave->y2;
867 } else {
868 /* showing entire cave - for example, overview in editor */
869 x1=0;
870 y1=0;
871 x2=cave->w-1;
872 y2=cave->h-1;
875 gd_select_pixbuf_colors(cave->color0, cave->color1, cave->color2, cave->color3, cave->color4, cave->color5);
877 /* get size of one cell in the original pixbuf */
878 cell_size=gdk_pixbuf_get_width (cells_pb[0]);
880 /* add two pixels black border: +4 +4 for width and height */
881 pixbuf=gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (cells_pb[0]), 8, (x2-x1+1)*cell_size+borderadd, (y2-y1+1)*cell_size+borderadd);
882 if (border)
883 gdk_pixbuf_fill(pixbuf, 0x000000ff); /* fill with opaque black, so border is black */
885 /* take visible part into consideration */
886 for (y=y1; y<=y2; y++)
887 for (x=x1; x<=x2; x++) {
888 GdElement element=cave->map[y][x]&O_MASK;
889 int draw;
891 if (game_view) {
892 /* visual effects */
893 switch(element) {
894 case O_DIRT:
895 element=cave->dirt_looks_like;
896 break;
897 case O_EXPANDING_WALL:
898 case O_H_EXPANDING_WALL:
899 case O_V_EXPANDING_WALL:
900 /* only change the view, if it is not brick wall (the default value). */
901 /* so arrows remain - as well as they always remaing for the steel expanding wall,
902 which has no visual effect. */
903 if (cave->expanding_wall_looks_like!=O_BRICK)
904 element=cave->expanding_wall_looks_like;
905 break;
906 case O_AMOEBA_2:
907 element=cave->amoeba_2_looks_like;
908 break;
909 default:
910 /* we check that this element has no visual effect. */
911 /* otherwise, we should have handled the element explicitely above! */
912 g_assert((gd_elements[element].properties & P_VISUAL_EFFECT) == 0);
913 break;
915 draw=ABS(gd_elements[element].image_simple); /* pixbuf like in the editor */
917 else
918 draw=gd_elements[element].image; /* pixbuf like in the editor */
919 gdk_pixbuf_copy_area (cells_pb[draw], 0, 0, cell_size, cell_size, pixbuf, (x-x1)*cell_size+borderpos, (y-y1)*cell_size+borderpos);
922 /* if requested size is 0, return unscaled */
923 if (width == 0 || height == 0)
924 return pixbuf;
926 /* decide which direction fits in rectangle */
927 /* cells are squares... no need to know cell_size here */
928 if ((float) gdk_pixbuf_get_width (pixbuf) / (float) gdk_pixbuf_get_height (pixbuf) >= (float) width / (float) height)
929 scale=width / ((float) gdk_pixbuf_get_width (pixbuf));
930 else
931 scale=height / ((float) gdk_pixbuf_get_height (pixbuf));
933 /* scale to specified size */
934 scaled=gdk_pixbuf_scale_simple (pixbuf, gdk_pixbuf_get_width (pixbuf)*scale, gdk_pixbuf_get_height (pixbuf)*scale, GDK_INTERP_BILINEAR);
935 g_object_unref (pixbuf);
937 return scaled;
941 GdkPixbuf *
942 gd_pixbuf_load_from_data(guchar *data, int length)
944 GdkPixbufLoader *loader;
945 GdkPixbuf *pixbuf;
946 GError *error=NULL;
948 /* push the data into a pixbuf loader */
949 loader=gdk_pixbuf_loader_new();
950 if (!gdk_pixbuf_loader_write(loader, data, length, &error) || !gdk_pixbuf_loader_close(loader, &error)) {
951 g_warning("%s", error->message);
952 g_error_free(error);
953 g_object_unref(loader);
954 return NULL;
956 pixbuf=gdk_pixbuf_loader_get_pixbuf(loader);
957 g_object_ref(pixbuf);
958 g_object_unref(loader);
959 return pixbuf;
962 GdkPixbuf *
963 gd_pixbuf_load_from_base64(gchar *base64)
965 guchar *data;
966 gsize length;
967 GdkPixbuf *pixbuf;
969 data=g_base64_decode(base64, &length);
970 pixbuf=gd_pixbuf_load_from_data(data, length);
971 g_free(data);
973 return pixbuf;
979 /* create a pixbuf of the caveset-specific title image. */
980 /* if there is no such image, return null. */
981 GdkPixbuf *
982 gd_create_title_image()
984 guchar *data;
985 gsize length;
986 GdkPixbuf *screen, *tile, *tile_black, *bigone;
987 int w, h; /* screen (large image) width and height */
988 int tw, th; /* tile (small image) width and height */
989 int x, y;
991 if (gd_caveset_data->title_screen->len==0)
992 return NULL;
994 data=g_base64_decode(gd_caveset_data->title_screen->str, &length);
995 screen=gd_pixbuf_load_from_data(data, length);
996 g_free(data);
997 if (!screen) {
998 g_warning("Invalid title image stored in caveset.");
999 g_string_assign(gd_caveset_data->title_screen, ""); /* forget if it had some error... */
1000 return NULL;
1003 tile=NULL;
1004 if (gd_caveset_data->title_screen_scroll->len!=0) {
1005 /* there is a tile, so load that also. */
1006 data=g_base64_decode(gd_caveset_data->title_screen_scroll->str, &length);
1007 tile=gd_pixbuf_load_from_data(data, length);
1008 g_free(data);
1009 if (!tile) {
1010 g_warning("Invalid title image scrolling background stored in caveset.");
1011 g_string_assign(gd_caveset_data->title_screen_scroll, ""); /* forget if it had some error... */
1012 return NULL;
1016 w=gdk_pixbuf_get_width(screen);
1017 h=gdk_pixbuf_get_height(screen);
1019 if (!tile) {
1020 /* one-row pixbuf, so no animation. */
1021 tile=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, 1);
1022 gdk_pixbuf_fill(tile, 0x000000FFU); /* opaque black */
1025 tw=gdk_pixbuf_get_width(tile);
1026 th=gdk_pixbuf_get_height(tile);
1028 /* either because the tile has no alpha channel, or because it cannot be transparent anymore... */
1029 /* also needed because the "bigone" pixbuf will have an alpha channel, and pixbuf_copy would not work otherwise. */
1030 tile_black=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, tw, th);
1031 gdk_pixbuf_fill(tile_black, 0x000000FFU); /* fill with opaque black, as even the tile may have an alpha channel */
1032 gdk_pixbuf_composite(tile, tile_black, 0, 0, tw, th, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
1033 g_object_unref(tile);
1034 tile=tile_black;
1036 /* create a big image, which is one tile larger than the title image size */
1037 bigone=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
1038 /* and fill it with the tile. */
1039 for (y=0; y<h; y+=th)
1040 for (x=0; x<w; x+=tw) {
1041 int cw, ch; /* copied width and height */
1043 /* check if out of bounds, as gdk does not clip rather sends errors */
1044 if (x+tw>w)
1045 cw=w-x;
1046 else
1047 cw=tw;
1048 if (y+th>h)
1049 ch=h-y;
1050 else
1051 ch=th;
1052 gdk_pixbuf_copy_area(tile, 0, 0, cw, ch, bigone, x, y);
1054 g_object_unref(tile);
1055 gdk_pixbuf_composite(screen, bigone, 0, 0, w, h, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
1056 g_object_unref(screen);
1058 /* the image is now loaded and ready. */
1059 return bigone;