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