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