a66049e97e0175cf9fef962ff2518d0d7a658ee2
[rocksndiamonds.git] / src / libgame / sdl.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // sdl.c
10 // ============================================================================
11
12 #include "system.h"
13 #include "sound.h"
14 #include "joystick.h"
15 #include "misc.h"
16 #include "setup.h"
17 #include "gadgets.h"
18
19 #define ENABLE_UNUSED_CODE      0       // currently unused functions
20
21 #define DEBUG_JOYSTICKS         0
22
23
24 // ============================================================================
25 // video functions
26 // ============================================================================
27
28 // SDL internal variables
29 static SDL_Window *sdl_window = NULL;
30 static SDL_Renderer *sdl_renderer = NULL;
31 static SDL_Texture *sdl_texture_stream = NULL;
32 static SDL_Texture *sdl_texture_target = NULL;
33 static boolean fullscreen_enabled = FALSE;
34 static boolean limit_screen_updates = FALSE;
35
36
37 // functions from SGE library
38 void sge_Line(SDL_Surface *, Sint16, Sint16, Sint16, Sint16, Uint32);
39
40 #if defined(USE_TOUCH_INPUT_OVERLAY)
41 // functions to draw overlay graphics for touch device input
42 static void DrawTouchInputOverlay(void);
43 static void DrawTouchGadgetsOverlay(void);
44 #endif
45
46 void SDLLimitScreenUpdates(boolean enable)
47 {
48   limit_screen_updates = enable;
49 }
50
51 static void FinalizeScreen(int draw_target)
52 {
53   // copy global animations to render target buffer, if defined (below border)
54   if (gfx.draw_global_anim_function != NULL)
55     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_1);
56
57   // copy global masked border to render target buffer, if defined
58   if (gfx.draw_global_border_function != NULL)
59     gfx.draw_global_border_function(draw_target);
60
61   // copy global animations to render target buffer, if defined (above border)
62   if (gfx.draw_global_anim_function != NULL)
63     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_2);
64
65   // copy tile selection cursor to render target buffer, if defined (above all)
66   if (gfx.draw_tile_cursor_function != NULL)
67     gfx.draw_tile_cursor_function(draw_target);
68 }
69
70 static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
71 {
72   static unsigned int update_screen_delay = 0;
73   unsigned int update_screen_delay_value = 50;          // (milliseconds)
74   SDL_Surface *screen = backbuffer->surface;
75
76   if (limit_screen_updates &&
77       !DelayReached(&update_screen_delay, update_screen_delay_value))
78     return;
79
80   LimitScreenUpdates(FALSE);
81
82 #if 0
83   {
84     static int LastFrameCounter = 0;
85     boolean changed = (FrameCounter != LastFrameCounter);
86
87     Debug("internal:frame", "FrameCounter == %d [%s]", FrameCounter,
88           (changed ? "-" : "SAME FRAME UPDATED"));
89
90     LastFrameCounter = FrameCounter;
91
92     /*
93     if (FrameCounter % 2)
94       return;
95     */
96   }
97 #endif
98
99   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP &&
100       gfx.final_screen_bitmap != NULL)  // may not be initialized yet
101   {
102     // draw global animations using bitmaps instead of using textures
103     // to prevent texture scaling artefacts (this is potentially slower)
104
105     BlitBitmap(backbuffer, gfx.final_screen_bitmap, 0, 0,
106                gfx.win_xsize, gfx.win_ysize, 0, 0);
107
108     FinalizeScreen(DRAW_TO_SCREEN);
109
110     screen = gfx.final_screen_bitmap->surface;
111
112     // force full window redraw
113     rect = NULL;
114   }
115
116   SDL_Texture *sdl_texture = sdl_texture_stream;
117
118   // deactivate use of target texture if render targets are not supported
119   if ((video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
120        video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE) &&
121       sdl_texture_target == NULL)
122     video.screen_rendering_mode = SPECIAL_RENDERING_OFF;
123
124   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET)
125     sdl_texture = sdl_texture_target;
126
127   if (rect)
128   {
129     int bytes_x = screen->pitch / video.width;
130     int bytes_y = screen->pitch;
131
132     SDL_UpdateTexture(sdl_texture, rect,
133                       screen->pixels + rect->x * bytes_x + rect->y * bytes_y,
134                       screen->pitch);
135   }
136   else
137   {
138     SDL_UpdateTexture(sdl_texture, NULL, screen->pixels, screen->pitch);
139   }
140
141   int xoff = video.screen_xoffset;
142   int yoff = video.screen_yoffset;
143   SDL_Rect dst_rect_screen = { xoff, yoff, video.width, video.height };
144   SDL_Rect *src_rect1 = NULL, *dst_rect1 = NULL;
145   SDL_Rect *src_rect2 = NULL, *dst_rect2 = NULL;
146
147   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
148       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
149     dst_rect2 = &dst_rect_screen;
150   else
151     dst_rect1 = &dst_rect_screen;
152
153 #if defined(HAS_SCREEN_KEYBOARD)
154   if (video.shifted_up || video.shifted_up_delay)
155   {
156     int time_current = SDL_GetTicks();
157     int pos = video.shifted_up_pos;
158     int pos_last = video.shifted_up_pos_last;
159
160     if (!DelayReachedExt(&video.shifted_up_delay, video.shifted_up_delay_value,
161                          time_current))
162     {
163       int delay = time_current - video.shifted_up_delay;
164       int delay_value = video.shifted_up_delay_value;
165
166       pos = pos_last + (pos - pos_last) * delay / delay_value;
167     }
168     else
169     {
170       video.shifted_up_pos_last = pos;
171       video.shifted_up_delay = 0;
172     }
173
174     SDL_Rect src_rect_up = { 0,    pos,  video.width, video.height - pos };
175     SDL_Rect dst_rect_up = { xoff, yoff, video.width, video.height - pos };
176
177     if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
178         video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
179     {
180       src_rect2 = &src_rect_up;
181       dst_rect2 = &dst_rect_up;
182     }
183     else
184     {
185       src_rect1 = &src_rect_up;
186       dst_rect1 = &dst_rect_up;
187     }
188   }
189 #endif
190
191   // clear render target buffer
192   SDL_RenderClear(sdl_renderer);
193
194   // set renderer to use target texture for rendering
195   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
196       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
197     SDL_SetRenderTarget(sdl_renderer, sdl_texture_target);
198
199   // copy backbuffer texture to render target buffer
200   if (video.screen_rendering_mode != SPECIAL_RENDERING_TARGET)
201     SDL_RenderCopy(sdl_renderer, sdl_texture_stream, src_rect1, dst_rect1);
202
203   if (video.screen_rendering_mode != SPECIAL_RENDERING_BITMAP)
204     FinalizeScreen(DRAW_TO_SCREEN);
205
206   // when using target texture, copy it to screen buffer
207   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
208       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
209   {
210     SDL_SetRenderTarget(sdl_renderer, NULL);
211     SDL_RenderCopy(sdl_renderer, sdl_texture_target, src_rect2, dst_rect2);
212   }
213
214 #if defined(USE_TOUCH_INPUT_OVERLAY)
215   // draw overlay graphics for touch device input, if needed
216   DrawTouchInputOverlay();
217
218   // draw overlay gadgets for touch device input, if needed
219   DrawTouchGadgetsOverlay();
220 #endif
221
222   // global synchronization point of the game to align video frame delay
223   if (with_frame_delay)
224     WaitUntilDelayReached(&video.frame_delay, video.frame_delay_value);
225
226   video.frame_counter++;
227
228   // show render target buffer on screen
229   SDL_RenderPresent(sdl_renderer);
230 }
231
232 static void UpdateScreen_WithFrameDelay(SDL_Rect *rect)
233 {
234   PumpEvents();         // execute event filter actions while waiting
235
236   UpdateScreenExt(rect, TRUE);
237 }
238
239 static void UpdateScreen_WithoutFrameDelay(SDL_Rect *rect)
240 {
241   UpdateScreenExt(rect, FALSE);
242 }
243
244 void Delay_WithScreenUpdates(unsigned int delay)
245 {
246   unsigned int time_current = SDL_GetTicks();
247   unsigned int time_delayed = time_current + delay;
248
249   while (time_current < time_delayed)
250   {
251     // updating the screen contains waiting for frame delay (non-busy)
252     UpdateScreen_WithFrameDelay(NULL);
253
254     time_current = SDL_GetTicks();
255   }
256 }
257
258 static void SDLSetWindowIcon(char *basename)
259 {
260   // (setting the window icon on Mac OS X would replace the high-quality
261   // dock icon with the currently smaller (and uglier) icon from file)
262
263 #if !defined(PLATFORM_MACOSX)
264   char *filename = getCustomImageFilename(basename);
265   SDL_Surface *surface;
266
267   if (filename == NULL)
268   {
269     Warn("SDLSetWindowIcon(): cannot find file '%s'", basename);
270
271     return;
272   }
273
274   if ((surface = IMG_Load(filename)) == NULL)
275   {
276     Warn("IMG_Load('%s') failed: %s", basename, SDL_GetError());
277
278     return;
279   }
280
281   // set transparent color
282   SDL_SetColorKey(surface, SET_TRANSPARENT_PIXEL,
283                   SDL_MapRGB(surface->format, 0x00, 0x00, 0x00));
284
285   SDL_SetWindowIcon(sdl_window, surface);
286 #endif
287 }
288
289 static boolean equalSDLPixelFormat(SDL_PixelFormat *format1,
290                                    SDL_PixelFormat *format2)
291 {
292   return (format1->format        == format2->format &&
293           format1->BitsPerPixel  == format2->BitsPerPixel &&
294           format1->BytesPerPixel == format2->BytesPerPixel &&
295           format1->Rmask         == format2->Rmask &&
296           format1->Gmask         == format2->Gmask &&
297           format1->Bmask         == format2->Bmask);
298 }
299
300 static Pixel SDLGetColorKey(SDL_Surface *surface)
301 {
302   Pixel color_key;
303
304   if (SDL_GetColorKey(surface, &color_key) != 0)
305     return -1;
306
307   return color_key;
308 }
309
310 static boolean SDLHasColorKey(SDL_Surface *surface)
311 {
312   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     Fail("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     Fail("SDL_CreateTextureFromSurface() failed: %s", SDL_GetError());
405
406   return texture;
407 }
408
409 void SDLCreateBitmapTextures(Bitmap *bitmap)
410 {
411   if (bitmap == NULL)
412     return;
413
414   if (bitmap->texture)
415     SDL_DestroyTexture(bitmap->texture);
416   if (bitmap->texture_masked)
417     SDL_DestroyTexture(bitmap->texture_masked);
418
419   bitmap->texture        = SDLCreateTextureFromSurface(bitmap->surface);
420   bitmap->texture_masked = SDLCreateTextureFromSurface(bitmap->surface_masked);
421 }
422
423 void SDLFreeBitmapTextures(Bitmap *bitmap)
424 {
425   if (bitmap == NULL)
426     return;
427
428   if (bitmap->texture)
429     SDL_DestroyTexture(bitmap->texture);
430   if (bitmap->texture_masked)
431     SDL_DestroyTexture(bitmap->texture_masked);
432
433   bitmap->texture = NULL;
434   bitmap->texture_masked = NULL;
435 }
436
437 void SDLInitVideoDisplay(void)
438 {
439   // set hint to select render driver as specified in setup config file
440   if (!strEqual(setup.system.sdl_renderdriver, ARG_DEFAULT))
441     SDL_SetHint(SDL_HINT_RENDER_DRIVER, setup.system.sdl_renderdriver);
442
443   // initialize SDL video
444   if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
445     Fail("SDL_InitSubSystem() failed: %s", SDL_GetError());
446
447   // set default SDL depth
448   video.default_depth = 32;     // (how to determine video depth in SDL2?)
449   //
450   // Code used with SDL 1.2:
451   // video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
452 }
453
454 static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
455 {
456   if (program.headless)
457     return;
458
459   video.window_scaling_percent = setup.window_scaling_percent;
460   video.window_scaling_quality = setup.window_scaling_quality;
461
462   SDLSetScreenRenderingMode(setup.screen_rendering_mode);
463
464   // SDL 2.0: support for (desktop) fullscreen mode available
465   video.fullscreen_available = TRUE;
466
467   // open SDL video output device (window or fullscreen mode)
468   if (!SDLSetVideoMode(fullscreen))
469     Fail("setting video mode failed");
470
471   // !!! SDL2 can only set the window icon if the window already exists !!!
472   // set window icon
473   SDLSetWindowIcon(program.icon_filename);
474
475   // set window and icon title
476   SDLSetWindowTitle();
477 }
478
479 static void SDLInitVideoBuffer_DrawBuffer(void)
480 {
481   /* SDL cannot directly draw to the visible video framebuffer like X11,
482      but always uses a backbuffer, which is then blitted to the visible
483      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
484      visible video framebuffer with 'SDL_Flip', if the hardware supports
485      this). Therefore do not use an additional backbuffer for drawing, but
486      use a symbolic buffer (distinguishable from the SDL backbuffer) called
487      'window', which indicates that the SDL backbuffer should be updated to
488      the visible video framebuffer when attempting to blit to it.
489
490      For convenience, it seems to be a good idea to create this symbolic
491      buffer 'window' at the same size as the SDL backbuffer. Although it
492      should never be drawn to directly, it would do no harm nevertheless. */
493
494   // create additional (symbolic) buffer for double-buffering
495   ReCreateBitmap(&window, video.width, video.height);
496
497   // create dummy drawing buffer for headless mode, if needed
498   if (program.headless)
499     ReCreateBitmap(&backbuffer, video.width, video.height);
500 }
501
502 void SDLInitVideoBuffer(boolean fullscreen)
503 {
504   SDLInitVideoBuffer_VideoBuffer(fullscreen);
505   SDLInitVideoBuffer_DrawBuffer();
506 }
507
508 static boolean SDLCreateScreen(boolean fullscreen)
509 {
510   SDL_Surface *new_surface = NULL;
511
512   int surface_flags_window     = SURFACE_FLAGS;
513   int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN_DESKTOP;
514
515 #if 1
516   int renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
517
518   video.vsync_mode = VSYNC_MODE_OFF;
519
520   if (!strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF))
521   {
522     renderer_flags |= SDL_RENDERER_PRESENTVSYNC;
523     video.vsync_mode = VSYNC_MODE_NORMAL;
524   }
525 #else
526   /* If SDL_CreateRenderer() is called from within a VirtualBox Windows VM
527      _without_ enabling 2D/3D acceleration and/or guest additions installed,
528      it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
529      it will try to use accelerated graphics and apparently fails miserably) */
530   int renderer_flags = SDL_RENDERER_SOFTWARE;
531 #endif
532
533   int width  = video.width;
534   int height = video.height;
535   int screen_width  = video.screen_width;
536   int screen_height = video.screen_height;
537   int surface_flags = (fullscreen ? surface_flags_fullscreen :
538                        surface_flags_window);
539
540   // default window size is unscaled
541   video.window_width  = screen_width;
542   video.window_height = screen_height;
543
544   // store if initial screen mode is fullscreen mode when changing screen size
545   video.fullscreen_initial = fullscreen;
546
547   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
548
549   video.window_width  = window_scaling_factor * screen_width;
550   video.window_height = window_scaling_factor * screen_height;
551
552   if (sdl_texture_stream)
553   {
554     SDL_DestroyTexture(sdl_texture_stream);
555     sdl_texture_stream = NULL;
556   }
557
558   if (sdl_texture_target)
559   {
560     SDL_DestroyTexture(sdl_texture_target);
561     sdl_texture_target = NULL;
562   }
563
564   if (!(fullscreen && fullscreen_enabled))
565   {
566     if (sdl_renderer)
567     {
568       SDL_DestroyRenderer(sdl_renderer);
569       sdl_renderer = NULL;
570     }
571
572     if (sdl_window)
573     {
574       SDL_DestroyWindow(sdl_window);
575       sdl_window = NULL;
576     }
577   }
578
579   if (sdl_window == NULL)
580     sdl_window = SDL_CreateWindow(program.window_title,
581                                   SDL_WINDOWPOS_CENTERED,
582                                   SDL_WINDOWPOS_CENTERED,
583                                   video.window_width,
584                                   video.window_height,
585                                   surface_flags);
586
587   if (sdl_window != NULL)
588   {
589     if (sdl_renderer == NULL)
590       sdl_renderer = SDL_CreateRenderer(sdl_window, -1, renderer_flags);
591
592     if (sdl_renderer != NULL)
593     {
594       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
595       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
596
597       // required for setting adaptive vsync when using OpenGL renderer
598       SDLSetScreenVsyncMode(setup.vsync_mode);
599
600       sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
601                                              SDL_PIXELFORMAT_ARGB8888,
602                                              SDL_TEXTUREACCESS_STREAMING,
603                                              width, height);
604
605       if (SDL_RenderTargetSupported(sdl_renderer))
606         sdl_texture_target = SDL_CreateTexture(sdl_renderer,
607                                                SDL_PIXELFORMAT_ARGB8888,
608                                                SDL_TEXTUREACCESS_TARGET,
609                                                width, height);
610
611       if (sdl_texture_stream != NULL)
612       {
613         // use SDL default values for RGB masks and no alpha channel
614         new_surface = SDL_CreateRGBSurface(0, width, height, 32, 0,0,0, 0);
615
616         if (new_surface == NULL)
617           Warn("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
618       }
619       else
620       {
621         Warn("SDL_CreateTexture() failed: %s", SDL_GetError());
622       }
623     }
624     else
625     {
626       Warn("SDL_CreateRenderer() failed: %s", SDL_GetError());
627     }
628   }
629   else
630   {
631     Warn("SDL_CreateWindow() failed: %s", SDL_GetError());
632   }
633
634   SDLSetScreenProperties();
635
636   // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
637   if (new_surface != NULL)
638     fullscreen_enabled = fullscreen;
639
640   if (backbuffer == NULL)
641     backbuffer = CreateBitmapStruct();
642
643   backbuffer->width  = video.width;
644   backbuffer->height = video.height;
645
646   if (backbuffer->surface)
647     SDL_FreeSurface(backbuffer->surface);
648
649   backbuffer->surface = new_surface;
650
651   return (new_surface != NULL);
652 }
653
654 boolean SDLSetVideoMode(boolean fullscreen)
655 {
656   boolean success = FALSE;
657
658   SetWindowTitle();
659
660   if (fullscreen && !video.fullscreen_enabled && video.fullscreen_available)
661   {
662     // switch display to fullscreen mode, if available
663     success = SDLCreateScreen(TRUE);
664
665     if (!success)
666     {
667       // switching display to fullscreen mode failed -- do not try it again
668       video.fullscreen_available = FALSE;
669     }
670     else
671     {
672       video.fullscreen_enabled = TRUE;
673     }
674   }
675
676   if ((!fullscreen && video.fullscreen_enabled) || !success)
677   {
678     // switch display to window mode
679     success = SDLCreateScreen(FALSE);
680
681     if (!success)
682     {
683       // switching display to window mode failed -- should not happen
684     }
685     else
686     {
687       video.fullscreen_enabled = FALSE;
688       video.window_scaling_percent = setup.window_scaling_percent;
689       video.window_scaling_quality = setup.window_scaling_quality;
690
691       SDLSetScreenRenderingMode(setup.screen_rendering_mode);
692     }
693   }
694
695   SDLRedrawWindow();                    // map window
696
697   return success;
698 }
699
700 void SDLSetWindowTitle(void)
701 {
702   if (sdl_window == NULL)
703     return;
704
705   SDL_SetWindowTitle(sdl_window, program.window_title);
706 }
707
708 void SDLSetWindowScaling(int window_scaling_percent)
709 {
710   if (sdl_window == NULL)
711     return;
712
713   float window_scaling_factor = (float)window_scaling_percent / 100;
714   int new_window_width  = (int)(window_scaling_factor * video.screen_width);
715   int new_window_height = (int)(window_scaling_factor * video.screen_height);
716
717   SDL_SetWindowSize(sdl_window, new_window_width, new_window_height);
718
719   video.window_scaling_percent = window_scaling_percent;
720   video.window_width  = new_window_width;
721   video.window_height = new_window_height;
722
723   SetWindowTitle();
724 }
725
726 void SDLSetWindowScalingQuality(char *window_scaling_quality)
727 {
728   SDL_Texture *new_texture;
729
730   if (sdl_texture_stream == NULL)
731     return;
732
733   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, window_scaling_quality);
734
735   new_texture = SDL_CreateTexture(sdl_renderer,
736                                   SDL_PIXELFORMAT_ARGB8888,
737                                   SDL_TEXTUREACCESS_STREAMING,
738                                   video.width, video.height);
739
740   if (new_texture != NULL)
741   {
742     SDL_DestroyTexture(sdl_texture_stream);
743
744     sdl_texture_stream = new_texture;
745   }
746
747   if (SDL_RenderTargetSupported(sdl_renderer))
748     new_texture = SDL_CreateTexture(sdl_renderer,
749                                     SDL_PIXELFORMAT_ARGB8888,
750                                     SDL_TEXTUREACCESS_TARGET,
751                                     video.width, video.height);
752   else
753     new_texture = NULL;
754
755   if (new_texture != NULL)
756   {
757     SDL_DestroyTexture(sdl_texture_target);
758
759     sdl_texture_target = new_texture;
760   }
761
762   SDLRedrawWindow();
763
764   video.window_scaling_quality = window_scaling_quality;
765 }
766
767 void SDLSetWindowFullscreen(boolean fullscreen)
768 {
769   if (sdl_window == NULL)
770     return;
771
772   int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
773
774   if (SDL_SetWindowFullscreen(sdl_window, flags) == 0)
775     video.fullscreen_enabled = fullscreen_enabled = fullscreen;
776
777   // if screen size was changed in fullscreen mode, correct desktop window size
778   if (!fullscreen && video.fullscreen_initial)
779   {
780     SDLSetWindowScaling(setup.window_scaling_percent);
781     SDL_SetWindowPosition(sdl_window,
782                           SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
783
784     video.fullscreen_initial = FALSE;
785   }
786 }
787
788 void SDLSetDisplaySize(void)
789 {
790   if (sdl_renderer != NULL)
791   {
792     int w, h;
793
794     SDL_GetRendererOutputSize(sdl_renderer, &w, &h);
795
796     video.display_width  = w;
797     video.display_height = h;
798
799 #if 0
800     Debug("video", "SDL renderer size: %d x %d",
801           video.display_width, video.display_height);
802 #endif
803   }
804   else
805   {
806     SDL_Rect display_bounds;
807
808     SDL_GetDisplayBounds(0, &display_bounds);
809
810     video.display_width  = display_bounds.w;
811     video.display_height = display_bounds.h;
812
813 #if 0
814     Debug("video", "SDL display size: %d x %d",
815           video.display_width, video.display_height);
816 #endif
817   }
818 }
819
820 void SDLSetScreenSizeAndOffsets(int width, int height)
821 {
822   // set default video screen size and offsets
823   video.screen_width = width;
824   video.screen_height = height;
825   video.screen_xoffset = 0;
826   video.screen_yoffset = 0;
827
828 #if defined(USE_COMPLETE_DISPLAY)
829   float ratio_video   = (float) width / height;
830   float ratio_display = (float) video.display_width / video.display_height;
831
832   if (ratio_video != ratio_display)
833   {
834     // adjust drawable screen size to cover the whole device display
835
836     if (ratio_video < ratio_display)
837       video.screen_width  *= ratio_display / ratio_video;
838     else
839       video.screen_height *= ratio_video / ratio_display;
840
841     video.screen_xoffset = (video.screen_width  - width)  / 2;
842     video.screen_yoffset = (video.screen_height - height) / 2;
843
844 #if 0
845     Debug("video", "Changing screen from %dx%d to %dx%d (%.2f to %.2f)",
846           width, height,
847           video.screen_width, video.screen_height,
848           ratio_video, ratio_display);
849 #endif
850   }
851 #endif
852 }
853
854 void SDLSetScreenSizeForRenderer(int width, int height)
855 {
856   SDL_RenderSetLogicalSize(sdl_renderer, width, height);
857 }
858
859 void SDLSetScreenProperties(void)
860 {
861   SDLSetDisplaySize();
862   SDLSetScreenSizeAndOffsets(video.width, video.height);
863   SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
864 }
865
866 void SDLSetScreenRenderingMode(char *screen_rendering_mode)
867 {
868   video.screen_rendering_mode =
869     (strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_BITMAP) ?
870      SPECIAL_RENDERING_BITMAP :
871      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_TARGET) ?
872      SPECIAL_RENDERING_TARGET:
873      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_DOUBLE) ?
874      SPECIAL_RENDERING_DOUBLE : SPECIAL_RENDERING_OFF);
875 }
876
877 void SDLSetScreenVsyncMode(char *vsync_mode)
878 {
879   // changing vsync mode without re-creating renderer only supported by OpenGL
880   if (!strPrefixLower((char *)SDLGetRendererName(), "opengl"))
881     return;
882
883   int interval = VSYNC_MODE_STR_TO_INT(vsync_mode);
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     Fail("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     Fail("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     Fail("IMG_Load('%s') failed: %s", getBaseNamePtr(filename), SDL_GetError());
2307
2308   print_timestamp_time("IMG_Load");
2309
2310   UPDATE_BUSY_STATE();
2311
2312   // create native non-transparent surface for current image
2313   if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
2314     Fail("SDLGetOpaqueSurface() failed");
2315
2316   print_timestamp_time("SDLGetNativeSurface (opaque)");
2317
2318   UPDATE_BUSY_STATE();
2319
2320   // set black pixel to transparent if no alpha channel / transparent color
2321   if (!SDLHasAlpha(sdl_image_tmp) &&
2322       !SDLHasColorKey(sdl_image_tmp))
2323     SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
2324                     SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
2325
2326   // create native transparent surface for current image
2327   if ((new_bitmap->surface_masked = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
2328     Fail("SDLGetNativeSurface() failed");
2329
2330   print_timestamp_time("SDLGetNativeSurface (masked)");
2331
2332   UPDATE_BUSY_STATE();
2333
2334   // free temporary surface
2335   SDL_FreeSurface(sdl_image_tmp);
2336
2337   new_bitmap->width = new_bitmap->surface->w;
2338   new_bitmap->height = new_bitmap->surface->h;
2339
2340   print_timestamp_done("SDLLoadImage");
2341
2342   return new_bitmap;
2343 }
2344
2345
2346 // ----------------------------------------------------------------------------
2347 // custom cursor fuctions
2348 // ----------------------------------------------------------------------------
2349
2350 static SDL_Cursor *create_cursor(struct MouseCursorInfo *cursor_info)
2351 {
2352   return SDL_CreateCursor(cursor_info->data, cursor_info->mask,
2353                           cursor_info->width, cursor_info->height,
2354                           cursor_info->hot_x, cursor_info->hot_y);
2355 }
2356
2357 void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
2358 {
2359   static struct MouseCursorInfo *last_cursor_info = NULL;
2360   static struct MouseCursorInfo *last_cursor_info2 = NULL;
2361   static SDL_Cursor *cursor_default = NULL;
2362   static SDL_Cursor *cursor_current = NULL;
2363
2364   // if invoked for the first time, store the SDL default cursor
2365   if (cursor_default == NULL)
2366     cursor_default = SDL_GetCursor();
2367
2368   // only create new cursor if cursor info (custom only) has changed
2369   if (cursor_info != NULL && cursor_info != last_cursor_info)
2370   {
2371     cursor_current = create_cursor(cursor_info);
2372     last_cursor_info = cursor_info;
2373   }
2374
2375   // only set new cursor if cursor info (custom or NULL) has changed
2376   if (cursor_info != last_cursor_info2)
2377     SDL_SetCursor(cursor_info ? cursor_current : cursor_default);
2378
2379   last_cursor_info2 = cursor_info;
2380 }
2381
2382
2383 // ============================================================================
2384 // audio functions
2385 // ============================================================================
2386
2387 void SDLOpenAudio(void)
2388 {
2389   if (program.headless)
2390     return;
2391
2392   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
2393   {
2394     Warn("SDL_InitSubSystem() failed: %s", SDL_GetError());
2395
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     Warn("Mix_OpenAudio() failed: %s", SDL_GetError());
2404
2405     return;
2406   }
2407
2408   audio.sound_available = TRUE;
2409   audio.music_available = TRUE;
2410   audio.loops_available = TRUE;
2411   audio.sound_enabled = TRUE;
2412
2413   // set number of available mixer channels
2414   audio.num_channels = Mix_AllocateChannels(NUM_MIXER_CHANNELS);
2415   audio.music_channel = MUSIC_CHANNEL;
2416   audio.first_sound_channel = FIRST_SOUND_CHANNEL;
2417
2418   Mixer_InitChannels();
2419 }
2420
2421 void SDLCloseAudio(void)
2422 {
2423   Mix_HaltMusic();
2424   Mix_HaltChannel(-1);
2425
2426   Mix_CloseAudio();
2427   SDL_QuitSubSystem(SDL_INIT_AUDIO);
2428 }
2429
2430
2431 // ============================================================================
2432 // event functions
2433 // ============================================================================
2434
2435 void SDLWaitEvent(Event *event)
2436 {
2437   SDL_WaitEvent(event);
2438 }
2439
2440 void SDLCorrectRawMousePosition(int *x, int *y)
2441 {
2442   if (sdl_renderer == NULL)
2443     return;
2444
2445   // this corrects the raw mouse position for logical screen size within event
2446   // filters (correction done later by SDL library when handling mouse events)
2447
2448   SDL_Rect viewport;
2449   float scale_x, scale_y;
2450
2451   SDL_RenderGetViewport(sdl_renderer, &viewport);
2452   SDL_RenderGetScale(sdl_renderer, &scale_x, &scale_y);
2453
2454   *x = (int)(*x / scale_x);
2455   *y = (int)(*y / scale_y);
2456
2457   *x -= viewport.x;
2458   *y -= viewport.y;
2459 }
2460
2461
2462 // ============================================================================
2463 // joystick functions
2464 // ============================================================================
2465
2466 static void *sdl_joystick[MAX_PLAYERS];         // game controller or joystick
2467 static int sdl_js_axis_raw[MAX_PLAYERS][2];
2468 static int sdl_js_axis[MAX_PLAYERS][2];
2469 static int sdl_js_button[MAX_PLAYERS][2];
2470 static boolean sdl_is_controller[MAX_PLAYERS];
2471
2472 void SDLClearJoystickState(void)
2473 {
2474   int i, j;
2475
2476   for (i = 0; i < MAX_PLAYERS; i++)
2477   {
2478     for (j = 0; j < 2; j++)
2479     {
2480       sdl_js_axis_raw[i][j] = -1;
2481       sdl_js_axis[i][j] = 0;
2482       sdl_js_button[i][j] = 0;
2483     }
2484   }
2485 }
2486
2487 boolean SDLOpenJoystick(int nr)
2488 {
2489   if (nr < 0 || nr >= MAX_PLAYERS)
2490     return FALSE;
2491
2492   sdl_is_controller[nr] = SDL_IsGameController(nr);
2493
2494 #if DEBUG_JOYSTICKS
2495   Debug("joystick", "opening joystick %d (%s)",
2496         nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
2497 #endif
2498
2499   if (sdl_is_controller[nr])
2500     sdl_joystick[nr] = SDL_GameControllerOpen(nr);
2501   else
2502     sdl_joystick[nr] = SDL_JoystickOpen(nr);
2503
2504   return (sdl_joystick[nr] != NULL);
2505 }
2506
2507 void SDLCloseJoystick(int nr)
2508 {
2509   if (nr < 0 || nr >= MAX_PLAYERS)
2510     return;
2511
2512 #if DEBUG_JOYSTICKS
2513   Debug("joystick", "closing joystick %d", nr);
2514 #endif
2515
2516   if (sdl_is_controller[nr])
2517     SDL_GameControllerClose(sdl_joystick[nr]);
2518   else
2519     SDL_JoystickClose(sdl_joystick[nr]);
2520
2521   sdl_joystick[nr] = NULL;
2522 }
2523
2524 boolean SDLCheckJoystickOpened(int nr)
2525 {
2526   if (nr < 0 || nr >= MAX_PLAYERS)
2527     return FALSE;
2528
2529   return (sdl_joystick[nr] != NULL ? TRUE : FALSE);
2530 }
2531
2532 static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
2533 {
2534   int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
2535                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
2536                  axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
2537                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
2538
2539   if (nr < 0 || nr >= MAX_PLAYERS)
2540     return;
2541
2542   if (axis_id == -1)
2543     return;
2544
2545   // prevent (slightly jittering, but centered) axis A from resetting axis B
2546   if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
2547       axis_id_raw != sdl_js_axis_raw[nr][axis_id])
2548     return;
2549
2550   sdl_js_axis[nr][axis_id] = axis_value;
2551   sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
2552 }
2553
2554 static void setJoystickButton(int nr, int button_id_raw, int button_state)
2555 {
2556   int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
2557                    button_id_raw == SDL_CONTROLLER_BUTTON_X ||
2558                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
2559                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
2560                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
2561                    button_id_raw == SDL_CONTROLLER_BUTTON_B ||
2562                    button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
2563                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
2564                    -1);
2565
2566   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
2567     sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
2568   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
2569     sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
2570   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
2571     sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
2572   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2573     sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
2574
2575   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
2576       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
2577       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
2578       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2579     sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
2580
2581   if (nr < 0 || nr >= MAX_PLAYERS)
2582     return;
2583
2584   if (button_id == -1)
2585     return;
2586
2587   sdl_js_button[nr][button_id] = button_state;
2588 }
2589
2590 void HandleJoystickEvent(Event *event)
2591 {
2592   // when using joystick, disable overlay touch buttons
2593   runtime.uses_touch_device = FALSE;
2594
2595   switch (event->type)
2596   {
2597     case SDL_CONTROLLERDEVICEADDED:
2598 #if DEBUG_JOYSTICKS
2599       Debug("joystick", "SDL_CONTROLLERDEVICEADDED: device %d added",
2600             event->cdevice.which);
2601 #endif
2602       InitJoysticks();
2603       break;
2604
2605     case SDL_CONTROLLERDEVICEREMOVED:
2606 #if DEBUG_JOYSTICKS
2607       Debug("joystick", "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
2608             event->cdevice.which);
2609 #endif
2610       InitJoysticks();
2611       break;
2612
2613     case SDL_CONTROLLERAXISMOTION:
2614 #if DEBUG_JOYSTICKS
2615       Debug("joystick", "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
2616             event->caxis.which, event->caxis.axis, event->caxis.value);
2617 #endif
2618       setJoystickAxis(event->caxis.which,
2619                       event->caxis.axis,
2620                       event->caxis.value);
2621       break;
2622
2623     case SDL_CONTROLLERBUTTONDOWN:
2624 #if DEBUG_JOYSTICKS
2625       Debug("joystick", "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
2626             event->cbutton.which, event->cbutton.button);
2627 #endif
2628       setJoystickButton(event->cbutton.which,
2629                         event->cbutton.button,
2630                         TRUE);
2631       break;
2632
2633     case SDL_CONTROLLERBUTTONUP:
2634 #if DEBUG_JOYSTICKS
2635       Debug("joystick", "SDL_CONTROLLERBUTTONUP: device %d, button %d",
2636             event->cbutton.which, event->cbutton.button);
2637 #endif
2638       setJoystickButton(event->cbutton.which,
2639                         event->cbutton.button,
2640                         FALSE);
2641       break;
2642
2643     case SDL_JOYAXISMOTION:
2644       if (sdl_is_controller[event->jaxis.which])
2645         break;
2646
2647 #if DEBUG_JOYSTICKS
2648       Debug("joystick", "SDL_JOYAXISMOTION: device %d, axis %d: %d",
2649             event->jaxis.which, event->jaxis.axis, event->jaxis.value);
2650 #endif
2651       if (event->jaxis.axis < 4)
2652         setJoystickAxis(event->jaxis.which,
2653                         event->jaxis.axis,
2654                         event->jaxis.value);
2655       break;
2656
2657     case SDL_JOYBUTTONDOWN:
2658       if (sdl_is_controller[event->jaxis.which])
2659         break;
2660
2661 #if DEBUG_JOYSTICKS
2662       Debug("joystick", "SDL_JOYBUTTONDOWN: device %d, button %d",
2663             event->jbutton.which, event->jbutton.button);
2664 #endif
2665       if (event->jbutton.button < 4)
2666         setJoystickButton(event->jbutton.which,
2667                           event->jbutton.button,
2668                           TRUE);
2669       break;
2670
2671     case SDL_JOYBUTTONUP:
2672       if (sdl_is_controller[event->jaxis.which])
2673         break;
2674
2675 #if DEBUG_JOYSTICKS
2676       Debug("joystick", "SDL_JOYBUTTONUP: device %d, button %d",
2677             event->jbutton.which, event->jbutton.button);
2678 #endif
2679       if (event->jbutton.button < 4)
2680         setJoystickButton(event->jbutton.which,
2681                           event->jbutton.button,
2682                           FALSE);
2683       break;
2684
2685     default:
2686       break;
2687   }
2688 }
2689
2690 void SDLInitJoysticks(void)
2691 {
2692   static boolean sdl_joystick_subsystem_initialized = FALSE;
2693   boolean print_warning = !sdl_joystick_subsystem_initialized;
2694   char *mappings_file_base = getPath2(options.conf_directory,
2695                                       GAMECONTROLLER_BASENAME);
2696   char *mappings_file_user = getPath2(getUserGameDataDir(),
2697                                       GAMECONTROLLER_BASENAME);
2698   int num_mappings;
2699   int i;
2700
2701   if (!sdl_joystick_subsystem_initialized)
2702   {
2703     sdl_joystick_subsystem_initialized = TRUE;
2704
2705     SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
2706
2707     if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
2708       Fail("SDL_Init() failed: %s", SDL_GetError());
2709
2710     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
2711
2712     // the included game controller base mappings should always be found
2713     if (num_mappings == -1)
2714       Warn("no game controller base mappings found");
2715 #if DEBUG_JOYSTICKS
2716     else
2717       Debug("joystick", "%d game controller base mapping(s) added",
2718             num_mappings);
2719 #endif
2720
2721     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
2722
2723 #if DEBUG_JOYSTICKS
2724     // the personal game controller user mappings may or may not be found
2725     if (num_mappings == -1)
2726       Warn("no game controller user mappings found");
2727     else
2728       Debug("joystick", , "%d game controller user mapping(s) added",
2729             num_mappings);
2730
2731     Debug("joystick", "%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       Debug("joystick", "- 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         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       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