cbdbdc1cfcac586f1a414bc5e2f3d053a3636d2b
[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     Debug("internal:frame", "FrameCounter == %d [%s]", 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   Pixel color_key;
313
314   return (SDL_GetColorKey(surface, &color_key) == 0);
315 }
316
317 static boolean SDLHasAlpha(SDL_Surface *surface)
318 {
319   SDL_BlendMode blend_mode;
320
321   if (SDL_GetSurfaceBlendMode(surface, &blend_mode) != 0)
322     return FALSE;
323
324   return (blend_mode == SDL_BLENDMODE_BLEND);
325 }
326
327 static void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha)
328 {
329   SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
330
331   SDL_SetSurfaceBlendMode(surface, blend_mode);
332   SDL_SetSurfaceAlphaMod(surface, alpha);
333 }
334
335 const char *SDLGetRendererName(void)
336 {
337   static SDL_RendererInfo renderer_info;
338
339   SDL_GetRendererInfo(sdl_renderer, &renderer_info);
340
341   return renderer_info.name;
342 }
343
344 SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
345 {
346   SDL_PixelFormat format;
347   SDL_Surface *new_surface;
348
349   if (surface == NULL)
350     return NULL;
351
352   if (backbuffer && backbuffer->surface)
353   {
354     format = *backbuffer->surface->format;
355     format.Amask = surface->format->Amask;      // keep alpha channel
356   }
357   else
358   {
359     format = *surface->format;
360   }
361
362   new_surface = SDL_ConvertSurface(surface, &format, 0);
363
364   if (new_surface == NULL)
365     Fail("SDL_ConvertSurface() failed: %s", SDL_GetError());
366
367   // workaround for a bug in SDL 2.0.12 (which does not convert the color key)
368   if (SDLHasColorKey(surface) && !SDLHasColorKey(new_surface))
369     SDL_SetColorKey(new_surface, SET_TRANSPARENT_PIXEL,
370                     SDLGetColorKey(surface));
371
372   return new_surface;
373 }
374
375 boolean SDLSetNativeSurface(SDL_Surface **surface)
376 {
377   SDL_Surface *new_surface;
378
379   if (surface == NULL ||
380       *surface == NULL ||
381       backbuffer == NULL ||
382       backbuffer->surface == NULL)
383     return FALSE;
384
385   // if pixel format already optimized for destination surface, do nothing
386   if (equalSDLPixelFormat((*surface)->format, backbuffer->surface->format))
387     return FALSE;
388
389   new_surface = SDLGetNativeSurface(*surface);
390
391   SDL_FreeSurface(*surface);
392
393   *surface = new_surface;
394
395   return TRUE;
396 }
397
398 static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
399 {
400   if (program.headless)
401     return NULL;
402
403   SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
404
405   if (texture == NULL)
406     Fail("SDL_CreateTextureFromSurface() failed: %s", SDL_GetError());
407
408   return texture;
409 }
410
411 void SDLCreateBitmapTextures(Bitmap *bitmap)
412 {
413   if (bitmap == NULL)
414     return;
415
416   if (bitmap->texture)
417     SDL_DestroyTexture(bitmap->texture);
418   if (bitmap->texture_masked)
419     SDL_DestroyTexture(bitmap->texture_masked);
420
421   bitmap->texture        = SDLCreateTextureFromSurface(bitmap->surface);
422   bitmap->texture_masked = SDLCreateTextureFromSurface(bitmap->surface_masked);
423 }
424
425 void SDLFreeBitmapTextures(Bitmap *bitmap)
426 {
427   if (bitmap == NULL)
428     return;
429
430   if (bitmap->texture)
431     SDL_DestroyTexture(bitmap->texture);
432   if (bitmap->texture_masked)
433     SDL_DestroyTexture(bitmap->texture_masked);
434
435   bitmap->texture = NULL;
436   bitmap->texture_masked = NULL;
437 }
438
439 void SDLInitVideoDisplay(void)
440 {
441   // set hint to select render driver as specified in setup config file
442   if (!strEqual(setup.system.sdl_renderdriver, ARG_DEFAULT))
443     SDL_SetHint(SDL_HINT_RENDER_DRIVER, setup.system.sdl_renderdriver);
444
445   // initialize SDL video
446   if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
447     Fail("SDL_InitSubSystem() failed: %s", SDL_GetError());
448
449   // set default SDL depth
450   video.default_depth = 32;     // (how to determine video depth in SDL2?)
451   //
452   // Code used with SDL 1.2:
453   // video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
454 }
455
456 static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
457 {
458   if (program.headless)
459     return;
460
461   video.window_scaling_percent = setup.window_scaling_percent;
462   video.window_scaling_quality = setup.window_scaling_quality;
463
464   SDLSetScreenRenderingMode(setup.screen_rendering_mode);
465
466   // SDL 2.0: support for (desktop) fullscreen mode available
467   video.fullscreen_available = TRUE;
468
469   // open SDL video output device (window or fullscreen mode)
470   if (!SDLSetVideoMode(fullscreen))
471     Fail("setting video mode failed");
472
473   // !!! SDL2 can only set the window icon if the window already exists !!!
474   // set window icon
475   SDLSetWindowIcon(program.icon_filename);
476
477   // set window and icon title
478   SDLSetWindowTitle();
479 }
480
481 static void SDLInitVideoBuffer_DrawBuffer(void)
482 {
483   /* SDL cannot directly draw to the visible video framebuffer like X11,
484      but always uses a backbuffer, which is then blitted to the visible
485      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
486      visible video framebuffer with 'SDL_Flip', if the hardware supports
487      this). Therefore do not use an additional backbuffer for drawing, but
488      use a symbolic buffer (distinguishable from the SDL backbuffer) called
489      'window', which indicates that the SDL backbuffer should be updated to
490      the visible video framebuffer when attempting to blit to it.
491
492      For convenience, it seems to be a good idea to create this symbolic
493      buffer 'window' at the same size as the SDL backbuffer. Although it
494      should never be drawn to directly, it would do no harm nevertheless. */
495
496   // create additional (symbolic) buffer for double-buffering
497   ReCreateBitmap(&window, video.width, video.height);
498
499   // create dummy drawing buffer for headless mode, if needed
500   if (program.headless)
501     ReCreateBitmap(&backbuffer, video.width, video.height);
502 }
503
504 void SDLInitVideoBuffer(boolean fullscreen)
505 {
506   SDLInitVideoBuffer_VideoBuffer(fullscreen);
507   SDLInitVideoBuffer_DrawBuffer();
508 }
509
510 static boolean SDLCreateScreen(boolean fullscreen)
511 {
512   SDL_Surface *new_surface = NULL;
513
514   int surface_flags_window     = SURFACE_FLAGS;
515   int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN_DESKTOP;
516
517 #if 1
518   int renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
519
520   video.vsync_mode = VSYNC_MODE_OFF;
521
522   if (!strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF))
523   {
524     renderer_flags |= SDL_RENDERER_PRESENTVSYNC;
525     video.vsync_mode = VSYNC_MODE_NORMAL;
526   }
527 #else
528   /* If SDL_CreateRenderer() is called from within a VirtualBox Windows VM
529      _without_ enabling 2D/3D acceleration and/or guest additions installed,
530      it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
531      it will try to use accelerated graphics and apparently fails miserably) */
532   int renderer_flags = SDL_RENDERER_SOFTWARE;
533 #endif
534
535   int width  = video.width;
536   int height = video.height;
537   int screen_width  = video.screen_width;
538   int screen_height = video.screen_height;
539   int surface_flags = (fullscreen ? surface_flags_fullscreen :
540                        surface_flags_window);
541
542   // default window size is unscaled
543   video.window_width  = screen_width;
544   video.window_height = screen_height;
545
546   // store if initial screen mode is fullscreen mode when changing screen size
547   video.fullscreen_initial = fullscreen;
548
549   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
550
551   video.window_width  = window_scaling_factor * screen_width;
552   video.window_height = window_scaling_factor * screen_height;
553
554   if (sdl_texture_stream)
555   {
556     SDL_DestroyTexture(sdl_texture_stream);
557     sdl_texture_stream = NULL;
558   }
559
560   if (sdl_texture_target)
561   {
562     SDL_DestroyTexture(sdl_texture_target);
563     sdl_texture_target = NULL;
564   }
565
566   if (!(fullscreen && fullscreen_enabled))
567   {
568     if (sdl_renderer)
569     {
570       SDL_DestroyRenderer(sdl_renderer);
571       sdl_renderer = NULL;
572     }
573
574     if (sdl_window)
575     {
576       SDL_DestroyWindow(sdl_window);
577       sdl_window = NULL;
578     }
579   }
580
581   if (sdl_window == NULL)
582     sdl_window = SDL_CreateWindow(program.window_title,
583                                   SDL_WINDOWPOS_CENTERED,
584                                   SDL_WINDOWPOS_CENTERED,
585                                   video.window_width,
586                                   video.window_height,
587                                   surface_flags);
588
589   if (sdl_window != NULL)
590   {
591     if (sdl_renderer == NULL)
592       sdl_renderer = SDL_CreateRenderer(sdl_window, -1, renderer_flags);
593
594     if (sdl_renderer != NULL)
595     {
596       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
597       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
598
599       // required for setting adaptive vsync when using OpenGL renderer
600       SDLSetScreenVsyncMode(setup.vsync_mode);
601
602       sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
603                                              SDL_PIXELFORMAT_ARGB8888,
604                                              SDL_TEXTUREACCESS_STREAMING,
605                                              width, height);
606
607       if (SDL_RenderTargetSupported(sdl_renderer))
608         sdl_texture_target = SDL_CreateTexture(sdl_renderer,
609                                                SDL_PIXELFORMAT_ARGB8888,
610                                                SDL_TEXTUREACCESS_TARGET,
611                                                width, height);
612
613       if (sdl_texture_stream != NULL)
614       {
615         // use SDL default values for RGB masks and no alpha channel
616         new_surface = SDL_CreateRGBSurface(0, width, height, 32, 0,0,0, 0);
617
618         if (new_surface == NULL)
619           Warn("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
620       }
621       else
622       {
623         Warn("SDL_CreateTexture() failed: %s", SDL_GetError());
624       }
625     }
626     else
627     {
628       Warn("SDL_CreateRenderer() failed: %s", SDL_GetError());
629     }
630   }
631   else
632   {
633     Warn("SDL_CreateWindow() failed: %s", SDL_GetError());
634   }
635
636   SDLSetScreenProperties();
637
638   // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
639   if (new_surface != NULL)
640     fullscreen_enabled = fullscreen;
641
642   if (backbuffer == NULL)
643     backbuffer = CreateBitmapStruct();
644
645   backbuffer->width  = video.width;
646   backbuffer->height = video.height;
647
648   if (backbuffer->surface)
649     SDL_FreeSurface(backbuffer->surface);
650
651   backbuffer->surface = new_surface;
652
653   return (new_surface != NULL);
654 }
655
656 boolean SDLSetVideoMode(boolean fullscreen)
657 {
658   boolean success = FALSE;
659
660   SetWindowTitle();
661
662   if (fullscreen && !video.fullscreen_enabled && video.fullscreen_available)
663   {
664     // switch display to fullscreen mode, if available
665     success = SDLCreateScreen(TRUE);
666
667     if (!success)
668     {
669       // switching display to fullscreen mode failed -- do not try it again
670       video.fullscreen_available = FALSE;
671     }
672     else
673     {
674       video.fullscreen_enabled = TRUE;
675     }
676   }
677
678   if ((!fullscreen && video.fullscreen_enabled) || !success)
679   {
680     // switch display to window mode
681     success = SDLCreateScreen(FALSE);
682
683     if (!success)
684     {
685       // switching display to window mode failed -- should not happen
686     }
687     else
688     {
689       video.fullscreen_enabled = FALSE;
690       video.window_scaling_percent = setup.window_scaling_percent;
691       video.window_scaling_quality = setup.window_scaling_quality;
692
693       SDLSetScreenRenderingMode(setup.screen_rendering_mode);
694     }
695   }
696
697   SDLRedrawWindow();                    // map window
698
699   return success;
700 }
701
702 void SDLSetWindowTitle(void)
703 {
704   if (sdl_window == NULL)
705     return;
706
707   SDL_SetWindowTitle(sdl_window, program.window_title);
708 }
709
710 void SDLSetWindowScaling(int window_scaling_percent)
711 {
712   if (sdl_window == NULL)
713     return;
714
715   float window_scaling_factor = (float)window_scaling_percent / 100;
716   int new_window_width  = (int)(window_scaling_factor * video.screen_width);
717   int new_window_height = (int)(window_scaling_factor * video.screen_height);
718
719   SDL_SetWindowSize(sdl_window, new_window_width, new_window_height);
720
721   video.window_scaling_percent = window_scaling_percent;
722   video.window_width  = new_window_width;
723   video.window_height = new_window_height;
724
725   SetWindowTitle();
726 }
727
728 void SDLSetWindowScalingQuality(char *window_scaling_quality)
729 {
730   SDL_Texture *new_texture;
731
732   if (sdl_texture_stream == NULL)
733     return;
734
735   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, window_scaling_quality);
736
737   new_texture = SDL_CreateTexture(sdl_renderer,
738                                   SDL_PIXELFORMAT_ARGB8888,
739                                   SDL_TEXTUREACCESS_STREAMING,
740                                   video.width, video.height);
741
742   if (new_texture != NULL)
743   {
744     SDL_DestroyTexture(sdl_texture_stream);
745
746     sdl_texture_stream = new_texture;
747   }
748
749   if (SDL_RenderTargetSupported(sdl_renderer))
750     new_texture = SDL_CreateTexture(sdl_renderer,
751                                     SDL_PIXELFORMAT_ARGB8888,
752                                     SDL_TEXTUREACCESS_TARGET,
753                                     video.width, video.height);
754   else
755     new_texture = NULL;
756
757   if (new_texture != NULL)
758   {
759     SDL_DestroyTexture(sdl_texture_target);
760
761     sdl_texture_target = new_texture;
762   }
763
764   SDLRedrawWindow();
765
766   video.window_scaling_quality = window_scaling_quality;
767 }
768
769 void SDLSetWindowFullscreen(boolean fullscreen)
770 {
771   if (sdl_window == NULL)
772     return;
773
774   int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
775
776   if (SDL_SetWindowFullscreen(sdl_window, flags) == 0)
777     video.fullscreen_enabled = fullscreen_enabled = fullscreen;
778
779   // if screen size was changed in fullscreen mode, correct desktop window size
780   if (!fullscreen && video.fullscreen_initial)
781   {
782     SDLSetWindowScaling(setup.window_scaling_percent);
783     SDL_SetWindowPosition(sdl_window,
784                           SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
785
786     video.fullscreen_initial = FALSE;
787   }
788 }
789
790 void SDLSetDisplaySize(void)
791 {
792   if (sdl_renderer != NULL)
793   {
794     int w, h;
795
796     SDL_GetRendererOutputSize(sdl_renderer, &w, &h);
797
798     video.display_width  = w;
799     video.display_height = h;
800
801 #if 0
802     Debug("video", "SDL renderer size: %d x %d",
803           video.display_width, video.display_height);
804 #endif
805   }
806   else
807   {
808     SDL_Rect display_bounds;
809
810     SDL_GetDisplayBounds(0, &display_bounds);
811
812     video.display_width  = display_bounds.w;
813     video.display_height = display_bounds.h;
814
815 #if 0
816     Debug("video", "SDL display size: %d x %d",
817           video.display_width, video.display_height);
818 #endif
819   }
820 }
821
822 void SDLSetScreenSizeAndOffsets(int width, int height)
823 {
824   // set default video screen size and offsets
825   video.screen_width = width;
826   video.screen_height = height;
827   video.screen_xoffset = 0;
828   video.screen_yoffset = 0;
829
830 #if defined(USE_COMPLETE_DISPLAY)
831   float ratio_video   = (float) width / height;
832   float ratio_display = (float) video.display_width / video.display_height;
833
834   if (ratio_video != ratio_display)
835   {
836     // adjust drawable screen size to cover the whole device display
837
838     if (ratio_video < ratio_display)
839       video.screen_width  *= ratio_display / ratio_video;
840     else
841       video.screen_height *= ratio_video / ratio_display;
842
843     video.screen_xoffset = (video.screen_width  - width)  / 2;
844     video.screen_yoffset = (video.screen_height - height) / 2;
845
846 #if 0
847     Debug("video", "Changing screen from %dx%d to %dx%d (%.2f to %.2f)",
848           width, height,
849           video.screen_width, video.screen_height,
850           ratio_video, ratio_display);
851 #endif
852   }
853 #endif
854 }
855
856 void SDLSetScreenSizeForRenderer(int width, int height)
857 {
858   SDL_RenderSetLogicalSize(sdl_renderer, width, height);
859 }
860
861 void SDLSetScreenProperties(void)
862 {
863   SDLSetDisplaySize();
864   SDLSetScreenSizeAndOffsets(video.width, video.height);
865   SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
866 }
867
868 void SDLSetScreenRenderingMode(char *screen_rendering_mode)
869 {
870   video.screen_rendering_mode =
871     (strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_BITMAP) ?
872      SPECIAL_RENDERING_BITMAP :
873      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_TARGET) ?
874      SPECIAL_RENDERING_TARGET:
875      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_DOUBLE) ?
876      SPECIAL_RENDERING_DOUBLE : SPECIAL_RENDERING_OFF);
877 }
878
879 void SDLSetScreenVsyncMode(char *vsync_mode)
880 {
881   // changing vsync mode without re-creating renderer only supported by OpenGL
882   if (!strPrefixLower((char *)SDLGetRendererName(), "opengl"))
883     return;
884
885   int interval = VSYNC_MODE_STR_TO_INT(vsync_mode);
886   int result = SDL_GL_SetSwapInterval(interval);
887
888   // if adaptive vsync requested, but not supported, retry with normal vsync
889   if (result == -1 && interval == VSYNC_MODE_ADAPTIVE)
890   {
891     interval = VSYNC_MODE_NORMAL;
892
893     result = SDL_GL_SetSwapInterval(interval);
894   }
895
896   if (result == -1)
897     interval = VSYNC_MODE_OFF;
898
899   video.vsync_mode = interval;
900 }
901
902 void SDLRedrawWindow(void)
903 {
904   UpdateScreen_WithoutFrameDelay(NULL);
905 }
906
907 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
908                             int depth)
909 {
910   if (program.headless)
911     return;
912
913   SDL_Surface *surface =
914     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
915
916   if (surface == NULL)
917     Fail("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
918
919   SDLSetNativeSurface(&surface);
920
921   bitmap->surface = surface;
922 }
923
924 void SDLFreeBitmapPointers(Bitmap *bitmap)
925 {
926   if (bitmap->surface)
927     SDL_FreeSurface(bitmap->surface);
928   if (bitmap->surface_masked)
929     SDL_FreeSurface(bitmap->surface_masked);
930
931   bitmap->surface = NULL;
932   bitmap->surface_masked = NULL;
933
934   if (bitmap->texture)
935     SDL_DestroyTexture(bitmap->texture);
936   if (bitmap->texture_masked)
937     SDL_DestroyTexture(bitmap->texture_masked);
938
939   bitmap->texture = NULL;
940   bitmap->texture_masked = NULL;
941 }
942
943 void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
944                  int src_x, int src_y, int width, int height,
945                  int dst_x, int dst_y, int mask_mode)
946 {
947   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
948   SDL_Rect src_rect, dst_rect;
949
950   src_rect.x = src_x;
951   src_rect.y = src_y;
952   src_rect.w = width;
953   src_rect.h = height;
954
955   dst_rect.x = dst_x;
956   dst_rect.y = dst_y;
957   dst_rect.w = width;
958   dst_rect.h = height;
959
960   // if (src_bitmap != backbuffer || dst_bitmap != window)
961   if (!(src_bitmap == backbuffer && dst_bitmap == window))
962     SDL_BlitSurface((mask_mode == BLIT_MASKED ?
963                      src_bitmap->surface_masked : src_bitmap->surface),
964                     &src_rect, real_dst_bitmap->surface, &dst_rect);
965
966   if (dst_bitmap == window)
967     UpdateScreen_WithFrameDelay(&dst_rect);
968 }
969
970 void SDLBlitTexture(Bitmap *bitmap,
971                     int src_x, int src_y, int width, int height,
972                     int dst_x, int dst_y, int mask_mode)
973 {
974   SDL_Texture *texture;
975   SDL_Rect src_rect;
976   SDL_Rect dst_rect;
977
978   texture =
979     (mask_mode == BLIT_MASKED ? bitmap->texture_masked : bitmap->texture);
980
981   if (texture == NULL)
982     return;
983
984   src_rect.x = src_x;
985   src_rect.y = src_y;
986   src_rect.w = width;
987   src_rect.h = height;
988
989   dst_rect.x = dst_x;
990   dst_rect.y = dst_y;
991   dst_rect.w = width;
992   dst_rect.h = height;
993
994   SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
995 }
996
997 void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
998                       Uint32 color)
999 {
1000   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
1001   SDL_Rect rect;
1002
1003   rect.x = x;
1004   rect.y = y;
1005   rect.w = width;
1006   rect.h = height;
1007
1008   SDL_FillRect(real_dst_bitmap->surface, &rect, color);
1009
1010   if (dst_bitmap == window)
1011     UpdateScreen_WithFrameDelay(&rect);
1012 }
1013
1014 void PrepareFadeBitmap(int draw_target)
1015 {
1016   Bitmap *fade_bitmap =
1017     (draw_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source :
1018      draw_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL);
1019
1020   if (fade_bitmap == NULL)
1021     return;
1022
1023   // copy backbuffer to fading buffer
1024   BlitBitmap(backbuffer, fade_bitmap, 0, 0, gfx.win_xsize, gfx.win_ysize, 0, 0);
1025
1026   // add border and animations to fading buffer
1027   FinalizeScreen(draw_target);
1028 }
1029
1030 void SDLFadeRectangle(int x, int y, int width, int height,
1031                       int fade_mode, int fade_delay, int post_delay,
1032                       void (*draw_border_function)(void))
1033 {
1034   SDL_Surface *surface_backup = gfx.fade_bitmap_backup->surface;
1035   SDL_Surface *surface_source = gfx.fade_bitmap_source->surface;
1036   SDL_Surface *surface_target = gfx.fade_bitmap_target->surface;
1037   SDL_Surface *surface_black  = gfx.fade_bitmap_black->surface;
1038   SDL_Surface *surface_screen = backbuffer->surface;
1039   SDL_Rect src_rect, dst_rect;
1040   SDL_Rect dst_rect2;
1041   int src_x = x, src_y = y;
1042   int dst_x = x, dst_y = y;
1043   unsigned int time_last, time_current;
1044
1045   // store function for drawing global masked border
1046   void (*draw_global_border_function)(int) = gfx.draw_global_border_function;
1047
1048   // deactivate drawing of global border while fading, if needed
1049   if (draw_border_function == NULL)
1050     gfx.draw_global_border_function = NULL;
1051
1052   src_rect.x = src_x;
1053   src_rect.y = src_y;
1054   src_rect.w = width;
1055   src_rect.h = height;
1056
1057   dst_rect.x = dst_x;
1058   dst_rect.y = dst_y;
1059   dst_rect.w = width;           // (ignored)
1060   dst_rect.h = height;          // (ignored)
1061
1062   dst_rect2 = dst_rect;
1063
1064   // before fading in, store backbuffer (without animation graphics)
1065   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1066     SDL_BlitSurface(surface_screen, &dst_rect, surface_backup, &src_rect);
1067
1068   // copy source and target surfaces to temporary surfaces for fading
1069   if (fade_mode & FADE_TYPE_TRANSFORM)
1070   {
1071     // (source and target fading buffer already prepared)
1072   }
1073   else if (fade_mode & FADE_TYPE_FADE_IN)
1074   {
1075     // (target fading buffer already prepared)
1076     SDL_BlitSurface(surface_black,  &src_rect, surface_source, &src_rect);
1077   }
1078   else          // FADE_TYPE_FADE_OUT
1079   {
1080     // (source fading buffer already prepared)
1081     SDL_BlitSurface(surface_black,  &src_rect, surface_target, &src_rect);
1082   }
1083
1084   time_current = SDL_GetTicks();
1085
1086   if (fade_delay <= 0)
1087   {
1088     // immediately draw final target frame without delay
1089     fade_mode &= (FADE_MODE_FADE | FADE_MODE_TRANSFORM);
1090     fade_delay = 1;
1091     time_current -= 1;
1092
1093     // when fading without delay, also skip post delay
1094     post_delay = 0;
1095   }
1096
1097   if (fade_mode == FADE_MODE_MELT)
1098   {
1099     boolean done = FALSE;
1100     int melt_pixels = 2;
1101     int melt_columns = width / melt_pixels;
1102     int ypos[melt_columns];
1103     int max_steps = height / 8 + 32;
1104     int steps_done = 0;
1105     float steps = 0;
1106     int i;
1107
1108     SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1109
1110     SDLSetAlpha(surface_target, FALSE, 0);      // disable alpha blending
1111
1112     ypos[0] = -GetSimpleRandom(16);
1113
1114     for (i = 1 ; i < melt_columns; i++)
1115     {
1116       int r = GetSimpleRandom(3) - 1;   // randomly choose from { -1, 0, -1 }
1117
1118       ypos[i] = ypos[i - 1] + r;
1119
1120       if (ypos[i] > 0)
1121         ypos[i] = 0;
1122       else
1123         if (ypos[i] == -16)
1124           ypos[i] = -15;
1125     }
1126
1127     while (!done)
1128     {
1129       int steps_final;
1130
1131       time_last = time_current;
1132       time_current = SDL_GetTicks();
1133       steps += max_steps * ((float)(time_current - time_last) / fade_delay);
1134       steps_final = MIN(MAX(0, steps), max_steps);
1135
1136       steps_done++;
1137
1138       done = (steps_done >= steps_final);
1139
1140       for (i = 0 ; i < melt_columns; i++)
1141       {
1142         if (ypos[i] < 0)
1143         {
1144           ypos[i]++;
1145
1146           done = FALSE;
1147         }
1148         else if (ypos[i] < height)
1149         {
1150           int y1 = 16;
1151           int y2 = 8;
1152           int y3 = 8;
1153           int dy = (ypos[i] < y1) ? ypos[i] + 1 : y2 + GetSimpleRandom(y3);
1154
1155           if (ypos[i] + dy >= height)
1156             dy = height - ypos[i];
1157
1158           // copy part of (appearing) target surface to upper area
1159           src_rect.x = src_x + i * melt_pixels;
1160           // src_rect.y = src_y + ypos[i];
1161           src_rect.y = src_y;
1162           src_rect.w = melt_pixels;
1163           // src_rect.h = dy;
1164           src_rect.h = ypos[i] + dy;
1165
1166           dst_rect.x = dst_x + i * melt_pixels;
1167           // dst_rect.y = dst_y + ypos[i];
1168           dst_rect.y = dst_y;
1169
1170           if (steps_done >= steps_final)
1171             SDL_BlitSurface(surface_target, &src_rect,
1172                             surface_screen, &dst_rect);
1173
1174           ypos[i] += dy;
1175
1176           // copy part of (disappearing) source surface to lower area
1177           src_rect.x = src_x + i * melt_pixels;
1178           src_rect.y = src_y;
1179           src_rect.w = melt_pixels;
1180           src_rect.h = height - ypos[i];
1181
1182           dst_rect.x = dst_x + i * melt_pixels;
1183           dst_rect.y = dst_y + ypos[i];
1184
1185           if (steps_done >= steps_final)
1186             SDL_BlitSurface(surface_source, &src_rect,
1187                             surface_screen, &dst_rect);
1188
1189           done = FALSE;
1190         }
1191         else
1192         {
1193           src_rect.x = src_x + i * melt_pixels;
1194           src_rect.y = src_y;
1195           src_rect.w = melt_pixels;
1196           src_rect.h = height;
1197
1198           dst_rect.x = dst_x + i * melt_pixels;
1199           dst_rect.y = dst_y;
1200
1201           if (steps_done >= steps_final)
1202             SDL_BlitSurface(surface_target, &src_rect,
1203                             surface_screen, &dst_rect);
1204         }
1205       }
1206
1207       if (steps_done >= steps_final)
1208       {
1209         if (draw_border_function != NULL)
1210           draw_border_function();
1211
1212         UpdateScreen_WithFrameDelay(&dst_rect2);
1213       }
1214     }
1215   }
1216   else if (fade_mode == FADE_MODE_CURTAIN)
1217   {
1218     float xx;
1219     int xx_final;
1220     int xx_size = width / 2;
1221
1222     SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1223
1224     SDLSetAlpha(surface_source, FALSE, 0);      // disable alpha blending
1225
1226     for (xx = 0; xx < xx_size;)
1227     {
1228       time_last = time_current;
1229       time_current = SDL_GetTicks();
1230       xx += xx_size * ((float)(time_current - time_last) / fade_delay);
1231       xx_final = MIN(MAX(0, xx), xx_size);
1232
1233       src_rect.x = src_x;
1234       src_rect.y = src_y;
1235       src_rect.w = width;
1236       src_rect.h = height;
1237
1238       dst_rect.x = dst_x;
1239       dst_rect.y = dst_y;
1240
1241       // draw new (target) image to screen buffer
1242       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1243
1244       if (xx_final < xx_size)
1245       {
1246         src_rect.w = xx_size - xx_final;
1247         src_rect.h = height;
1248
1249         // draw old (source) image to screen buffer (left side)
1250
1251         src_rect.x = src_x + xx_final;
1252         dst_rect.x = dst_x;
1253
1254         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1255
1256         // draw old (source) image to screen buffer (right side)
1257
1258         src_rect.x = src_x + xx_size;
1259         dst_rect.x = dst_x + xx_size + xx_final;
1260
1261         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1262       }
1263
1264       if (draw_border_function != NULL)
1265         draw_border_function();
1266
1267       // only update the region of the screen that is affected from fading
1268       UpdateScreen_WithFrameDelay(&dst_rect2);
1269     }
1270   }
1271   else          // fading in, fading out or cross-fading
1272   {
1273     float alpha;
1274     int alpha_final;
1275
1276     for (alpha = 0.0; alpha < 255.0;)
1277     {
1278       time_last = time_current;
1279       time_current = SDL_GetTicks();
1280       alpha += 255 * ((float)(time_current - time_last) / fade_delay);
1281       alpha_final = MIN(MAX(0, alpha), 255);
1282
1283       // draw existing (source) image to screen buffer
1284       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1285
1286       // draw new (target) image to screen buffer using alpha blending
1287       SDLSetAlpha(surface_target, TRUE, alpha_final);
1288       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1289
1290       if (draw_border_function != NULL)
1291         draw_border_function();
1292
1293       // only update the region of the screen that is affected from fading
1294       UpdateScreen_WithFrameDelay(&dst_rect);
1295     }
1296   }
1297
1298   if (post_delay > 0)
1299     Delay_WithScreenUpdates(post_delay);
1300
1301   // restore function for drawing global masked border
1302   gfx.draw_global_border_function = draw_global_border_function;
1303
1304   // after fading in, restore backbuffer (without animation graphics)
1305   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1306     SDL_BlitSurface(surface_backup, &dst_rect, surface_screen, &src_rect);
1307 }
1308
1309 void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
1310                        int to_x, int to_y, Uint32 color)
1311 {
1312   SDL_Surface *surface = dst_bitmap->surface;
1313   SDL_Rect rect;
1314
1315   if (from_x > to_x)
1316     swap_numbers(&from_x, &to_x);
1317
1318   if (from_y > to_y)
1319     swap_numbers(&from_y, &to_y);
1320
1321   rect.x = from_x;
1322   rect.y = from_y;
1323   rect.w = (to_x - from_x + 1);
1324   rect.h = (to_y - from_y + 1);
1325
1326   SDL_FillRect(surface, &rect, color);
1327 }
1328
1329 void SDLDrawLine(Bitmap *dst_bitmap, int from_x, int from_y,
1330                  int to_x, int to_y, Uint32 color)
1331 {
1332   sge_Line(dst_bitmap->surface, from_x, from_y, to_x, to_y, color);
1333 }
1334
1335 #if ENABLE_UNUSED_CODE
1336 void SDLDrawLines(SDL_Surface *surface, struct XY *points,
1337                   int num_points, Uint32 color)
1338 {
1339   int i, x, y;
1340   int line_width = 4;
1341
1342   for (i = 0; i < num_points - 1; i++)
1343   {
1344     for (x = 0; x < line_width; x++)
1345     {
1346       for (y = 0; y < line_width; y++)
1347       {
1348         int dx = x - line_width / 2;
1349         int dy = y - line_width / 2;
1350
1351         if ((x == 0 && y == 0) ||
1352             (x == 0 && y == line_width - 1) ||
1353             (x == line_width - 1 && y == 0) ||
1354             (x == line_width - 1 && y == line_width - 1))
1355           continue;
1356
1357         sge_Line(surface, points[i].x + dx, points[i].y + dy,
1358                  points[i+1].x + dx, points[i+1].y + dy, color);
1359       }
1360     }
1361   }
1362 }
1363 #endif
1364
1365 Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
1366 {
1367   SDL_Surface *surface = src_bitmap->surface;
1368
1369   switch (surface->format->BytesPerPixel)
1370   {
1371     case 1:             // assuming 8-bpp
1372     {
1373       return *((Uint8 *)surface->pixels + y * surface->pitch + x);
1374     }
1375     break;
1376
1377     case 2:             // probably 15-bpp or 16-bpp
1378     {
1379       return *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x);
1380     }
1381     break;
1382
1383   case 3:               // slow 24-bpp mode; usually not used
1384     {
1385       // does this work?
1386       Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1387       Uint32 color = 0;
1388       int shift;
1389
1390       shift = surface->format->Rshift;
1391       color |= *(pix + shift / 8) >> shift;
1392       shift = surface->format->Gshift;
1393       color |= *(pix + shift / 8) >> shift;
1394       shift = surface->format->Bshift;
1395       color |= *(pix + shift / 8) >> shift;
1396
1397       return color;
1398     }
1399     break;
1400
1401   case 4:               // probably 32-bpp
1402     {
1403       return *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x);
1404     }
1405     break;
1406   }
1407
1408   return 0;
1409 }
1410
1411
1412 // ============================================================================
1413 // The following functions were taken from the SGE library
1414 // (SDL Graphics Extension Library) by Anders Lindström
1415 // http://www.etek.chalmers.se/~e8cal1/sge/index.html
1416 // ============================================================================
1417
1418 static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1419 {
1420   if (x >= 0 && x <= surface->w - 1 && y >= 0 && y <= surface->h - 1)
1421   {
1422     switch (surface->format->BytesPerPixel)
1423     {
1424       case 1:
1425       {
1426         // Assuming 8-bpp
1427         *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1428       }
1429       break;
1430
1431       case 2:
1432       {
1433         // Probably 15-bpp or 16-bpp
1434         *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1435       }
1436       break;
1437
1438       case 3:
1439       {
1440         // Slow 24-bpp mode, usually not used
1441         Uint8 *pix;
1442         int shift;
1443
1444         // Gack - slow, but endian correct
1445         pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1446         shift = surface->format->Rshift;
1447         *(pix+shift/8) = color>>shift;
1448         shift = surface->format->Gshift;
1449         *(pix+shift/8) = color>>shift;
1450         shift = surface->format->Bshift;
1451         *(pix+shift/8) = color>>shift;
1452       }
1453       break;
1454
1455       case 4:
1456       {
1457         // Probably 32-bpp
1458         *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1459       }
1460       break;
1461     }
1462   }
1463 }
1464
1465 #if 0
1466 static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1467                          Uint8 R, Uint8 G, Uint8 B)
1468 {
1469   _PutPixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
1470 }
1471
1472 static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1473 {
1474   *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1475 }
1476
1477 static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1478 {
1479   *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1480 }
1481
1482 static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1483 {
1484   Uint8 *pix;
1485   int shift;
1486
1487   // Gack - slow, but endian correct
1488   pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1489   shift = surface->format->Rshift;
1490   *(pix+shift/8) = color>>shift;
1491   shift = surface->format->Gshift;
1492   *(pix+shift/8) = color>>shift;
1493   shift = surface->format->Bshift;
1494   *(pix+shift/8) = color>>shift;
1495 }
1496
1497 static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1498 {
1499   *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1500 }
1501
1502 static void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
1503 {
1504   switch (dest->format->BytesPerPixel)
1505   {
1506     case 1:
1507       *((Uint8 *)dest->pixels + y*dest->pitch + x) = color;
1508       break;
1509
1510     case 2:
1511       *((Uint16 *)dest->pixels + y*dest->pitch/2 + x) = color;
1512       break;
1513
1514     case 3:
1515       _PutPixel24(dest,x,y,color);
1516       break;
1517
1518     case 4:
1519       *((Uint32 *)dest->pixels + y*dest->pitch/4 + x) = color;
1520       break;
1521   }
1522 }
1523 #endif
1524
1525 static void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1526 {
1527   if (SDL_MUSTLOCK(surface))
1528   {
1529     if (SDL_LockSurface(surface) < 0)
1530     {
1531       return;
1532     }
1533   }
1534
1535   _PutPixel(surface, x, y, color);
1536
1537   if (SDL_MUSTLOCK(surface))
1538   {
1539     SDL_UnlockSurface(surface);
1540   }
1541 }
1542
1543 #if 0
1544 static void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1545                             Uint8 r, Uint8 g, Uint8 b)
1546 {
1547   sge_PutPixel(surface, x, y, SDL_MapRGB(surface->format, r, g, b));
1548 }
1549
1550 static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
1551 {
1552   if (y >= 0 && y <= dest->h - 1)
1553   {
1554     switch (dest->format->BytesPerPixel)
1555     {
1556       case 1:
1557         return y*dest->pitch;
1558         break;
1559
1560       case 2:
1561         return y*dest->pitch/2;
1562         break;
1563
1564       case 3:
1565         return y*dest->pitch;
1566         break;
1567
1568       case 4:
1569         return y*dest->pitch/4;
1570         break;
1571     }
1572   }
1573
1574   return -1;
1575 }
1576
1577 static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch,
1578                           Uint32 color)
1579 {
1580   if (x >= 0 && x <= surface->w - 1 && ypitch >= 0)
1581   {
1582     switch (surface->format->BytesPerPixel)
1583     {
1584       case 1:
1585       {
1586         // Assuming 8-bpp
1587         *((Uint8 *)surface->pixels + ypitch + x) = color;
1588       }
1589       break;
1590
1591       case 2:
1592       {
1593         // Probably 15-bpp or 16-bpp
1594         *((Uint16 *)surface->pixels + ypitch + x) = color;
1595       }
1596       break;
1597
1598       case 3:
1599       {
1600         // Slow 24-bpp mode, usually not used
1601         Uint8 *pix;
1602         int shift;
1603
1604         // Gack - slow, but endian correct
1605         pix = (Uint8 *)surface->pixels + ypitch + x*3;
1606         shift = surface->format->Rshift;
1607         *(pix+shift/8) = color>>shift;
1608         shift = surface->format->Gshift;
1609         *(pix+shift/8) = color>>shift;
1610         shift = surface->format->Bshift;
1611         *(pix+shift/8) = color>>shift;
1612       }
1613       break;
1614
1615       case 4:
1616       {
1617         // Probably 32-bpp
1618         *((Uint32 *)surface->pixels + ypitch + x) = color;
1619       }
1620       break;
1621     }
1622   }
1623 }
1624
1625 static void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1626                       Uint32 Color)
1627 {
1628   SDL_Rect l;
1629
1630   if (SDL_MUSTLOCK(Surface))
1631   {
1632     if (SDL_LockSurface(Surface) < 0)
1633     {
1634       return;
1635     }
1636   }
1637
1638   if (x1 > x2)
1639   {
1640     Sint16 tmp = x1;
1641     x1 = x2;
1642     x2 = tmp;
1643   }
1644
1645   // Do the clipping
1646   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1647     return;
1648   if (x1 < 0)
1649     x1 = 0;
1650   if (x2 > Surface->w - 1)
1651     x2 = Surface->w - 1;
1652
1653   l.x = x1;
1654   l.y = y;
1655   l.w = x2 - x1 + 1;
1656   l.h = 1;
1657
1658   SDL_FillRect(Surface, &l, Color);
1659
1660   if (SDL_MUSTLOCK(Surface))
1661   {
1662     SDL_UnlockSurface(Surface);
1663   }
1664 }
1665
1666 static void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1667                          Uint8 R, Uint8 G, Uint8 B)
1668 {
1669   sge_HLine(Surface, x1, x2, y, SDL_MapRGB(Surface->format, R, G, B));
1670 }
1671
1672 static void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1673                    Uint32 Color)
1674 {
1675   SDL_Rect l;
1676
1677   if (x1 > x2)
1678   {
1679     Sint16 tmp = x1;
1680     x1 = x2;
1681     x2 = tmp;
1682   }
1683
1684   // Do the clipping
1685   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1686     return;
1687   if (x1 < 0)
1688     x1 = 0;
1689   if (x2 > Surface->w - 1)
1690     x2 = Surface->w - 1;
1691
1692   l.x = x1;
1693   l.y = y;
1694   l.w = x2 - x1 + 1;
1695   l.h = 1;
1696
1697   SDL_FillRect(Surface, &l, Color);
1698 }
1699
1700 static void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1701                       Uint32 Color)
1702 {
1703   SDL_Rect l;
1704
1705   if (SDL_MUSTLOCK(Surface))
1706   {
1707     if (SDL_LockSurface(Surface) < 0)
1708     {
1709       return;
1710     }
1711   }
1712
1713   if (y1 > y2)
1714   {
1715     Sint16 tmp = y1;
1716     y1 = y2;
1717     y2 = tmp;
1718   }
1719
1720   // Do the clipping
1721   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1722     return;
1723   if (y1 < 0)
1724     y1 = 0;
1725   if (y2 > Surface->h - 1)
1726     y2 = Surface->h - 1;
1727
1728   l.x = x;
1729   l.y = y1;
1730   l.w = 1;
1731   l.h = y2 - y1 + 1;
1732
1733   SDL_FillRect(Surface, &l, Color);
1734
1735   if (SDL_MUSTLOCK(Surface))
1736   {
1737     SDL_UnlockSurface(Surface);
1738   }
1739 }
1740
1741 static void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1742                          Uint8 R, Uint8 G, Uint8 B)
1743 {
1744   sge_VLine(Surface, x, y1, y2, SDL_MapRGB(Surface->format, R, G, B));
1745 }
1746
1747 static void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1748                    Uint32 Color)
1749 {
1750   SDL_Rect l;
1751
1752   if (y1 > y2)
1753   {
1754     Sint16 tmp = y1;
1755     y1 = y2;
1756     y2 = tmp;
1757   }
1758
1759   // Do the clipping
1760   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1761     return;
1762   if (y1 < 0)
1763     y1 = 0;
1764   if (y2 > Surface->h - 1)
1765     y2 = Surface->h - 1;
1766
1767   l.x = x;
1768   l.y = y1;
1769   l.w = 1;
1770   l.h = y2 - y1 + 1;
1771
1772   SDL_FillRect(Surface, &l, Color);
1773 }
1774 #endif
1775
1776 static void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
1777                        Sint16 x2, Sint16 y2, Uint32 Color,
1778                        void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1779                                      Uint32 Color))
1780 {
1781   Sint16 dx, dy, sdx, sdy, x, y, px, py;
1782
1783   dx = x2 - x1;
1784   dy = y2 - y1;
1785
1786   sdx = (dx < 0) ? -1 : 1;
1787   sdy = (dy < 0) ? -1 : 1;
1788
1789   dx = sdx * dx + 1;
1790   dy = sdy * dy + 1;
1791
1792   x = y = 0;
1793
1794   px = x1;
1795   py = y1;
1796
1797   if (dx >= dy)
1798   {
1799     for (x = 0; x < dx; x++)
1800     {
1801       Callback(Surface, px, py, Color);
1802
1803       y += dy;
1804       if (y >= dx)
1805       {
1806         y -= dx;
1807         py += sdy;
1808       }
1809
1810       px += sdx;
1811     }
1812   }
1813   else
1814   {
1815     for (y = 0; y < dy; y++)
1816     {
1817       Callback(Surface, px, py, Color);
1818
1819       x += dx;
1820       if (x >= dy)
1821       {
1822         x -= dy;
1823         px += sdx;
1824       }
1825
1826       py += sdy;
1827     }
1828   }
1829 }
1830
1831 #if 0
1832 static void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
1833                           Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
1834                           void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1835                                         Uint32 Color))
1836 {
1837   sge_DoLine(Surface, X1, Y1, X2, Y2,
1838              SDL_MapRGB(Surface->format, R, G, B), Callback);
1839 }
1840 #endif
1841
1842 void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
1843               Uint32 Color)
1844 {
1845   if (SDL_MUSTLOCK(Surface))
1846   {
1847     if (SDL_LockSurface(Surface) < 0)
1848       return;
1849    }
1850
1851    // Draw the line
1852    sge_DoLine(Surface, x1, y1, x2, y2, Color, _PutPixel);
1853
1854    // unlock the display
1855    if (SDL_MUSTLOCK(Surface))
1856    {
1857       SDL_UnlockSurface(Surface);
1858    }
1859 }
1860
1861 #if 0
1862 static void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
1863                         Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
1864 {
1865   sge_Line(Surface, x1, y1, x2, y2, SDL_MapRGB(Surface->format, R, G, B));
1866 }
1867 #endif
1868
1869 void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel)
1870 {
1871   sge_PutPixel(dst_bitmap->surface, x, y, pixel);
1872 }
1873
1874
1875 // ----------------------------------------------------------------------------
1876 // quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
1877 // ----------------------------------------------------------------------------
1878
1879 void SDLInvertArea(Bitmap *bitmap, int src_x, int src_y,
1880                    int width, int height, Uint32 color)
1881 {
1882   int x, y;
1883
1884   for (y = src_y; y < src_y + height; y++)
1885   {
1886     for (x = src_x; x < src_x + width; x++)
1887     {
1888       Uint32 pixel = SDLGetPixel(bitmap, x, y);
1889
1890       SDLPutPixel(bitmap, x, y, pixel == BLACK_PIXEL ? color : BLACK_PIXEL);
1891     }
1892   }
1893 }
1894
1895 void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
1896                           int src_x, int src_y, int width, int height,
1897                           int dst_x, int dst_y)
1898 {
1899   int x, y;
1900
1901   for (y = 0; y < height; y++)
1902   {
1903     for (x = 0; x < width; x++)
1904     {
1905       Uint32 pixel = SDLGetPixel(src_bitmap, src_x + x, src_y + y);
1906
1907       if (pixel != BLACK_PIXEL)
1908         SDLPutPixel(dst_bitmap, dst_x + x, dst_y + y, BLACK_PIXEL);
1909     }
1910   }
1911 }
1912
1913
1914 // ============================================================================
1915 // The following functions were taken from the SDL_gfx library version 2.0.3
1916 // (Rotozoomer) by Andreas Schiffler
1917 // http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html
1918 // ============================================================================
1919
1920 // ----------------------------------------------------------------------------
1921 // 32 bit zoomer
1922 //
1923 // zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
1924 // ----------------------------------------------------------------------------
1925
1926 typedef struct
1927 {
1928   Uint8 r;
1929   Uint8 g;
1930   Uint8 b;
1931   Uint8 a;
1932 } tColorRGBA;
1933
1934 static int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
1935 {
1936   int x, y;
1937   tColorRGBA *sp, *csp, *dp;
1938   int dgap;
1939
1940   // pointer setup
1941   sp = csp = (tColorRGBA *) src->pixels;
1942   dp = (tColorRGBA *) dst->pixels;
1943   dgap = dst->pitch - dst->w * 4;
1944
1945   for (y = 0; y < dst->h; y++)
1946   {
1947     sp = csp;
1948
1949     for (x = 0; x < dst->w; x++)
1950     {
1951       tColorRGBA *sp0 = sp;
1952       tColorRGBA *sp1 = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
1953       tColorRGBA *sp00 = &sp0[0];
1954       tColorRGBA *sp01 = &sp0[1];
1955       tColorRGBA *sp10 = &sp1[0];
1956       tColorRGBA *sp11 = &sp1[1];
1957       tColorRGBA new;
1958
1959       // create new color pixel from all four source color pixels
1960       new.r = (sp00->r + sp01->r + sp10->r + sp11->r) / 4;
1961       new.g = (sp00->g + sp01->g + sp10->g + sp11->g) / 4;
1962       new.b = (sp00->b + sp01->b + sp10->b + sp11->b) / 4;
1963       new.a = (sp00->a + sp01->a + sp10->a + sp11->a) / 4;
1964
1965       // draw
1966       *dp = new;
1967
1968       // advance source pointers
1969       sp += 2;
1970
1971       // advance destination pointer
1972       dp++;
1973     }
1974
1975     // advance source pointer
1976     csp = (tColorRGBA *) ((Uint8 *) csp + 2 * src->pitch);
1977
1978     // advance destination pointers
1979     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
1980   }
1981
1982   return 0;
1983 }
1984
1985 static int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
1986 {
1987   int x, y, *sax, *say, *csax, *csay;
1988   float sx, sy;
1989   tColorRGBA *sp, *csp, *csp0, *dp;
1990   int dgap;
1991
1992   // use specialized zoom function when scaling down to exactly half size
1993   if (src->w == 2 * dst->w &&
1994       src->h == 2 * dst->h)
1995     return zoomSurfaceRGBA_scaleDownBy2(src, dst);
1996
1997   // variable setup
1998   sx = (float) src->w / (float) dst->w;
1999   sy = (float) src->h / (float) dst->h;
2000
2001   // allocate memory for row increments
2002   csax = sax = (int *)checked_malloc((dst->w + 1) * sizeof(Uint32));
2003   csay = say = (int *)checked_malloc((dst->h + 1) * sizeof(Uint32));
2004
2005   // precalculate row increments
2006   for (x = 0; x <= dst->w; x++)
2007     *csax++ = (int)(sx * x);
2008
2009   for (y = 0; y <= dst->h; y++)
2010     *csay++ = (int)(sy * y);
2011
2012   // pointer setup
2013   sp = csp = csp0 = (tColorRGBA *) src->pixels;
2014   dp = (tColorRGBA *) dst->pixels;
2015   dgap = dst->pitch - dst->w * 4;
2016
2017   csay = say;
2018   for (y = 0; y < dst->h; y++)
2019   {
2020     sp = csp;
2021     csax = sax;
2022
2023     for (x = 0; x < dst->w; x++)
2024     {
2025       // draw
2026       *dp = *sp;
2027
2028       // advance source pointers
2029       csax++;
2030       sp = csp + *csax;
2031
2032       // advance destination pointer
2033       dp++;
2034     }
2035
2036     // advance source pointer
2037     csay++;
2038     csp = (tColorRGBA *) ((Uint8 *) csp0 + *csay * src->pitch);
2039
2040     // advance destination pointers
2041     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
2042   }
2043
2044   free(sax);
2045   free(say);
2046
2047   return 0;
2048 }
2049
2050 // ----------------------------------------------------------------------------
2051 // 8 bit zoomer
2052 //
2053 // zoomes 8 bit palette/Y 'src' surface to 'dst' surface
2054 // ----------------------------------------------------------------------------
2055
2056 static int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
2057 {
2058   Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
2059   Uint8 *sp, *dp, *csp;
2060   int dgap;
2061
2062   // variable setup
2063   sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
2064   sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
2065
2066   // allocate memory for row increments
2067   sax = (Uint32 *)checked_malloc(dst->w * sizeof(Uint32));
2068   say = (Uint32 *)checked_malloc(dst->h * sizeof(Uint32));
2069
2070   // precalculate row increments
2071   csx = 0;
2072   csax = sax;
2073   for (x = 0; x < dst->w; x++)
2074   {
2075     csx += sx;
2076     *csax = (csx >> 16);
2077     csx &= 0xffff;
2078     csax++;
2079   }
2080
2081   csy = 0;
2082   csay = say;
2083   for (y = 0; y < dst->h; y++)
2084   {
2085     csy += sy;
2086     *csay = (csy >> 16);
2087     csy &= 0xffff;
2088     csay++;
2089   }
2090
2091   csx = 0;
2092   csax = sax;
2093   for (x = 0; x < dst->w; x++)
2094   {
2095     csx += (*csax);
2096     csax++;
2097   }
2098
2099   csy = 0;
2100   csay = say;
2101   for (y = 0; y < dst->h; y++)
2102   {
2103     csy += (*csay);
2104     csay++;
2105   }
2106
2107   // pointer setup
2108   sp = csp = (Uint8 *) src->pixels;
2109   dp = (Uint8 *) dst->pixels;
2110   dgap = dst->pitch - dst->w;
2111
2112   // draw
2113   csay = say;
2114   for (y = 0; y < dst->h; y++)
2115   {
2116     csax = sax;
2117     sp = csp;
2118     for (x = 0; x < dst->w; x++)
2119     {
2120       // draw
2121       *dp = *sp;
2122
2123       // advance source pointers
2124       sp += (*csax);
2125       csax++;
2126
2127       // advance destination pointer
2128       dp++;
2129     }
2130
2131     // advance source pointer (for row)
2132     csp += ((*csay) * src->pitch);
2133     csay++;
2134
2135     // advance destination pointers
2136     dp += dgap;
2137   }
2138
2139   free(sax);
2140   free(say);
2141
2142   return 0;
2143 }
2144
2145 // ----------------------------------------------------------------------------
2146 // zoomSurface()
2147 //
2148 // Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
2149 // 'zoomx' and 'zoomy' are scaling factors for width and height.
2150 // If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
2151 // into a 32bit RGBA format on the fly.
2152 // ----------------------------------------------------------------------------
2153
2154 static SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
2155 {
2156   SDL_Surface *zoom_src = NULL;
2157   SDL_Surface *zoom_dst = NULL;
2158   boolean is_converted = FALSE;
2159   boolean is_32bit;
2160   int i;
2161
2162   if (src == NULL)
2163     return NULL;
2164
2165   // determine if source surface is 32 bit or 8 bit
2166   is_32bit = (src->format->BitsPerPixel == 32);
2167
2168   if (is_32bit || src->format->BitsPerPixel == 8)
2169   {
2170     // use source surface 'as is'
2171     zoom_src = src;
2172   }
2173   else
2174   {
2175     // new source surface is 32 bit with a defined RGB ordering
2176     zoom_src = SDL_CreateRGBSurface(SURFACE_FLAGS, src->w, src->h, 32,
2177                                     0x000000ff, 0x0000ff00, 0x00ff0000,
2178                                     (src->format->Amask ? 0xff000000 : 0));
2179     SDL_BlitSurface(src, NULL, zoom_src, NULL);
2180     is_32bit = TRUE;
2181     is_converted = TRUE;
2182   }
2183
2184   // allocate surface to completely contain the zoomed surface
2185   if (is_32bit)
2186   {
2187     // target surface is 32 bit with source RGBA/ABGR ordering
2188     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 32,
2189                                     zoom_src->format->Rmask,
2190                                     zoom_src->format->Gmask,
2191                                     zoom_src->format->Bmask,
2192                                     zoom_src->format->Amask);
2193   }
2194   else
2195   {
2196     // target surface is 8 bit
2197     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 8,
2198                                     0, 0, 0, 0);
2199   }
2200
2201   // lock source surface
2202   SDL_LockSurface(zoom_src);
2203
2204   // check which kind of surface we have
2205   if (is_32bit)
2206   {
2207     // call the 32 bit transformation routine to do the zooming
2208     zoomSurfaceRGBA(zoom_src, zoom_dst);
2209   }
2210   else
2211   {
2212     // copy palette
2213     for (i = 0; i < zoom_src->format->palette->ncolors; i++)
2214       zoom_dst->format->palette->colors[i] =
2215         zoom_src->format->palette->colors[i];
2216     zoom_dst->format->palette->ncolors = zoom_src->format->palette->ncolors;
2217
2218     // call the 8 bit transformation routine to do the zooming
2219     zoomSurfaceY(zoom_src, zoom_dst);
2220   }
2221
2222   // unlock source surface
2223   SDL_UnlockSurface(zoom_src);
2224
2225   // free temporary surface
2226   if (is_converted)
2227     SDL_FreeSurface(zoom_src);
2228
2229   // return destination surface
2230   return zoom_dst;
2231 }
2232
2233 static SDL_Surface *SDLGetOpaqueSurface(SDL_Surface *surface)
2234 {
2235   SDL_Surface *new_surface;
2236
2237   if (surface == NULL)
2238     return NULL;
2239
2240   if ((new_surface = SDLGetNativeSurface(surface)) == NULL)
2241     Fail("SDLGetNativeSurface() failed");
2242
2243   // remove alpha channel from native non-transparent surface, if defined
2244   SDLSetAlpha(new_surface, FALSE, 0);
2245
2246   // remove transparent color from native non-transparent surface, if defined
2247   SDL_SetColorKey(new_surface, UNSET_TRANSPARENT_PIXEL, 0);
2248
2249   return new_surface;
2250 }
2251
2252 Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
2253 {
2254   Bitmap *dst_bitmap = CreateBitmapStruct();
2255   SDL_Surface *src_surface = src_bitmap->surface_masked;
2256   SDL_Surface *dst_surface;
2257
2258   dst_width  = MAX(1, dst_width);       // prevent zero bitmap width
2259   dst_height = MAX(1, dst_height);      // prevent zero bitmap height
2260
2261   dst_bitmap->width  = dst_width;
2262   dst_bitmap->height = dst_height;
2263
2264   // create zoomed temporary surface from source surface
2265   dst_surface = zoomSurface(src_surface, dst_width, dst_height);
2266
2267   // create native format destination surface from zoomed temporary surface
2268   SDLSetNativeSurface(&dst_surface);
2269
2270   // set color key for zoomed surface from source surface, if defined
2271   if (SDLHasColorKey(src_surface))
2272     SDL_SetColorKey(dst_surface, SET_TRANSPARENT_PIXEL,
2273                     SDLGetColorKey(src_surface));
2274
2275   // create native non-transparent surface for opaque blitting
2276   dst_bitmap->surface = SDLGetOpaqueSurface(dst_surface);
2277
2278   // set native transparent surface for masked blitting
2279   dst_bitmap->surface_masked = dst_surface;
2280
2281   return dst_bitmap;
2282 }
2283
2284
2285 // ============================================================================
2286 // load image to bitmap
2287 // ============================================================================
2288
2289 Bitmap *SDLLoadImage(char *filename)
2290 {
2291   Bitmap *new_bitmap = CreateBitmapStruct();
2292   SDL_Surface *sdl_image_tmp;
2293
2294   if (program.headless)
2295   {
2296     // prevent sanity check warnings at later stage
2297     new_bitmap->width = new_bitmap->height = 1;
2298
2299     return new_bitmap;
2300   }
2301
2302   print_timestamp_init("SDLLoadImage");
2303
2304   print_timestamp_time(getBaseNamePtr(filename));
2305
2306   // load image to temporary surface
2307   if ((sdl_image_tmp = IMG_Load(filename)) == NULL)
2308     Fail("IMG_Load('%s') failed: %s", getBaseNamePtr(filename), 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     Fail("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     Fail("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       Fail("SDL_Init() failed: %s", SDL_GetError());
2711
2712     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
2713
2714     // the included game controller base mappings should always be found
2715     if (num_mappings == -1)
2716       Warn("no game controller base mappings found");
2717 #if DEBUG_JOYSTICKS
2718     else
2719       Debug("joystick", "%d game controller base mapping(s) added",
2720             num_mappings);
2721 #endif
2722
2723     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
2724
2725 #if DEBUG_JOYSTICKS
2726     // the personal game controller user mappings may or may not be found
2727     if (num_mappings == -1)
2728       Warn("no game controller user mappings found");
2729     else
2730       Debug("joystick", , "%d game controller user mapping(s) added",
2731             num_mappings);
2732
2733     Debug("joystick", "%d joystick(s) found:", SDL_NumJoysticks());
2734 #endif
2735
2736     checked_free(mappings_file_base);
2737     checked_free(mappings_file_user);
2738
2739 #if DEBUG_JOYSTICKS
2740     for (i = 0; i < SDL_NumJoysticks(); i++)
2741     {
2742       const char *name, *type;
2743
2744       if (SDL_IsGameController(i))
2745       {
2746         name = SDL_GameControllerNameForIndex(i);
2747         type = "game controller";
2748       }
2749       else
2750       {
2751         name = SDL_JoystickNameForIndex(i);
2752         type = "joystick";
2753       }
2754
2755       Debug("joystick", "- joystick %d (%s): '%s'",
2756             i, type, (name ? name : "(Unknown)"));
2757     }
2758 #endif
2759   }
2760
2761   // assign joysticks from configured to connected joystick for all players
2762   for (i = 0; i < MAX_PLAYERS; i++)
2763   {
2764     // get configured joystick for this player
2765     char *device_name = setup.input[i].joy.device_name;
2766     int joystick_nr = getJoystickNrFromDeviceName(device_name);
2767
2768     if (joystick_nr >= SDL_NumJoysticks())
2769     {
2770       if (setup.input[i].use_joystick && print_warning)
2771         Warn("cannot find joystick %d", joystick_nr);
2772
2773       joystick_nr = -1;
2774     }
2775
2776     // store configured joystick number for each player
2777     joystick.nr[i] = joystick_nr;
2778   }
2779
2780   // now open all connected joysticks (regardless if configured or not)
2781   for (i = 0; i < SDL_NumJoysticks(); i++)
2782   {
2783     // this allows subsequent calls to 'InitJoysticks' for re-initialization
2784     if (SDLCheckJoystickOpened(i))
2785       SDLCloseJoystick(i);
2786
2787     if (SDLOpenJoystick(i))
2788       joystick.status = JOYSTICK_ACTIVATED;
2789     else if (print_warning)
2790       Warn("cannot open joystick %d", i);
2791   }
2792
2793   SDLClearJoystickState();
2794 }
2795
2796 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
2797 {
2798   if (nr < 0 || nr >= MAX_PLAYERS)
2799     return FALSE;
2800
2801   if (x != NULL)
2802     *x = sdl_js_axis[nr][0];
2803   if (y != NULL)
2804     *y = sdl_js_axis[nr][1];
2805
2806   if (b1 != NULL)
2807     *b1 = sdl_js_button[nr][0];
2808   if (b2 != NULL)
2809     *b2 = sdl_js_button[nr][1];
2810
2811   return TRUE;
2812 }
2813
2814
2815 // ============================================================================
2816 // touch input overlay functions
2817 // ============================================================================
2818
2819 #if defined(USE_TOUCH_INPUT_OVERLAY)
2820 static void DrawTouchInputOverlay_ShowGrid(int alpha)
2821 {
2822   SDL_Rect rect;
2823   int grid_xsize = overlay.grid_xsize;
2824   int grid_ysize = overlay.grid_ysize;
2825   int x, y;
2826
2827   SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha);
2828   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2829
2830   for (x = 0; x < grid_xsize; x++)
2831   {
2832     rect.x = (x + 0) * video.screen_width / grid_xsize;
2833     rect.w = (x + 1) * video.screen_width / grid_xsize - rect.x;
2834
2835     for (y = 0; y < grid_ysize; y++)
2836     {
2837       rect.y = (y + 0) * video.screen_height / grid_ysize;
2838       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2839
2840       if (overlay.grid_button[x][y] == CHAR_GRID_BUTTON_NONE)
2841         SDL_RenderDrawRect(sdl_renderer, &rect);
2842     }
2843   }
2844
2845   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2846 }
2847
2848 static void RenderFillRectangle(int x, int y, int width, int height)
2849 {
2850   SDL_Rect rect = { x, y, width, height };
2851
2852   SDL_RenderFillRect(sdl_renderer, &rect);
2853 }
2854
2855 static void DrawTouchInputOverlay_ShowGridButtons(int alpha)
2856 {
2857   static int alpha_direction = 0;
2858   static int alpha_highlight = 0;
2859   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2860   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2861   SDL_Rect rect;
2862   int grid_xsize = overlay.grid_xsize;
2863   int grid_ysize = overlay.grid_ysize;
2864   int x, y;
2865
2866   if (alpha == alpha_max)
2867   {
2868     if (alpha_direction < 0)
2869     {
2870       alpha_highlight = MAX(0, alpha_highlight - alpha_step);
2871
2872       if (alpha_highlight == 0)
2873         alpha_direction = 1;
2874     }
2875     else
2876     {
2877       alpha_highlight = MIN(alpha_highlight + alpha_step, alpha_max);
2878
2879       if (alpha_highlight == alpha_max)
2880         alpha_direction = -1;
2881     }
2882   }
2883   else
2884   {
2885     alpha_direction = 1;
2886     alpha_highlight = alpha;
2887   }
2888
2889   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2890
2891   for (x = 0; x < grid_xsize; x++)
2892   {
2893     for (y = 0; y < grid_ysize; y++)
2894     {
2895       int grid_button = overlay.grid_button[x][y];
2896       int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
2897       int alpha_draw = alpha;
2898       int outline_border = MV_NONE;
2899       int border_size = 2;
2900       boolean draw_outlined = setup.touch.draw_outlined;
2901       boolean draw_pressed = setup.touch.draw_pressed;
2902
2903       if (grid_button == CHAR_GRID_BUTTON_NONE)
2904         continue;
2905
2906       if (grid_button == overlay.grid_button_highlight)
2907       {
2908         draw_outlined = FALSE;
2909         alpha_draw = MIN((float)alpha_highlight * 1.5, SDL_ALPHA_OPAQUE);
2910       }
2911
2912       if (draw_pressed && overlay.grid_button_action & grid_button_action)
2913       {
2914         if (draw_outlined)
2915           draw_outlined = FALSE;
2916         else
2917           alpha_draw = MIN((float)alpha_draw * 1.5, SDL_ALPHA_OPAQUE);
2918       }
2919
2920       SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha_draw);
2921
2922       rect.x = (x + 0) * video.screen_width  / grid_xsize;
2923       rect.y = (y + 0) * video.screen_height / grid_ysize;
2924       rect.w = (x + 1) * video.screen_width  / grid_xsize - rect.x;
2925       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2926
2927       if (x == 0 || overlay.grid_button[x - 1][y] != grid_button)
2928       {
2929         rect.x += border_size;
2930         rect.w -= border_size;
2931
2932         outline_border |= MV_LEFT;
2933       }
2934
2935       if (x == grid_xsize - 1 || overlay.grid_button[x + 1][y] != grid_button)
2936       {
2937         rect.w -= border_size;
2938
2939         outline_border |= MV_RIGHT;
2940       }
2941
2942       if (y == 0 || overlay.grid_button[x][y - 1] != grid_button)
2943       {
2944         rect.y += border_size;
2945         rect.h -= border_size;
2946
2947         outline_border |= MV_UP;
2948       }
2949
2950       if (y == grid_ysize - 1 || overlay.grid_button[x][y + 1] != grid_button)
2951       {
2952         rect.h -= border_size;
2953
2954         outline_border |= MV_DOWN;
2955       }
2956
2957       if (draw_outlined)
2958       {
2959         int rect_x = rect.x +
2960           (outline_border & MV_LEFT  ? border_size : 0);
2961         int rect_w = rect.w -
2962           (outline_border & MV_LEFT  ? border_size : 0) -
2963           (outline_border & MV_RIGHT ? border_size : 0);
2964
2965         if (outline_border & MV_LEFT)
2966           RenderFillRectangle(rect.x, rect.y, border_size, rect.h);
2967
2968         if (outline_border & MV_RIGHT)
2969           RenderFillRectangle(rect.x + rect.w - border_size, rect.y,
2970                               border_size, rect.h);
2971
2972         if (outline_border & MV_UP)
2973           RenderFillRectangle(rect_x, rect.y, rect_w, border_size);
2974
2975         if (outline_border & MV_DOWN)
2976           RenderFillRectangle(rect_x, rect.y + rect.h - border_size,
2977                               rect_w, border_size);
2978       }
2979       else
2980       {
2981         SDL_RenderFillRect(sdl_renderer, &rect);
2982       }
2983     }
2984   }
2985
2986   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2987 }
2988
2989 static void DrawTouchInputOverlay(void)
2990 {
2991   static boolean deactivated = TRUE;
2992   static boolean show_grid = FALSE;
2993   static int alpha = 0;
2994   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2995   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2996   boolean active = (overlay.enabled && overlay.active);
2997
2998   if (!active && deactivated)
2999     return;
3000
3001   if (active)
3002   {
3003     if (alpha < alpha_max)
3004       alpha = MIN(alpha + alpha_step, alpha_max);
3005
3006     deactivated = FALSE;
3007   }
3008   else
3009   {
3010     alpha = MAX(0, alpha - alpha_step);
3011
3012     if (alpha == 0)
3013       deactivated = TRUE;
3014   }
3015
3016   if (overlay.show_grid)
3017     show_grid = TRUE;
3018   else if (deactivated)
3019     show_grid = FALSE;
3020
3021   if (show_grid)
3022     DrawTouchInputOverlay_ShowGrid(alpha);
3023
3024   DrawTouchInputOverlay_ShowGridButtons(alpha);
3025 }
3026
3027 static void DrawTouchGadgetsOverlay(void)
3028 {
3029   DrawGadgets_OverlayTouchButtons();
3030 }
3031 #endif