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