fixed crash bug with masked blitting in headless mode
[rocksndiamonds.git] / src / libgame / system.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // system.c
10 // ============================================================================
11
12 #include <string.h>
13 #include <signal.h>
14
15 #include "platform.h"
16
17 #include "system.h"
18 #include "image.h"
19 #include "sound.h"
20 #include "setup.h"
21 #include "joystick.h"
22 #include "misc.h"
23
24 #define ENABLE_UNUSED_CODE      0       // currently unused functions
25
26
27 // ============================================================================
28 // exported variables
29 // ============================================================================
30
31 struct ProgramInfo      program;
32 struct NetworkInfo      network;
33 struct RuntimeInfo      runtime;
34 struct OptionInfo       options;
35 struct VideoSystemInfo  video;
36 struct AudioSystemInfo  audio;
37 struct GfxInfo          gfx;
38 struct TileCursorInfo   tile_cursor;
39 struct OverlayInfo      overlay;
40 struct ArtworkInfo      artwork;
41 struct JoystickInfo     joystick;
42 struct SetupInfo        setup;
43 struct UserInfo         user;
44
45 LevelDirTree           *leveldir_first_all = NULL;
46 LevelDirTree           *leveldir_first = NULL;
47 LevelDirTree           *leveldir_current = NULL;
48 int                     level_nr;
49
50 struct LevelSetInfo     levelset;
51 struct LevelStats       level_stats[MAX_LEVELS];
52
53 DrawWindow             *window = NULL;
54 DrawBuffer             *backbuffer = NULL;
55 DrawBuffer             *drawto = NULL;
56
57 int                     button_status = MB_NOT_PRESSED;
58 boolean                 motion_status = FALSE;
59 int                     wheel_steps = DEFAULT_WHEEL_STEPS;
60 boolean                 keyrepeat_status = TRUE;
61 boolean                 textinput_status = FALSE;
62
63 int                     redraw_mask = REDRAW_NONE;
64
65 int                     FrameCounter = 0;
66
67
68 // ============================================================================
69 // init/close functions
70 // ============================================================================
71
72 void InitProgramInfo(char *command_filename,
73                      char *config_filename, char *userdata_subdir,
74                      char *program_basename, char *program_title,
75                      char *icon_filename, char *cookie_prefix,
76                      char *program_version_string, int program_version)
77 {
78   program.command_basepath = getBasePath(command_filename);
79   program.command_basename = getBaseName(command_filename);
80
81   program.config_filename = config_filename;
82
83   program.userdata_subdir = userdata_subdir;
84   program.userdata_path = getMainUserGameDataDir();
85
86   program.program_basename = program_basename;
87   program.program_title = program_title;
88   program.window_title = "(undefined)";
89
90   program.icon_filename = icon_filename;
91
92   program.cookie_prefix = cookie_prefix;
93
94   program.version_super = VERSION_SUPER(program_version);
95   program.version_major = VERSION_MAJOR(program_version);
96   program.version_minor = VERSION_MINOR(program_version);
97   program.version_patch = VERSION_PATCH(program_version);
98   program.version_ident = program_version;
99
100   program.version_string = program_version_string;
101
102   program.log_filename = getLogFilename(getLogBasename(program_basename));
103   program.log_file = program.log_file_default = stdout;
104
105   program.api_thread_count = 0;
106
107   program.headless = FALSE;
108 }
109
110 void InitNetworkInfo(boolean enabled, boolean connected, boolean serveronly,
111                      char *server_host, int server_port)
112 {
113   network.enabled = enabled;
114   network.connected = connected;
115   network.serveronly = serveronly;
116
117   network.server_host = server_host;
118   network.server_port = server_port;
119
120   network.server_thread = NULL;
121   network.is_server_thread = FALSE;
122 }
123
124 void InitRuntimeInfo(void)
125 {
126 #if defined(HAS_TOUCH_DEVICE)
127   runtime.uses_touch_device = TRUE;
128 #else
129   runtime.uses_touch_device = FALSE;
130 #endif
131
132   runtime.use_api_server = setup.use_api_server;
133 }
134
135 void SetWindowTitle(void)
136 {
137   program.window_title = program.window_title_function();
138
139   SDLSetWindowTitle();
140 }
141
142 void InitWindowTitleFunction(char *(*window_title_function)(void))
143 {
144   program.window_title_function = window_title_function;
145 }
146
147 void InitExitMessageFunction(void (*exit_message_function)(char *, va_list))
148 {
149   program.exit_message_function = exit_message_function;
150 }
151
152 void InitExitFunction(void (*exit_function)(int))
153 {
154   program.exit_function = exit_function;
155
156   // set signal handlers to custom exit function
157   // signal(SIGINT, exit_function);
158   signal(SIGTERM, exit_function);
159
160   // set exit function to automatically cleanup SDL stuff after exit()
161   atexit(SDL_Quit);
162 }
163
164 void InitPlatformDependentStuff(void)
165 {
166   InitEmscriptenFilesystem();
167
168   // this is initialized in GetOptions(), but may already be used before
169   options.verbose = TRUE;
170
171   OpenLogFile();
172
173   int sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_NOPARACHUTE;
174
175   if (SDL_Init(sdl_init_flags) < 0)
176     Fail("SDL_Init() failed: %s", SDL_GetError());
177
178   SDLNet_Init();
179 }
180
181 void ClosePlatformDependentStuff(void)
182 {
183   CloseLogFile();
184 }
185
186 void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize,
187                       int real_sx, int real_sy,
188                       int full_sxsize, int full_sysize,
189                       Bitmap *field_save_buffer)
190 {
191   gfx.sx = sx;
192   gfx.sy = sy;
193   gfx.sxsize = sxsize;
194   gfx.sysize = sysize;
195   gfx.real_sx = real_sx;
196   gfx.real_sy = real_sy;
197   gfx.full_sxsize = full_sxsize;
198   gfx.full_sysize = full_sysize;
199
200   gfx.field_save_buffer = field_save_buffer;
201
202   SetDrawDeactivationMask(REDRAW_NONE);         // do not deactivate drawing
203   SetDrawBackgroundMask(REDRAW_NONE);           // deactivate masked drawing
204 }
205
206 void InitGfxTileSizeInfo(int game_tile_size, int standard_tile_size)
207 {
208   gfx.game_tile_size = game_tile_size;
209   gfx.standard_tile_size = standard_tile_size;
210 }
211
212 void InitGfxDoor1Info(int dx, int dy, int dxsize, int dysize)
213 {
214   gfx.dx = dx;
215   gfx.dy = dy;
216   gfx.dxsize = dxsize;
217   gfx.dysize = dysize;
218 }
219
220 void InitGfxDoor2Info(int vx, int vy, int vxsize, int vysize)
221 {
222   gfx.vx = vx;
223   gfx.vy = vy;
224   gfx.vxsize = vxsize;
225   gfx.vysize = vysize;
226 }
227
228 void InitGfxDoor3Info(int ex, int ey, int exsize, int eysize)
229 {
230   gfx.ex = ex;
231   gfx.ey = ey;
232   gfx.exsize = exsize;
233   gfx.eysize = eysize;
234 }
235
236 void InitGfxWindowInfo(int win_xsize, int win_ysize)
237 {
238   if (win_xsize != gfx.win_xsize || win_ysize != gfx.win_ysize)
239   {
240     ReCreateBitmap(&gfx.background_bitmap, win_xsize, win_ysize);
241
242     ReCreateBitmap(&gfx.final_screen_bitmap, win_xsize, win_ysize);
243
244     ReCreateBitmap(&gfx.fade_bitmap_backup, win_xsize, win_ysize);
245     ReCreateBitmap(&gfx.fade_bitmap_source, win_xsize, win_ysize);
246     ReCreateBitmap(&gfx.fade_bitmap_target, win_xsize, win_ysize);
247     ReCreateBitmap(&gfx.fade_bitmap_black,  win_xsize, win_ysize);
248
249     ClearRectangle(gfx.fade_bitmap_black, 0, 0, win_xsize, win_ysize);
250   }
251
252   gfx.win_xsize = win_xsize;
253   gfx.win_ysize = win_ysize;
254
255   gfx.background_bitmap_mask = REDRAW_NONE;
256 }
257
258 void InitGfxScrollbufferInfo(int scrollbuffer_width, int scrollbuffer_height)
259 {
260   // currently only used by MSDOS code to alloc VRAM buffer, if available
261   // 2009-03-24: also (temporarily?) used for overlapping blit workaround
262   gfx.scrollbuffer_width = scrollbuffer_width;
263   gfx.scrollbuffer_height = scrollbuffer_height;
264 }
265
266 void InitGfxClipRegion(boolean enabled, int x, int y, int width, int height)
267 {
268   gfx.clipping_enabled = enabled;
269   gfx.clip_x = x;
270   gfx.clip_y = y;
271   gfx.clip_width = width;
272   gfx.clip_height = height;
273 }
274
275 void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(boolean))
276 {
277   gfx.draw_busy_anim_function = draw_busy_anim_function;
278 }
279
280 void InitGfxDrawGlobalAnimFunction(void (*draw_global_anim_function)(int, int))
281 {
282   gfx.draw_global_anim_function = draw_global_anim_function;
283 }
284
285 void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int))
286 {
287   gfx.draw_global_border_function = draw_global_border_function;
288 }
289
290 void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int, int))
291 {
292   gfx.draw_tile_cursor_function = draw_tile_cursor_function;
293 }
294
295 void InitGfxDrawEnvelopeRequestFunction(void (*draw_envelope_request_function)(int))
296 {
297   gfx.draw_envelope_request_function = draw_envelope_request_function;
298 }
299
300 void InitGfxCustomArtworkInfo(void)
301 {
302   gfx.override_level_graphics = FALSE;
303   gfx.override_level_sounds = FALSE;
304   gfx.override_level_music = FALSE;
305
306   gfx.draw_init_text = TRUE;
307 }
308
309 void InitGfxOtherSettings(void)
310 {
311   gfx.cursor_mode = CURSOR_DEFAULT;
312   gfx.cursor_mode_override = CURSOR_UNDEFINED;
313   gfx.cursor_mode_final = gfx.cursor_mode;
314
315   // prevent initially displaying custom mouse cursor in upper left corner
316   gfx.mouse_x = POS_OFFSCREEN;
317   gfx.mouse_y = POS_OFFSCREEN;
318 }
319
320 void InitTileCursorInfo(void)
321 {
322   tile_cursor.enabled = FALSE;
323   tile_cursor.active = FALSE;
324   tile_cursor.moving = FALSE;
325
326   tile_cursor.xpos = 0;
327   tile_cursor.ypos = 0;
328   tile_cursor.x = 0;
329   tile_cursor.y = 0;
330   tile_cursor.target_x = 0;
331   tile_cursor.target_y = 0;
332
333   tile_cursor.sx = 0;
334   tile_cursor.sy = 0;
335
336   tile_cursor.xsn_debug = FALSE;
337 }
338
339 void InitOverlayInfo(void)
340 {
341   overlay.enabled = FALSE;
342   overlay.active = FALSE;
343
344   overlay.show_grid = FALSE;
345
346   overlay.grid_button_highlight = CHAR_GRID_BUTTON_NONE;
347   overlay.grid_button_action = JOY_NO_ACTION;
348
349   SetOverlayGridSizeAndButtons();
350
351 #if defined(USE_TOUCH_INPUT_OVERLAY)
352   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
353     overlay.enabled = TRUE;
354 #endif
355 }
356
357 void SetOverlayGridSizeAndButtons(void)
358 {
359   int nr = GRID_ACTIVE_NR();
360   int x, y;
361
362   overlay.grid_xsize = setup.touch.grid_xsize[nr];
363   overlay.grid_ysize = setup.touch.grid_ysize[nr];
364
365   for (x = 0; x < MAX_GRID_XSIZE; x++)
366     for (y = 0; y < MAX_GRID_YSIZE; y++)
367       overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
368 }
369
370 void SetTileCursorEnabled(boolean enabled)
371 {
372   tile_cursor.enabled = enabled;
373 }
374
375 void SetTileCursorActive(boolean active)
376 {
377   tile_cursor.active = active;
378 }
379
380 void SetTileCursorTargetXY(int x, int y)
381 {
382   // delayed placement of tile selection cursor at target position
383   // (tile cursor will be moved to target position step by step)
384
385   tile_cursor.xpos = x;
386   tile_cursor.ypos = y;
387   tile_cursor.target_x = tile_cursor.sx + x * gfx.game_tile_size;
388   tile_cursor.target_y = tile_cursor.sy + y * gfx.game_tile_size;
389
390   tile_cursor.moving = TRUE;
391 }
392
393 void SetTileCursorXY(int x, int y)
394 {
395   // immediate placement of tile selection cursor at target position
396
397   SetTileCursorTargetXY(x, y);
398
399   tile_cursor.x = tile_cursor.target_x;
400   tile_cursor.y = tile_cursor.target_y;
401
402   tile_cursor.moving = FALSE;
403 }
404
405 void SetTileCursorSXSY(int sx, int sy)
406 {
407   tile_cursor.sx = sx;
408   tile_cursor.sy = sy;
409 }
410
411 void SetOverlayEnabled(boolean enabled)
412 {
413   overlay.enabled = enabled;
414 }
415
416 void SetOverlayActive(boolean active)
417 {
418   overlay.active = active;
419 }
420
421 void SetOverlayShowGrid(boolean show_grid)
422 {
423   overlay.show_grid = show_grid;
424
425   SetOverlayActive(show_grid);
426
427   if (show_grid)
428     SetOverlayEnabled(TRUE);
429 }
430
431 boolean GetOverlayEnabled(void)
432 {
433   return overlay.enabled;
434 }
435
436 boolean GetOverlayActive(void)
437 {
438   return overlay.active;
439 }
440
441 void SetDrawDeactivationMask(int draw_deactivation_mask)
442 {
443   gfx.draw_deactivation_mask = draw_deactivation_mask;
444 }
445
446 int GetDrawDeactivationMask(void)
447 {
448   return gfx.draw_deactivation_mask;
449 }
450
451 void SetDrawBackgroundMask(int draw_background_mask)
452 {
453   gfx.draw_background_mask = draw_background_mask;
454 }
455
456 void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask,
457                          int x, int y, int width, int height)
458 {
459   if (background_bitmap_tile != NULL)
460     gfx.background_bitmap_mask |= mask;
461   else
462     gfx.background_bitmap_mask &= ~mask;
463
464   if (background_bitmap_tile == NULL)   // empty background requested
465     return;
466
467   if (mask == REDRAW_ALL)
468     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap,
469                     x, y, width, height,
470                     0, 0, video.width, video.height);
471   else if (mask == REDRAW_FIELD)
472     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap,
473                     x, y, width, height,
474                     gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize);
475   else if (mask == REDRAW_DOOR_1)
476     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap,
477                     x, y, width, height,
478                     gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize);
479 }
480
481
482 // ============================================================================
483 // video functions
484 // ============================================================================
485
486 static int GetRealDepth(int depth)
487 {
488   return (depth == DEFAULT_DEPTH ? video.default_depth : depth);
489 }
490
491 static void sysFillRectangle(Bitmap *bitmap, int x, int y,
492                              int width, int height, Pixel color)
493 {
494   SDLFillRectangle(bitmap, x, y, width, height, color);
495
496   if (bitmap == backbuffer)
497     SetRedrawMaskFromArea(x, y, width, height);
498 }
499
500 static void sysCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
501                         int src_x, int src_y, int width, int height,
502                         int dst_x, int dst_y, int mask_mode)
503 {
504   SDLCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
505               dst_x, dst_y, mask_mode);
506
507   if (dst_bitmap == backbuffer)
508     SetRedrawMaskFromArea(dst_x, dst_y, width, height);
509 }
510
511 void LimitScreenUpdates(boolean enable)
512 {
513   SDLLimitScreenUpdates(enable);
514 }
515
516 void InitVideoDefaults(void)
517 {
518   video.default_depth = 32;
519 }
520
521 void InitVideoDisplay(void)
522 {
523   if (program.headless)
524     return;
525
526   SDLInitVideoDisplay();
527   SDLSetDisplaySize();
528 }
529
530 void CloseVideoDisplay(void)
531 {
532   KeyboardAutoRepeatOn();
533
534   SDL_QuitSubSystem(SDL_INIT_VIDEO);
535 }
536
537 void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
538 {
539   video.width = width;
540   video.height = height;
541   video.depth = GetRealDepth(depth);
542
543   video.screen_width = width;
544   video.screen_height = height;
545   video.screen_xoffset = 0;
546   video.screen_yoffset = 0;
547
548   video.fullscreen_available = FULLSCREEN_STATUS;
549   video.fullscreen_enabled = FALSE;
550
551   video.window_scaling_available = WINDOW_SCALING_STATUS;
552
553   video.frame_counter = 0;
554   video.frame_delay.count = 0;
555   video.frame_delay.value = GAME_FRAME_DELAY;
556
557   video.shifted_up = FALSE;
558   video.shifted_up_pos = 0;
559   video.shifted_up_pos_last = 0;
560   video.shifted_up_delay.count = 0;
561   video.shifted_up_delay.value = ONE_SECOND_DELAY / 4;
562
563   SDLInitVideoBuffer(fullscreen);
564
565   video.initialized = !program.headless;
566
567   drawto = backbuffer;
568 }
569
570 static void FreeBitmapPointers(Bitmap *bitmap)
571 {
572   if (bitmap == NULL)
573     return;
574
575   SDLFreeBitmapPointers(bitmap);
576
577   checked_free(bitmap->source_filename);
578   bitmap->source_filename = NULL;
579 }
580
581 static void TransferBitmapPointers(Bitmap *src_bitmap,
582                                    Bitmap *dst_bitmap)
583 {
584   if (src_bitmap == NULL || dst_bitmap == NULL)
585     return;
586
587   FreeBitmapPointers(dst_bitmap);
588
589   *dst_bitmap = *src_bitmap;
590 }
591
592 void FreeBitmap(Bitmap *bitmap)
593 {
594   if (bitmap == NULL)
595     return;
596
597   FreeBitmapPointers(bitmap);
598
599   free(bitmap);
600 }
601
602 void ResetBitmapAlpha(Bitmap *bitmap)
603 {
604   bitmap->alpha[0][0] = -1;
605   bitmap->alpha[0][1] = -1;
606   bitmap->alpha[1][0] = -1;
607   bitmap->alpha[1][1] = -1;
608   bitmap->alpha_next_blit = -1;
609 }
610
611 Bitmap *CreateBitmapStruct(void)
612 {
613   Bitmap *new_bitmap = checked_calloc(sizeof(Bitmap));
614
615   ResetBitmapAlpha(new_bitmap);
616
617   return new_bitmap;
618 }
619
620 Bitmap *CreateBitmap(int width, int height, int depth)
621 {
622   Bitmap *new_bitmap = CreateBitmapStruct();
623   int real_width  = MAX(1, width);      // prevent zero bitmap width
624   int real_height = MAX(1, height);     // prevent zero bitmap height
625   int real_depth  = GetRealDepth(depth);
626
627   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
628
629   new_bitmap->width  = real_width;
630   new_bitmap->height = real_height;
631
632   return new_bitmap;
633 }
634
635 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
636 {
637   if (*bitmap != NULL)
638   {
639     // if new bitmap size fits into old one, no need to re-create it
640     if (width  <= (*bitmap)->width &&
641         height <= (*bitmap)->height)
642       return;
643
644     // else adjust size so that old and new bitmap size fit into it
645     width  = MAX(width,  (*bitmap)->width);
646     height = MAX(height, (*bitmap)->height);
647   }
648
649   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
650
651   if (*bitmap == NULL)
652   {
653     *bitmap = new_bitmap;
654   }
655   else
656   {
657     TransferBitmapPointers(new_bitmap, *bitmap);
658     free(new_bitmap);
659   }
660 }
661
662 #if 0
663 static void CloseWindow(DrawWindow *window)
664 {
665 }
666 #endif
667
668 void SetRedrawMaskFromArea(int x, int y, int width, int height)
669 {
670   int x1 = x;
671   int y1 = y;
672   int x2 = x + width - 1;
673   int y2 = y + height - 1;
674
675   if (width == 0 || height == 0)
676     return;
677
678   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
679     redraw_mask |= REDRAW_FIELD;
680   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
681     redraw_mask |= REDRAW_DOOR_1;
682   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
683     redraw_mask |= REDRAW_DOOR_2;
684   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
685     redraw_mask |= REDRAW_DOOR_3;
686   else
687     redraw_mask = REDRAW_ALL;
688 }
689
690 static boolean CheckDrawingArea(int x, int y, int draw_mask)
691 {
692   if (draw_mask == REDRAW_NONE)
693     return FALSE;
694
695   if (draw_mask & REDRAW_ALL)
696     return TRUE;
697
698   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
699     return TRUE;
700
701   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
702     return TRUE;
703
704   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
705     return TRUE;
706
707   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
708     return TRUE;
709
710   return FALSE;
711 }
712
713 boolean DrawingDeactivatedField(void)
714 {
715   if (program.headless)
716     return TRUE;
717
718   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
719     return TRUE;
720
721   return FALSE;
722 }
723
724 boolean DrawingDeactivated(int x, int y)
725 {
726   return CheckDrawingArea(x, y, gfx.draw_deactivation_mask);
727 }
728
729 boolean DrawingOnBackground(int x, int y)
730 {
731   return (CheckDrawingArea(x, y, gfx.background_bitmap_mask) &&
732           CheckDrawingArea(x, y, gfx.draw_background_mask));
733 }
734
735 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
736                                   int *width, int *height, boolean is_dest)
737 {
738   int clip_x, clip_y, clip_width, clip_height;
739
740   if (gfx.clipping_enabled && is_dest)  // only clip destination bitmap
741   {
742     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
743     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
744     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
745     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
746   }
747   else
748   {
749     clip_x = 0;
750     clip_y = 0;
751     clip_width = bitmap->width;
752     clip_height = bitmap->height;
753   }
754
755   // skip if rectangle completely outside bitmap
756
757   if (*x + *width  <= clip_x ||
758       *y + *height <= clip_y ||
759       *x >= clip_x + clip_width ||
760       *y >= clip_y + clip_height)
761     return FALSE;
762
763   // clip if rectangle overlaps bitmap
764
765   if (*x < clip_x)
766   {
767     *width -= clip_x - *x;
768     *x = clip_x;
769   }
770   else if (*x + *width > clip_x + clip_width)
771   {
772     *width = clip_x + clip_width - *x;
773   }
774
775   if (*y < clip_y)
776   {
777     *height -= clip_y - *y;
778     *y = clip_y;
779   }
780   else if (*y + *height > clip_y + clip_height)
781   {
782     *height = clip_y + clip_height - *y;
783   }
784
785   return TRUE;
786 }
787
788 void SetBitmapAlphaNextBlit(Bitmap *bitmap, int alpha)
789 {
790   // set alpha value for next blitting of bitmap
791   bitmap->alpha_next_blit = alpha;
792 }
793
794 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
795                 int src_x, int src_y, int width, int height,
796                 int dst_x, int dst_y)
797 {
798   int dst_x_unclipped = dst_x;
799   int dst_y_unclipped = dst_y;
800
801   if (program.headless)
802     return;
803
804   if (src_bitmap == NULL || dst_bitmap == NULL)
805     return;
806
807   if (DrawingDeactivated(dst_x, dst_y))
808     return;
809
810   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
811       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
812     return;
813
814   // source x/y might need adjustment if destination x/y was clipped top/left
815   src_x += dst_x - dst_x_unclipped;
816   src_y += dst_y - dst_y_unclipped;
817
818   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
819   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
820   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
821      but is already fixed in SVN and should therefore finally be fixed with
822      the next official SDL release, which is probably version 1.2.14.) */
823   // !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!!
824
825   if (src_bitmap == dst_bitmap)
826   {
827     // needed when blitting directly to same bitmap -- should not be needed with
828     // recent SDL libraries, but apparently does not work in 1.2.11 directly
829
830     static Bitmap *tmp_bitmap = NULL;
831     static int tmp_bitmap_xsize = 0;
832     static int tmp_bitmap_ysize = 0;
833
834     // start with largest static bitmaps for initial bitmap size ...
835     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
836     {
837       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
838       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
839     }
840
841     // ... and allow for later re-adjustments due to custom artwork bitmaps
842     if (src_bitmap->width > tmp_bitmap_xsize ||
843         src_bitmap->height > tmp_bitmap_ysize)
844     {
845       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
846       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
847
848       FreeBitmap(tmp_bitmap);
849
850       tmp_bitmap = NULL;
851     }
852
853     if (tmp_bitmap == NULL)
854       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
855                                 DEFAULT_DEPTH);
856
857     sysCopyArea(src_bitmap, tmp_bitmap,
858                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
859     sysCopyArea(tmp_bitmap, dst_bitmap,
860                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
861
862     return;
863   }
864
865   sysCopyArea(src_bitmap, dst_bitmap,
866               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
867 }
868
869 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
870                      int src_x, int src_y, int src_width, int src_height,
871                      int dst_x, int dst_y, int dst_width, int dst_height)
872 {
873   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
874   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
875   int dst_xsize = dst_width;
876   int dst_ysize = dst_height;
877   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
878   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
879   int x, y;
880
881   for (y = 0; y < src_ysteps; y++)
882   {
883     for (x = 0; x < src_xsteps; x++)
884     {
885       int draw_x = dst_x + x * src_xsize;
886       int draw_y = dst_y + y * src_ysize;
887       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
888       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
889
890       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
891                  draw_x, draw_y);
892     }
893   }
894 }
895
896 void FadeRectangle(int x, int y, int width, int height,
897                    int fade_mode, int fade_delay, int post_delay,
898                    void (*draw_border_function)(void))
899 {
900   // (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined)
901   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
902     return;
903
904   SDLFadeRectangle(x, y, width, height,
905                    fade_mode, fade_delay, post_delay, draw_border_function);
906 }
907
908 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
909                    Pixel color)
910 {
911   if (program.headless)
912     return;
913
914   if (DrawingDeactivated(x, y))
915     return;
916
917   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
918     return;
919
920   sysFillRectangle(bitmap, x, y, width, height, color);
921 }
922
923 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
924 {
925   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
926 }
927
928 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
929                                 int width, int height)
930 {
931   if (DrawingOnBackground(x, y))
932     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
933   else
934     ClearRectangle(bitmap, x, y, width, height);
935 }
936
937 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
938                       int src_x, int src_y, int width, int height,
939                       int dst_x, int dst_y)
940 {
941   if (program.headless)
942     return;
943
944   if (src_bitmap == NULL || dst_bitmap == NULL)
945     return;
946
947   if (DrawingDeactivated(dst_x, dst_y))
948     return;
949
950   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
951               dst_x, dst_y, BLIT_MASKED);
952 }
953
954 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
955                             int src_x, int src_y, int width, int height,
956                             int dst_x, int dst_y)
957 {
958   if (DrawingOnBackground(dst_x, dst_y))
959   {
960     // draw background
961     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
962                dst_x, dst_y);
963
964     // draw foreground
965     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
966                      dst_x, dst_y);
967   }
968   else
969     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
970                dst_x, dst_y);
971 }
972
973 void BlitTexture(Bitmap *bitmap,
974                 int src_x, int src_y, int width, int height,
975                 int dst_x, int dst_y)
976 {
977   if (bitmap == NULL)
978     return;
979
980   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
981                  BLIT_OPAQUE);
982 }
983
984 void BlitTextureMasked(Bitmap *bitmap,
985                        int src_x, int src_y, int width, int height,
986                        int dst_x, int dst_y)
987 {
988   if (bitmap == NULL)
989     return;
990
991   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
992                  BLIT_MASKED);
993 }
994
995 void BlitToScreen(Bitmap *bitmap,
996                   int src_x, int src_y, int width, int height,
997                   int dst_x, int dst_y)
998 {
999   if (bitmap == NULL)
1000     return;
1001
1002   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1003     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1004                width, height, dst_x, dst_y);
1005   else
1006     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1007 }
1008
1009 void BlitToScreenMasked(Bitmap *bitmap,
1010                         int src_x, int src_y, int width, int height,
1011                         int dst_x, int dst_y)
1012 {
1013   if (bitmap == NULL)
1014     return;
1015
1016   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1017     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1018                      width, height, dst_x, dst_y);
1019   else
1020     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1021 }
1022
1023 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1024                          int to_x, int to_y)
1025 {
1026   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1027 }
1028
1029 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1030                      int to_x, int to_y, Pixel pixel, int line_width)
1031 {
1032   int x, y;
1033
1034   if (program.headless)
1035     return;
1036
1037   for (x = 0; x < line_width; x++)
1038   {
1039     for (y = 0; y < line_width; y++)
1040     {
1041       int dx = x - line_width / 2;
1042       int dy = y - line_width / 2;
1043
1044       if ((x == 0 && y == 0) ||
1045           (x == 0 && y == line_width - 1) ||
1046           (x == line_width - 1 && y == 0) ||
1047           (x == line_width - 1 && y == line_width - 1))
1048         continue;
1049
1050       SDLDrawLine(bitmap,
1051                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1052     }
1053   }
1054 }
1055
1056 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1057 {
1058   int line_width = 4;
1059   int i;
1060
1061   for (i = 0; i < num_points - 1; i++)
1062     DrawLine(bitmap, points[i].x, points[i].y,
1063              points[i + 1].x, points[i + 1].y, pixel, line_width);
1064
1065   /*
1066   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1067   */
1068 }
1069
1070 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1071 {
1072   if (program.headless)
1073     return BLACK_PIXEL;
1074
1075   if (x < 0 || x >= bitmap->width ||
1076       y < 0 || y >= bitmap->height)
1077     return BLACK_PIXEL;
1078
1079   return SDLGetPixel(bitmap, x, y);
1080 }
1081
1082 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1083                       unsigned int color_g, unsigned int color_b)
1084 {
1085   if (program.headless)
1086     return BLACK_PIXEL;
1087
1088   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1089 }
1090
1091 void KeyboardAutoRepeatOn(void)
1092 {
1093   keyrepeat_status = TRUE;
1094 }
1095
1096 void KeyboardAutoRepeatOff(void)
1097 {
1098   keyrepeat_status = FALSE;
1099 }
1100
1101 boolean SetVideoMode(boolean fullscreen)
1102 {
1103   return SDLSetVideoMode(fullscreen);
1104 }
1105
1106 void SetVideoFrameDelay(unsigned int frame_delay_value)
1107 {
1108   video.frame_delay.value = frame_delay_value;
1109 }
1110
1111 unsigned int GetVideoFrameDelay(void)
1112 {
1113   return video.frame_delay.value;
1114 }
1115
1116 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1117 {
1118   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1119       (!fullscreen && video.fullscreen_enabled))
1120     fullscreen = SetVideoMode(fullscreen);
1121
1122   return fullscreen;
1123 }
1124
1125 Bitmap *LoadImage(char *filename)
1126 {
1127   Bitmap *new_bitmap;
1128
1129   new_bitmap = SDLLoadImage(filename);
1130
1131   if (new_bitmap)
1132     new_bitmap->source_filename = getStringCopy(filename);
1133
1134   return new_bitmap;
1135 }
1136
1137 Bitmap *LoadCustomImage(char *basename)
1138 {
1139   char *filename = getCustomImageFilename(basename);
1140   Bitmap *new_bitmap;
1141
1142   if (filename == NULL)
1143     Fail("LoadCustomImage(): cannot find file '%s'", basename);
1144
1145   if ((new_bitmap = LoadImage(filename)) == NULL)
1146     Fail("LoadImage('%s') failed", basename);
1147
1148   return new_bitmap;
1149 }
1150
1151 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1152 {
1153   char *filename = getCustomImageFilename(basename);
1154   Bitmap *new_bitmap;
1155
1156   if (filename == NULL)         // (should never happen)
1157   {
1158     Warn("ReloadCustomImage(): cannot find file '%s'", basename);
1159
1160     return;
1161   }
1162
1163   if (strEqual(filename, bitmap->source_filename))
1164   {
1165     // The old and new image are the same (have the same filename and path).
1166     // This usually means that this image does not exist in this graphic set
1167     // and a fallback to the existing image is done.
1168
1169     return;
1170   }
1171
1172   if ((new_bitmap = LoadImage(filename)) == NULL)
1173   {
1174     Warn("LoadImage('%s') failed", basename);
1175
1176     return;
1177   }
1178
1179   if (bitmap->width != new_bitmap->width ||
1180       bitmap->height != new_bitmap->height)
1181   {
1182     Warn("ReloadCustomImage: new image '%s' has wrong dimensions",
1183           filename);
1184
1185     FreeBitmap(new_bitmap);
1186
1187     return;
1188   }
1189
1190   TransferBitmapPointers(new_bitmap, bitmap);
1191   free(new_bitmap);
1192 }
1193
1194 Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1195 {
1196   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1197 }
1198
1199 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1200 {
1201   if (bitmaps[IMG_BITMAP_CUSTOM])
1202   {
1203     // check if original sized bitmap points to custom sized bitmap
1204     if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM])
1205     {
1206       SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1207
1208       // keep pointer of previous custom size bitmap
1209       bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM];
1210
1211       // set original bitmap pointer to scaled original bitmap of other size
1212       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1213
1214       SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1215     }
1216     else
1217     {
1218       FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1219     }
1220
1221     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1222   }
1223
1224   if (gfx.game_tile_size == gfx.standard_tile_size)
1225   {
1226     // set game bitmap pointer to standard sized bitmap (already existing)
1227     bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1228
1229     return;
1230   }
1231
1232   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1233   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1234   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1235
1236   bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height);
1237
1238   // set game bitmap pointer to custom sized bitmap (newly created)
1239   bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1240 }
1241
1242 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1243                                 int tile_size, boolean create_small_bitmaps)
1244 {
1245   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1246   Bitmap *tmp_bitmap_final = NULL;
1247   Bitmap *tmp_bitmap_0 = NULL;
1248   Bitmap *tmp_bitmap_1 = NULL;
1249   Bitmap *tmp_bitmap_2 = NULL;
1250   Bitmap *tmp_bitmap_4 = NULL;
1251   Bitmap *tmp_bitmap_8 = NULL;
1252   Bitmap *tmp_bitmap_16 = NULL;
1253   Bitmap *tmp_bitmap_32 = NULL;
1254   int width_final, height_final;
1255   int width_0, height_0;
1256   int width_1, height_1;
1257   int width_2, height_2;
1258   int width_4, height_4;
1259   int width_8, height_8;
1260   int width_16, height_16;
1261   int width_32, height_32;
1262   int old_width, old_height;
1263   int i;
1264
1265   print_timestamp_init("CreateScaledBitmaps");
1266
1267   old_width  = old_bitmap->width;
1268   old_height = old_bitmap->height;
1269
1270   // calculate new image dimensions for final image size
1271   width_final  = old_width  * zoom_factor;
1272   height_final = old_height * zoom_factor;
1273
1274   // get image with final size (this might require scaling up)
1275   // ("final" size may result in non-standard tile size image)
1276   if (zoom_factor != 1)
1277     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1278   else
1279     tmp_bitmap_final = old_bitmap;
1280
1281   UPDATE_BUSY_STATE();
1282
1283   width_0  = width_1  = width_final;
1284   height_0 = height_1 = height_final;
1285
1286   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1287
1288   if (create_small_bitmaps)
1289   {
1290     // check if we have a non-gameplay tile size image
1291     if (tile_size != gfx.game_tile_size)
1292     {
1293       // get image with gameplay tile size
1294       width_0  = width_final  * gfx.game_tile_size / tile_size;
1295       height_0 = height_final * gfx.game_tile_size / tile_size;
1296
1297       if (width_0 == old_width)
1298         tmp_bitmap_0 = old_bitmap;
1299       else if (width_0 == width_final)
1300         tmp_bitmap_0 = tmp_bitmap_final;
1301       else
1302         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1303
1304       UPDATE_BUSY_STATE();
1305     }
1306
1307     // check if we have a non-standard tile size image
1308     if (tile_size != gfx.standard_tile_size)
1309     {
1310       // get image with standard tile size
1311       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1312       height_1 = height_final * gfx.standard_tile_size / tile_size;
1313
1314       if (width_1 == old_width)
1315         tmp_bitmap_1 = old_bitmap;
1316       else if (width_1 == width_final)
1317         tmp_bitmap_1 = tmp_bitmap_final;
1318       else if (width_1 == width_0)
1319         tmp_bitmap_1 = tmp_bitmap_0;
1320       else
1321         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1322
1323       UPDATE_BUSY_STATE();
1324     }
1325
1326     // calculate new image dimensions for small images
1327     width_2  = width_1  / 2;
1328     height_2 = height_1 / 2;
1329     width_4  = width_1  / 4;
1330     height_4 = height_1 / 4;
1331     width_8  = width_1  / 8;
1332     height_8 = height_1 / 8;
1333     width_16  = width_1  / 16;
1334     height_16 = height_1 / 16;
1335     width_32  = width_1  / 32;
1336     height_32 = height_1 / 32;
1337
1338     // get image with 1/2 of normal size (for use in the level editor)
1339     if (width_2 == old_width)
1340       tmp_bitmap_2 = old_bitmap;
1341     else
1342       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1343
1344     UPDATE_BUSY_STATE();
1345
1346     // get image with 1/4 of normal size (for use in the level editor)
1347     if (width_4 == old_width)
1348       tmp_bitmap_4 = old_bitmap;
1349     else
1350       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1351
1352     UPDATE_BUSY_STATE();
1353
1354     // get image with 1/8 of normal size (for use on the preview screen)
1355     if (width_8 == old_width)
1356       tmp_bitmap_8 = old_bitmap;
1357     else
1358       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1359
1360     UPDATE_BUSY_STATE();
1361
1362     // get image with 1/16 of normal size (for use on the preview screen)
1363     if (width_16 == old_width)
1364       tmp_bitmap_16 = old_bitmap;
1365     else
1366       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1367
1368     UPDATE_BUSY_STATE();
1369
1370     // get image with 1/32 of normal size (for use on the preview screen)
1371     if (width_32 == old_width)
1372       tmp_bitmap_32 = old_bitmap;
1373     else
1374       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1375
1376     UPDATE_BUSY_STATE();
1377
1378     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1379     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1380     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1381     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1382     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1383     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1384
1385     if (width_0 != width_1)
1386       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1387
1388     if (bitmaps[IMG_BITMAP_CUSTOM])
1389       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1390     else
1391       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1392
1393     // store the "final" (up-scaled) original bitmap, if not already stored
1394
1395     int tmp_bitmap_final_nr = -1;
1396
1397     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1398       if (bitmaps[i] == tmp_bitmap_final)
1399         tmp_bitmap_final_nr = i;
1400
1401     if (tmp_bitmap_final_nr == -1)      // scaled original bitmap not stored
1402     {
1403       // store pointer of scaled original bitmap (not used for any other size)
1404       bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final;
1405
1406       // set original bitmap pointer to scaled original bitmap of other size
1407       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1408     }
1409     else
1410     {
1411       // set original bitmap pointer to corresponding sized bitmap
1412       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr];
1413     }
1414
1415     // free the "old" (unscaled) original bitmap, if not already stored
1416
1417     boolean free_old_bitmap = TRUE;
1418
1419     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1420       if (bitmaps[i] == old_bitmap)
1421         free_old_bitmap = FALSE;
1422
1423     if (free_old_bitmap)
1424     {
1425       // copy image filename from old to new standard sized bitmap
1426       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1427         getStringCopy(old_bitmap->source_filename);
1428
1429       FreeBitmap(old_bitmap);
1430     }
1431   }
1432   else
1433   {
1434     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1435
1436     // set original bitmap pointer to corresponding sized bitmap
1437     bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32];
1438
1439     if (old_bitmap != tmp_bitmap_1)
1440       FreeBitmap(old_bitmap);
1441   }
1442
1443   UPDATE_BUSY_STATE();
1444
1445   print_timestamp_done("CreateScaledBitmaps");
1446 }
1447
1448 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1449                                   int tile_size)
1450 {
1451   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1452 }
1453
1454 void CreateBitmapTextures(Bitmap **bitmaps)
1455 {
1456   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1457     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1458   else
1459     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1460 }
1461
1462 void FreeBitmapTextures(Bitmap **bitmaps)
1463 {
1464   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1465     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1466   else
1467     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1468 }
1469
1470 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1471 {
1472   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1473 }
1474
1475
1476 // ----------------------------------------------------------------------------
1477 // mouse pointer functions
1478 // ----------------------------------------------------------------------------
1479
1480 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1481
1482 // XPM image definitions
1483 static const char *cursor_image_none[] =
1484 {
1485   // width height num_colors chars_per_pixel
1486   "    16    16        3            1",
1487
1488   // colors
1489   "X c #000000",
1490   ". c #ffffff",
1491   "  c None",
1492
1493   // pixels
1494   "                ",
1495   "                ",
1496   "                ",
1497   "                ",
1498   "                ",
1499   "                ",
1500   "                ",
1501   "                ",
1502   "                ",
1503   "                ",
1504   "                ",
1505   "                ",
1506   "                ",
1507   "                ",
1508   "                ",
1509   "                ",
1510
1511   // hot spot
1512   "0,0"
1513 };
1514
1515 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1516 static const char *cursor_image_dot[] =
1517 {
1518   // width height num_colors chars_per_pixel
1519   "    16    16        3            1",
1520
1521   // colors
1522   "X c #000000",
1523   ". c #ffffff",
1524   "  c None",
1525
1526   // pixels
1527   " X              ",
1528   "X.X             ",
1529   " X              ",
1530   "                ",
1531   "                ",
1532   "                ",
1533   "                ",
1534   "                ",
1535   "                ",
1536   "                ",
1537   "                ",
1538   "                ",
1539   "                ",
1540   "                ",
1541   "                ",
1542   "                ",
1543
1544   // hot spot
1545   "1,1"
1546 };
1547 static const char **cursor_image_playfield = cursor_image_dot;
1548 #else
1549 // some people complained about a "white dot" on the screen and thought it
1550 // was a graphical error... OK, let's just remove the whole pointer :-)
1551 static const char **cursor_image_playfield = cursor_image_none;
1552 #endif
1553
1554 static const int cursor_bit_order = BIT_ORDER_MSB;
1555
1556 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1557 {
1558   struct MouseCursorInfo *cursor;
1559   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1560   int header_lines = 4;
1561   int x, y, i;
1562
1563   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1564
1565   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1566
1567   i = -1;
1568   for (y = 0; y < cursor->width; y++)
1569   {
1570     for (x = 0; x < cursor->height; x++)
1571     {
1572       int bit_nr = x % 8;
1573       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1574
1575       if (bit_nr == 0)
1576       {
1577         i++;
1578         cursor->data[i] = cursor->mask[i] = 0;
1579       }
1580
1581       switch (image[header_lines + y][x])
1582       {
1583         case 'X':
1584           cursor->data[i] |= bit_mask;
1585           cursor->mask[i] |= bit_mask;
1586           break;
1587
1588         case '.':
1589           cursor->mask[i] |= bit_mask;
1590           break;
1591
1592         case ' ':
1593           break;
1594       }
1595     }
1596   }
1597
1598   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1599
1600   return cursor;
1601 }
1602
1603 void SetMouseCursor(int mode)
1604 {
1605   static struct MouseCursorInfo *cursor_none = NULL;
1606   static struct MouseCursorInfo *cursor_playfield = NULL;
1607   struct MouseCursorInfo *cursor_new;
1608   int mode_final = mode;
1609
1610   if (cursor_none == NULL)
1611     cursor_none = get_cursor_from_image(cursor_image_none);
1612
1613   if (cursor_playfield == NULL)
1614     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1615
1616   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1617     mode_final = gfx.cursor_mode_override;
1618
1619   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1620                 mode_final == CURSOR_NONE      ? cursor_none :
1621                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1622
1623   SDLSetMouseCursor(cursor_new);
1624
1625   gfx.cursor_mode = mode;
1626   gfx.cursor_mode_final = mode_final;
1627 }
1628
1629 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1630 {
1631   // mouse events do not contain logical screen size corrections yet
1632   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1633
1634   mouse_x -= video.screen_xoffset;
1635   mouse_y -= video.screen_yoffset;
1636
1637   gfx.mouse_x = mouse_x;
1638   gfx.mouse_y = mouse_y;
1639 }
1640
1641 void UpdateMousePosition(void)
1642 {
1643   int mouse_x, mouse_y;
1644
1645   SDL_PumpEvents();
1646   SDL_GetMouseState(&mouse_x, &mouse_y);
1647
1648   UpdateRawMousePosition(mouse_x, mouse_y);
1649 }
1650
1651
1652 // ============================================================================
1653 // audio functions
1654 // ============================================================================
1655
1656 void OpenAudio(void)
1657 {
1658   // always start with reliable default values
1659   audio.sound_available = FALSE;
1660   audio.music_available = FALSE;
1661   audio.loops_available = FALSE;
1662
1663   audio.sound_enabled = FALSE;
1664   audio.sound_deactivated = FALSE;
1665
1666   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1667   audio.mixer_pid = 0;
1668   audio.device_name = NULL;
1669   audio.device_fd = -1;
1670
1671   audio.num_channels = 0;
1672   audio.music_channel = 0;
1673   audio.first_sound_channel = 0;
1674
1675   SDLOpenAudio();
1676 }
1677
1678 void CloseAudio(void)
1679 {
1680   SDLCloseAudio();
1681
1682   audio.sound_enabled = FALSE;
1683 }
1684
1685 void SetAudioMode(boolean enabled)
1686 {
1687   if (!audio.sound_available)
1688     return;
1689
1690   audio.sound_enabled = enabled;
1691 }
1692
1693
1694 // ============================================================================
1695 // event functions
1696 // ============================================================================
1697
1698 void InitEventFilter(EventFilter filter_function)
1699 {
1700   SDL_SetEventFilter(filter_function, NULL);
1701 }
1702
1703 boolean PendingEvent(void)
1704 {
1705   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1706 }
1707
1708 void WaitEvent(Event *event)
1709 {
1710   SDLWaitEvent(event);
1711 }
1712
1713 void PeekEvent(Event *event)
1714 {
1715   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1716 }
1717
1718 void PumpEvents(void)
1719 {
1720   SDL_PumpEvents();
1721 }
1722
1723 void CheckQuitEvent(void)
1724 {
1725   if (SDL_QuitRequested())
1726     program.exit_function(0);
1727 }
1728
1729 Key GetEventKey(KeyEvent *event)
1730 {
1731   // key up/down events in SDL2 do not return text characters anymore
1732   return event->keysym.sym;
1733 }
1734
1735 KeyMod HandleKeyModState(Key key, int key_status)
1736 {
1737   static KeyMod current_modifiers = KMOD_None;
1738
1739   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1740   {
1741     KeyMod new_modifier = KMOD_None;
1742
1743     switch (key)
1744     {
1745       case KSYM_Shift_L:
1746         new_modifier = KMOD_Shift_L;
1747         break;
1748       case KSYM_Shift_R:
1749         new_modifier = KMOD_Shift_R;
1750         break;
1751       case KSYM_Control_L:
1752         new_modifier = KMOD_Control_L;
1753         break;
1754       case KSYM_Control_R:
1755         new_modifier = KMOD_Control_R;
1756         break;
1757       case KSYM_Meta_L:
1758         new_modifier = KMOD_Meta_L;
1759         break;
1760       case KSYM_Meta_R:
1761         new_modifier = KMOD_Meta_R;
1762         break;
1763       case KSYM_Alt_L:
1764         new_modifier = KMOD_Alt_L;
1765         break;
1766       case KSYM_Alt_R:
1767         new_modifier = KMOD_Alt_R;
1768         break;
1769       default:
1770         break;
1771     }
1772
1773     if (key_status == KEY_PRESSED)
1774       current_modifiers |= new_modifier;
1775     else
1776       current_modifiers &= ~new_modifier;
1777   }
1778
1779   return current_modifiers;
1780 }
1781
1782 KeyMod GetKeyModState(void)
1783 {
1784   return (KeyMod)SDL_GetModState();
1785 }
1786
1787 KeyMod GetKeyModStateFromEvents(void)
1788 {
1789   /* always use key modifier state as tracked from key events (this is needed
1790      if the modifier key event was injected into the event queue, but the key
1791      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1792      query the keys as held pressed on the keyboard) -- this case is currently
1793      only used to filter out clipboard insert events from "True X-Mouse" tool */
1794
1795   return HandleKeyModState(KSYM_UNDEFINED, 0);
1796 }
1797
1798 void StartTextInput(int x, int y, int width, int height)
1799 {
1800   textinput_status = TRUE;
1801
1802 #if defined(HAS_SCREEN_KEYBOARD)
1803   SDL_StartTextInput();
1804
1805   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1806   {
1807     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1808     video.shifted_up_delay.count = SDL_GetTicks();
1809     video.shifted_up = TRUE;
1810   }
1811 #endif
1812 }
1813
1814 void StopTextInput(void)
1815 {
1816   textinput_status = FALSE;
1817
1818 #if defined(HAS_SCREEN_KEYBOARD)
1819   SDL_StopTextInput();
1820
1821   if (video.shifted_up)
1822   {
1823     video.shifted_up_pos = 0;
1824     video.shifted_up_delay.count = SDL_GetTicks();
1825     video.shifted_up = FALSE;
1826   }
1827 #endif
1828 }
1829
1830 void PushUserEvent(int code, int value1, int value2)
1831 {
1832   UserEvent event;
1833
1834   SDL_memset(&event, 0, sizeof(event));
1835
1836   event.type = EVENT_USER;
1837   event.code = code;
1838   event.value1 = value1;
1839   event.value2 = value2;
1840
1841   SDL_PushEvent((SDL_Event *)&event);
1842 }
1843
1844 boolean PendingEscapeKeyEvent(void)
1845 {
1846   if (PendingEvent())
1847   {
1848     Event event;
1849
1850     // check if any key press event is pending
1851     if (SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN) != 1)
1852       return FALSE;
1853
1854     // check if pressed key is "Escape" key
1855     if (event.key.keysym.sym == KSYM_Escape)
1856       return TRUE;
1857   }
1858
1859   return FALSE;
1860 }
1861
1862
1863 // ============================================================================
1864 // joystick functions
1865 // ============================================================================
1866
1867 void InitJoysticks(void)
1868 {
1869   int i;
1870
1871 #if defined(NO_JOYSTICK)
1872   return;       // joysticks generally deactivated by compile-time directive
1873 #endif
1874
1875   // always start with reliable default values
1876   joystick.status = JOYSTICK_NOT_AVAILABLE;
1877   for (i = 0; i < MAX_PLAYERS; i++)
1878     joystick.nr[i] = -1;                // no joystick configured
1879
1880   SDLInitJoysticks();
1881 }
1882
1883 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1884 {
1885   return SDLReadJoystick(nr, x, y, b1, b2);
1886 }
1887
1888 boolean CheckJoystickOpened(int nr)
1889 {
1890   return SDLCheckJoystickOpened(nr);
1891 }
1892
1893 void ClearJoystickState(void)
1894 {
1895   SDLClearJoystickState();
1896 }
1897
1898
1899 // ============================================================================
1900 // Emscripten functions
1901 // ============================================================================
1902
1903 void InitEmscriptenFilesystem(void)
1904 {
1905 #if defined(PLATFORM_EMSCRIPTEN)
1906   EM_ASM
1907   ({
1908     dir = UTF8ToString($0);
1909
1910     Module.sync_done = 0;
1911
1912     FS.mkdir(dir);                      // create persistent data directory
1913     FS.mount(IDBFS, {}, dir);           // mount with IDBFS filesystem type
1914     FS.syncfs(true, function(err)       // sync persistent data into memory
1915     {
1916       assert(!err);
1917       Module.sync_done = 1;
1918     });
1919   }, PERSISTENT_DIRECTORY);
1920
1921   // wait for persistent data to be synchronized to memory
1922   while (emscripten_run_script_int("Module.sync_done") == 0)
1923     Delay(20);
1924 #endif
1925 }
1926
1927 void SyncEmscriptenFilesystem(void)
1928 {
1929 #if defined(PLATFORM_EMSCRIPTEN)
1930   EM_ASM
1931   (
1932     FS.syncfs(function(err)
1933     {
1934       assert(!err);
1935     });
1936   );
1937 #endif
1938 }