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