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