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