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