407667452dbac5f8632587e9014d403159acd1f8
[rocksndiamonds.git] / src / libgame / sdl.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 // sdl.c
10 // ============================================================================
11
12 #include "system.h"
13 #include "sound.h"
14 #include "joystick.h"
15 #include "misc.h"
16 #include "setup.h"
17 #include "gadgets.h"
18
19 #define ENABLE_UNUSED_CODE      0       // currently unused functions
20
21 #define DEBUG_JOYSTICKS         0
22
23
24 // ============================================================================
25 // video functions
26 // ============================================================================
27
28 // SDL internal variables
29 static SDL_Window *sdl_window = NULL;
30 static SDL_Renderer *sdl_renderer = NULL;
31 static SDL_Texture *sdl_texture_stream = NULL;
32 static SDL_Texture *sdl_texture_target = NULL;
33 static boolean fullscreen_enabled = FALSE;
34 static boolean limit_screen_updates = FALSE;
35
36
37 // functions from SGE library
38 void sge_Line(SDL_Surface *, Sint16, Sint16, Sint16, Sint16, Uint32);
39
40 #if defined(USE_TOUCH_INPUT_OVERLAY)
41 // functions to draw overlay graphics for touch device input
42 static void DrawTouchInputOverlay(void);
43 static void DrawTouchGadgetsOverlay(void);
44 #endif
45
46 void SDLLimitScreenUpdates(boolean enable)
47 {
48   limit_screen_updates = enable;
49 }
50
51 static void FinalizeScreen(int draw_target)
52 {
53   // copy global animations to render target buffer, if defined (below border)
54   if (gfx.draw_global_anim_function != NULL)
55     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_1);
56
57   // copy global masked border to render target buffer, if defined
58   if (gfx.draw_global_border_function != NULL)
59     gfx.draw_global_border_function(draw_target);
60
61   // copy global animations to render target buffer, if defined (above border)
62   if (gfx.draw_global_anim_function != NULL)
63     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_2);
64
65   // copy tile selection cursor to render target buffer, if defined (above all)
66   if (gfx.draw_tile_cursor_function != NULL)
67     gfx.draw_tile_cursor_function(draw_target);
68 }
69
70 static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
71 {
72   static unsigned int update_screen_delay = 0;
73   unsigned int update_screen_delay_value = 50;          // (milliseconds)
74   SDL_Surface *screen = backbuffer->surface;
75
76   if (limit_screen_updates &&
77       !DelayReached(&update_screen_delay, update_screen_delay_value))
78     return;
79
80   LimitScreenUpdates(FALSE);
81
82 #if 0
83   {
84     static int LastFrameCounter = 0;
85     boolean changed = (FrameCounter != LastFrameCounter);
86
87     printf("::: FrameCounter == %d [%s]\n", FrameCounter,
88            (changed ? "-" : "SAME FRAME UPDATED"));
89
90     LastFrameCounter = FrameCounter;
91
92     /*
93     if (FrameCounter % 2)
94       return;
95     */
96   }
97 #endif
98
99   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP &&
100       gfx.final_screen_bitmap != NULL)  // may not be initialized yet
101   {
102     // draw global animations using bitmaps instead of using textures
103     // to prevent texture scaling artefacts (this is potentially slower)
104
105     BlitBitmap(backbuffer, gfx.final_screen_bitmap, 0, 0,
106                gfx.win_xsize, gfx.win_ysize, 0, 0);
107
108     FinalizeScreen(DRAW_TO_SCREEN);
109
110     screen = gfx.final_screen_bitmap->surface;
111
112     // force full window redraw
113     rect = NULL;
114   }
115
116   SDL_Texture *sdl_texture = sdl_texture_stream;
117
118   // deactivate use of target texture if render targets are not supported
119   if ((video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
120        video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE) &&
121       sdl_texture_target == NULL)
122     video.screen_rendering_mode = SPECIAL_RENDERING_OFF;
123
124   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET)
125     sdl_texture = sdl_texture_target;
126
127   if (rect)
128   {
129     int bytes_x = screen->pitch / video.width;
130     int bytes_y = screen->pitch;
131
132     SDL_UpdateTexture(sdl_texture, rect,
133                       screen->pixels + rect->x * bytes_x + rect->y * bytes_y,
134                       screen->pitch);
135   }
136   else
137   {
138     SDL_UpdateTexture(sdl_texture, NULL, screen->pixels, screen->pitch);
139   }
140
141   int xoff = video.screen_xoffset;
142   int yoff = video.screen_yoffset;
143   SDL_Rect dst_rect_screen = { xoff, yoff, video.width, video.height };
144   SDL_Rect *src_rect1 = NULL, *dst_rect1 = NULL;
145   SDL_Rect *src_rect2 = NULL, *dst_rect2 = NULL;
146
147   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
148       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
149     dst_rect2 = &dst_rect_screen;
150   else
151     dst_rect1 = &dst_rect_screen;
152
153 #if defined(HAS_SCREEN_KEYBOARD)
154   if (video.shifted_up || video.shifted_up_delay)
155   {
156     int time_current = SDL_GetTicks();
157     int pos = video.shifted_up_pos;
158     int pos_last = video.shifted_up_pos_last;
159
160     if (!DelayReachedExt(&video.shifted_up_delay, video.shifted_up_delay_value,
161                          time_current))
162     {
163       int delay = time_current - video.shifted_up_delay;
164       int delay_value = video.shifted_up_delay_value;
165
166       pos = pos_last + (pos - pos_last) * delay / delay_value;
167     }
168     else
169     {
170       video.shifted_up_pos_last = pos;
171       video.shifted_up_delay = 0;
172     }
173
174     SDL_Rect src_rect_up = { 0,    pos,  video.width, video.height - pos };
175     SDL_Rect dst_rect_up = { xoff, yoff, video.width, video.height - pos };
176
177     if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
178         video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
179     {
180       src_rect2 = &src_rect_up;
181       dst_rect2 = &dst_rect_up;
182     }
183     else
184     {
185       src_rect1 = &src_rect_up;
186       dst_rect1 = &dst_rect_up;
187     }
188   }
189 #endif
190
191   // clear render target buffer
192   SDL_RenderClear(sdl_renderer);
193
194   // set renderer to use target texture for rendering
195   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
196       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
197     SDL_SetRenderTarget(sdl_renderer, sdl_texture_target);
198
199   // copy backbuffer texture to render target buffer
200   if (video.screen_rendering_mode != SPECIAL_RENDERING_TARGET)
201     SDL_RenderCopy(sdl_renderer, sdl_texture_stream, src_rect1, dst_rect1);
202
203   if (video.screen_rendering_mode != SPECIAL_RENDERING_BITMAP)
204     FinalizeScreen(DRAW_TO_SCREEN);
205
206   // when using target texture, copy it to screen buffer
207   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
208       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
209   {
210     SDL_SetRenderTarget(sdl_renderer, NULL);
211     SDL_RenderCopy(sdl_renderer, sdl_texture_target, src_rect2, dst_rect2);
212   }
213
214 #if defined(USE_TOUCH_INPUT_OVERLAY)
215   // draw overlay graphics for touch device input, if needed
216   DrawTouchInputOverlay();
217
218   // draw overlay gadgets for touch device input, if needed
219   DrawTouchGadgetsOverlay();
220 #endif
221
222   // global synchronization point of the game to align video frame delay
223   if (with_frame_delay)
224     WaitUntilDelayReached(&video.frame_delay, video.frame_delay_value);
225
226   video.frame_counter++;
227
228   // show render target buffer on screen
229   SDL_RenderPresent(sdl_renderer);
230 }
231
232 static void UpdateScreen_WithFrameDelay(SDL_Rect *rect)
233 {
234   PumpEvents();         // execute event filter actions while waiting
235
236   UpdateScreenExt(rect, TRUE);
237 }
238
239 static void UpdateScreen_WithoutFrameDelay(SDL_Rect *rect)
240 {
241   UpdateScreenExt(rect, FALSE);
242 }
243
244 void Delay_WithScreenUpdates(unsigned int delay)
245 {
246   unsigned int time_current = SDL_GetTicks();
247   unsigned int time_delayed = time_current + delay;
248
249   while (time_current < time_delayed)
250   {
251     // updating the screen contains waiting for frame delay (non-busy)
252     UpdateScreen_WithFrameDelay(NULL);
253
254     time_current = SDL_GetTicks();
255   }
256 }
257
258 static void SDLSetWindowIcon(char *basename)
259 {
260   // (setting the window icon on Mac OS X would replace the high-quality
261   // dock icon with the currently smaller (and uglier) icon from file)
262
263 #if !defined(PLATFORM_MACOSX)
264   char *filename = getCustomImageFilename(basename);
265   SDL_Surface *surface;
266
267   if (filename == NULL)
268   {
269     Error(ERR_WARN, "SDLSetWindowIcon(): cannot find file '%s'", basename);
270
271     return;
272   }
273
274   if ((surface = IMG_Load(filename)) == NULL)
275   {
276     Error(ERR_WARN, "IMG_Load('%s') failed: %s", basename, SDL_GetError());
277
278     return;
279   }
280
281   // set transparent color
282   SDL_SetColorKey(surface, SET_TRANSPARENT_PIXEL,
283                   SDL_MapRGB(surface->format, 0x00, 0x00, 0x00));
284
285   SDL_SetWindowIcon(sdl_window, surface);
286 #endif
287 }
288
289 static boolean equalSDLPixelFormat(SDL_PixelFormat *format1,
290                                    SDL_PixelFormat *format2)
291 {
292   return (format1->format        == format2->format &&
293           format1->BitsPerPixel  == format2->BitsPerPixel &&
294           format1->BytesPerPixel == format2->BytesPerPixel &&
295           format1->Rmask         == format2->Rmask &&
296           format1->Gmask         == format2->Gmask &&
297           format1->Bmask         == format2->Bmask);
298 }
299
300 static Pixel SDLGetColorKey(SDL_Surface *surface)
301 {
302   Pixel color_key;
303
304   if (SDL_GetColorKey(surface, &color_key) != 0)
305     return -1;
306
307   return color_key;
308 }
309
310 static boolean SDLHasColorKey(SDL_Surface *surface)
311 {
312   return (SDLGetColorKey(surface) != -1);
313 }
314
315 static boolean SDLHasAlpha(SDL_Surface *surface)
316 {
317   SDL_BlendMode blend_mode;
318
319   if (SDL_GetSurfaceBlendMode(surface, &blend_mode) != 0)
320     return FALSE;
321
322   return (blend_mode == SDL_BLENDMODE_BLEND);
323 }
324
325 static void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha)
326 {
327   SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
328
329   SDL_SetSurfaceBlendMode(surface, blend_mode);
330   SDL_SetSurfaceAlphaMod(surface, alpha);
331 }
332
333 const char *SDLGetRendererName(void)
334 {
335   static SDL_RendererInfo renderer_info;
336
337   SDL_GetRendererInfo(sdl_renderer, &renderer_info);
338
339   return renderer_info.name;
340 }
341
342 SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
343 {
344   SDL_PixelFormat format;
345   SDL_Surface *new_surface;
346
347   if (surface == NULL)
348     return NULL;
349
350   if (backbuffer && backbuffer->surface)
351   {
352     format = *backbuffer->surface->format;
353     format.Amask = surface->format->Amask;      // keep alpha channel
354   }
355   else
356   {
357     format = *surface->format;
358   }
359
360   new_surface = SDL_ConvertSurface(surface, &format, 0);
361
362   if (new_surface == NULL)
363     Error(ERR_EXIT, "SDL_ConvertSurface() failed: %s", SDL_GetError());
364
365   // workaround for a bug in SDL 2.0.12 (which does not convert the color key)
366   if (SDLHasColorKey(surface) && !SDLHasColorKey(new_surface))
367     SDL_SetColorKey(new_surface, SET_TRANSPARENT_PIXEL,
368                     SDLGetColorKey(surface));
369
370   return new_surface;
371 }
372
373 boolean SDLSetNativeSurface(SDL_Surface **surface)
374 {
375   SDL_Surface *new_surface;
376
377   if (surface == NULL ||
378       *surface == NULL ||
379       backbuffer == NULL ||
380       backbuffer->surface == NULL)
381     return FALSE;
382
383   // if pixel format already optimized for destination surface, do nothing
384   if (equalSDLPixelFormat((*surface)->format, backbuffer->surface->format))
385     return FALSE;
386
387   new_surface = SDLGetNativeSurface(*surface);
388
389   SDL_FreeSurface(*surface);
390
391   *surface = new_surface;
392
393   return TRUE;
394 }
395
396 static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
397 {
398   if (program.headless)
399     return NULL;
400
401   SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
402
403   if (texture == NULL)
404     Error(ERR_EXIT, "SDL_CreateTextureFromSurface() failed: %s",
405           SDL_GetError());
406
407   return texture;
408 }
409
410 void SDLCreateBitmapTextures(Bitmap *bitmap)
411 {
412   if (bitmap == NULL)
413     return;
414
415   if (bitmap->texture)
416     SDL_DestroyTexture(bitmap->texture);
417   if (bitmap->texture_masked)
418     SDL_DestroyTexture(bitmap->texture_masked);
419
420   bitmap->texture        = SDLCreateTextureFromSurface(bitmap->surface);
421   bitmap->texture_masked = SDLCreateTextureFromSurface(bitmap->surface_masked);
422 }
423
424 void SDLFreeBitmapTextures(Bitmap *bitmap)
425 {
426   if (bitmap == NULL)
427     return;
428
429   if (bitmap->texture)
430     SDL_DestroyTexture(bitmap->texture);
431   if (bitmap->texture_masked)
432     SDL_DestroyTexture(bitmap->texture_masked);
433
434   bitmap->texture = NULL;
435   bitmap->texture_masked = NULL;
436 }
437
438 void SDLInitVideoDisplay(void)
439 {
440   // set hint to select render driver as specified in setup config file
441   if (!strEqual(setup.system.sdl_renderdriver, ARG_DEFAULT))
442     SDL_SetHint(SDL_HINT_RENDER_DRIVER, setup.system.sdl_renderdriver);
443
444   // initialize SDL video
445   if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
446     Error(ERR_EXIT, "SDL_InitSubSystem() failed: %s", SDL_GetError());
447
448   // set default SDL depth
449   video.default_depth = 32;     // (how to determine video depth in SDL2?)
450   //
451   // Code used with SDL 1.2:
452   // video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
453 }
454
455 static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
456 {
457   if (program.headless)
458     return;
459
460   video.window_scaling_percent = setup.window_scaling_percent;
461   video.window_scaling_quality = setup.window_scaling_quality;
462
463   SDLSetScreenRenderingMode(setup.screen_rendering_mode);
464
465   // SDL 2.0: support for (desktop) fullscreen mode available
466   video.fullscreen_available = TRUE;
467
468   // open SDL video output device (window or fullscreen mode)
469   if (!SDLSetVideoMode(fullscreen))
470     Error(ERR_EXIT, "setting video mode failed");
471
472   // !!! SDL2 can only set the window icon if the window already exists !!!
473   // set window icon
474   SDLSetWindowIcon(program.icon_filename);
475
476   // set window and icon title
477   SDLSetWindowTitle();
478 }
479
480 static void SDLInitVideoBuffer_DrawBuffer(void)
481 {
482   /* SDL cannot directly draw to the visible video framebuffer like X11,
483      but always uses a backbuffer, which is then blitted to the visible
484      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
485      visible video framebuffer with 'SDL_Flip', if the hardware supports
486      this). Therefore do not use an additional backbuffer for drawing, but
487      use a symbolic buffer (distinguishable from the SDL backbuffer) called
488      'window', which indicates that the SDL backbuffer should be updated to
489      the visible video framebuffer when attempting to blit to it.
490
491      For convenience, it seems to be a good idea to create this symbolic
492      buffer 'window' at the same size as the SDL backbuffer. Although it
493      should never be drawn to directly, it would do no harm nevertheless. */
494
495   // create additional (symbolic) buffer for double-buffering
496   ReCreateBitmap(&window, video.width, video.height);
497
498   // create dummy drawing buffer for headless mode, if needed
499   if (program.headless)
500     ReCreateBitmap(&backbuffer, video.width, video.height);
501 }
502
503 void SDLInitVideoBuffer(boolean fullscreen)
504 {
505   SDLInitVideoBuffer_VideoBuffer(fullscreen);
506   SDLInitVideoBuffer_DrawBuffer();
507 }
508
509 static boolean SDLCreateScreen(boolean fullscreen)
510 {
511   SDL_Surface *new_surface = NULL;
512
513   int surface_flags_window     = SURFACE_FLAGS;
514   int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN_DESKTOP;
515
516 #if 1
517   int renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
518
519   video.vsync_mode = VSYNC_MODE_OFF;
520
521   if (!strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF))
522   {
523     renderer_flags |= SDL_RENDERER_PRESENTVSYNC;
524     video.vsync_mode = VSYNC_MODE_NORMAL;
525   }
526 #else
527   /* If SDL_CreateRenderer() is called from within a VirtualBox Windows VM
528      _without_ enabling 2D/3D acceleration and/or guest additions installed,
529      it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
530      it will try to use accelerated graphics and apparently fails miserably) */
531   int renderer_flags = SDL_RENDERER_SOFTWARE;
532 #endif
533
534   int width  = video.width;
535   int height = video.height;
536   int screen_width  = video.screen_width;
537   int screen_height = video.screen_height;
538   int surface_flags = (fullscreen ? surface_flags_fullscreen :
539                        surface_flags_window);
540
541   // default window size is unscaled
542   video.window_width  = screen_width;
543   video.window_height = screen_height;
544
545   // store if initial screen mode is fullscreen mode when changing screen size
546   video.fullscreen_initial = fullscreen;
547
548   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
549
550   video.window_width  = window_scaling_factor * screen_width;
551   video.window_height = window_scaling_factor * screen_height;
552
553   if (sdl_texture_stream)
554   {
555     SDL_DestroyTexture(sdl_texture_stream);
556     sdl_texture_stream = NULL;
557   }
558
559   if (sdl_texture_target)
560   {
561     SDL_DestroyTexture(sdl_texture_target);
562     sdl_texture_target = NULL;
563   }
564
565   if (!(fullscreen && fullscreen_enabled))
566   {
567     if (sdl_renderer)
568     {
569       SDL_DestroyRenderer(sdl_renderer);
570       sdl_renderer = NULL;
571     }
572
573     if (sdl_window)
574     {
575       SDL_DestroyWindow(sdl_window);
576       sdl_window = NULL;
577     }
578   }
579
580   if (sdl_window == NULL)
581     sdl_window = SDL_CreateWindow(program.window_title,
582                                   SDL_WINDOWPOS_CENTERED,
583                                   SDL_WINDOWPOS_CENTERED,
584                                   video.window_width,
585                                   video.window_height,
586                                   surface_flags);
587
588   if (sdl_window != NULL)
589   {
590     if (sdl_renderer == NULL)
591       sdl_renderer = SDL_CreateRenderer(sdl_window, -1, renderer_flags);
592
593     if (sdl_renderer != NULL)
594     {
595       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
596       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
597
598       // required for setting adaptive vsync when using OpenGL renderer
599       SDLSetScreenVsyncMode(setup.vsync_mode);
600
601       sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
602                                              SDL_PIXELFORMAT_ARGB8888,
603                                              SDL_TEXTUREACCESS_STREAMING,
604                                              width, height);
605
606       if (SDL_RenderTargetSupported(sdl_renderer))
607         sdl_texture_target = SDL_CreateTexture(sdl_renderer,
608                                                SDL_PIXELFORMAT_ARGB8888,
609                                                SDL_TEXTUREACCESS_TARGET,
610                                                width, height);
611
612       if (sdl_texture_stream != NULL)
613       {
614         // use SDL default values for RGB masks and no alpha channel
615         new_surface = SDL_CreateRGBSurface(0, width, height, 32, 0,0,0, 0);
616
617         if (new_surface == NULL)
618           Error(ERR_WARN, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
619       }
620       else
621       {
622         Error(ERR_WARN, "SDL_CreateTexture() failed: %s", SDL_GetError());
623       }
624     }
625     else
626     {
627       Error(ERR_WARN, "SDL_CreateRenderer() failed: %s", SDL_GetError());
628     }
629   }
630   else
631   {
632     Error(ERR_WARN, "SDL_CreateWindow() failed: %s", SDL_GetError());
633   }
634
635   SDLSetScreenProperties();
636
637   // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
638   if (new_surface != NULL)
639     fullscreen_enabled = fullscreen;
640
641   if (backbuffer == NULL)
642     backbuffer = CreateBitmapStruct();
643
644   backbuffer->width  = video.width;
645   backbuffer->height = video.height;
646
647   if (backbuffer->surface)
648     SDL_FreeSurface(backbuffer->surface);
649
650   backbuffer->surface = new_surface;
651
652   return (new_surface != NULL);
653 }
654
655 boolean SDLSetVideoMode(boolean fullscreen)
656 {
657   boolean success = FALSE;
658
659   SetWindowTitle();
660
661   if (fullscreen && !video.fullscreen_enabled && video.fullscreen_available)
662   {
663     // switch display to fullscreen mode, if available
664     success = SDLCreateScreen(TRUE);
665
666     if (!success)
667     {
668       // switching display to fullscreen mode failed -- do not try it again
669       video.fullscreen_available = FALSE;
670     }
671     else
672     {
673       video.fullscreen_enabled = TRUE;
674     }
675   }
676
677   if ((!fullscreen && video.fullscreen_enabled) || !success)
678   {
679     // switch display to window mode
680     success = SDLCreateScreen(FALSE);
681
682     if (!success)
683     {
684       // switching display to window mode failed -- should not happen
685     }
686     else
687     {
688       video.fullscreen_enabled = FALSE;
689       video.window_scaling_percent = setup.window_scaling_percent;
690       video.window_scaling_quality = setup.window_scaling_quality;
691
692       SDLSetScreenRenderingMode(setup.screen_rendering_mode);
693     }
694   }
695
696   SDLRedrawWindow();                    // map window
697
698   return success;
699 }
700
701 void SDLSetWindowTitle(void)
702 {
703   if (sdl_window == NULL)
704     return;
705
706   SDL_SetWindowTitle(sdl_window, program.window_title);
707 }
708
709 void SDLSetWindowScaling(int window_scaling_percent)
710 {
711   if (sdl_window == NULL)
712     return;
713
714   float window_scaling_factor = (float)window_scaling_percent / 100;
715   int new_window_width  = (int)(window_scaling_factor * video.screen_width);
716   int new_window_height = (int)(window_scaling_factor * video.screen_height);
717
718   SDL_SetWindowSize(sdl_window, new_window_width, new_window_height);
719
720   video.window_scaling_percent = window_scaling_percent;
721   video.window_width  = new_window_width;
722   video.window_height = new_window_height;
723
724   SetWindowTitle();
725 }
726
727 void SDLSetWindowScalingQuality(char *window_scaling_quality)
728 {
729   SDL_Texture *new_texture;
730
731   if (sdl_texture_stream == NULL)
732     return;
733
734   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, window_scaling_quality);
735
736   new_texture = SDL_CreateTexture(sdl_renderer,
737                                   SDL_PIXELFORMAT_ARGB8888,
738                                   SDL_TEXTUREACCESS_STREAMING,
739                                   video.width, video.height);
740
741   if (new_texture != NULL)
742   {
743     SDL_DestroyTexture(sdl_texture_stream);
744
745     sdl_texture_stream = new_texture;
746   }
747
748   if (SDL_RenderTargetSupported(sdl_renderer))
749     new_texture = SDL_CreateTexture(sdl_renderer,
750                                     SDL_PIXELFORMAT_ARGB8888,
751                                     SDL_TEXTUREACCESS_TARGET,
752                                     video.width, video.height);
753   else
754     new_texture = NULL;
755
756   if (new_texture != NULL)
757   {
758     SDL_DestroyTexture(sdl_texture_target);
759
760     sdl_texture_target = new_texture;
761   }
762
763   SDLRedrawWindow();
764
765   video.window_scaling_quality = window_scaling_quality;
766 }
767
768 void SDLSetWindowFullscreen(boolean fullscreen)
769 {
770   if (sdl_window == NULL)
771     return;
772
773   int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
774
775   if (SDL_SetWindowFullscreen(sdl_window, flags) == 0)
776     video.fullscreen_enabled = fullscreen_enabled = fullscreen;
777
778   // if screen size was changed in fullscreen mode, correct desktop window size
779   if (!fullscreen && video.fullscreen_initial)
780   {
781     SDLSetWindowScaling(setup.window_scaling_percent);
782     SDL_SetWindowPosition(sdl_window,
783                           SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
784
785     video.fullscreen_initial = FALSE;
786   }
787 }
788
789 void SDLSetDisplaySize(void)
790 {
791   if (sdl_renderer != NULL)
792   {
793     int w, h;
794
795     SDL_GetRendererOutputSize(sdl_renderer, &w, &h);
796
797     video.display_width  = w;
798     video.display_height = h;
799
800 #if 0
801     Error(ERR_DEBUG, "SDL renderer size: %d x %d",
802           video.display_width, video.display_height);
803 #endif
804   }
805   else
806   {
807     SDL_Rect display_bounds;
808
809     SDL_GetDisplayBounds(0, &display_bounds);
810
811     video.display_width  = display_bounds.w;
812     video.display_height = display_bounds.h;
813
814 #if 0
815     Error(ERR_DEBUG, "SDL display size: %d x %d",
816           video.display_width, video.display_height);
817 #endif
818   }
819 }
820
821 void SDLSetScreenSizeAndOffsets(int width, int height)
822 {
823   // set default video screen size and offsets
824   video.screen_width = width;
825   video.screen_height = height;
826   video.screen_xoffset = 0;
827   video.screen_yoffset = 0;
828
829 #if defined(USE_COMPLETE_DISPLAY)
830   float ratio_video   = (float) width / height;
831   float ratio_display = (float) video.display_width / video.display_height;
832
833   if (ratio_video != ratio_display)
834   {
835     // adjust drawable screen size to cover the whole device display
836
837     if (ratio_video < ratio_display)
838       video.screen_width  *= ratio_display / ratio_video;
839     else
840       video.screen_height *= ratio_video / ratio_display;
841
842     video.screen_xoffset = (video.screen_width  - width)  / 2;
843     video.screen_yoffset = (video.screen_height - height) / 2;
844
845 #if 0
846     Error(ERR_DEBUG, "Changing screen from %dx%d to %dx%d (%.2f to %.2f)",
847           width, height,
848           video.screen_width, video.screen_height,
849           ratio_video, ratio_display);
850 #endif
851   }
852 #endif
853 }
854
855 void SDLSetScreenSizeForRenderer(int width, int height)
856 {
857   SDL_RenderSetLogicalSize(sdl_renderer, width, height);
858 }
859
860 void SDLSetScreenProperties(void)
861 {
862   SDLSetDisplaySize();
863   SDLSetScreenSizeAndOffsets(video.width, video.height);
864   SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
865 }
866
867 void SDLSetScreenRenderingMode(char *screen_rendering_mode)
868 {
869   video.screen_rendering_mode =
870     (strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_BITMAP) ?
871      SPECIAL_RENDERING_BITMAP :
872      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_TARGET) ?
873      SPECIAL_RENDERING_TARGET:
874      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_DOUBLE) ?
875      SPECIAL_RENDERING_DOUBLE : SPECIAL_RENDERING_OFF);
876 }
877
878 void SDLSetScreenVsyncMode(char *vsync_mode)
879 {
880   // changing vsync mode without re-creating renderer only supported by OpenGL
881   if (!strPrefixLower((char *)SDLGetRendererName(), "opengl"))
882     return;
883
884   int interval = VSYNC_MODE_STR_TO_INT(vsync_mode);
885   int result = SDL_GL_SetSwapInterval(interval);
886
887   // if adaptive vsync requested, but not supported, retry with normal vsync
888   if (result == -1 && interval == VSYNC_MODE_ADAPTIVE)
889   {
890     interval = VSYNC_MODE_NORMAL;
891
892     result = SDL_GL_SetSwapInterval(interval);
893   }
894
895   if (result == -1)
896     interval = VSYNC_MODE_OFF;
897
898   video.vsync_mode = interval;
899 }
900
901 void SDLRedrawWindow(void)
902 {
903   UpdateScreen_WithoutFrameDelay(NULL);
904 }
905
906 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
907                             int depth)
908 {
909   if (program.headless)
910     return;
911
912   SDL_Surface *surface =
913     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
914
915   if (surface == NULL)
916     Error(ERR_EXIT, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
917
918   SDLSetNativeSurface(&surface);
919
920   bitmap->surface = surface;
921 }
922
923 void SDLFreeBitmapPointers(Bitmap *bitmap)
924 {
925   if (bitmap->surface)
926     SDL_FreeSurface(bitmap->surface);
927   if (bitmap->surface_masked)
928     SDL_FreeSurface(bitmap->surface_masked);
929
930   bitmap->surface = NULL;
931   bitmap->surface_masked = NULL;
932
933   if (bitmap->texture)
934     SDL_DestroyTexture(bitmap->texture);
935   if (bitmap->texture_masked)
936     SDL_DestroyTexture(bitmap->texture_masked);
937
938   bitmap->texture = NULL;
939   bitmap->texture_masked = NULL;
940 }
941
942 void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
943                  int src_x, int src_y, int width, int height,
944                  int dst_x, int dst_y, int mask_mode)
945 {
946   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
947   SDL_Rect src_rect, dst_rect;
948
949   src_rect.x = src_x;
950   src_rect.y = src_y;
951   src_rect.w = width;
952   src_rect.h = height;
953
954   dst_rect.x = dst_x;
955   dst_rect.y = dst_y;
956   dst_rect.w = width;
957   dst_rect.h = height;
958
959   // if (src_bitmap != backbuffer || dst_bitmap != window)
960   if (!(src_bitmap == backbuffer && dst_bitmap == window))
961     SDL_BlitSurface((mask_mode == BLIT_MASKED ?
962                      src_bitmap->surface_masked : src_bitmap->surface),
963                     &src_rect, real_dst_bitmap->surface, &dst_rect);
964
965   if (dst_bitmap == window)
966     UpdateScreen_WithFrameDelay(&dst_rect);
967 }
968
969 void SDLBlitTexture(Bitmap *bitmap,
970                     int src_x, int src_y, int width, int height,
971                     int dst_x, int dst_y, int mask_mode)
972 {
973   SDL_Texture *texture;
974   SDL_Rect src_rect;
975   SDL_Rect dst_rect;
976
977   texture =
978     (mask_mode == BLIT_MASKED ? bitmap->texture_masked : bitmap->texture);
979
980   if (texture == NULL)
981     return;
982
983   src_rect.x = src_x;
984   src_rect.y = src_y;
985   src_rect.w = width;
986   src_rect.h = height;
987
988   dst_rect.x = dst_x;
989   dst_rect.y = dst_y;
990   dst_rect.w = width;
991   dst_rect.h = height;
992
993   SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
994 }
995
996 void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
997                       Uint32 color)
998 {
999   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
1000   SDL_Rect rect;
1001
1002   rect.x = x;
1003   rect.y = y;
1004   rect.w = width;
1005   rect.h = height;
1006
1007   SDL_FillRect(real_dst_bitmap->surface, &rect, color);
1008
1009   if (dst_bitmap == window)
1010     UpdateScreen_WithFrameDelay(&rect);
1011 }
1012
1013 void PrepareFadeBitmap(int draw_target)
1014 {
1015   Bitmap *fade_bitmap =
1016     (draw_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source :
1017      draw_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL);
1018
1019   if (fade_bitmap == NULL)
1020     return;
1021
1022   // copy backbuffer to fading buffer
1023   BlitBitmap(backbuffer, fade_bitmap, 0, 0, gfx.win_xsize, gfx.win_ysize, 0, 0);
1024
1025   // add border and animations to fading buffer
1026   FinalizeScreen(draw_target);
1027 }
1028
1029 void SDLFadeRectangle(int x, int y, int width, int height,
1030                       int fade_mode, int fade_delay, int post_delay,
1031                       void (*draw_border_function)(void))
1032 {
1033   SDL_Surface *surface_backup = gfx.fade_bitmap_backup->surface;
1034   SDL_Surface *surface_source = gfx.fade_bitmap_source->surface;
1035   SDL_Surface *surface_target = gfx.fade_bitmap_target->surface;
1036   SDL_Surface *surface_black  = gfx.fade_bitmap_black->surface;
1037   SDL_Surface *surface_screen = backbuffer->surface;
1038   SDL_Rect src_rect, dst_rect;
1039   SDL_Rect dst_rect2;
1040   int src_x = x, src_y = y;
1041   int dst_x = x, dst_y = y;
1042   unsigned int time_last, time_current;
1043
1044   // store function for drawing global masked border
1045   void (*draw_global_border_function)(int) = gfx.draw_global_border_function;
1046
1047   // deactivate drawing of global border while fading, if needed
1048   if (draw_border_function == NULL)
1049     gfx.draw_global_border_function = NULL;
1050
1051   src_rect.x = src_x;
1052   src_rect.y = src_y;
1053   src_rect.w = width;
1054   src_rect.h = height;
1055
1056   dst_rect.x = dst_x;
1057   dst_rect.y = dst_y;
1058   dst_rect.w = width;           // (ignored)
1059   dst_rect.h = height;          // (ignored)
1060
1061   dst_rect2 = dst_rect;
1062
1063   // before fading in, store backbuffer (without animation graphics)
1064   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1065     SDL_BlitSurface(surface_screen, &dst_rect, surface_backup, &src_rect);
1066
1067   // copy source and target surfaces to temporary surfaces for fading
1068   if (fade_mode & FADE_TYPE_TRANSFORM)
1069   {
1070     // (source and target fading buffer already prepared)
1071   }
1072   else if (fade_mode & FADE_TYPE_FADE_IN)
1073   {
1074     // (target fading buffer already prepared)
1075     SDL_BlitSurface(surface_black,  &src_rect, surface_source, &src_rect);
1076   }
1077   else          // FADE_TYPE_FADE_OUT
1078   {
1079     // (source fading buffer already prepared)
1080     SDL_BlitSurface(surface_black,  &src_rect, surface_target, &src_rect);
1081   }
1082
1083   time_current = SDL_GetTicks();
1084
1085   if (fade_delay <= 0)
1086   {
1087     // immediately draw final target frame without delay
1088     fade_mode &= (FADE_MODE_FADE | FADE_MODE_TRANSFORM);
1089     fade_delay = 1;
1090     time_current -= 1;
1091
1092     // when fading without delay, also skip post delay
1093     post_delay = 0;
1094   }
1095
1096   if (fade_mode == FADE_MODE_MELT)
1097   {
1098     boolean done = FALSE;
1099     int melt_pixels = 2;
1100     int melt_columns = width / melt_pixels;
1101     int ypos[melt_columns];
1102     int max_steps = height / 8 + 32;
1103     int steps_done = 0;
1104     float steps = 0;
1105     int i;
1106
1107     SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1108
1109     SDLSetAlpha(surface_target, FALSE, 0);      // disable alpha blending
1110
1111     ypos[0] = -GetSimpleRandom(16);
1112
1113     for (i = 1 ; i < melt_columns; i++)
1114     {
1115       int r = GetSimpleRandom(3) - 1;   // randomly choose from { -1, 0, -1 }
1116
1117       ypos[i] = ypos[i - 1] + r;
1118
1119       if (ypos[i] > 0)
1120         ypos[i] = 0;
1121       else
1122         if (ypos[i] == -16)
1123           ypos[i] = -15;
1124     }
1125
1126     while (!done)
1127     {
1128       int steps_final;
1129
1130       time_last = time_current;
1131       time_current = SDL_GetTicks();
1132       steps += max_steps * ((float)(time_current - time_last) / fade_delay);
1133       steps_final = MIN(MAX(0, steps), max_steps);
1134
1135       steps_done++;
1136
1137       done = (steps_done >= steps_final);
1138
1139       for (i = 0 ; i < melt_columns; i++)
1140       {
1141         if (ypos[i] < 0)
1142         {
1143           ypos[i]++;
1144
1145           done = FALSE;
1146         }
1147         else if (ypos[i] < height)
1148         {
1149           int y1 = 16;
1150           int y2 = 8;
1151           int y3 = 8;
1152           int dy = (ypos[i] < y1) ? ypos[i] + 1 : y2 + GetSimpleRandom(y3);
1153
1154           if (ypos[i] + dy >= height)
1155             dy = height - ypos[i];
1156
1157           // copy part of (appearing) target surface to upper area
1158           src_rect.x = src_x + i * melt_pixels;
1159           // src_rect.y = src_y + ypos[i];
1160           src_rect.y = src_y;
1161           src_rect.w = melt_pixels;
1162           // src_rect.h = dy;
1163           src_rect.h = ypos[i] + dy;
1164
1165           dst_rect.x = dst_x + i * melt_pixels;
1166           // dst_rect.y = dst_y + ypos[i];
1167           dst_rect.y = dst_y;
1168
1169           if (steps_done >= steps_final)
1170             SDL_BlitSurface(surface_target, &src_rect,
1171                             surface_screen, &dst_rect);
1172
1173           ypos[i] += dy;
1174
1175           // copy part of (disappearing) source surface to lower area
1176           src_rect.x = src_x + i * melt_pixels;
1177           src_rect.y = src_y;
1178           src_rect.w = melt_pixels;
1179           src_rect.h = height - ypos[i];
1180
1181           dst_rect.x = dst_x + i * melt_pixels;
1182           dst_rect.y = dst_y + ypos[i];
1183
1184           if (steps_done >= steps_final)
1185             SDL_BlitSurface(surface_source, &src_rect,
1186                             surface_screen, &dst_rect);
1187
1188           done = FALSE;
1189         }
1190         else
1191         {
1192           src_rect.x = src_x + i * melt_pixels;
1193           src_rect.y = src_y;
1194           src_rect.w = melt_pixels;
1195           src_rect.h = height;
1196
1197           dst_rect.x = dst_x + i * melt_pixels;
1198           dst_rect.y = dst_y;
1199
1200           if (steps_done >= steps_final)
1201             SDL_BlitSurface(surface_target, &src_rect,
1202                             surface_screen, &dst_rect);
1203         }
1204       }
1205
1206       if (steps_done >= steps_final)
1207       {
1208         if (draw_border_function != NULL)
1209           draw_border_function();
1210
1211         UpdateScreen_WithFrameDelay(&dst_rect2);
1212       }
1213     }
1214   }
1215   else if (fade_mode == FADE_MODE_CURTAIN)
1216   {
1217     float xx;
1218     int xx_final;
1219     int xx_size = width / 2;
1220
1221     SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1222
1223     SDLSetAlpha(surface_source, FALSE, 0);      // disable alpha blending
1224
1225     for (xx = 0; xx < xx_size;)
1226     {
1227       time_last = time_current;
1228       time_current = SDL_GetTicks();
1229       xx += xx_size * ((float)(time_current - time_last) / fade_delay);
1230       xx_final = MIN(MAX(0, xx), xx_size);
1231
1232       src_rect.x = src_x;
1233       src_rect.y = src_y;
1234       src_rect.w = width;
1235       src_rect.h = height;
1236
1237       dst_rect.x = dst_x;
1238       dst_rect.y = dst_y;
1239
1240       // draw new (target) image to screen buffer
1241       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1242
1243       if (xx_final < xx_size)
1244       {
1245         src_rect.w = xx_size - xx_final;
1246         src_rect.h = height;
1247
1248         // draw old (source) image to screen buffer (left side)
1249
1250         src_rect.x = src_x + xx_final;
1251         dst_rect.x = dst_x;
1252
1253         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1254
1255         // draw old (source) image to screen buffer (right side)
1256
1257         src_rect.x = src_x + xx_size;
1258         dst_rect.x = dst_x + xx_size + xx_final;
1259
1260         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1261       }
1262
1263       if (draw_border_function != NULL)
1264         draw_border_function();
1265
1266       // only update the region of the screen that is affected from fading
1267       UpdateScreen_WithFrameDelay(&dst_rect2);
1268     }
1269   }
1270   else          // fading in, fading out or cross-fading
1271   {
1272     float alpha;
1273     int alpha_final;
1274
1275     for (alpha = 0.0; alpha < 255.0;)
1276     {
1277       time_last = time_current;
1278       time_current = SDL_GetTicks();
1279       alpha += 255 * ((float)(time_current - time_last) / fade_delay);
1280       alpha_final = MIN(MAX(0, alpha), 255);
1281
1282       // draw existing (source) image to screen buffer
1283       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1284
1285       // draw new (target) image to screen buffer using alpha blending
1286       SDLSetAlpha(surface_target, TRUE, alpha_final);
1287       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1288
1289       if (draw_border_function != NULL)
1290         draw_border_function();
1291
1292       // only update the region of the screen that is affected from fading
1293       UpdateScreen_WithFrameDelay(&dst_rect);
1294     }
1295   }
1296
1297   if (post_delay > 0)
1298     Delay_WithScreenUpdates(post_delay);
1299
1300   // restore function for drawing global masked border
1301   gfx.draw_global_border_function = draw_global_border_function;
1302
1303   // after fading in, restore backbuffer (without animation graphics)
1304   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1305     SDL_BlitSurface(surface_backup, &dst_rect, surface_screen, &src_rect);
1306 }
1307
1308 void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
1309                        int to_x, int to_y, Uint32 color)
1310 {
1311   SDL_Surface *surface = dst_bitmap->surface;
1312   SDL_Rect rect;
1313
1314   if (from_x > to_x)
1315     swap_numbers(&from_x, &to_x);
1316
1317   if (from_y > to_y)
1318     swap_numbers(&from_y, &to_y);
1319
1320   rect.x = from_x;
1321   rect.y = from_y;
1322   rect.w = (to_x - from_x + 1);
1323   rect.h = (to_y - from_y + 1);
1324
1325   SDL_FillRect(surface, &rect, color);
1326 }
1327
1328 void SDLDrawLine(Bitmap *dst_bitmap, int from_x, int from_y,
1329                  int to_x, int to_y, Uint32 color)
1330 {
1331   sge_Line(dst_bitmap->surface, from_x, from_y, to_x, to_y, color);
1332 }
1333
1334 #if ENABLE_UNUSED_CODE
1335 void SDLDrawLines(SDL_Surface *surface, struct XY *points,
1336                   int num_points, Uint32 color)
1337 {
1338   int i, x, y;
1339   int line_width = 4;
1340
1341   for (i = 0; i < num_points - 1; i++)
1342   {
1343     for (x = 0; x < line_width; x++)
1344     {
1345       for (y = 0; y < line_width; y++)
1346       {
1347         int dx = x - line_width / 2;
1348         int dy = y - line_width / 2;
1349
1350         if ((x == 0 && y == 0) ||
1351             (x == 0 && y == line_width - 1) ||
1352             (x == line_width - 1 && y == 0) ||
1353             (x == line_width - 1 && y == line_width - 1))
1354           continue;
1355
1356         sge_Line(surface, points[i].x + dx, points[i].y + dy,
1357                  points[i+1].x + dx, points[i+1].y + dy, color);
1358       }
1359     }
1360   }
1361 }
1362 #endif
1363
1364 Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
1365 {
1366   SDL_Surface *surface = src_bitmap->surface;
1367
1368   switch (surface->format->BytesPerPixel)
1369   {
1370     case 1:             // assuming 8-bpp
1371     {
1372       return *((Uint8 *)surface->pixels + y * surface->pitch + x);
1373     }
1374     break;
1375
1376     case 2:             // probably 15-bpp or 16-bpp
1377     {
1378       return *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x);
1379     }
1380     break;
1381
1382   case 3:               // slow 24-bpp mode; usually not used
1383     {
1384       // does this work?
1385       Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1386       Uint32 color = 0;
1387       int shift;
1388
1389       shift = surface->format->Rshift;
1390       color |= *(pix + shift / 8) >> shift;
1391       shift = surface->format->Gshift;
1392       color |= *(pix + shift / 8) >> shift;
1393       shift = surface->format->Bshift;
1394       color |= *(pix + shift / 8) >> shift;
1395
1396       return color;
1397     }
1398     break;
1399
1400   case 4:               // probably 32-bpp
1401     {
1402       return *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x);
1403     }
1404     break;
1405   }
1406
1407   return 0;
1408 }
1409
1410
1411 // ============================================================================
1412 // The following functions were taken from the SGE library
1413 // (SDL Graphics Extension Library) by Anders Lindström
1414 // http://www.etek.chalmers.se/~e8cal1/sge/index.html
1415 // ============================================================================
1416
1417 static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1418 {
1419   if (x >= 0 && x <= surface->w - 1 && y >= 0 && y <= surface->h - 1)
1420   {
1421     switch (surface->format->BytesPerPixel)
1422     {
1423       case 1:
1424       {
1425         // Assuming 8-bpp
1426         *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1427       }
1428       break;
1429
1430       case 2:
1431       {
1432         // Probably 15-bpp or 16-bpp
1433         *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1434       }
1435       break;
1436
1437       case 3:
1438       {
1439         // Slow 24-bpp mode, usually not used
1440         Uint8 *pix;
1441         int shift;
1442
1443         // Gack - slow, but endian correct
1444         pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1445         shift = surface->format->Rshift;
1446         *(pix+shift/8) = color>>shift;
1447         shift = surface->format->Gshift;
1448         *(pix+shift/8) = color>>shift;
1449         shift = surface->format->Bshift;
1450         *(pix+shift/8) = color>>shift;
1451       }
1452       break;
1453
1454       case 4:
1455       {
1456         // Probably 32-bpp
1457         *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1458       }
1459       break;
1460     }
1461   }
1462 }
1463
1464 #if 0
1465 static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1466                          Uint8 R, Uint8 G, Uint8 B)
1467 {
1468   _PutPixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
1469 }
1470
1471 static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1472 {
1473   *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1474 }
1475
1476 static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1477 {
1478   *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1479 }
1480
1481 static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1482 {
1483   Uint8 *pix;
1484   int shift;
1485
1486   // Gack - slow, but endian correct
1487   pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1488   shift = surface->format->Rshift;
1489   *(pix+shift/8) = color>>shift;
1490   shift = surface->format->Gshift;
1491   *(pix+shift/8) = color>>shift;
1492   shift = surface->format->Bshift;
1493   *(pix+shift/8) = color>>shift;
1494 }
1495
1496 static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1497 {
1498   *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1499 }
1500
1501 static void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
1502 {
1503   switch (dest->format->BytesPerPixel)
1504   {
1505     case 1:
1506       *((Uint8 *)dest->pixels + y*dest->pitch + x) = color;
1507       break;
1508
1509     case 2:
1510       *((Uint16 *)dest->pixels + y*dest->pitch/2 + x) = color;
1511       break;
1512
1513     case 3:
1514       _PutPixel24(dest,x,y,color);
1515       break;
1516
1517     case 4:
1518       *((Uint32 *)dest->pixels + y*dest->pitch/4 + x) = color;
1519       break;
1520   }
1521 }
1522 #endif
1523
1524 static void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1525 {
1526   if (SDL_MUSTLOCK(surface))
1527   {
1528     if (SDL_LockSurface(surface) < 0)
1529     {
1530       return;
1531     }
1532   }
1533
1534   _PutPixel(surface, x, y, color);
1535
1536   if (SDL_MUSTLOCK(surface))
1537   {
1538     SDL_UnlockSurface(surface);
1539   }
1540 }
1541
1542 #if 0
1543 static void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1544                             Uint8 r, Uint8 g, Uint8 b)
1545 {
1546   sge_PutPixel(surface, x, y, SDL_MapRGB(surface->format, r, g, b));
1547 }
1548
1549 static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
1550 {
1551   if (y >= 0 && y <= dest->h - 1)
1552   {
1553     switch (dest->format->BytesPerPixel)
1554     {
1555       case 1:
1556         return y*dest->pitch;
1557         break;
1558
1559       case 2:
1560         return y*dest->pitch/2;
1561         break;
1562
1563       case 3:
1564         return y*dest->pitch;
1565         break;
1566
1567       case 4:
1568         return y*dest->pitch/4;
1569         break;
1570     }
1571   }
1572
1573   return -1;
1574 }
1575
1576 static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch,
1577                           Uint32 color)
1578 {
1579   if (x >= 0 && x <= surface->w - 1 && ypitch >= 0)
1580   {
1581     switch (surface->format->BytesPerPixel)
1582     {
1583       case 1:
1584       {
1585         // Assuming 8-bpp
1586         *((Uint8 *)surface->pixels + ypitch + x) = color;
1587       }
1588       break;
1589
1590       case 2:
1591       {
1592         // Probably 15-bpp or 16-bpp
1593         *((Uint16 *)surface->pixels + ypitch + x) = color;
1594       }
1595       break;
1596
1597       case 3:
1598       {
1599         // Slow 24-bpp mode, usually not used
1600         Uint8 *pix;
1601         int shift;
1602
1603         // Gack - slow, but endian correct
1604         pix = (Uint8 *)surface->pixels + ypitch + x*3;
1605         shift = surface->format->Rshift;
1606         *(pix+shift/8) = color>>shift;
1607         shift = surface->format->Gshift;
1608         *(pix+shift/8) = color>>shift;
1609         shift = surface->format->Bshift;
1610         *(pix+shift/8) = color>>shift;
1611       }
1612       break;
1613
1614       case 4:
1615       {
1616         // Probably 32-bpp
1617         *((Uint32 *)surface->pixels + ypitch + x) = color;
1618       }
1619       break;
1620     }
1621   }
1622 }
1623
1624 static void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1625                       Uint32 Color)
1626 {
1627   SDL_Rect l;
1628
1629   if (SDL_MUSTLOCK(Surface))
1630   {
1631     if (SDL_LockSurface(Surface) < 0)
1632     {
1633       return;
1634     }
1635   }
1636
1637   if (x1 > x2)
1638   {
1639     Sint16 tmp = x1;
1640     x1 = x2;
1641     x2 = tmp;
1642   }
1643
1644   // Do the clipping
1645   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1646     return;
1647   if (x1 < 0)
1648     x1 = 0;
1649   if (x2 > Surface->w - 1)
1650     x2 = Surface->w - 1;
1651
1652   l.x = x1;
1653   l.y = y;
1654   l.w = x2 - x1 + 1;
1655   l.h = 1;
1656
1657   SDL_FillRect(Surface, &l, Color);
1658
1659   if (SDL_MUSTLOCK(Surface))
1660   {
1661     SDL_UnlockSurface(Surface);
1662   }
1663 }
1664
1665 static void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1666                          Uint8 R, Uint8 G, Uint8 B)
1667 {
1668   sge_HLine(Surface, x1, x2, y, SDL_MapRGB(Surface->format, R, G, B));
1669 }
1670
1671 static void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1672                    Uint32 Color)
1673 {
1674   SDL_Rect l;
1675
1676   if (x1 > x2)
1677   {
1678     Sint16 tmp = x1;
1679     x1 = x2;
1680     x2 = tmp;
1681   }
1682
1683   // Do the clipping
1684   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1685     return;
1686   if (x1 < 0)
1687     x1 = 0;
1688   if (x2 > Surface->w - 1)
1689     x2 = Surface->w - 1;
1690
1691   l.x = x1;
1692   l.y = y;
1693   l.w = x2 - x1 + 1;
1694   l.h = 1;
1695
1696   SDL_FillRect(Surface, &l, Color);
1697 }
1698
1699 static void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1700                       Uint32 Color)
1701 {
1702   SDL_Rect l;
1703
1704   if (SDL_MUSTLOCK(Surface))
1705   {
1706     if (SDL_LockSurface(Surface) < 0)
1707     {
1708       return;
1709     }
1710   }
1711
1712   if (y1 > y2)
1713   {
1714     Sint16 tmp = y1;
1715     y1 = y2;
1716     y2 = tmp;
1717   }
1718
1719   // Do the clipping
1720   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1721     return;
1722   if (y1 < 0)
1723     y1 = 0;
1724   if (y2 > Surface->h - 1)
1725     y2 = Surface->h - 1;
1726
1727   l.x = x;
1728   l.y = y1;
1729   l.w = 1;
1730   l.h = y2 - y1 + 1;
1731
1732   SDL_FillRect(Surface, &l, Color);
1733
1734   if (SDL_MUSTLOCK(Surface))
1735   {
1736     SDL_UnlockSurface(Surface);
1737   }
1738 }
1739
1740 static void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1741                          Uint8 R, Uint8 G, Uint8 B)
1742 {
1743   sge_VLine(Surface, x, y1, y2, SDL_MapRGB(Surface->format, R, G, B));
1744 }
1745
1746 static void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1747                    Uint32 Color)
1748 {
1749   SDL_Rect l;
1750
1751   if (y1 > y2)
1752   {
1753     Sint16 tmp = y1;
1754     y1 = y2;
1755     y2 = tmp;
1756   }
1757
1758   // Do the clipping
1759   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1760     return;
1761   if (y1 < 0)
1762     y1 = 0;
1763   if (y2 > Surface->h - 1)
1764     y2 = Surface->h - 1;
1765
1766   l.x = x;
1767   l.y = y1;
1768   l.w = 1;
1769   l.h = y2 - y1 + 1;
1770
1771   SDL_FillRect(Surface, &l, Color);
1772 }
1773 #endif
1774
1775 static void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
1776                        Sint16 x2, Sint16 y2, Uint32 Color,
1777                        void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1778                                      Uint32 Color))
1779 {
1780   Sint16 dx, dy, sdx, sdy, x, y, px, py;
1781
1782   dx = x2 - x1;
1783   dy = y2 - y1;
1784
1785   sdx = (dx < 0) ? -1 : 1;
1786   sdy = (dy < 0) ? -1 : 1;
1787
1788   dx = sdx * dx + 1;
1789   dy = sdy * dy + 1;
1790
1791   x = y = 0;
1792
1793   px = x1;
1794   py = y1;
1795
1796   if (dx >= dy)
1797   {
1798     for (x = 0; x < dx; x++)
1799     {
1800       Callback(Surface, px, py, Color);
1801
1802       y += dy;
1803       if (y >= dx)
1804       {
1805         y -= dx;
1806         py += sdy;
1807       }
1808
1809       px += sdx;
1810     }
1811   }
1812   else
1813   {
1814     for (y = 0; y < dy; y++)
1815     {
1816       Callback(Surface, px, py, Color);
1817
1818       x += dx;
1819       if (x >= dy)
1820       {
1821         x -= dy;
1822         px += sdx;
1823       }
1824
1825       py += sdy;
1826     }
1827   }
1828 }
1829
1830 #if 0
1831 static void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
1832                           Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
1833                           void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1834                                         Uint32 Color))
1835 {
1836   sge_DoLine(Surface, X1, Y1, X2, Y2,
1837              SDL_MapRGB(Surface->format, R, G, B), Callback);
1838 }
1839 #endif
1840
1841 void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
1842               Uint32 Color)
1843 {
1844   if (SDL_MUSTLOCK(Surface))
1845   {
1846     if (SDL_LockSurface(Surface) < 0)
1847       return;
1848    }
1849
1850    // Draw the line
1851    sge_DoLine(Surface, x1, y1, x2, y2, Color, _PutPixel);
1852
1853    // unlock the display
1854    if (SDL_MUSTLOCK(Surface))
1855    {
1856       SDL_UnlockSurface(Surface);
1857    }
1858 }
1859
1860 #if 0
1861 static void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
1862                         Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
1863 {
1864   sge_Line(Surface, x1, y1, x2, y2, SDL_MapRGB(Surface->format, R, G, B));
1865 }
1866 #endif
1867
1868 void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel)
1869 {
1870   sge_PutPixel(dst_bitmap->surface, x, y, pixel);
1871 }
1872
1873
1874 // ----------------------------------------------------------------------------
1875 // quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
1876 // ----------------------------------------------------------------------------
1877
1878 void SDLInvertArea(Bitmap *bitmap, int src_x, int src_y,
1879                    int width, int height, Uint32 color)
1880 {
1881   int x, y;
1882
1883   for (y = src_y; y < src_y + height; y++)
1884   {
1885     for (x = src_x; x < src_x + width; x++)
1886     {
1887       Uint32 pixel = SDLGetPixel(bitmap, x, y);
1888
1889       SDLPutPixel(bitmap, x, y, pixel == BLACK_PIXEL ? color : BLACK_PIXEL);
1890     }
1891   }
1892 }
1893
1894 void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
1895                           int src_x, int src_y, int width, int height,
1896                           int dst_x, int dst_y)
1897 {
1898   int x, y;
1899
1900   for (y = 0; y < height; y++)
1901   {
1902     for (x = 0; x < width; x++)
1903     {
1904       Uint32 pixel = SDLGetPixel(src_bitmap, src_x + x, src_y + y);
1905
1906       if (pixel != BLACK_PIXEL)
1907         SDLPutPixel(dst_bitmap, dst_x + x, dst_y + y, BLACK_PIXEL);
1908     }
1909   }
1910 }
1911
1912
1913 // ============================================================================
1914 // The following functions were taken from the SDL_gfx library version 2.0.3
1915 // (Rotozoomer) by Andreas Schiffler
1916 // http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html
1917 // ============================================================================
1918
1919 // ----------------------------------------------------------------------------
1920 // 32 bit zoomer
1921 //
1922 // zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
1923 // ----------------------------------------------------------------------------
1924
1925 typedef struct
1926 {
1927   Uint8 r;
1928   Uint8 g;
1929   Uint8 b;
1930   Uint8 a;
1931 } tColorRGBA;
1932
1933 static int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
1934 {
1935   int x, y;
1936   tColorRGBA *sp, *csp, *dp;
1937   int dgap;
1938
1939   // pointer setup
1940   sp = csp = (tColorRGBA *) src->pixels;
1941   dp = (tColorRGBA *) dst->pixels;
1942   dgap = dst->pitch - dst->w * 4;
1943
1944   for (y = 0; y < dst->h; y++)
1945   {
1946     sp = csp;
1947
1948     for (x = 0; x < dst->w; x++)
1949     {
1950       tColorRGBA *sp0 = sp;
1951       tColorRGBA *sp1 = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
1952       tColorRGBA *sp00 = &sp0[0];
1953       tColorRGBA *sp01 = &sp0[1];
1954       tColorRGBA *sp10 = &sp1[0];
1955       tColorRGBA *sp11 = &sp1[1];
1956       tColorRGBA new;
1957
1958       // create new color pixel from all four source color pixels
1959       new.r = (sp00->r + sp01->r + sp10->r + sp11->r) / 4;
1960       new.g = (sp00->g + sp01->g + sp10->g + sp11->g) / 4;
1961       new.b = (sp00->b + sp01->b + sp10->b + sp11->b) / 4;
1962       new.a = (sp00->a + sp01->a + sp10->a + sp11->a) / 4;
1963
1964       // draw
1965       *dp = new;
1966
1967       // advance source pointers
1968       sp += 2;
1969
1970       // advance destination pointer
1971       dp++;
1972     }
1973
1974     // advance source pointer
1975     csp = (tColorRGBA *) ((Uint8 *) csp + 2 * src->pitch);
1976
1977     // advance destination pointers
1978     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
1979   }
1980
1981   return 0;
1982 }
1983
1984 static int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
1985 {
1986   int x, y, *sax, *say, *csax, *csay;
1987   float sx, sy;
1988   tColorRGBA *sp, *csp, *csp0, *dp;
1989   int dgap;
1990
1991   // use specialized zoom function when scaling down to exactly half size
1992   if (src->w == 2 * dst->w &&
1993       src->h == 2 * dst->h)
1994     return zoomSurfaceRGBA_scaleDownBy2(src, dst);
1995
1996   // variable setup
1997   sx = (float) src->w / (float) dst->w;
1998   sy = (float) src->h / (float) dst->h;
1999
2000   // allocate memory for row increments
2001   csax = sax = (int *)checked_malloc((dst->w + 1) * sizeof(Uint32));
2002   csay = say = (int *)checked_malloc((dst->h + 1) * sizeof(Uint32));
2003
2004   // precalculate row increments
2005   for (x = 0; x <= dst->w; x++)
2006     *csax++ = (int)(sx * x);
2007
2008   for (y = 0; y <= dst->h; y++)
2009     *csay++ = (int)(sy * y);
2010
2011   // pointer setup
2012   sp = csp = csp0 = (tColorRGBA *) src->pixels;
2013   dp = (tColorRGBA *) dst->pixels;
2014   dgap = dst->pitch - dst->w * 4;
2015
2016   csay = say;
2017   for (y = 0; y < dst->h; y++)
2018   {
2019     sp = csp;
2020     csax = sax;
2021
2022     for (x = 0; x < dst->w; x++)
2023     {
2024       // draw
2025       *dp = *sp;
2026
2027       // advance source pointers
2028       csax++;
2029       sp = csp + *csax;
2030
2031       // advance destination pointer
2032       dp++;
2033     }
2034
2035     // advance source pointer
2036     csay++;
2037     csp = (tColorRGBA *) ((Uint8 *) csp0 + *csay * src->pitch);
2038
2039     // advance destination pointers
2040     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
2041   }
2042
2043   free(sax);
2044   free(say);
2045
2046   return 0;
2047 }
2048
2049 // ----------------------------------------------------------------------------
2050 // 8 bit zoomer
2051 //
2052 // zoomes 8 bit palette/Y 'src' surface to 'dst' surface
2053 // ----------------------------------------------------------------------------
2054
2055 static int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
2056 {
2057   Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
2058   Uint8 *sp, *dp, *csp;
2059   int dgap;
2060
2061   // variable setup
2062   sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
2063   sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
2064
2065   // allocate memory for row increments
2066   sax = (Uint32 *)checked_malloc(dst->w * sizeof(Uint32));
2067   say = (Uint32 *)checked_malloc(dst->h * sizeof(Uint32));
2068
2069   // precalculate row increments
2070   csx = 0;
2071   csax = sax;
2072   for (x = 0; x < dst->w; x++)
2073   {
2074     csx += sx;
2075     *csax = (csx >> 16);
2076     csx &= 0xffff;
2077     csax++;
2078   }
2079
2080   csy = 0;
2081   csay = say;
2082   for (y = 0; y < dst->h; y++)
2083   {
2084     csy += sy;
2085     *csay = (csy >> 16);
2086     csy &= 0xffff;
2087     csay++;
2088   }
2089
2090   csx = 0;
2091   csax = sax;
2092   for (x = 0; x < dst->w; x++)
2093   {
2094     csx += (*csax);
2095     csax++;
2096   }
2097
2098   csy = 0;
2099   csay = say;
2100   for (y = 0; y < dst->h; y++)
2101   {
2102     csy += (*csay);
2103     csay++;
2104   }
2105
2106   // pointer setup
2107   sp = csp = (Uint8 *) src->pixels;
2108   dp = (Uint8 *) dst->pixels;
2109   dgap = dst->pitch - dst->w;
2110
2111   // draw
2112   csay = say;
2113   for (y = 0; y < dst->h; y++)
2114   {
2115     csax = sax;
2116     sp = csp;
2117     for (x = 0; x < dst->w; x++)
2118     {
2119       // draw
2120       *dp = *sp;
2121
2122       // advance source pointers
2123       sp += (*csax);
2124       csax++;
2125
2126       // advance destination pointer
2127       dp++;
2128     }
2129
2130     // advance source pointer (for row)
2131     csp += ((*csay) * src->pitch);
2132     csay++;
2133
2134     // advance destination pointers
2135     dp += dgap;
2136   }
2137
2138   free(sax);
2139   free(say);
2140
2141   return 0;
2142 }
2143
2144 // ----------------------------------------------------------------------------
2145 // zoomSurface()
2146 //
2147 // Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
2148 // 'zoomx' and 'zoomy' are scaling factors for width and height.
2149 // If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
2150 // into a 32bit RGBA format on the fly.
2151 // ----------------------------------------------------------------------------
2152
2153 static SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
2154 {
2155   SDL_Surface *zoom_src = NULL;
2156   SDL_Surface *zoom_dst = NULL;
2157   boolean is_converted = FALSE;
2158   boolean is_32bit;
2159   int i;
2160
2161   if (src == NULL)
2162     return NULL;
2163
2164   // determine if source surface is 32 bit or 8 bit
2165   is_32bit = (src->format->BitsPerPixel == 32);
2166
2167   if (is_32bit || src->format->BitsPerPixel == 8)
2168   {
2169     // use source surface 'as is'
2170     zoom_src = src;
2171   }
2172   else
2173   {
2174     // new source surface is 32 bit with a defined RGB ordering
2175     zoom_src = SDL_CreateRGBSurface(SURFACE_FLAGS, src->w, src->h, 32,
2176                                     0x000000ff, 0x0000ff00, 0x00ff0000,
2177                                     (src->format->Amask ? 0xff000000 : 0));
2178     SDL_BlitSurface(src, NULL, zoom_src, NULL);
2179     is_32bit = TRUE;
2180     is_converted = TRUE;
2181   }
2182
2183   // allocate surface to completely contain the zoomed surface
2184   if (is_32bit)
2185   {
2186     // target surface is 32 bit with source RGBA/ABGR ordering
2187     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 32,
2188                                     zoom_src->format->Rmask,
2189                                     zoom_src->format->Gmask,
2190                                     zoom_src->format->Bmask,
2191                                     zoom_src->format->Amask);
2192   }
2193   else
2194   {
2195     // target surface is 8 bit
2196     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 8,
2197                                     0, 0, 0, 0);
2198   }
2199
2200   // lock source surface
2201   SDL_LockSurface(zoom_src);
2202
2203   // check which kind of surface we have
2204   if (is_32bit)
2205   {
2206     // call the 32 bit transformation routine to do the zooming
2207     zoomSurfaceRGBA(zoom_src, zoom_dst);
2208   }
2209   else
2210   {
2211     // copy palette
2212     for (i = 0; i < zoom_src->format->palette->ncolors; i++)
2213       zoom_dst->format->palette->colors[i] =
2214         zoom_src->format->palette->colors[i];
2215     zoom_dst->format->palette->ncolors = zoom_src->format->palette->ncolors;
2216
2217     // call the 8 bit transformation routine to do the zooming
2218     zoomSurfaceY(zoom_src, zoom_dst);
2219   }
2220
2221   // unlock source surface
2222   SDL_UnlockSurface(zoom_src);
2223
2224   // free temporary surface
2225   if (is_converted)
2226     SDL_FreeSurface(zoom_src);
2227
2228   // return destination surface
2229   return zoom_dst;
2230 }
2231
2232 static SDL_Surface *SDLGetOpaqueSurface(SDL_Surface *surface)
2233 {
2234   SDL_Surface *new_surface;
2235
2236   if (surface == NULL)
2237     return NULL;
2238
2239   if ((new_surface = SDLGetNativeSurface(surface)) == NULL)
2240     Error(ERR_EXIT, "SDLGetNativeSurface() failed");
2241
2242   // remove alpha channel from native non-transparent surface, if defined
2243   SDLSetAlpha(new_surface, FALSE, 0);
2244
2245   // remove transparent color from native non-transparent surface, if defined
2246   SDL_SetColorKey(new_surface, UNSET_TRANSPARENT_PIXEL, 0);
2247
2248   return new_surface;
2249 }
2250
2251 Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
2252 {
2253   Bitmap *dst_bitmap = CreateBitmapStruct();
2254   SDL_Surface *src_surface = src_bitmap->surface_masked;
2255   SDL_Surface *dst_surface;
2256
2257   dst_width  = MAX(1, dst_width);       // prevent zero bitmap width
2258   dst_height = MAX(1, dst_height);      // prevent zero bitmap height
2259
2260   dst_bitmap->width  = dst_width;
2261   dst_bitmap->height = dst_height;
2262
2263   // create zoomed temporary surface from source surface
2264   dst_surface = zoomSurface(src_surface, dst_width, dst_height);
2265
2266   // create native format destination surface from zoomed temporary surface
2267   SDLSetNativeSurface(&dst_surface);
2268
2269   // set color key for zoomed surface from source surface, if defined
2270   if (SDLHasColorKey(src_surface))
2271     SDL_SetColorKey(dst_surface, SET_TRANSPARENT_PIXEL,
2272                     SDLGetColorKey(src_surface));
2273
2274   // create native non-transparent surface for opaque blitting
2275   dst_bitmap->surface = SDLGetOpaqueSurface(dst_surface);
2276
2277   // set native transparent surface for masked blitting
2278   dst_bitmap->surface_masked = dst_surface;
2279
2280   return dst_bitmap;
2281 }
2282
2283
2284 // ============================================================================
2285 // load image to bitmap
2286 // ============================================================================
2287
2288 Bitmap *SDLLoadImage(char *filename)
2289 {
2290   Bitmap *new_bitmap = CreateBitmapStruct();
2291   SDL_Surface *sdl_image_tmp;
2292
2293   if (program.headless)
2294   {
2295     // prevent sanity check warnings at later stage
2296     new_bitmap->width = new_bitmap->height = 1;
2297
2298     return new_bitmap;
2299   }
2300
2301   print_timestamp_init("SDLLoadImage");
2302
2303   print_timestamp_time(getBaseNamePtr(filename));
2304
2305   // load image to temporary surface
2306   if ((sdl_image_tmp = IMG_Load(filename)) == NULL)
2307     Error(ERR_EXIT, "IMG_Load('%s') failed: %s", getBaseNamePtr(filename),
2308           SDL_GetError());
2309
2310   print_timestamp_time("IMG_Load");
2311
2312   UPDATE_BUSY_STATE();
2313
2314   // create native non-transparent surface for current image
2315   if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
2316     Error(ERR_EXIT, "SDLGetOpaqueSurface() failed");
2317
2318   print_timestamp_time("SDLGetNativeSurface (opaque)");
2319
2320   UPDATE_BUSY_STATE();
2321
2322   // set black pixel to transparent if no alpha channel / transparent color
2323   if (!SDLHasAlpha(sdl_image_tmp) &&
2324       !SDLHasColorKey(sdl_image_tmp))
2325     SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
2326                     SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
2327
2328   // create native transparent surface for current image
2329   if ((new_bitmap->surface_masked = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
2330     Error(ERR_EXIT, "SDLGetNativeSurface() failed");
2331
2332   print_timestamp_time("SDLGetNativeSurface (masked)");
2333
2334   UPDATE_BUSY_STATE();
2335
2336   // free temporary surface
2337   SDL_FreeSurface(sdl_image_tmp);
2338
2339   new_bitmap->width = new_bitmap->surface->w;
2340   new_bitmap->height = new_bitmap->surface->h;
2341
2342   print_timestamp_done("SDLLoadImage");
2343
2344   return new_bitmap;
2345 }
2346
2347
2348 // ----------------------------------------------------------------------------
2349 // custom cursor fuctions
2350 // ----------------------------------------------------------------------------
2351
2352 static SDL_Cursor *create_cursor(struct MouseCursorInfo *cursor_info)
2353 {
2354   return SDL_CreateCursor(cursor_info->data, cursor_info->mask,
2355                           cursor_info->width, cursor_info->height,
2356                           cursor_info->hot_x, cursor_info->hot_y);
2357 }
2358
2359 void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
2360 {
2361   static struct MouseCursorInfo *last_cursor_info = NULL;
2362   static struct MouseCursorInfo *last_cursor_info2 = NULL;
2363   static SDL_Cursor *cursor_default = NULL;
2364   static SDL_Cursor *cursor_current = NULL;
2365
2366   // if invoked for the first time, store the SDL default cursor
2367   if (cursor_default == NULL)
2368     cursor_default = SDL_GetCursor();
2369
2370   // only create new cursor if cursor info (custom only) has changed
2371   if (cursor_info != NULL && cursor_info != last_cursor_info)
2372   {
2373     cursor_current = create_cursor(cursor_info);
2374     last_cursor_info = cursor_info;
2375   }
2376
2377   // only set new cursor if cursor info (custom or NULL) has changed
2378   if (cursor_info != last_cursor_info2)
2379     SDL_SetCursor(cursor_info ? cursor_current : cursor_default);
2380
2381   last_cursor_info2 = cursor_info;
2382 }
2383
2384
2385 // ============================================================================
2386 // audio functions
2387 // ============================================================================
2388
2389 void SDLOpenAudio(void)
2390 {
2391   if (program.headless)
2392     return;
2393
2394   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
2395   {
2396     Error(ERR_WARN, "SDL_InitSubSystem() failed: %s", SDL_GetError());
2397     return;
2398   }
2399
2400   if (Mix_OpenAudio(DEFAULT_AUDIO_SAMPLE_RATE, MIX_DEFAULT_FORMAT,
2401                     AUDIO_NUM_CHANNELS_STEREO,
2402                     setup.system.audio_fragment_size) < 0)
2403   {
2404     Error(ERR_WARN, "Mix_OpenAudio() failed: %s", SDL_GetError());
2405     return;
2406   }
2407
2408   audio.sound_available = TRUE;
2409   audio.music_available = TRUE;
2410   audio.loops_available = TRUE;
2411   audio.sound_enabled = TRUE;
2412
2413   // set number of available mixer channels
2414   audio.num_channels = Mix_AllocateChannels(NUM_MIXER_CHANNELS);
2415   audio.music_channel = MUSIC_CHANNEL;
2416   audio.first_sound_channel = FIRST_SOUND_CHANNEL;
2417
2418   Mixer_InitChannels();
2419 }
2420
2421 void SDLCloseAudio(void)
2422 {
2423   Mix_HaltMusic();
2424   Mix_HaltChannel(-1);
2425
2426   Mix_CloseAudio();
2427   SDL_QuitSubSystem(SDL_INIT_AUDIO);
2428 }
2429
2430
2431 // ============================================================================
2432 // event functions
2433 // ============================================================================
2434
2435 void SDLWaitEvent(Event *event)
2436 {
2437   SDL_WaitEvent(event);
2438 }
2439
2440 void SDLCorrectRawMousePosition(int *x, int *y)
2441 {
2442   if (sdl_renderer == NULL)
2443     return;
2444
2445   // this corrects the raw mouse position for logical screen size within event
2446   // filters (correction done later by SDL library when handling mouse events)
2447
2448   SDL_Rect viewport;
2449   float scale_x, scale_y;
2450
2451   SDL_RenderGetViewport(sdl_renderer, &viewport);
2452   SDL_RenderGetScale(sdl_renderer, &scale_x, &scale_y);
2453
2454   *x = (int)(*x / scale_x);
2455   *y = (int)(*y / scale_y);
2456
2457   *x -= viewport.x;
2458   *y -= viewport.y;
2459 }
2460
2461
2462 // ============================================================================
2463 // joystick functions
2464 // ============================================================================
2465
2466 static void *sdl_joystick[MAX_PLAYERS];         // game controller or joystick
2467 static int sdl_js_axis_raw[MAX_PLAYERS][2];
2468 static int sdl_js_axis[MAX_PLAYERS][2];
2469 static int sdl_js_button[MAX_PLAYERS][2];
2470 static boolean sdl_is_controller[MAX_PLAYERS];
2471
2472 void SDLClearJoystickState(void)
2473 {
2474   int i, j;
2475
2476   for (i = 0; i < MAX_PLAYERS; i++)
2477   {
2478     for (j = 0; j < 2; j++)
2479     {
2480       sdl_js_axis_raw[i][j] = -1;
2481       sdl_js_axis[i][j] = 0;
2482       sdl_js_button[i][j] = 0;
2483     }
2484   }
2485 }
2486
2487 boolean SDLOpenJoystick(int nr)
2488 {
2489   if (nr < 0 || nr >= MAX_PLAYERS)
2490     return FALSE;
2491
2492   sdl_is_controller[nr] = SDL_IsGameController(nr);
2493
2494 #if DEBUG_JOYSTICKS
2495   Error(ERR_DEBUG, "opening joystick %d (%s)",
2496         nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
2497 #endif
2498
2499   if (sdl_is_controller[nr])
2500     sdl_joystick[nr] = SDL_GameControllerOpen(nr);
2501   else
2502     sdl_joystick[nr] = SDL_JoystickOpen(nr);
2503
2504   return (sdl_joystick[nr] != NULL);
2505 }
2506
2507 void SDLCloseJoystick(int nr)
2508 {
2509   if (nr < 0 || nr >= MAX_PLAYERS)
2510     return;
2511
2512 #if DEBUG_JOYSTICKS
2513   Error(ERR_DEBUG, "closing joystick %d", nr);
2514 #endif
2515
2516   if (sdl_is_controller[nr])
2517     SDL_GameControllerClose(sdl_joystick[nr]);
2518   else
2519     SDL_JoystickClose(sdl_joystick[nr]);
2520
2521   sdl_joystick[nr] = NULL;
2522 }
2523
2524 boolean SDLCheckJoystickOpened(int nr)
2525 {
2526   if (nr < 0 || nr >= MAX_PLAYERS)
2527     return FALSE;
2528
2529   return (sdl_joystick[nr] != NULL ? TRUE : FALSE);
2530 }
2531
2532 static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
2533 {
2534   int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
2535                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
2536                  axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
2537                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
2538
2539   if (nr < 0 || nr >= MAX_PLAYERS)
2540     return;
2541
2542   if (axis_id == -1)
2543     return;
2544
2545   // prevent (slightly jittering, but centered) axis A from resetting axis B
2546   if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
2547       axis_id_raw != sdl_js_axis_raw[nr][axis_id])
2548     return;
2549
2550   sdl_js_axis[nr][axis_id] = axis_value;
2551   sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
2552 }
2553
2554 static void setJoystickButton(int nr, int button_id_raw, int button_state)
2555 {
2556   int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
2557                    button_id_raw == SDL_CONTROLLER_BUTTON_X ||
2558                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
2559                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
2560                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
2561                    button_id_raw == SDL_CONTROLLER_BUTTON_B ||
2562                    button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
2563                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
2564                    -1);
2565
2566   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
2567     sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
2568   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
2569     sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
2570   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
2571     sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
2572   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2573     sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
2574
2575   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
2576       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
2577       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
2578       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2579     sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
2580
2581   if (nr < 0 || nr >= MAX_PLAYERS)
2582     return;
2583
2584   if (button_id == -1)
2585     return;
2586
2587   sdl_js_button[nr][button_id] = button_state;
2588 }
2589
2590 void HandleJoystickEvent(Event *event)
2591 {
2592   // when using joystick, disable overlay touch buttons
2593   runtime.uses_touch_device = FALSE;
2594
2595   switch (event->type)
2596   {
2597     case SDL_CONTROLLERDEVICEADDED:
2598 #if DEBUG_JOYSTICKS
2599       Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEADDED: device %d added",
2600             event->cdevice.which);
2601 #endif
2602       InitJoysticks();
2603       break;
2604
2605     case SDL_CONTROLLERDEVICEREMOVED:
2606 #if DEBUG_JOYSTICKS
2607       Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
2608             event->cdevice.which);
2609 #endif
2610       InitJoysticks();
2611       break;
2612
2613     case SDL_CONTROLLERAXISMOTION:
2614 #if DEBUG_JOYSTICKS
2615       Error(ERR_DEBUG, "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
2616             event->caxis.which, event->caxis.axis, event->caxis.value);
2617 #endif
2618       setJoystickAxis(event->caxis.which,
2619                       event->caxis.axis,
2620                       event->caxis.value);
2621       break;
2622
2623     case SDL_CONTROLLERBUTTONDOWN:
2624 #if DEBUG_JOYSTICKS
2625       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
2626             event->cbutton.which, event->cbutton.button);
2627 #endif
2628       setJoystickButton(event->cbutton.which,
2629                         event->cbutton.button,
2630                         TRUE);
2631       break;
2632
2633     case SDL_CONTROLLERBUTTONUP:
2634 #if DEBUG_JOYSTICKS
2635       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONUP: device %d, button %d",
2636             event->cbutton.which, event->cbutton.button);
2637 #endif
2638       setJoystickButton(event->cbutton.which,
2639                         event->cbutton.button,
2640                         FALSE);
2641       break;
2642
2643     case SDL_JOYAXISMOTION:
2644       if (sdl_is_controller[event->jaxis.which])
2645         break;
2646
2647 #if DEBUG_JOYSTICKS
2648       Error(ERR_DEBUG, "SDL_JOYAXISMOTION: device %d, axis %d: %d",
2649             event->jaxis.which, event->jaxis.axis, event->jaxis.value);
2650 #endif
2651       if (event->jaxis.axis < 4)
2652         setJoystickAxis(event->jaxis.which,
2653                         event->jaxis.axis,
2654                         event->jaxis.value);
2655       break;
2656
2657     case SDL_JOYBUTTONDOWN:
2658       if (sdl_is_controller[event->jaxis.which])
2659         break;
2660
2661 #if DEBUG_JOYSTICKS
2662       Error(ERR_DEBUG, "SDL_JOYBUTTONDOWN: device %d, button %d",
2663             event->jbutton.which, event->jbutton.button);
2664 #endif
2665       if (event->jbutton.button < 4)
2666         setJoystickButton(event->jbutton.which,
2667                           event->jbutton.button,
2668                           TRUE);
2669       break;
2670
2671     case SDL_JOYBUTTONUP:
2672       if (sdl_is_controller[event->jaxis.which])
2673         break;
2674
2675 #if DEBUG_JOYSTICKS
2676       Error(ERR_DEBUG, "SDL_JOYBUTTONUP: device %d, button %d",
2677             event->jbutton.which, event->jbutton.button);
2678 #endif
2679       if (event->jbutton.button < 4)
2680         setJoystickButton(event->jbutton.which,
2681                           event->jbutton.button,
2682                           FALSE);
2683       break;
2684
2685     default:
2686       break;
2687   }
2688 }
2689
2690 void SDLInitJoysticks(void)
2691 {
2692   static boolean sdl_joystick_subsystem_initialized = FALSE;
2693   boolean print_warning = !sdl_joystick_subsystem_initialized;
2694   char *mappings_file_base = getPath2(options.conf_directory,
2695                                       GAMECONTROLLER_BASENAME);
2696   char *mappings_file_user = getPath2(getUserGameDataDir(),
2697                                       GAMECONTROLLER_BASENAME);
2698   int num_mappings;
2699   int i;
2700
2701   if (!sdl_joystick_subsystem_initialized)
2702   {
2703     sdl_joystick_subsystem_initialized = TRUE;
2704
2705     SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
2706
2707     if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
2708     {
2709       Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
2710       return;
2711     }
2712
2713     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
2714
2715     // the included game controller base mappings should always be found
2716     if (num_mappings == -1)
2717       Error(ERR_WARN, "no game controller base mappings found");
2718 #if DEBUG_JOYSTICKS
2719     else
2720       Error(ERR_INFO, "%d game controller base mapping(s) added", num_mappings);
2721 #endif
2722
2723     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
2724
2725 #if DEBUG_JOYSTICKS
2726     // the personal game controller user mappings may or may not be found
2727     if (num_mappings == -1)
2728       Error(ERR_WARN, "no game controller user mappings found");
2729     else
2730       Error(ERR_INFO, "%d game controller user mapping(s) added", num_mappings);
2731
2732     Error(ERR_INFO, "%d joystick(s) found:", SDL_NumJoysticks());
2733 #endif
2734
2735     checked_free(mappings_file_base);
2736     checked_free(mappings_file_user);
2737
2738 #if DEBUG_JOYSTICKS
2739     for (i = 0; i < SDL_NumJoysticks(); i++)
2740     {
2741       const char *name, *type;
2742
2743       if (SDL_IsGameController(i))
2744       {
2745         name = SDL_GameControllerNameForIndex(i);
2746         type = "game controller";
2747       }
2748       else
2749       {
2750         name = SDL_JoystickNameForIndex(i);
2751         type = "joystick";
2752       }
2753
2754       Error(ERR_INFO, "- joystick %d (%s): '%s'",
2755             i, type, (name ? name : "(Unknown)"));
2756     }
2757 #endif
2758   }
2759
2760   // assign joysticks from configured to connected joystick for all players
2761   for (i = 0; i < MAX_PLAYERS; i++)
2762   {
2763     // get configured joystick for this player
2764     char *device_name = setup.input[i].joy.device_name;
2765     int joystick_nr = getJoystickNrFromDeviceName(device_name);
2766
2767     if (joystick_nr >= SDL_NumJoysticks())
2768     {
2769       if (setup.input[i].use_joystick && print_warning)
2770         Error(ERR_WARN, "cannot find joystick %d", joystick_nr);
2771
2772       joystick_nr = -1;
2773     }
2774
2775     // store configured joystick number for each player
2776     joystick.nr[i] = joystick_nr;
2777   }
2778
2779   // now open all connected joysticks (regardless if configured or not)
2780   for (i = 0; i < SDL_NumJoysticks(); i++)
2781   {
2782     // this allows subsequent calls to 'InitJoysticks' for re-initialization
2783     if (SDLCheckJoystickOpened(i))
2784       SDLCloseJoystick(i);
2785
2786     if (SDLOpenJoystick(i))
2787       joystick.status = JOYSTICK_ACTIVATED;
2788     else if (print_warning)
2789       Error(ERR_WARN, "cannot open joystick %d", i);
2790   }
2791
2792   SDLClearJoystickState();
2793 }
2794
2795 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
2796 {
2797   if (nr < 0 || nr >= MAX_PLAYERS)
2798     return FALSE;
2799
2800   if (x != NULL)
2801     *x = sdl_js_axis[nr][0];
2802   if (y != NULL)
2803     *y = sdl_js_axis[nr][1];
2804
2805   if (b1 != NULL)
2806     *b1 = sdl_js_button[nr][0];
2807   if (b2 != NULL)
2808     *b2 = sdl_js_button[nr][1];
2809
2810   return TRUE;
2811 }
2812
2813
2814 // ============================================================================
2815 // touch input overlay functions
2816 // ============================================================================
2817
2818 #if defined(USE_TOUCH_INPUT_OVERLAY)
2819 static void DrawTouchInputOverlay_ShowGrid(int alpha)
2820 {
2821   SDL_Rect rect;
2822   int grid_xsize = overlay.grid_xsize;
2823   int grid_ysize = overlay.grid_ysize;
2824   int x, y;
2825
2826   SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha);
2827   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2828
2829   for (x = 0; x < grid_xsize; x++)
2830   {
2831     rect.x = (x + 0) * video.screen_width / grid_xsize;
2832     rect.w = (x + 1) * video.screen_width / grid_xsize - rect.x;
2833
2834     for (y = 0; y < grid_ysize; y++)
2835     {
2836       rect.y = (y + 0) * video.screen_height / grid_ysize;
2837       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2838
2839       if (overlay.grid_button[x][y] == CHAR_GRID_BUTTON_NONE)
2840         SDL_RenderDrawRect(sdl_renderer, &rect);
2841     }
2842   }
2843
2844   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2845 }
2846
2847 static void RenderFillRectangle(int x, int y, int width, int height)
2848 {
2849   SDL_Rect rect = { x, y, width, height };
2850
2851   SDL_RenderFillRect(sdl_renderer, &rect);
2852 }
2853
2854 static void DrawTouchInputOverlay_ShowGridButtons(int alpha)
2855 {
2856   static int alpha_direction = 0;
2857   static int alpha_highlight = 0;
2858   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2859   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2860   SDL_Rect rect;
2861   int grid_xsize = overlay.grid_xsize;
2862   int grid_ysize = overlay.grid_ysize;
2863   int x, y;
2864
2865   if (alpha == alpha_max)
2866   {
2867     if (alpha_direction < 0)
2868     {
2869       alpha_highlight = MAX(0, alpha_highlight - alpha_step);
2870
2871       if (alpha_highlight == 0)
2872         alpha_direction = 1;
2873     }
2874     else
2875     {
2876       alpha_highlight = MIN(alpha_highlight + alpha_step, alpha_max);
2877
2878       if (alpha_highlight == alpha_max)
2879         alpha_direction = -1;
2880     }
2881   }
2882   else
2883   {
2884     alpha_direction = 1;
2885     alpha_highlight = alpha;
2886   }
2887
2888   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2889
2890   for (x = 0; x < grid_xsize; x++)
2891   {
2892     for (y = 0; y < grid_ysize; y++)
2893     {
2894       int grid_button = overlay.grid_button[x][y];
2895       int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
2896       int alpha_draw = alpha;
2897       int outline_border = MV_NONE;
2898       int border_size = 2;
2899       boolean draw_outlined = setup.touch.draw_outlined;
2900       boolean draw_pressed = setup.touch.draw_pressed;
2901
2902       if (grid_button == CHAR_GRID_BUTTON_NONE)
2903         continue;
2904
2905       if (grid_button == overlay.grid_button_highlight)
2906       {
2907         draw_outlined = FALSE;
2908         alpha_draw = MIN((float)alpha_highlight * 1.5, SDL_ALPHA_OPAQUE);
2909       }
2910
2911       if (draw_pressed && overlay.grid_button_action & grid_button_action)
2912       {
2913         if (draw_outlined)
2914           draw_outlined = FALSE;
2915         else
2916           alpha_draw = MIN((float)alpha_draw * 1.5, SDL_ALPHA_OPAQUE);
2917       }
2918
2919       SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha_draw);
2920
2921       rect.x = (x + 0) * video.screen_width  / grid_xsize;
2922       rect.y = (y + 0) * video.screen_height / grid_ysize;
2923       rect.w = (x + 1) * video.screen_width  / grid_xsize - rect.x;
2924       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2925
2926       if (x == 0 || overlay.grid_button[x - 1][y] != grid_button)
2927       {
2928         rect.x += border_size;
2929         rect.w -= border_size;
2930
2931         outline_border |= MV_LEFT;
2932       }
2933
2934       if (x == grid_xsize - 1 || overlay.grid_button[x + 1][y] != grid_button)
2935       {
2936         rect.w -= border_size;
2937
2938         outline_border |= MV_RIGHT;
2939       }
2940
2941       if (y == 0 || overlay.grid_button[x][y - 1] != grid_button)
2942       {
2943         rect.y += border_size;
2944         rect.h -= border_size;
2945
2946         outline_border |= MV_UP;
2947       }
2948
2949       if (y == grid_ysize - 1 || overlay.grid_button[x][y + 1] != grid_button)
2950       {
2951         rect.h -= border_size;
2952
2953         outline_border |= MV_DOWN;
2954       }
2955
2956       if (draw_outlined)
2957       {
2958         int rect_x = rect.x +
2959           (outline_border & MV_LEFT  ? border_size : 0);
2960         int rect_w = rect.w -
2961           (outline_border & MV_LEFT  ? border_size : 0) -
2962           (outline_border & MV_RIGHT ? border_size : 0);
2963
2964         if (outline_border & MV_LEFT)
2965           RenderFillRectangle(rect.x, rect.y, border_size, rect.h);
2966
2967         if (outline_border & MV_RIGHT)
2968           RenderFillRectangle(rect.x + rect.w - border_size, rect.y,
2969                               border_size, rect.h);
2970
2971         if (outline_border & MV_UP)
2972           RenderFillRectangle(rect_x, rect.y, rect_w, border_size);
2973
2974         if (outline_border & MV_DOWN)
2975           RenderFillRectangle(rect_x, rect.y + rect.h - border_size,
2976                               rect_w, border_size);
2977       }
2978       else
2979       {
2980         SDL_RenderFillRect(sdl_renderer, &rect);
2981       }
2982     }
2983   }
2984
2985   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2986 }
2987
2988 static void DrawTouchInputOverlay(void)
2989 {
2990   static boolean deactivated = TRUE;
2991   static boolean show_grid = FALSE;
2992   static int alpha = 0;
2993   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2994   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2995   boolean active = (overlay.enabled && overlay.active);
2996
2997   if (!active && deactivated)
2998     return;
2999
3000   if (active)
3001   {
3002     if (alpha < alpha_max)
3003       alpha = MIN(alpha + alpha_step, alpha_max);
3004
3005     deactivated = FALSE;
3006   }
3007   else
3008   {
3009     alpha = MAX(0, alpha - alpha_step);
3010
3011     if (alpha == 0)
3012       deactivated = TRUE;
3013   }
3014
3015   if (overlay.show_grid)
3016     show_grid = TRUE;
3017   else if (deactivated)
3018     show_grid = FALSE;
3019
3020   if (show_grid)
3021     DrawTouchInputOverlay_ShowGrid(alpha);
3022
3023   DrawTouchInputOverlay_ShowGridButtons(alpha);
3024 }
3025
3026 static void DrawTouchGadgetsOverlay(void)
3027 {
3028   DrawGadgets_OverlayTouchButtons();
3029 }
3030 #endif