c7a87297821c5fecb4e4c0cdae20f406711fc4a6
[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 =
881     (strEqual(vsync_mode, STR_VSYNC_MODE_NORMAL)   ? VSYNC_MODE_NORMAL :
882      strEqual(vsync_mode, STR_VSYNC_MODE_ADAPTIVE) ? VSYNC_MODE_ADAPTIVE :
883      VSYNC_MODE_OFF);
884   int result = SDL_GL_SetSwapInterval(interval);
885
886   // if adaptive vsync requested, but not supported, retry with normal vsync
887   if (result == -1 && interval == VSYNC_MODE_ADAPTIVE)
888   {
889     interval = VSYNC_MODE_NORMAL;
890
891     result = SDL_GL_SetSwapInterval(interval);
892   }
893
894   if (result == -1)
895     interval = VSYNC_MODE_OFF;
896
897   video.vsync_mode = interval;
898 }
899
900 void SDLRedrawWindow(void)
901 {
902   UpdateScreen_WithoutFrameDelay(NULL);
903 }
904
905 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
906                             int depth)
907 {
908   if (program.headless)
909     return;
910
911   SDL_Surface *surface =
912     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
913
914   if (surface == NULL)
915     Error(ERR_EXIT, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
916
917   SDLSetNativeSurface(&surface);
918
919   bitmap->surface = surface;
920 }
921
922 void SDLFreeBitmapPointers(Bitmap *bitmap)
923 {
924   if (bitmap->surface)
925     SDL_FreeSurface(bitmap->surface);
926   if (bitmap->surface_masked)
927     SDL_FreeSurface(bitmap->surface_masked);
928
929   bitmap->surface = NULL;
930   bitmap->surface_masked = NULL;
931
932   if (bitmap->texture)
933     SDL_DestroyTexture(bitmap->texture);
934   if (bitmap->texture_masked)
935     SDL_DestroyTexture(bitmap->texture_masked);
936
937   bitmap->texture = NULL;
938   bitmap->texture_masked = NULL;
939 }
940
941 void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
942                  int src_x, int src_y, int width, int height,
943                  int dst_x, int dst_y, int mask_mode)
944 {
945   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
946   SDL_Rect src_rect, dst_rect;
947
948   src_rect.x = src_x;
949   src_rect.y = src_y;
950   src_rect.w = width;
951   src_rect.h = height;
952
953   dst_rect.x = dst_x;
954   dst_rect.y = dst_y;
955   dst_rect.w = width;
956   dst_rect.h = height;
957
958   // if (src_bitmap != backbuffer || dst_bitmap != window)
959   if (!(src_bitmap == backbuffer && dst_bitmap == window))
960     SDL_BlitSurface((mask_mode == BLIT_MASKED ?
961                      src_bitmap->surface_masked : src_bitmap->surface),
962                     &src_rect, real_dst_bitmap->surface, &dst_rect);
963
964   if (dst_bitmap == window)
965     UpdateScreen_WithFrameDelay(&dst_rect);
966 }
967
968 void SDLBlitTexture(Bitmap *bitmap,
969                     int src_x, int src_y, int width, int height,
970                     int dst_x, int dst_y, int mask_mode)
971 {
972   SDL_Texture *texture;
973   SDL_Rect src_rect;
974   SDL_Rect dst_rect;
975
976   texture =
977     (mask_mode == BLIT_MASKED ? bitmap->texture_masked : bitmap->texture);
978
979   if (texture == NULL)
980     return;
981
982   src_rect.x = src_x;
983   src_rect.y = src_y;
984   src_rect.w = width;
985   src_rect.h = height;
986
987   dst_rect.x = dst_x;
988   dst_rect.y = dst_y;
989   dst_rect.w = width;
990   dst_rect.h = height;
991
992   SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
993 }
994
995 void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
996                       Uint32 color)
997 {
998   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
999   SDL_Rect rect;
1000
1001   rect.x = x;
1002   rect.y = y;
1003   rect.w = width;
1004   rect.h = height;
1005
1006   SDL_FillRect(real_dst_bitmap->surface, &rect, color);
1007
1008   if (dst_bitmap == window)
1009     UpdateScreen_WithFrameDelay(&rect);
1010 }
1011
1012 void PrepareFadeBitmap(int draw_target)
1013 {
1014   Bitmap *fade_bitmap =
1015     (draw_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source :
1016      draw_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL);
1017
1018   if (fade_bitmap == NULL)
1019     return;
1020
1021   // copy backbuffer to fading buffer
1022   BlitBitmap(backbuffer, fade_bitmap, 0, 0, gfx.win_xsize, gfx.win_ysize, 0, 0);
1023
1024   // add border and animations to fading buffer
1025   FinalizeScreen(draw_target);
1026 }
1027
1028 void SDLFadeRectangle(int x, int y, int width, int height,
1029                       int fade_mode, int fade_delay, int post_delay,
1030                       void (*draw_border_function)(void))
1031 {
1032   SDL_Surface *surface_backup = gfx.fade_bitmap_backup->surface;
1033   SDL_Surface *surface_source = gfx.fade_bitmap_source->surface;
1034   SDL_Surface *surface_target = gfx.fade_bitmap_target->surface;
1035   SDL_Surface *surface_black  = gfx.fade_bitmap_black->surface;
1036   SDL_Surface *surface_screen = backbuffer->surface;
1037   SDL_Rect src_rect, dst_rect;
1038   SDL_Rect dst_rect2;
1039   int src_x = x, src_y = y;
1040   int dst_x = x, dst_y = y;
1041   unsigned int time_last, time_current;
1042
1043   // store function for drawing global masked border
1044   void (*draw_global_border_function)(int) = gfx.draw_global_border_function;
1045
1046   // deactivate drawing of global border while fading, if needed
1047   if (draw_border_function == NULL)
1048     gfx.draw_global_border_function = NULL;
1049
1050   src_rect.x = src_x;
1051   src_rect.y = src_y;
1052   src_rect.w = width;
1053   src_rect.h = height;
1054
1055   dst_rect.x = dst_x;
1056   dst_rect.y = dst_y;
1057   dst_rect.w = width;           // (ignored)
1058   dst_rect.h = height;          // (ignored)
1059
1060   dst_rect2 = dst_rect;
1061
1062   // before fading in, store backbuffer (without animation graphics)
1063   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1064     SDL_BlitSurface(surface_screen, &dst_rect, surface_backup, &src_rect);
1065
1066   // copy source and target surfaces to temporary surfaces for fading
1067   if (fade_mode & FADE_TYPE_TRANSFORM)
1068   {
1069     // (source and target fading buffer already prepared)
1070   }
1071   else if (fade_mode & FADE_TYPE_FADE_IN)
1072   {
1073     // (target fading buffer already prepared)
1074     SDL_BlitSurface(surface_black,  &src_rect, surface_source, &src_rect);
1075   }
1076   else          // FADE_TYPE_FADE_OUT
1077   {
1078     // (source fading buffer already prepared)
1079     SDL_BlitSurface(surface_black,  &src_rect, surface_target, &src_rect);
1080   }
1081
1082   time_current = SDL_GetTicks();
1083
1084   if (fade_delay <= 0)
1085   {
1086     // immediately draw final target frame without delay
1087     fade_mode &= (FADE_MODE_FADE | FADE_MODE_TRANSFORM);
1088     fade_delay = 1;
1089     time_current -= 1;
1090
1091     // when fading without delay, also skip post delay
1092     post_delay = 0;
1093   }
1094
1095   if (fade_mode == FADE_MODE_MELT)
1096   {
1097     boolean done = FALSE;
1098     int melt_pixels = 2;
1099     int melt_columns = width / melt_pixels;
1100     int ypos[melt_columns];
1101     int max_steps = height / 8 + 32;
1102     int steps_done = 0;
1103     float steps = 0;
1104     int i;
1105
1106     SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1107
1108     SDLSetAlpha(surface_target, FALSE, 0);      // disable alpha blending
1109
1110     ypos[0] = -GetSimpleRandom(16);
1111
1112     for (i = 1 ; i < melt_columns; i++)
1113     {
1114       int r = GetSimpleRandom(3) - 1;   // randomly choose from { -1, 0, -1 }
1115
1116       ypos[i] = ypos[i - 1] + r;
1117
1118       if (ypos[i] > 0)
1119         ypos[i] = 0;
1120       else
1121         if (ypos[i] == -16)
1122           ypos[i] = -15;
1123     }
1124
1125     while (!done)
1126     {
1127       int steps_final;
1128
1129       time_last = time_current;
1130       time_current = SDL_GetTicks();
1131       steps += max_steps * ((float)(time_current - time_last) / fade_delay);
1132       steps_final = MIN(MAX(0, steps), max_steps);
1133
1134       steps_done++;
1135
1136       done = (steps_done >= steps_final);
1137
1138       for (i = 0 ; i < melt_columns; i++)
1139       {
1140         if (ypos[i] < 0)
1141         {
1142           ypos[i]++;
1143
1144           done = FALSE;
1145         }
1146         else if (ypos[i] < height)
1147         {
1148           int y1 = 16;
1149           int y2 = 8;
1150           int y3 = 8;
1151           int dy = (ypos[i] < y1) ? ypos[i] + 1 : y2 + GetSimpleRandom(y3);
1152
1153           if (ypos[i] + dy >= height)
1154             dy = height - ypos[i];
1155
1156           // copy part of (appearing) target surface to upper area
1157           src_rect.x = src_x + i * melt_pixels;
1158           // src_rect.y = src_y + ypos[i];
1159           src_rect.y = src_y;
1160           src_rect.w = melt_pixels;
1161           // src_rect.h = dy;
1162           src_rect.h = ypos[i] + dy;
1163
1164           dst_rect.x = dst_x + i * melt_pixels;
1165           // dst_rect.y = dst_y + ypos[i];
1166           dst_rect.y = dst_y;
1167
1168           if (steps_done >= steps_final)
1169             SDL_BlitSurface(surface_target, &src_rect,
1170                             surface_screen, &dst_rect);
1171
1172           ypos[i] += dy;
1173
1174           // copy part of (disappearing) source surface to lower area
1175           src_rect.x = src_x + i * melt_pixels;
1176           src_rect.y = src_y;
1177           src_rect.w = melt_pixels;
1178           src_rect.h = height - ypos[i];
1179
1180           dst_rect.x = dst_x + i * melt_pixels;
1181           dst_rect.y = dst_y + ypos[i];
1182
1183           if (steps_done >= steps_final)
1184             SDL_BlitSurface(surface_source, &src_rect,
1185                             surface_screen, &dst_rect);
1186
1187           done = FALSE;
1188         }
1189         else
1190         {
1191           src_rect.x = src_x + i * melt_pixels;
1192           src_rect.y = src_y;
1193           src_rect.w = melt_pixels;
1194           src_rect.h = height;
1195
1196           dst_rect.x = dst_x + i * melt_pixels;
1197           dst_rect.y = dst_y;
1198
1199           if (steps_done >= steps_final)
1200             SDL_BlitSurface(surface_target, &src_rect,
1201                             surface_screen, &dst_rect);
1202         }
1203       }
1204
1205       if (steps_done >= steps_final)
1206       {
1207         if (draw_border_function != NULL)
1208           draw_border_function();
1209
1210         UpdateScreen_WithFrameDelay(&dst_rect2);
1211       }
1212     }
1213   }
1214   else if (fade_mode == FADE_MODE_CURTAIN)
1215   {
1216     float xx;
1217     int xx_final;
1218     int xx_size = width / 2;
1219
1220     SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1221
1222     SDLSetAlpha(surface_source, FALSE, 0);      // disable alpha blending
1223
1224     for (xx = 0; xx < xx_size;)
1225     {
1226       time_last = time_current;
1227       time_current = SDL_GetTicks();
1228       xx += xx_size * ((float)(time_current - time_last) / fade_delay);
1229       xx_final = MIN(MAX(0, xx), xx_size);
1230
1231       src_rect.x = src_x;
1232       src_rect.y = src_y;
1233       src_rect.w = width;
1234       src_rect.h = height;
1235
1236       dst_rect.x = dst_x;
1237       dst_rect.y = dst_y;
1238
1239       // draw new (target) image to screen buffer
1240       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1241
1242       if (xx_final < xx_size)
1243       {
1244         src_rect.w = xx_size - xx_final;
1245         src_rect.h = height;
1246
1247         // draw old (source) image to screen buffer (left side)
1248
1249         src_rect.x = src_x + xx_final;
1250         dst_rect.x = dst_x;
1251
1252         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1253
1254         // draw old (source) image to screen buffer (right side)
1255
1256         src_rect.x = src_x + xx_size;
1257         dst_rect.x = dst_x + xx_size + xx_final;
1258
1259         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1260       }
1261
1262       if (draw_border_function != NULL)
1263         draw_border_function();
1264
1265       // only update the region of the screen that is affected from fading
1266       UpdateScreen_WithFrameDelay(&dst_rect2);
1267     }
1268   }
1269   else          // fading in, fading out or cross-fading
1270   {
1271     float alpha;
1272     int alpha_final;
1273
1274     for (alpha = 0.0; alpha < 255.0;)
1275     {
1276       time_last = time_current;
1277       time_current = SDL_GetTicks();
1278       alpha += 255 * ((float)(time_current - time_last) / fade_delay);
1279       alpha_final = MIN(MAX(0, alpha), 255);
1280
1281       // draw existing (source) image to screen buffer
1282       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1283
1284       // draw new (target) image to screen buffer using alpha blending
1285       SDLSetAlpha(surface_target, TRUE, alpha_final);
1286       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1287
1288       if (draw_border_function != NULL)
1289         draw_border_function();
1290
1291       // only update the region of the screen that is affected from fading
1292       UpdateScreen_WithFrameDelay(&dst_rect);
1293     }
1294   }
1295
1296   if (post_delay > 0)
1297     Delay_WithScreenUpdates(post_delay);
1298
1299   // restore function for drawing global masked border
1300   gfx.draw_global_border_function = draw_global_border_function;
1301
1302   // after fading in, restore backbuffer (without animation graphics)
1303   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1304     SDL_BlitSurface(surface_backup, &dst_rect, surface_screen, &src_rect);
1305 }
1306
1307 void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
1308                        int to_x, int to_y, Uint32 color)
1309 {
1310   SDL_Surface *surface = dst_bitmap->surface;
1311   SDL_Rect rect;
1312
1313   if (from_x > to_x)
1314     swap_numbers(&from_x, &to_x);
1315
1316   if (from_y > to_y)
1317     swap_numbers(&from_y, &to_y);
1318
1319   rect.x = from_x;
1320   rect.y = from_y;
1321   rect.w = (to_x - from_x + 1);
1322   rect.h = (to_y - from_y + 1);
1323
1324   SDL_FillRect(surface, &rect, color);
1325 }
1326
1327 void SDLDrawLine(Bitmap *dst_bitmap, int from_x, int from_y,
1328                  int to_x, int to_y, Uint32 color)
1329 {
1330   sge_Line(dst_bitmap->surface, from_x, from_y, to_x, to_y, color);
1331 }
1332
1333 #if ENABLE_UNUSED_CODE
1334 void SDLDrawLines(SDL_Surface *surface, struct XY *points,
1335                   int num_points, Uint32 color)
1336 {
1337   int i, x, y;
1338   int line_width = 4;
1339
1340   for (i = 0; i < num_points - 1; i++)
1341   {
1342     for (x = 0; x < line_width; x++)
1343     {
1344       for (y = 0; y < line_width; y++)
1345       {
1346         int dx = x - line_width / 2;
1347         int dy = y - line_width / 2;
1348
1349         if ((x == 0 && y == 0) ||
1350             (x == 0 && y == line_width - 1) ||
1351             (x == line_width - 1 && y == 0) ||
1352             (x == line_width - 1 && y == line_width - 1))
1353           continue;
1354
1355         sge_Line(surface, points[i].x + dx, points[i].y + dy,
1356                  points[i+1].x + dx, points[i+1].y + dy, color);
1357       }
1358     }
1359   }
1360 }
1361 #endif
1362
1363 Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
1364 {
1365   SDL_Surface *surface = src_bitmap->surface;
1366
1367   switch (surface->format->BytesPerPixel)
1368   {
1369     case 1:             // assuming 8-bpp
1370     {
1371       return *((Uint8 *)surface->pixels + y * surface->pitch + x);
1372     }
1373     break;
1374
1375     case 2:             // probably 15-bpp or 16-bpp
1376     {
1377       return *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x);
1378     }
1379     break;
1380
1381   case 3:               // slow 24-bpp mode; usually not used
1382     {
1383       // does this work?
1384       Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1385       Uint32 color = 0;
1386       int shift;
1387
1388       shift = surface->format->Rshift;
1389       color |= *(pix + shift / 8) >> shift;
1390       shift = surface->format->Gshift;
1391       color |= *(pix + shift / 8) >> shift;
1392       shift = surface->format->Bshift;
1393       color |= *(pix + shift / 8) >> shift;
1394
1395       return color;
1396     }
1397     break;
1398
1399   case 4:               // probably 32-bpp
1400     {
1401       return *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x);
1402     }
1403     break;
1404   }
1405
1406   return 0;
1407 }
1408
1409
1410 // ============================================================================
1411 // The following functions were taken from the SGE library
1412 // (SDL Graphics Extension Library) by Anders Lindström
1413 // http://www.etek.chalmers.se/~e8cal1/sge/index.html
1414 // ============================================================================
1415
1416 static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1417 {
1418   if (x >= 0 && x <= surface->w - 1 && y >= 0 && y <= surface->h - 1)
1419   {
1420     switch (surface->format->BytesPerPixel)
1421     {
1422       case 1:
1423       {
1424         // Assuming 8-bpp
1425         *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1426       }
1427       break;
1428
1429       case 2:
1430       {
1431         // Probably 15-bpp or 16-bpp
1432         *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1433       }
1434       break;
1435
1436       case 3:
1437       {
1438         // Slow 24-bpp mode, usually not used
1439         Uint8 *pix;
1440         int shift;
1441
1442         // Gack - slow, but endian correct
1443         pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1444         shift = surface->format->Rshift;
1445         *(pix+shift/8) = color>>shift;
1446         shift = surface->format->Gshift;
1447         *(pix+shift/8) = color>>shift;
1448         shift = surface->format->Bshift;
1449         *(pix+shift/8) = color>>shift;
1450       }
1451       break;
1452
1453       case 4:
1454       {
1455         // Probably 32-bpp
1456         *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1457       }
1458       break;
1459     }
1460   }
1461 }
1462
1463 #if 0
1464 static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1465                          Uint8 R, Uint8 G, Uint8 B)
1466 {
1467   _PutPixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
1468 }
1469
1470 static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1471 {
1472   *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
1473 }
1474
1475 static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1476 {
1477   *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
1478 }
1479
1480 static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1481 {
1482   Uint8 *pix;
1483   int shift;
1484
1485   // Gack - slow, but endian correct
1486   pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
1487   shift = surface->format->Rshift;
1488   *(pix+shift/8) = color>>shift;
1489   shift = surface->format->Gshift;
1490   *(pix+shift/8) = color>>shift;
1491   shift = surface->format->Bshift;
1492   *(pix+shift/8) = color>>shift;
1493 }
1494
1495 static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1496 {
1497   *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
1498 }
1499
1500 static void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
1501 {
1502   switch (dest->format->BytesPerPixel)
1503   {
1504     case 1:
1505       *((Uint8 *)dest->pixels + y*dest->pitch + x) = color;
1506       break;
1507
1508     case 2:
1509       *((Uint16 *)dest->pixels + y*dest->pitch/2 + x) = color;
1510       break;
1511
1512     case 3:
1513       _PutPixel24(dest,x,y,color);
1514       break;
1515
1516     case 4:
1517       *((Uint32 *)dest->pixels + y*dest->pitch/4 + x) = color;
1518       break;
1519   }
1520 }
1521 #endif
1522
1523 static void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1524 {
1525   if (SDL_MUSTLOCK(surface))
1526   {
1527     if (SDL_LockSurface(surface) < 0)
1528     {
1529       return;
1530     }
1531   }
1532
1533   _PutPixel(surface, x, y, color);
1534
1535   if (SDL_MUSTLOCK(surface))
1536   {
1537     SDL_UnlockSurface(surface);
1538   }
1539 }
1540
1541 #if 0
1542 static void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1543                             Uint8 r, Uint8 g, Uint8 b)
1544 {
1545   sge_PutPixel(surface, x, y, SDL_MapRGB(surface->format, r, g, b));
1546 }
1547
1548 static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
1549 {
1550   if (y >= 0 && y <= dest->h - 1)
1551   {
1552     switch (dest->format->BytesPerPixel)
1553     {
1554       case 1:
1555         return y*dest->pitch;
1556         break;
1557
1558       case 2:
1559         return y*dest->pitch/2;
1560         break;
1561
1562       case 3:
1563         return y*dest->pitch;
1564         break;
1565
1566       case 4:
1567         return y*dest->pitch/4;
1568         break;
1569     }
1570   }
1571
1572   return -1;
1573 }
1574
1575 static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch,
1576                           Uint32 color)
1577 {
1578   if (x >= 0 && x <= surface->w - 1 && ypitch >= 0)
1579   {
1580     switch (surface->format->BytesPerPixel)
1581     {
1582       case 1:
1583       {
1584         // Assuming 8-bpp
1585         *((Uint8 *)surface->pixels + ypitch + x) = color;
1586       }
1587       break;
1588
1589       case 2:
1590       {
1591         // Probably 15-bpp or 16-bpp
1592         *((Uint16 *)surface->pixels + ypitch + x) = color;
1593       }
1594       break;
1595
1596       case 3:
1597       {
1598         // Slow 24-bpp mode, usually not used
1599         Uint8 *pix;
1600         int shift;
1601
1602         // Gack - slow, but endian correct
1603         pix = (Uint8 *)surface->pixels + ypitch + x*3;
1604         shift = surface->format->Rshift;
1605         *(pix+shift/8) = color>>shift;
1606         shift = surface->format->Gshift;
1607         *(pix+shift/8) = color>>shift;
1608         shift = surface->format->Bshift;
1609         *(pix+shift/8) = color>>shift;
1610       }
1611       break;
1612
1613       case 4:
1614       {
1615         // Probably 32-bpp
1616         *((Uint32 *)surface->pixels + ypitch + x) = color;
1617       }
1618       break;
1619     }
1620   }
1621 }
1622
1623 static void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1624                       Uint32 Color)
1625 {
1626   SDL_Rect l;
1627
1628   if (SDL_MUSTLOCK(Surface))
1629   {
1630     if (SDL_LockSurface(Surface) < 0)
1631     {
1632       return;
1633     }
1634   }
1635
1636   if (x1 > x2)
1637   {
1638     Sint16 tmp = x1;
1639     x1 = x2;
1640     x2 = tmp;
1641   }
1642
1643   // Do the clipping
1644   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1645     return;
1646   if (x1 < 0)
1647     x1 = 0;
1648   if (x2 > Surface->w - 1)
1649     x2 = Surface->w - 1;
1650
1651   l.x = x1;
1652   l.y = y;
1653   l.w = x2 - x1 + 1;
1654   l.h = 1;
1655
1656   SDL_FillRect(Surface, &l, Color);
1657
1658   if (SDL_MUSTLOCK(Surface))
1659   {
1660     SDL_UnlockSurface(Surface);
1661   }
1662 }
1663
1664 static void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1665                          Uint8 R, Uint8 G, Uint8 B)
1666 {
1667   sge_HLine(Surface, x1, x2, y, SDL_MapRGB(Surface->format, R, G, B));
1668 }
1669
1670 static void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1671                    Uint32 Color)
1672 {
1673   SDL_Rect l;
1674
1675   if (x1 > x2)
1676   {
1677     Sint16 tmp = x1;
1678     x1 = x2;
1679     x2 = tmp;
1680   }
1681
1682   // Do the clipping
1683   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1684     return;
1685   if (x1 < 0)
1686     x1 = 0;
1687   if (x2 > Surface->w - 1)
1688     x2 = Surface->w - 1;
1689
1690   l.x = x1;
1691   l.y = y;
1692   l.w = x2 - x1 + 1;
1693   l.h = 1;
1694
1695   SDL_FillRect(Surface, &l, Color);
1696 }
1697
1698 static void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1699                       Uint32 Color)
1700 {
1701   SDL_Rect l;
1702
1703   if (SDL_MUSTLOCK(Surface))
1704   {
1705     if (SDL_LockSurface(Surface) < 0)
1706     {
1707       return;
1708     }
1709   }
1710
1711   if (y1 > y2)
1712   {
1713     Sint16 tmp = y1;
1714     y1 = y2;
1715     y2 = tmp;
1716   }
1717
1718   // Do the clipping
1719   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1720     return;
1721   if (y1 < 0)
1722     y1 = 0;
1723   if (y2 > Surface->h - 1)
1724     y2 = Surface->h - 1;
1725
1726   l.x = x;
1727   l.y = y1;
1728   l.w = 1;
1729   l.h = y2 - y1 + 1;
1730
1731   SDL_FillRect(Surface, &l, Color);
1732
1733   if (SDL_MUSTLOCK(Surface))
1734   {
1735     SDL_UnlockSurface(Surface);
1736   }
1737 }
1738
1739 static void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1740                          Uint8 R, Uint8 G, Uint8 B)
1741 {
1742   sge_VLine(Surface, x, y1, y2, SDL_MapRGB(Surface->format, R, G, B));
1743 }
1744
1745 static void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1746                    Uint32 Color)
1747 {
1748   SDL_Rect l;
1749
1750   if (y1 > y2)
1751   {
1752     Sint16 tmp = y1;
1753     y1 = y2;
1754     y2 = tmp;
1755   }
1756
1757   // Do the clipping
1758   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1759     return;
1760   if (y1 < 0)
1761     y1 = 0;
1762   if (y2 > Surface->h - 1)
1763     y2 = Surface->h - 1;
1764
1765   l.x = x;
1766   l.y = y1;
1767   l.w = 1;
1768   l.h = y2 - y1 + 1;
1769
1770   SDL_FillRect(Surface, &l, Color);
1771 }
1772 #endif
1773
1774 static void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
1775                        Sint16 x2, Sint16 y2, Uint32 Color,
1776                        void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1777                                      Uint32 Color))
1778 {
1779   Sint16 dx, dy, sdx, sdy, x, y, px, py;
1780
1781   dx = x2 - x1;
1782   dy = y2 - y1;
1783
1784   sdx = (dx < 0) ? -1 : 1;
1785   sdy = (dy < 0) ? -1 : 1;
1786
1787   dx = sdx * dx + 1;
1788   dy = sdy * dy + 1;
1789
1790   x = y = 0;
1791
1792   px = x1;
1793   py = y1;
1794
1795   if (dx >= dy)
1796   {
1797     for (x = 0; x < dx; x++)
1798     {
1799       Callback(Surface, px, py, Color);
1800
1801       y += dy;
1802       if (y >= dx)
1803       {
1804         y -= dx;
1805         py += sdy;
1806       }
1807
1808       px += sdx;
1809     }
1810   }
1811   else
1812   {
1813     for (y = 0; y < dy; y++)
1814     {
1815       Callback(Surface, px, py, Color);
1816
1817       x += dx;
1818       if (x >= dy)
1819       {
1820         x -= dy;
1821         px += sdx;
1822       }
1823
1824       py += sdy;
1825     }
1826   }
1827 }
1828
1829 #if 0
1830 static void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
1831                           Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
1832                           void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1833                                         Uint32 Color))
1834 {
1835   sge_DoLine(Surface, X1, Y1, X2, Y2,
1836              SDL_MapRGB(Surface->format, R, G, B), Callback);
1837 }
1838 #endif
1839
1840 void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
1841               Uint32 Color)
1842 {
1843   if (SDL_MUSTLOCK(Surface))
1844   {
1845     if (SDL_LockSurface(Surface) < 0)
1846       return;
1847    }
1848
1849    // Draw the line
1850    sge_DoLine(Surface, x1, y1, x2, y2, Color, _PutPixel);
1851
1852    // unlock the display
1853    if (SDL_MUSTLOCK(Surface))
1854    {
1855       SDL_UnlockSurface(Surface);
1856    }
1857 }
1858
1859 #if 0
1860 static void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
1861                         Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
1862 {
1863   sge_Line(Surface, x1, y1, x2, y2, SDL_MapRGB(Surface->format, R, G, B));
1864 }
1865 #endif
1866
1867 void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel)
1868 {
1869   sge_PutPixel(dst_bitmap->surface, x, y, pixel);
1870 }
1871
1872
1873 // ----------------------------------------------------------------------------
1874 // quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
1875 // ----------------------------------------------------------------------------
1876
1877 void SDLInvertArea(Bitmap *bitmap, int src_x, int src_y,
1878                    int width, int height, Uint32 color)
1879 {
1880   int x, y;
1881
1882   for (y = src_y; y < src_y + height; y++)
1883   {
1884     for (x = src_x; x < src_x + width; x++)
1885     {
1886       Uint32 pixel = SDLGetPixel(bitmap, x, y);
1887
1888       SDLPutPixel(bitmap, x, y, pixel == BLACK_PIXEL ? color : BLACK_PIXEL);
1889     }
1890   }
1891 }
1892
1893 void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
1894                           int src_x, int src_y, int width, int height,
1895                           int dst_x, int dst_y)
1896 {
1897   int x, y;
1898
1899   for (y = 0; y < height; y++)
1900   {
1901     for (x = 0; x < width; x++)
1902     {
1903       Uint32 pixel = SDLGetPixel(src_bitmap, src_x + x, src_y + y);
1904
1905       if (pixel != BLACK_PIXEL)
1906         SDLPutPixel(dst_bitmap, dst_x + x, dst_y + y, BLACK_PIXEL);
1907     }
1908   }
1909 }
1910
1911
1912 // ============================================================================
1913 // The following functions were taken from the SDL_gfx library version 2.0.3
1914 // (Rotozoomer) by Andreas Schiffler
1915 // http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html
1916 // ============================================================================
1917
1918 // ----------------------------------------------------------------------------
1919 // 32 bit zoomer
1920 //
1921 // zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
1922 // ----------------------------------------------------------------------------
1923
1924 typedef struct
1925 {
1926   Uint8 r;
1927   Uint8 g;
1928   Uint8 b;
1929   Uint8 a;
1930 } tColorRGBA;
1931
1932 static int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
1933 {
1934   int x, y;
1935   tColorRGBA *sp, *csp, *dp;
1936   int dgap;
1937
1938   // pointer setup
1939   sp = csp = (tColorRGBA *) src->pixels;
1940   dp = (tColorRGBA *) dst->pixels;
1941   dgap = dst->pitch - dst->w * 4;
1942
1943   for (y = 0; y < dst->h; y++)
1944   {
1945     sp = csp;
1946
1947     for (x = 0; x < dst->w; x++)
1948     {
1949       tColorRGBA *sp0 = sp;
1950       tColorRGBA *sp1 = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
1951       tColorRGBA *sp00 = &sp0[0];
1952       tColorRGBA *sp01 = &sp0[1];
1953       tColorRGBA *sp10 = &sp1[0];
1954       tColorRGBA *sp11 = &sp1[1];
1955       tColorRGBA new;
1956
1957       // create new color pixel from all four source color pixels
1958       new.r = (sp00->r + sp01->r + sp10->r + sp11->r) / 4;
1959       new.g = (sp00->g + sp01->g + sp10->g + sp11->g) / 4;
1960       new.b = (sp00->b + sp01->b + sp10->b + sp11->b) / 4;
1961       new.a = (sp00->a + sp01->a + sp10->a + sp11->a) / 4;
1962
1963       // draw
1964       *dp = new;
1965
1966       // advance source pointers
1967       sp += 2;
1968
1969       // advance destination pointer
1970       dp++;
1971     }
1972
1973     // advance source pointer
1974     csp = (tColorRGBA *) ((Uint8 *) csp + 2 * src->pitch);
1975
1976     // advance destination pointers
1977     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
1978   }
1979
1980   return 0;
1981 }
1982
1983 static int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
1984 {
1985   int x, y, *sax, *say, *csax, *csay;
1986   float sx, sy;
1987   tColorRGBA *sp, *csp, *csp0, *dp;
1988   int dgap;
1989
1990   // use specialized zoom function when scaling down to exactly half size
1991   if (src->w == 2 * dst->w &&
1992       src->h == 2 * dst->h)
1993     return zoomSurfaceRGBA_scaleDownBy2(src, dst);
1994
1995   // variable setup
1996   sx = (float) src->w / (float) dst->w;
1997   sy = (float) src->h / (float) dst->h;
1998
1999   // allocate memory for row increments
2000   csax = sax = (int *)checked_malloc((dst->w + 1) * sizeof(Uint32));
2001   csay = say = (int *)checked_malloc((dst->h + 1) * sizeof(Uint32));
2002
2003   // precalculate row increments
2004   for (x = 0; x <= dst->w; x++)
2005     *csax++ = (int)(sx * x);
2006
2007   for (y = 0; y <= dst->h; y++)
2008     *csay++ = (int)(sy * y);
2009
2010   // pointer setup
2011   sp = csp = csp0 = (tColorRGBA *) src->pixels;
2012   dp = (tColorRGBA *) dst->pixels;
2013   dgap = dst->pitch - dst->w * 4;
2014
2015   csay = say;
2016   for (y = 0; y < dst->h; y++)
2017   {
2018     sp = csp;
2019     csax = sax;
2020
2021     for (x = 0; x < dst->w; x++)
2022     {
2023       // draw
2024       *dp = *sp;
2025
2026       // advance source pointers
2027       csax++;
2028       sp = csp + *csax;
2029
2030       // advance destination pointer
2031       dp++;
2032     }
2033
2034     // advance source pointer
2035     csay++;
2036     csp = (tColorRGBA *) ((Uint8 *) csp0 + *csay * src->pitch);
2037
2038     // advance destination pointers
2039     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
2040   }
2041
2042   free(sax);
2043   free(say);
2044
2045   return 0;
2046 }
2047
2048 // ----------------------------------------------------------------------------
2049 // 8 bit zoomer
2050 //
2051 // zoomes 8 bit palette/Y 'src' surface to 'dst' surface
2052 // ----------------------------------------------------------------------------
2053
2054 static int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
2055 {
2056   Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
2057   Uint8 *sp, *dp, *csp;
2058   int dgap;
2059
2060   // variable setup
2061   sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
2062   sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
2063
2064   // allocate memory for row increments
2065   sax = (Uint32 *)checked_malloc(dst->w * sizeof(Uint32));
2066   say = (Uint32 *)checked_malloc(dst->h * sizeof(Uint32));
2067
2068   // precalculate row increments
2069   csx = 0;
2070   csax = sax;
2071   for (x = 0; x < dst->w; x++)
2072   {
2073     csx += sx;
2074     *csax = (csx >> 16);
2075     csx &= 0xffff;
2076     csax++;
2077   }
2078
2079   csy = 0;
2080   csay = say;
2081   for (y = 0; y < dst->h; y++)
2082   {
2083     csy += sy;
2084     *csay = (csy >> 16);
2085     csy &= 0xffff;
2086     csay++;
2087   }
2088
2089   csx = 0;
2090   csax = sax;
2091   for (x = 0; x < dst->w; x++)
2092   {
2093     csx += (*csax);
2094     csax++;
2095   }
2096
2097   csy = 0;
2098   csay = say;
2099   for (y = 0; y < dst->h; y++)
2100   {
2101     csy += (*csay);
2102     csay++;
2103   }
2104
2105   // pointer setup
2106   sp = csp = (Uint8 *) src->pixels;
2107   dp = (Uint8 *) dst->pixels;
2108   dgap = dst->pitch - dst->w;
2109
2110   // draw
2111   csay = say;
2112   for (y = 0; y < dst->h; y++)
2113   {
2114     csax = sax;
2115     sp = csp;
2116     for (x = 0; x < dst->w; x++)
2117     {
2118       // draw
2119       *dp = *sp;
2120
2121       // advance source pointers
2122       sp += (*csax);
2123       csax++;
2124
2125       // advance destination pointer
2126       dp++;
2127     }
2128
2129     // advance source pointer (for row)
2130     csp += ((*csay) * src->pitch);
2131     csay++;
2132
2133     // advance destination pointers
2134     dp += dgap;
2135   }
2136
2137   free(sax);
2138   free(say);
2139
2140   return 0;
2141 }
2142
2143 // ----------------------------------------------------------------------------
2144 // zoomSurface()
2145 //
2146 // Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
2147 // 'zoomx' and 'zoomy' are scaling factors for width and height.
2148 // If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
2149 // into a 32bit RGBA format on the fly.
2150 // ----------------------------------------------------------------------------
2151
2152 static SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
2153 {
2154   SDL_Surface *zoom_src = NULL;
2155   SDL_Surface *zoom_dst = NULL;
2156   boolean is_converted = FALSE;
2157   boolean is_32bit;
2158   int i;
2159
2160   if (src == NULL)
2161     return NULL;
2162
2163   // determine if source surface is 32 bit or 8 bit
2164   is_32bit = (src->format->BitsPerPixel == 32);
2165
2166   if (is_32bit || src->format->BitsPerPixel == 8)
2167   {
2168     // use source surface 'as is'
2169     zoom_src = src;
2170   }
2171   else
2172   {
2173     // new source surface is 32 bit with a defined RGB ordering
2174     zoom_src = SDL_CreateRGBSurface(SURFACE_FLAGS, src->w, src->h, 32,
2175                                     0x000000ff, 0x0000ff00, 0x00ff0000,
2176                                     (src->format->Amask ? 0xff000000 : 0));
2177     SDL_BlitSurface(src, NULL, zoom_src, NULL);
2178     is_32bit = TRUE;
2179     is_converted = TRUE;
2180   }
2181
2182   // allocate surface to completely contain the zoomed surface
2183   if (is_32bit)
2184   {
2185     // target surface is 32 bit with source RGBA/ABGR ordering
2186     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 32,
2187                                     zoom_src->format->Rmask,
2188                                     zoom_src->format->Gmask,
2189                                     zoom_src->format->Bmask,
2190                                     zoom_src->format->Amask);
2191   }
2192   else
2193   {
2194     // target surface is 8 bit
2195     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 8,
2196                                     0, 0, 0, 0);
2197   }
2198
2199   // lock source surface
2200   SDL_LockSurface(zoom_src);
2201
2202   // check which kind of surface we have
2203   if (is_32bit)
2204   {
2205     // call the 32 bit transformation routine to do the zooming
2206     zoomSurfaceRGBA(zoom_src, zoom_dst);
2207   }
2208   else
2209   {
2210     // copy palette
2211     for (i = 0; i < zoom_src->format->palette->ncolors; i++)
2212       zoom_dst->format->palette->colors[i] =
2213         zoom_src->format->palette->colors[i];
2214     zoom_dst->format->palette->ncolors = zoom_src->format->palette->ncolors;
2215
2216     // call the 8 bit transformation routine to do the zooming
2217     zoomSurfaceY(zoom_src, zoom_dst);
2218   }
2219
2220   // unlock source surface
2221   SDL_UnlockSurface(zoom_src);
2222
2223   // free temporary surface
2224   if (is_converted)
2225     SDL_FreeSurface(zoom_src);
2226
2227   // return destination surface
2228   return zoom_dst;
2229 }
2230
2231 static SDL_Surface *SDLGetOpaqueSurface(SDL_Surface *surface)
2232 {
2233   SDL_Surface *new_surface;
2234
2235   if (surface == NULL)
2236     return NULL;
2237
2238   if ((new_surface = SDLGetNativeSurface(surface)) == NULL)
2239     Error(ERR_EXIT, "SDLGetNativeSurface() failed");
2240
2241   // remove alpha channel from native non-transparent surface, if defined
2242   SDLSetAlpha(new_surface, FALSE, 0);
2243
2244   // remove transparent color from native non-transparent surface, if defined
2245   SDL_SetColorKey(new_surface, UNSET_TRANSPARENT_PIXEL, 0);
2246
2247   return new_surface;
2248 }
2249
2250 Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
2251 {
2252   Bitmap *dst_bitmap = CreateBitmapStruct();
2253   SDL_Surface *src_surface = src_bitmap->surface_masked;
2254   SDL_Surface *dst_surface;
2255
2256   dst_width  = MAX(1, dst_width);       // prevent zero bitmap width
2257   dst_height = MAX(1, dst_height);      // prevent zero bitmap height
2258
2259   dst_bitmap->width  = dst_width;
2260   dst_bitmap->height = dst_height;
2261
2262   // create zoomed temporary surface from source surface
2263   dst_surface = zoomSurface(src_surface, dst_width, dst_height);
2264
2265   // create native format destination surface from zoomed temporary surface
2266   SDLSetNativeSurface(&dst_surface);
2267
2268   // set color key for zoomed surface from source surface, if defined
2269   if (SDLHasColorKey(src_surface))
2270     SDL_SetColorKey(dst_surface, SET_TRANSPARENT_PIXEL,
2271                     SDLGetColorKey(src_surface));
2272
2273   // create native non-transparent surface for opaque blitting
2274   dst_bitmap->surface = SDLGetOpaqueSurface(dst_surface);
2275
2276   // set native transparent surface for masked blitting
2277   dst_bitmap->surface_masked = dst_surface;
2278
2279   return dst_bitmap;
2280 }
2281
2282
2283 // ============================================================================
2284 // load image to bitmap
2285 // ============================================================================
2286
2287 Bitmap *SDLLoadImage(char *filename)
2288 {
2289   Bitmap *new_bitmap = CreateBitmapStruct();
2290   SDL_Surface *sdl_image_tmp;
2291
2292   if (program.headless)
2293   {
2294     // prevent sanity check warnings at later stage
2295     new_bitmap->width = new_bitmap->height = 1;
2296
2297     return new_bitmap;
2298   }
2299
2300   print_timestamp_init("SDLLoadImage");
2301
2302   print_timestamp_time(getBaseNamePtr(filename));
2303
2304   // load image to temporary surface
2305   if ((sdl_image_tmp = IMG_Load(filename)) == NULL)
2306     Error(ERR_EXIT, "IMG_Load('%s') failed: %s", getBaseNamePtr(filename),
2307           SDL_GetError());
2308
2309   print_timestamp_time("IMG_Load");
2310
2311   UPDATE_BUSY_STATE();
2312
2313   // create native non-transparent surface for current image
2314   if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
2315     Error(ERR_EXIT, "SDLGetOpaqueSurface() failed");
2316
2317   print_timestamp_time("SDLGetNativeSurface (opaque)");
2318
2319   UPDATE_BUSY_STATE();
2320
2321   // set black pixel to transparent if no alpha channel / transparent color
2322   if (!SDLHasAlpha(sdl_image_tmp) &&
2323       !SDLHasColorKey(sdl_image_tmp))
2324     SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
2325                     SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
2326
2327   // create native transparent surface for current image
2328   if ((new_bitmap->surface_masked = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
2329     Error(ERR_EXIT, "SDLGetNativeSurface() failed");
2330
2331   print_timestamp_time("SDLGetNativeSurface (masked)");
2332
2333   UPDATE_BUSY_STATE();
2334
2335   // free temporary surface
2336   SDL_FreeSurface(sdl_image_tmp);
2337
2338   new_bitmap->width = new_bitmap->surface->w;
2339   new_bitmap->height = new_bitmap->surface->h;
2340
2341   print_timestamp_done("SDLLoadImage");
2342
2343   return new_bitmap;
2344 }
2345
2346
2347 // ----------------------------------------------------------------------------
2348 // custom cursor fuctions
2349 // ----------------------------------------------------------------------------
2350
2351 static SDL_Cursor *create_cursor(struct MouseCursorInfo *cursor_info)
2352 {
2353   return SDL_CreateCursor(cursor_info->data, cursor_info->mask,
2354                           cursor_info->width, cursor_info->height,
2355                           cursor_info->hot_x, cursor_info->hot_y);
2356 }
2357
2358 void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
2359 {
2360   static struct MouseCursorInfo *last_cursor_info = NULL;
2361   static struct MouseCursorInfo *last_cursor_info2 = NULL;
2362   static SDL_Cursor *cursor_default = NULL;
2363   static SDL_Cursor *cursor_current = NULL;
2364
2365   // if invoked for the first time, store the SDL default cursor
2366   if (cursor_default == NULL)
2367     cursor_default = SDL_GetCursor();
2368
2369   // only create new cursor if cursor info (custom only) has changed
2370   if (cursor_info != NULL && cursor_info != last_cursor_info)
2371   {
2372     cursor_current = create_cursor(cursor_info);
2373     last_cursor_info = cursor_info;
2374   }
2375
2376   // only set new cursor if cursor info (custom or NULL) has changed
2377   if (cursor_info != last_cursor_info2)
2378     SDL_SetCursor(cursor_info ? cursor_current : cursor_default);
2379
2380   last_cursor_info2 = cursor_info;
2381 }
2382
2383
2384 // ============================================================================
2385 // audio functions
2386 // ============================================================================
2387
2388 void SDLOpenAudio(void)
2389 {
2390   if (program.headless)
2391     return;
2392
2393   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
2394   {
2395     Error(ERR_WARN, "SDL_InitSubSystem() failed: %s", SDL_GetError());
2396     return;
2397   }
2398
2399   if (Mix_OpenAudio(DEFAULT_AUDIO_SAMPLE_RATE, MIX_DEFAULT_FORMAT,
2400                     AUDIO_NUM_CHANNELS_STEREO,
2401                     setup.system.audio_fragment_size) < 0)
2402   {
2403     Error(ERR_WARN, "Mix_OpenAudio() failed: %s", SDL_GetError());
2404     return;
2405   }
2406
2407   audio.sound_available = TRUE;
2408   audio.music_available = TRUE;
2409   audio.loops_available = TRUE;
2410   audio.sound_enabled = TRUE;
2411
2412   // set number of available mixer channels
2413   audio.num_channels = Mix_AllocateChannels(NUM_MIXER_CHANNELS);
2414   audio.music_channel = MUSIC_CHANNEL;
2415   audio.first_sound_channel = FIRST_SOUND_CHANNEL;
2416
2417   Mixer_InitChannels();
2418 }
2419
2420 void SDLCloseAudio(void)
2421 {
2422   Mix_HaltMusic();
2423   Mix_HaltChannel(-1);
2424
2425   Mix_CloseAudio();
2426   SDL_QuitSubSystem(SDL_INIT_AUDIO);
2427 }
2428
2429
2430 // ============================================================================
2431 // event functions
2432 // ============================================================================
2433
2434 void SDLWaitEvent(Event *event)
2435 {
2436   SDL_WaitEvent(event);
2437 }
2438
2439 void SDLCorrectRawMousePosition(int *x, int *y)
2440 {
2441   if (sdl_renderer == NULL)
2442     return;
2443
2444   // this corrects the raw mouse position for logical screen size within event
2445   // filters (correction done later by SDL library when handling mouse events)
2446
2447   SDL_Rect viewport;
2448   float scale_x, scale_y;
2449
2450   SDL_RenderGetViewport(sdl_renderer, &viewport);
2451   SDL_RenderGetScale(sdl_renderer, &scale_x, &scale_y);
2452
2453   *x = (int)(*x / scale_x);
2454   *y = (int)(*y / scale_y);
2455
2456   *x -= viewport.x;
2457   *y -= viewport.y;
2458 }
2459
2460
2461 // ============================================================================
2462 // joystick functions
2463 // ============================================================================
2464
2465 static void *sdl_joystick[MAX_PLAYERS];         // game controller or joystick
2466 static int sdl_js_axis_raw[MAX_PLAYERS][2];
2467 static int sdl_js_axis[MAX_PLAYERS][2];
2468 static int sdl_js_button[MAX_PLAYERS][2];
2469 static boolean sdl_is_controller[MAX_PLAYERS];
2470
2471 void SDLClearJoystickState(void)
2472 {
2473   int i, j;
2474
2475   for (i = 0; i < MAX_PLAYERS; i++)
2476   {
2477     for (j = 0; j < 2; j++)
2478     {
2479       sdl_js_axis_raw[i][j] = -1;
2480       sdl_js_axis[i][j] = 0;
2481       sdl_js_button[i][j] = 0;
2482     }
2483   }
2484 }
2485
2486 boolean SDLOpenJoystick(int nr)
2487 {
2488   if (nr < 0 || nr >= MAX_PLAYERS)
2489     return FALSE;
2490
2491   sdl_is_controller[nr] = SDL_IsGameController(nr);
2492
2493 #if DEBUG_JOYSTICKS
2494   Error(ERR_DEBUG, "opening joystick %d (%s)",
2495         nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
2496 #endif
2497
2498   if (sdl_is_controller[nr])
2499     sdl_joystick[nr] = SDL_GameControllerOpen(nr);
2500   else
2501     sdl_joystick[nr] = SDL_JoystickOpen(nr);
2502
2503   return (sdl_joystick[nr] != NULL);
2504 }
2505
2506 void SDLCloseJoystick(int nr)
2507 {
2508   if (nr < 0 || nr >= MAX_PLAYERS)
2509     return;
2510
2511 #if DEBUG_JOYSTICKS
2512   Error(ERR_DEBUG, "closing joystick %d", nr);
2513 #endif
2514
2515   if (sdl_is_controller[nr])
2516     SDL_GameControllerClose(sdl_joystick[nr]);
2517   else
2518     SDL_JoystickClose(sdl_joystick[nr]);
2519
2520   sdl_joystick[nr] = NULL;
2521 }
2522
2523 boolean SDLCheckJoystickOpened(int nr)
2524 {
2525   if (nr < 0 || nr >= MAX_PLAYERS)
2526     return FALSE;
2527
2528   return (sdl_joystick[nr] != NULL ? TRUE : FALSE);
2529 }
2530
2531 static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
2532 {
2533   int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
2534                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
2535                  axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
2536                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
2537
2538   if (nr < 0 || nr >= MAX_PLAYERS)
2539     return;
2540
2541   if (axis_id == -1)
2542     return;
2543
2544   // prevent (slightly jittering, but centered) axis A from resetting axis B
2545   if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
2546       axis_id_raw != sdl_js_axis_raw[nr][axis_id])
2547     return;
2548
2549   sdl_js_axis[nr][axis_id] = axis_value;
2550   sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
2551 }
2552
2553 static void setJoystickButton(int nr, int button_id_raw, int button_state)
2554 {
2555   int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
2556                    button_id_raw == SDL_CONTROLLER_BUTTON_X ||
2557                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
2558                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
2559                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
2560                    button_id_raw == SDL_CONTROLLER_BUTTON_B ||
2561                    button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
2562                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
2563                    -1);
2564
2565   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
2566     sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
2567   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
2568     sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
2569   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
2570     sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
2571   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2572     sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
2573
2574   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
2575       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
2576       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
2577       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2578     sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
2579
2580   if (nr < 0 || nr >= MAX_PLAYERS)
2581     return;
2582
2583   if (button_id == -1)
2584     return;
2585
2586   sdl_js_button[nr][button_id] = button_state;
2587 }
2588
2589 void HandleJoystickEvent(Event *event)
2590 {
2591   // when using joystick, disable overlay touch buttons
2592   runtime.uses_touch_device = FALSE;
2593
2594   switch (event->type)
2595   {
2596     case SDL_CONTROLLERDEVICEADDED:
2597 #if DEBUG_JOYSTICKS
2598       Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEADDED: device %d added",
2599             event->cdevice.which);
2600 #endif
2601       InitJoysticks();
2602       break;
2603
2604     case SDL_CONTROLLERDEVICEREMOVED:
2605 #if DEBUG_JOYSTICKS
2606       Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
2607             event->cdevice.which);
2608 #endif
2609       InitJoysticks();
2610       break;
2611
2612     case SDL_CONTROLLERAXISMOTION:
2613 #if DEBUG_JOYSTICKS
2614       Error(ERR_DEBUG, "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
2615             event->caxis.which, event->caxis.axis, event->caxis.value);
2616 #endif
2617       setJoystickAxis(event->caxis.which,
2618                       event->caxis.axis,
2619                       event->caxis.value);
2620       break;
2621
2622     case SDL_CONTROLLERBUTTONDOWN:
2623 #if DEBUG_JOYSTICKS
2624       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
2625             event->cbutton.which, event->cbutton.button);
2626 #endif
2627       setJoystickButton(event->cbutton.which,
2628                         event->cbutton.button,
2629                         TRUE);
2630       break;
2631
2632     case SDL_CONTROLLERBUTTONUP:
2633 #if DEBUG_JOYSTICKS
2634       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONUP: device %d, button %d",
2635             event->cbutton.which, event->cbutton.button);
2636 #endif
2637       setJoystickButton(event->cbutton.which,
2638                         event->cbutton.button,
2639                         FALSE);
2640       break;
2641
2642     case SDL_JOYAXISMOTION:
2643       if (sdl_is_controller[event->jaxis.which])
2644         break;
2645
2646 #if DEBUG_JOYSTICKS
2647       Error(ERR_DEBUG, "SDL_JOYAXISMOTION: device %d, axis %d: %d",
2648             event->jaxis.which, event->jaxis.axis, event->jaxis.value);
2649 #endif
2650       if (event->jaxis.axis < 4)
2651         setJoystickAxis(event->jaxis.which,
2652                         event->jaxis.axis,
2653                         event->jaxis.value);
2654       break;
2655
2656     case SDL_JOYBUTTONDOWN:
2657       if (sdl_is_controller[event->jaxis.which])
2658         break;
2659
2660 #if DEBUG_JOYSTICKS
2661       Error(ERR_DEBUG, "SDL_JOYBUTTONDOWN: device %d, button %d",
2662             event->jbutton.which, event->jbutton.button);
2663 #endif
2664       if (event->jbutton.button < 4)
2665         setJoystickButton(event->jbutton.which,
2666                           event->jbutton.button,
2667                           TRUE);
2668       break;
2669
2670     case SDL_JOYBUTTONUP:
2671       if (sdl_is_controller[event->jaxis.which])
2672         break;
2673
2674 #if DEBUG_JOYSTICKS
2675       Error(ERR_DEBUG, "SDL_JOYBUTTONUP: device %d, button %d",
2676             event->jbutton.which, event->jbutton.button);
2677 #endif
2678       if (event->jbutton.button < 4)
2679         setJoystickButton(event->jbutton.which,
2680                           event->jbutton.button,
2681                           FALSE);
2682       break;
2683
2684     default:
2685       break;
2686   }
2687 }
2688
2689 void SDLInitJoysticks(void)
2690 {
2691   static boolean sdl_joystick_subsystem_initialized = FALSE;
2692   boolean print_warning = !sdl_joystick_subsystem_initialized;
2693   char *mappings_file_base = getPath2(options.conf_directory,
2694                                       GAMECONTROLLER_BASENAME);
2695   char *mappings_file_user = getPath2(getUserGameDataDir(),
2696                                       GAMECONTROLLER_BASENAME);
2697   int num_mappings;
2698   int i;
2699
2700   if (!sdl_joystick_subsystem_initialized)
2701   {
2702     sdl_joystick_subsystem_initialized = TRUE;
2703
2704     SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
2705
2706     if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
2707     {
2708       Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
2709       return;
2710     }
2711
2712     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
2713
2714     // the included game controller base mappings should always be found
2715     if (num_mappings == -1)
2716       Error(ERR_WARN, "no game controller base mappings found");
2717 #if DEBUG_JOYSTICKS
2718     else
2719       Error(ERR_INFO, "%d game controller base mapping(s) added", num_mappings);
2720 #endif
2721
2722     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
2723
2724 #if DEBUG_JOYSTICKS
2725     // the personal game controller user mappings may or may not be found
2726     if (num_mappings == -1)
2727       Error(ERR_WARN, "no game controller user mappings found");
2728     else
2729       Error(ERR_INFO, "%d game controller user mapping(s) added", num_mappings);
2730
2731     Error(ERR_INFO, "%d joystick(s) found:", SDL_NumJoysticks());
2732 #endif
2733
2734     checked_free(mappings_file_base);
2735     checked_free(mappings_file_user);
2736
2737 #if DEBUG_JOYSTICKS
2738     for (i = 0; i < SDL_NumJoysticks(); i++)
2739     {
2740       const char *name, *type;
2741
2742       if (SDL_IsGameController(i))
2743       {
2744         name = SDL_GameControllerNameForIndex(i);
2745         type = "game controller";
2746       }
2747       else
2748       {
2749         name = SDL_JoystickNameForIndex(i);
2750         type = "joystick";
2751       }
2752
2753       Error(ERR_INFO, "- joystick %d (%s): '%s'",
2754             i, type, (name ? name : "(Unknown)"));
2755     }
2756 #endif
2757   }
2758
2759   // assign joysticks from configured to connected joystick for all players
2760   for (i = 0; i < MAX_PLAYERS; i++)
2761   {
2762     // get configured joystick for this player
2763     char *device_name = setup.input[i].joy.device_name;
2764     int joystick_nr = getJoystickNrFromDeviceName(device_name);
2765
2766     if (joystick_nr >= SDL_NumJoysticks())
2767     {
2768       if (setup.input[i].use_joystick && print_warning)
2769         Error(ERR_WARN, "cannot find joystick %d", joystick_nr);
2770
2771       joystick_nr = -1;
2772     }
2773
2774     // store configured joystick number for each player
2775     joystick.nr[i] = joystick_nr;
2776   }
2777
2778   // now open all connected joysticks (regardless if configured or not)
2779   for (i = 0; i < SDL_NumJoysticks(); i++)
2780   {
2781     // this allows subsequent calls to 'InitJoysticks' for re-initialization
2782     if (SDLCheckJoystickOpened(i))
2783       SDLCloseJoystick(i);
2784
2785     if (SDLOpenJoystick(i))
2786       joystick.status = JOYSTICK_ACTIVATED;
2787     else if (print_warning)
2788       Error(ERR_WARN, "cannot open joystick %d", i);
2789   }
2790
2791   SDLClearJoystickState();
2792 }
2793
2794 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
2795 {
2796   if (nr < 0 || nr >= MAX_PLAYERS)
2797     return FALSE;
2798
2799   if (x != NULL)
2800     *x = sdl_js_axis[nr][0];
2801   if (y != NULL)
2802     *y = sdl_js_axis[nr][1];
2803
2804   if (b1 != NULL)
2805     *b1 = sdl_js_button[nr][0];
2806   if (b2 != NULL)
2807     *b2 = sdl_js_button[nr][1];
2808
2809   return TRUE;
2810 }
2811
2812
2813 // ============================================================================
2814 // touch input overlay functions
2815 // ============================================================================
2816
2817 #if defined(USE_TOUCH_INPUT_OVERLAY)
2818 static void DrawTouchInputOverlay_ShowGrid(int alpha)
2819 {
2820   SDL_Rect rect;
2821   int grid_xsize = overlay.grid_xsize;
2822   int grid_ysize = overlay.grid_ysize;
2823   int x, y;
2824
2825   SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha);
2826   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2827
2828   for (x = 0; x < grid_xsize; x++)
2829   {
2830     rect.x = (x + 0) * video.screen_width / grid_xsize;
2831     rect.w = (x + 1) * video.screen_width / grid_xsize - rect.x;
2832
2833     for (y = 0; y < grid_ysize; y++)
2834     {
2835       rect.y = (y + 0) * video.screen_height / grid_ysize;
2836       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2837
2838       if (overlay.grid_button[x][y] == CHAR_GRID_BUTTON_NONE)
2839         SDL_RenderDrawRect(sdl_renderer, &rect);
2840     }
2841   }
2842
2843   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2844 }
2845
2846 static void RenderFillRectangle(int x, int y, int width, int height)
2847 {
2848   SDL_Rect rect = { x, y, width, height };
2849
2850   SDL_RenderFillRect(sdl_renderer, &rect);
2851 }
2852
2853 static void DrawTouchInputOverlay_ShowGridButtons(int alpha)
2854 {
2855   static int alpha_direction = 0;
2856   static int alpha_highlight = 0;
2857   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2858   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2859   SDL_Rect rect;
2860   int grid_xsize = overlay.grid_xsize;
2861   int grid_ysize = overlay.grid_ysize;
2862   int x, y;
2863
2864   if (alpha == alpha_max)
2865   {
2866     if (alpha_direction < 0)
2867     {
2868       alpha_highlight = MAX(0, alpha_highlight - alpha_step);
2869
2870       if (alpha_highlight == 0)
2871         alpha_direction = 1;
2872     }
2873     else
2874     {
2875       alpha_highlight = MIN(alpha_highlight + alpha_step, alpha_max);
2876
2877       if (alpha_highlight == alpha_max)
2878         alpha_direction = -1;
2879     }
2880   }
2881   else
2882   {
2883     alpha_direction = 1;
2884     alpha_highlight = alpha;
2885   }
2886
2887   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2888
2889   for (x = 0; x < grid_xsize; x++)
2890   {
2891     for (y = 0; y < grid_ysize; y++)
2892     {
2893       int grid_button = overlay.grid_button[x][y];
2894       int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
2895       int alpha_draw = alpha;
2896       int outline_border = MV_NONE;
2897       int border_size = 2;
2898       boolean draw_outlined = setup.touch.draw_outlined;
2899       boolean draw_pressed = setup.touch.draw_pressed;
2900
2901       if (grid_button == CHAR_GRID_BUTTON_NONE)
2902         continue;
2903
2904       if (grid_button == overlay.grid_button_highlight)
2905       {
2906         draw_outlined = FALSE;
2907         alpha_draw = MIN((float)alpha_highlight * 1.5, SDL_ALPHA_OPAQUE);
2908       }
2909
2910       if (draw_pressed && overlay.grid_button_action & grid_button_action)
2911       {
2912         if (draw_outlined)
2913           draw_outlined = FALSE;
2914         else
2915           alpha_draw = MIN((float)alpha_draw * 1.5, SDL_ALPHA_OPAQUE);
2916       }
2917
2918       SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha_draw);
2919
2920       rect.x = (x + 0) * video.screen_width  / grid_xsize;
2921       rect.y = (y + 0) * video.screen_height / grid_ysize;
2922       rect.w = (x + 1) * video.screen_width  / grid_xsize - rect.x;
2923       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2924
2925       if (x == 0 || overlay.grid_button[x - 1][y] != grid_button)
2926       {
2927         rect.x += border_size;
2928         rect.w -= border_size;
2929
2930         outline_border |= MV_LEFT;
2931       }
2932
2933       if (x == grid_xsize - 1 || overlay.grid_button[x + 1][y] != grid_button)
2934       {
2935         rect.w -= border_size;
2936
2937         outline_border |= MV_RIGHT;
2938       }
2939
2940       if (y == 0 || overlay.grid_button[x][y - 1] != grid_button)
2941       {
2942         rect.y += border_size;
2943         rect.h -= border_size;
2944
2945         outline_border |= MV_UP;
2946       }
2947
2948       if (y == grid_ysize - 1 || overlay.grid_button[x][y + 1] != grid_button)
2949       {
2950         rect.h -= border_size;
2951
2952         outline_border |= MV_DOWN;
2953       }
2954
2955       if (draw_outlined)
2956       {
2957         int rect_x = rect.x +
2958           (outline_border & MV_LEFT  ? border_size : 0);
2959         int rect_w = rect.w -
2960           (outline_border & MV_LEFT  ? border_size : 0) -
2961           (outline_border & MV_RIGHT ? border_size : 0);
2962
2963         if (outline_border & MV_LEFT)
2964           RenderFillRectangle(rect.x, rect.y, border_size, rect.h);
2965
2966         if (outline_border & MV_RIGHT)
2967           RenderFillRectangle(rect.x + rect.w - border_size, rect.y,
2968                               border_size, rect.h);
2969
2970         if (outline_border & MV_UP)
2971           RenderFillRectangle(rect_x, rect.y, rect_w, border_size);
2972
2973         if (outline_border & MV_DOWN)
2974           RenderFillRectangle(rect_x, rect.y + rect.h - border_size,
2975                               rect_w, border_size);
2976       }
2977       else
2978       {
2979         SDL_RenderFillRect(sdl_renderer, &rect);
2980       }
2981     }
2982   }
2983
2984   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2985 }
2986
2987 static void DrawTouchInputOverlay(void)
2988 {
2989   static boolean deactivated = TRUE;
2990   static boolean show_grid = FALSE;
2991   static int alpha = 0;
2992   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2993   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2994   boolean active = (overlay.enabled && overlay.active);
2995
2996   if (!active && deactivated)
2997     return;
2998
2999   if (active)
3000   {
3001     if (alpha < alpha_max)
3002       alpha = MIN(alpha + alpha_step, alpha_max);
3003
3004     deactivated = FALSE;
3005   }
3006   else
3007   {
3008     alpha = MAX(0, alpha - alpha_step);
3009
3010     if (alpha == 0)
3011       deactivated = TRUE;
3012   }
3013
3014   if (overlay.show_grid)
3015     show_grid = TRUE;
3016   else if (deactivated)
3017     show_grid = FALSE;
3018
3019   if (show_grid)
3020     DrawTouchInputOverlay_ShowGrid(alpha);
3021
3022   DrawTouchInputOverlay_ShowGridButtons(alpha);
3023 }
3024
3025 static void DrawTouchGadgetsOverlay(void)
3026 {
3027   DrawGadgets_OverlayTouchButtons();
3028 }
3029 #endif