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