2009050
[gdash.git] / src / gtkgfx.c
blob30c7c18b39f9684741d9849dc782b0139c93a5d0
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 /* load cells, eg. create cells_pb and combo_pb
362 from a big pixbuf.
364 static void
365 loadcells_from_pixbuf(GdkPixbuf *cells_pixbuf)
367 int i;
368 int pixbuf_cell_size;
370 /* now that we have the pixbuf, we can start freeing old graphics. */
371 for (i=0; i<G_N_ELEMENTS(cells_pb); i++) {
372 if (cells_pb[i]) {
373 g_object_unref (cells_pb[i]);
374 cells_pb[i]=NULL;
376 /* scaled cells for editor combo boxes. created by editor, but we free them if we load a new theme */
377 if (combo_pb[i]) {
378 g_object_unref (combo_pb[i]);
379 combo_pb[i]=NULL;
382 /* if we have scaled pixmaps, remove them */
383 free_pixmaps();
385 /* 8 (NUM_OF_CELLS_X) cells in a row, so divide by it and we get the size of a cell in pixels */
386 pixbuf_cell_size=gdk_pixbuf_get_width (cells_pixbuf) / NUM_OF_CELLS_X;
388 /* make individual cell pixbufs */
389 for (i=0; i < NUM_OF_CELLS_Y*NUM_OF_CELLS_X; i++)
390 /* copy one cell */
391 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);
393 /* set cell sizes */
394 gd_cell_size_game=pixbuf_cell_size*gd_scaling_scale[gd_cell_scale_game];
395 gd_cell_size_editor=pixbuf_cell_size*gd_scaling_scale[gd_cell_scale_editor];
397 /* draw some elements, combining them with arrows and the like */
398 add_arrow_to_cell(O_STEEL_EATABLE, O_STEEL, O_EATABLE, GDK_PIXBUF_ROTATE_NONE);
399 add_arrow_to_cell(O_BRICK_EATABLE, O_BRICK, O_EATABLE, GDK_PIXBUF_ROTATE_NONE);
400 create_composite_cell_pixbuf(O_BRICK_NON_SLOPED, O_STEEL, O_BRICK);
402 create_composite_cell_pixbuf(O_WALLED_KEY_1, O_KEY_1, O_BRICK);
403 create_composite_cell_pixbuf(O_WALLED_KEY_2, O_KEY_2, O_BRICK);
404 create_composite_cell_pixbuf(O_WALLED_KEY_3, O_KEY_3, O_BRICK);
405 create_composite_cell_pixbuf(O_WALLED_DIAMOND, O_DIAMOND, O_BRICK);
407 add_arrow_to_cell(O_FIREFLY_1, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
408 add_arrow_to_cell(O_FIREFLY_2, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
409 add_arrow_to_cell(O_FIREFLY_3, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
410 add_arrow_to_cell(O_FIREFLY_4, O_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
412 add_arrow_to_cell(O_ALT_FIREFLY_1, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
413 add_arrow_to_cell(O_ALT_FIREFLY_2, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
414 add_arrow_to_cell(O_ALT_FIREFLY_3, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
415 add_arrow_to_cell(O_ALT_FIREFLY_4, O_ALT_FIREFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
417 add_arrow_to_cell(O_H_EXPANDING_WALL, O_H_EXPANDING_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_NONE);
418 add_arrow_to_cell(O_V_EXPANDING_WALL, O_V_EXPANDING_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
419 add_arrow_to_cell(O_EXPANDING_WALL, O_EXPANDING_WALL, O_EVERYDIR_ARROW, GDK_PIXBUF_ROTATE_NONE);
421 add_arrow_to_cell(O_H_EXPANDING_STEEL_WALL, O_H_EXPANDING_STEEL_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_NONE);
422 add_arrow_to_cell(O_V_EXPANDING_STEEL_WALL, O_V_EXPANDING_STEEL_WALL, O_LEFTRIGHT_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
423 add_arrow_to_cell(O_EXPANDING_STEEL_WALL, O_EXPANDING_STEEL_WALL, O_EVERYDIR_ARROW, GDK_PIXBUF_ROTATE_NONE);
425 add_arrow_to_cell(O_BUTTER_1, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
426 add_arrow_to_cell(O_BUTTER_2, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
427 add_arrow_to_cell(O_BUTTER_3, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
428 add_arrow_to_cell(O_BUTTER_4, O_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
430 add_arrow_to_cell(O_DRAGONFLY_1, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
431 add_arrow_to_cell(O_DRAGONFLY_2, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
432 add_arrow_to_cell(O_DRAGONFLY_3, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
433 add_arrow_to_cell(O_DRAGONFLY_4, O_DRAGONFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
435 add_arrow_to_cell(O_COW_1, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
436 add_arrow_to_cell(O_COW_2, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
437 add_arrow_to_cell(O_COW_3, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
438 add_arrow_to_cell(O_COW_4, O_COW_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
439 add_arrow_to_cell(O_COW_ENCLOSED_1, O_COW_1, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
441 add_arrow_to_cell(O_ALT_BUTTER_1, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
442 add_arrow_to_cell(O_ALT_BUTTER_2, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
443 add_arrow_to_cell(O_ALT_BUTTER_3, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
444 add_arrow_to_cell(O_ALT_BUTTER_4, O_ALT_BUTTER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
446 add_arrow_to_cell(O_PLAYER_GLUED, O_PLAYER, O_GLUED, 0);
447 add_arrow_to_cell(O_PLAYER, O_PLAYER, O_EXCLAMATION_MARK, 0);
448 add_arrow_to_cell(O_STONE_GLUED, O_STONE, O_GLUED, 0);
449 add_arrow_to_cell(O_DIAMOND_GLUED, O_DIAMOND, O_GLUED, 0);
450 add_arrow_to_cell(O_DIRT_GLUED, O_DIRT, O_GLUED, 0);
451 add_arrow_to_cell(O_STONE_F, O_STONE, O_DOWN_ARROW, 0);
452 add_arrow_to_cell(O_FLYING_STONE_F, O_FLYING_STONE, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
453 add_arrow_to_cell(O_MEGA_STONE_F, O_MEGA_STONE, O_DOWN_ARROW, 0);
454 add_arrow_to_cell(O_DIAMOND_F, O_DIAMOND, O_DOWN_ARROW, 0);
455 add_arrow_to_cell(O_FLYING_DIAMOND_F, O_FLYING_DIAMOND, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
456 add_arrow_to_cell(O_NUT_F, O_NUT, O_DOWN_ARROW, 0);
457 add_arrow_to_cell(O_FALLING_WALL, O_BRICK, O_EXCLAMATION_MARK, 0);
458 add_arrow_to_cell(O_FALLING_WALL_F, O_BRICK, O_DOWN_ARROW, 0);
459 add_arrow_to_cell(O_TIME_PENALTY, O_GRAVESTONE, O_EXCLAMATION_MARK, 0);
460 add_arrow_to_cell(O_NITRO_PACK_F, O_NITRO_PACK, O_DOWN_ARROW, 0);
461 add_arrow_to_cell(O_NITRO_PACK_EXPLODE, O_NITRO_PACK, O_EXCLAMATION_MARK, 0);
462 add_arrow_to_cell(O_CONVEYOR_LEFT, O_CONVEYOR_LEFT, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
463 add_arrow_to_cell(O_CONVEYOR_RIGHT, O_CONVEYOR_RIGHT, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
465 add_arrow_to_cell(O_STONEFLY_1, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
466 add_arrow_to_cell(O_STONEFLY_2, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
467 add_arrow_to_cell(O_STONEFLY_3, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
468 add_arrow_to_cell(O_STONEFLY_4, O_STONEFLY_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
470 add_arrow_to_cell(O_BITER_1, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
471 add_arrow_to_cell(O_BITER_2, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
472 add_arrow_to_cell(O_BITER_3, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_NONE);
473 add_arrow_to_cell(O_BITER_4, O_BITER_1, O_DOWN_ARROW, GDK_PIXBUF_ROTATE_CLOCKWISE);
475 add_arrow_to_cell(O_PRE_INVIS_OUTBOX, O_OUTBOX_CLOSED, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
476 add_arrow_to_cell(O_PRE_OUTBOX, O_OUTBOX_OPEN, O_GLUED, GDK_PIXBUF_ROTATE_NONE);
477 add_arrow_to_cell(O_INVIS_OUTBOX, O_OUTBOX_CLOSED, O_OUT, GDK_PIXBUF_ROTATE_NONE);
478 add_arrow_to_cell(O_OUTBOX, O_OUTBOX_OPEN, O_OUT, GDK_PIXBUF_ROTATE_NONE);
480 add_arrow_to_cell(O_UNKNOWN, O_STEEL, O_QUESTION_MARK, GDK_PIXBUF_ROTATE_NONE);
481 add_arrow_to_cell(O_WAITING_STONE, O_STONE, O_EXCLAMATION_MARK, GDK_PIXBUF_ROTATE_NONE);
483 /* blinking outbox: helps editor, drawing the cave is more simple */
484 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+0, gd_elements[O_OUTBOX_OPEN].image_game);
485 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+1, gd_elements[O_OUTBOX_OPEN].image_game);
486 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+2, gd_elements[O_OUTBOX_OPEN].image_game);
487 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+3, gd_elements[O_OUTBOX_OPEN].image_game);
488 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+4, gd_elements[O_OUTBOX_CLOSED].image_game);
489 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+5, gd_elements[O_OUTBOX_CLOSED].image_game);
490 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+6, gd_elements[O_OUTBOX_CLOSED].image_game);
491 copy_cell(ABS(gd_elements[O_PRE_OUTBOX].image_simple)+7, gd_elements[O_OUTBOX_CLOSED].image_game);
494 static guint32
495 rgba_pixel_from_color(GdColor col, guint8 a)
497 guint8 r=gd_color_get_r(col);
498 guint8 g=gd_color_get_g(col);
499 guint8 b=gd_color_get_b(col);
500 #if G_BYTE_ORDER==G_LITTLE_ENDIAN
501 return r+(g<<8)+(b<<16)+(a<<24);
502 #else
503 return (r<<24)+(g<<16)+(b<<8)+a;
504 #endif
507 static void
508 loadcells_c64_with_colors (GdColor c0, GdColor c1, GdColor c2, GdColor c3, GdColor c4, GdColor c5)
510 const guchar *gfx; /* currently used graphics, will point to c64_gfx or c64_custom_gfx */
511 GdkPixbuf *cells_pixbuf;
512 guint32 cols[9]; /* holds rgba for color indexes internally used */
513 int rowstride, n_channels;
514 guchar *pixels;
515 int pos, x, y;
517 gfx=c64_custom_gfx?c64_custom_gfx:c64_gfx;
519 cols[0]=rgba_pixel_from_color(0, 0);
520 cols[1]=rgba_pixel_from_color(c0, 0xff); /* c64 background */
521 cols[2]=rgba_pixel_from_color(c1, 0xff); /* foreg1 */
522 cols[3]=rgba_pixel_from_color(c2, 0xff); /* foreg2 */
523 cols[4]=rgba_pixel_from_color(c3, 0xff); /* foreg3 */
524 cols[5]=rgba_pixel_from_color(c4, 0xff); /* amoeba */
525 cols[6]=rgba_pixel_from_color(c5, 0xff); /* slime */
526 cols[7]=rgba_pixel_from_color(0, 0xff); /* black, opaque*/
527 cols[8]=rgba_pixel_from_color(0xffffff, 0xff); /* white, opaque*/
529 cells_pixbuf=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, NUM_OF_CELLS_X*gfx[0], NUM_OF_CELLS_Y*gfx[0]);
530 n_channels=gdk_pixbuf_get_n_channels (cells_pixbuf);
531 rowstride=gdk_pixbuf_get_rowstride (cells_pixbuf); /* bytes / row */
532 pixels=gdk_pixbuf_get_pixels (cells_pixbuf); /* pointer to pixbuf memory */
534 pos=1; /* index to gfx array */
535 /* create colored pixbuf from c64 graphics, using c0, c1, c2, c3 */
536 for (y=0; y<NUM_OF_CELLS_Y*gfx[0]; y++) {
537 guint32 *p=(guint32*) (pixels + y * rowstride); /* write 32bits at once - faster than writing bytes */
539 for (x=0; x<NUM_OF_CELLS_X*gfx[0]; x++)
540 p[x]=cols[(int) gfx[pos++]];
543 /* from here, same as any other png */
544 loadcells_from_pixbuf(cells_pixbuf);
545 g_object_unref(cells_pixbuf);
550 static guchar *
551 c64_gfx_data_from_pixbuf(GdkPixbuf *pixbuf)
553 int width, height, rowstride, n_channels;
554 guchar *pixels;
555 int x, y;
556 guchar *data;
557 int out;
559 g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
560 g_assert (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
561 g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
563 n_channels=gdk_pixbuf_get_n_channels (pixbuf);
564 width=gdk_pixbuf_get_width (pixbuf);
565 height=gdk_pixbuf_get_height (pixbuf);
566 rowstride=gdk_pixbuf_get_rowstride (pixbuf);
567 pixels=gdk_pixbuf_get_pixels (pixbuf);
569 g_assert (n_channels == 4);
571 data=g_new(guchar, width*height+1);
572 out=0;
573 data[out++]=width/NUM_OF_CELLS_X;
575 for (y=0; y<height; y++)
576 for (x=0; x<width; x++) {
577 int r, g, b, a;
579 guchar *p=pixels + y * rowstride + x * n_channels;
580 r=p[0];
581 g=p[1];
582 b=p[2];
583 a=p[3];
584 data[out++]=c64_png_colors(r, g, b, a);
586 return data;
591 gboolean
592 gd_loadcells_file(const char *filename)
594 GdkPixbuf *cells_pixbuf;
595 GError *error=NULL;
596 const char *error_msg;
598 /* load cell graphics */
599 /* load from file */
600 cells_pixbuf=gdk_pixbuf_new_from_file (filename, &error);
601 if (error) {
602 g_warning("%s", error->message);
603 g_error_free(error);
604 return FALSE;
606 /* check if file has the properties which we need for a theme */
607 error_msg=gd_is_pixbuf_ok_for_theme(cells_pixbuf);
608 if (error_msg) {
609 g_object_unref(cells_pixbuf);
610 g_warning("%s", error_msg);
611 return FALSE;
614 if (check_if_pixbuf_c64_png(cells_pixbuf)) {
615 /* c64 pixbuf with a small number of colors which can be changed */
616 g_free(c64_custom_gfx);
617 c64_custom_gfx=c64_gfx_data_from_pixbuf(cells_pixbuf);
618 using_png_gfx=FALSE;
619 } else {
620 /* normal, "truecolor" pixbuf */
621 g_free(c64_custom_gfx);
622 c64_custom_gfx=NULL;
623 loadcells_from_pixbuf(cells_pixbuf);
624 using_png_gfx=TRUE;
626 g_object_unref(cells_pixbuf);
627 color0=GD_COLOR_INVALID; /* so that pixbufs will be recreated */
629 return TRUE;
632 void
633 gd_loadcells_default()
635 g_free(c64_custom_gfx);
636 c64_custom_gfx=NULL;
637 using_png_gfx=FALSE;
638 color0=GD_COLOR_INVALID; /* so that pixbufs will be recreated */
640 /* size of array (in bytes), -1 which is the cell size */
641 g_assert(sizeof(c64_gfx)-1 == NUM_OF_CELLS_X*NUM_OF_CELLS_Y*c64_gfx[0]*c64_gfx[0]);
644 /* wrapper */
645 /* exported for settings window */
646 void
647 gd_pal_emulate_pixbuf(GdkPixbuf *pixbuf)
649 #if G_BYTE_ORDER == G_BIG_ENDIAN
650 guint32 rshift=24;
651 guint32 gshift=16;
652 guint32 bshift=8;
653 guint32 ashift=0;
654 #else
655 guint32 rshift=0;
656 guint32 gshift=8;
657 guint32 bshift=16;
658 guint32 ashift=24;
659 #endif
660 g_assert(gdk_pixbuf_get_has_alpha(pixbuf));
662 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);
665 /* create pixmap for image number 'index', using cell size. */
666 /* a pixmap * array is to be given; that may be cells_game or cells_editor */
667 /* pal emulation does just that. */
668 /* if render_selected is true, it will render the blue cell, too - that is used only in the editor. */
669 static void
670 create_pixmap(GdkPixmap **cells, int index, int cell_size, GdScalingType type, gboolean render_selected, gboolean pal_emul)
672 GdkPixbuf *selected, *normal, *element;
673 GdkWindow *window;
675 g_assert(index>=0 && index<NUM_OF_CELLS);
676 g_assert(cells_pb[index]!=NULL);
678 window=gdk_get_default_root_window();
680 /* scale the cell.
681 * scale every cell on its own, or else some pixels might be merged on borders */
682 normal=gd_pixbuf_scale(cells_pb[index], type);
683 if (pal_emul)
684 gd_pal_emulate_pixbuf(normal);
685 /* create colored cells. */
686 /* here no scaling is done, so interp_nearest is ok. */
688 /* create pixmap containing pixbuf */
689 /* draw the pixbufs to the new pixmaps */
690 cells[index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
691 gdk_draw_pixbuf(cells[index], NULL, normal, 0, 0, 0, 0, cell_size, cell_size, GDK_RGB_DITHER_MAX, 0, 0);
693 element=gdk_pixbuf_composite_color_simple(normal, cell_size, cell_size, GDK_INTERP_NEAREST, 128, 1, gd_flash_color, gd_flash_color);
694 cells[NUM_OF_CELLS + index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
695 gdk_draw_pixbuf(cells[NUM_OF_CELLS + index], NULL, element, 0, 0, 0, 0, cell_size, cell_size, GDK_RGB_DITHER_MAX, 0, 0);
696 g_object_unref(element);
698 if (render_selected) {
699 selected=gdk_pixbuf_composite_color_simple(normal, cell_size, cell_size, GDK_INTERP_NEAREST, 128, 1, gd_select_color, gd_select_color);
700 cells[2 * NUM_OF_CELLS + index]=gdk_pixmap_new (window, cell_size, cell_size, -1);
701 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);
702 g_object_unref(selected);
705 /* forget scaled pixbufs, as only the pixmaps are needed */
706 g_object_unref(normal);
709 /* return a pixmap scaled for the game */
710 GdkPixmap *
711 gd_game_pixmap(int index)
713 int i;
715 g_assert(index<2*NUM_OF_CELLS); /* make sure that no blue/"selected" pixbuf is requested for a game */
716 i=index%NUM_OF_CELLS; /* might request a colored, so we do a % NUM_OF_CELLS */
717 if (cells_game[i]==NULL) /* check if pixmap already exists */
718 create_pixmap(cells_game, i, gd_cell_size_game, gd_cell_scale_game, FALSE, gd_pal_emulation_game);
719 return cells_game[index];
722 /* return a pixmap scaled for the editor */
723 GdkPixmap *
724 gd_editor_pixmap(int index)
726 int i;
728 i=index%NUM_OF_CELLS; /* might request a colored, so we do a % NUM_OF_CELLS */
729 if (cells_editor[i]==NULL) /* check if pixmap already exists */
730 create_pixmap(cells_editor, i, gd_cell_size_editor, gd_cell_scale_editor, TRUE, gd_pal_emulation_editor);
731 return cells_editor[index];
734 /* set pixbuf colors. */
735 /* (if png graphics is used, it does nothing. */
736 void
737 gd_select_pixbuf_colors(GdColor c0, GdColor c1, GdColor c2, GdColor c3, GdColor c4, GdColor c5)
739 /* if non-c64 gfx, nothing to do */
740 if (using_png_gfx)
741 return;
743 /* convert to rgb value */
744 c0=gd_color_get_rgb(c0);
745 c1=gd_color_get_rgb(c1);
746 c2=gd_color_get_rgb(c2);
747 c3=gd_color_get_rgb(c3);
748 c4=gd_color_get_rgb(c4);
749 c5=gd_color_get_rgb(c5);
751 /* and compare rgb values! */
752 if (c0!=color0 || c1!=color1 || c2!=color2 || c3!=color3 || c4!=color4 || c5!=color5) {
753 /* if not the same colors as requested before */
754 color0=c0;
755 color1=c1;
756 color2=c2;
757 color3=c3;
758 color4=c4;
759 color5=c5;
761 loadcells_c64_with_colors(c0, c1, c2, c3, c4, c5);
766 static GdkPixbuf *
767 get_element_pixbuf_with_border(int index)
769 if (!combo_pb[index]) {
770 /* create small size pixbuf if needed. */
771 int x, y;
772 GdkPixbuf *pixbuf, *pixbuf_border;
774 /* scale pixbuf to that specified by gtk */
775 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &x, &y);
776 pixbuf=gdk_pixbuf_scale_simple(cells_pb[index], x, y, GDK_INTERP_BILINEAR);
777 /* draw a little black border around image, makes the icons look much better */
778 pixbuf_border=gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (pixbuf), 8, x+2, y+2);
779 gdk_pixbuf_fill(pixbuf_border, 0x000000ff); /* RGBA: opaque black */
780 gdk_pixbuf_copy_area(pixbuf, 0, 0, x, y, pixbuf_border, 1, 1);
781 g_object_unref(pixbuf);
782 combo_pb[index]=pixbuf_border;
784 return combo_pb[index];
788 returns a cell pixbuf, scaled to gtk icon size.
789 it also adds a little black border, which makes them look much better
791 GdkPixbuf *
792 gd_get_element_pixbuf_with_border(GdElement element)
794 int index;
795 /* which pixbuf to show? */
796 index=ABS(gd_elements[element].image);
797 return get_element_pixbuf_with_border(index);
801 returns a cell pixbuf, scaled to gtk icon size.
802 it also adds a little black border, which makes them look much better
804 GdkPixbuf *
805 gd_get_element_pixbuf_simple_with_border (GdElement element)
807 int index;
808 /* which pixbuf to show? */
809 index=ABS(gd_elements[element].image_simple);
810 return get_element_pixbuf_with_border(index);
814 creates a pixbuf, which shows the cave.
815 if width and height are given (nonzero),
816 scale pixbuf proportionally, so it fits in width*height
817 pixels. otherwise return in original size.
818 up to the caller to unref the returned pixbuf.
819 also up to the caller to call this function only for rendered caves.
821 GdkPixbuf *
822 gd_drawcave_to_pixbuf(const GdCave *cave, const int width, const int height, const gboolean game_view, const gboolean border)
824 int x, y;
825 int cell_size;
826 GdkPixbuf *pixbuf, *scaled;
827 float scale;
828 int x1, y1, x2, y2;
829 int borderadd=border?4:0, borderpos=border?2:0;
831 g_assert(cave->map!=NULL);
832 if (game_view) {
833 /* if showing the visible part only */
834 x1=cave->x1;
835 y1=cave->y1;
836 x2=cave->x2;
837 y2=cave->y2;
838 } else {
839 /* showing entire cave - for example, overview in editor */
840 x1=0;
841 y1=0;
842 x2=cave->w-1;
843 y2=cave->h-1;
846 gd_select_pixbuf_colors(cave->color0, cave->color1, cave->color2, cave->color3, cave->color4, cave->color5);
848 /* get size of one cell in the original pixbuf */
849 cell_size=gdk_pixbuf_get_width (cells_pb[0]);
851 /* add two pixels black border: +4 +4 for width and height */
852 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);
853 if (border)
854 gdk_pixbuf_fill(pixbuf, 0x000000ff); /* fill with opaque black, so border is black */
856 /* take visible part into consideration */
857 for (y=y1; y<=y2; y++)
858 for (x=x1; x<=x2; x++) {
859 GdElement element=cave->map[y][x]&O_MASK;
860 int draw;
862 if (game_view) {
863 /* visual effects */
864 switch(element) {
865 case O_DIRT:
866 element=cave->dirt_looks_like;
867 break;
868 case O_EXPANDING_WALL:
869 case O_H_EXPANDING_WALL:
870 case O_V_EXPANDING_WALL:
871 /* only change the view, if it is not brick wall (the default value). */
872 /* so arrows remain - as well as they always remaing for the steel expanding wall,
873 which has no visual effect. */
874 if (cave->expanding_wall_looks_like!=O_BRICK)
875 element=cave->expanding_wall_looks_like;
876 break;
877 case O_AMOEBA_2:
878 element=cave->amoeba_2_looks_like;
879 break;
880 default:
881 /* we check that this element has no visual effect. */
882 /* otherwise, we should have handled the element explicitely above! */
883 g_assert((gd_elements[element].properties & P_VISUAL_EFFECT) == 0);
884 break;
886 draw=ABS(gd_elements[element].image_simple); /* pixbuf like in the editor */
888 else
889 draw=gd_elements[element].image; /* pixbuf like in the editor */
890 gdk_pixbuf_copy_area (cells_pb[draw], 0, 0, cell_size, cell_size, pixbuf, (x-x1)*cell_size+borderpos, (y-y1)*cell_size+borderpos);
893 /* if requested size is 0, return unscaled */
894 if (width == 0 || height == 0)
895 return pixbuf;
897 /* decide which direction fits in rectangle */
898 /* cells are squares... no need to know cell_size here */
899 if ((float) gdk_pixbuf_get_width (pixbuf) / (float) gdk_pixbuf_get_height (pixbuf) >= (float) width / (float) height)
900 scale=width / ((float) gdk_pixbuf_get_width (pixbuf));
901 else
902 scale=height / ((float) gdk_pixbuf_get_height (pixbuf));
904 /* scale to specified size */
905 scaled=gdk_pixbuf_scale_simple (pixbuf, gdk_pixbuf_get_width (pixbuf)*scale, gdk_pixbuf_get_height (pixbuf)*scale, GDK_INTERP_BILINEAR);
906 g_object_unref (pixbuf);
908 return scaled;
912 GdkPixbuf *
913 gd_pixbuf_load_from_data(guchar *data, int length)
915 GdkPixbufLoader *loader;
916 GdkPixbuf *pixbuf;
917 GError *error=NULL;
919 /* push the data into a pixbuf loader */
920 loader=gdk_pixbuf_loader_new();
921 if (!gdk_pixbuf_loader_write(loader, data, length, &error) || !gdk_pixbuf_loader_close(loader, &error)) {
922 g_warning("%s", error->message);
923 g_error_free(error);
924 g_object_unref(loader);
925 return NULL;
927 pixbuf=gdk_pixbuf_loader_get_pixbuf(loader);
928 g_object_ref(pixbuf);
929 g_object_unref(loader);
930 return pixbuf;
933 GdkPixbuf *
934 gd_pixbuf_load_from_base64(gchar *base64)
936 guchar *data;
937 gsize length;
938 GdkPixbuf *pixbuf;
940 data=g_base64_decode(base64, &length);
941 pixbuf=gd_pixbuf_load_from_data(data, length);
942 g_free(data);
944 return pixbuf;
950 /* create a pixbuf of the caveset-specific title image. */
951 /* if there is no such image, return null. */
952 GdkPixbuf *
953 gd_create_title_image()
955 guchar *data;
956 gsize length;
957 GdkPixbuf *screen, *tile, *tile_black, *bigone;
958 int w, h; /* screen (large image) width and height */
959 int tw, th; /* tile (small image) width and height */
960 int x, y;
962 if (gd_caveset_data->title_screen->len==0)
963 return NULL;
965 data=g_base64_decode(gd_caveset_data->title_screen->str, &length);
966 screen=gd_pixbuf_load_from_data(data, length);
967 g_free(data);
968 if (!screen) {
969 g_warning("Invalid title image stored in caveset.");
970 g_string_assign(gd_caveset_data->title_screen, ""); /* forget if it had some error... */
971 return NULL;
974 tile=NULL;
975 if (gd_caveset_data->title_screen_scroll->len!=0) {
976 /* there is a tile, so load that also. */
977 data=g_base64_decode(gd_caveset_data->title_screen_scroll->str, &length);
978 tile=gd_pixbuf_load_from_data(data, length);
979 g_free(data);
980 if (!tile) {
981 g_warning("Invalid title image scrolling background stored in caveset.");
982 g_string_assign(gd_caveset_data->title_screen_scroll, ""); /* forget if it had some error... */
983 return NULL;
987 w=gdk_pixbuf_get_width(screen);
988 h=gdk_pixbuf_get_height(screen);
990 if (!tile) {
991 /* one-row pixbuf, so no animation. */
992 tile=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, 1);
993 gdk_pixbuf_fill(tile, 0x000000FFU); /* opaque black */
996 tw=gdk_pixbuf_get_width(tile);
997 th=gdk_pixbuf_get_height(tile);
999 /* either because the tile has no alpha channel, or because it cannot be transparent anymore... */
1000 /* also needed because the "bigone" pixbuf will have an alpha channel, and pixbuf_copy would not work otherwise. */
1001 tile_black=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, tw, th);
1002 gdk_pixbuf_fill(tile_black, 0x000000FFU); /* fill with opaque black, as even the tile may have an alpha channel */
1003 gdk_pixbuf_composite(tile, tile_black, 0, 0, tw, th, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
1004 g_object_unref(tile);
1005 tile=tile_black;
1007 /* create a big image, which is one tile larger than the title image size */
1008 bigone=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
1009 /* and fill it with the tile. */
1010 for (y=0; y<h; y+=th)
1011 for (x=0; x<w; x+=tw) {
1012 int cw, ch; /* copied width and height */
1014 /* check if out of bounds, as gdk does not clip rather sends errors */
1015 if (x+tw>w)
1016 cw=w-x;
1017 else
1018 cw=tw;
1019 if (y+th>h)
1020 ch=h-y;
1021 else
1022 ch=th;
1023 gdk_pixbuf_copy_area(tile, 0, 0, cw, ch, bigone, x, y);
1025 g_object_unref(tile);
1026 gdk_pixbuf_composite(screen, bigone, 0, 0, w, h, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
1027 g_object_unref(screen);
1029 /* the image is now loaded and ready. */
1030 return bigone;