1 /*
2 Copyright (c) 2009-2013 Peter "Corsix" Cawley and Edvin "Lego3" Linge
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 of the Software, and to permit persons to whom the Software is furnished to do
9 so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 */
22 
23 #include "config.h"
24 
25 #include "th_gfx.h"
26 #ifdef CORSIX_TH_USE_FREETYPE2
27 #include "th_gfx_font.h"
28 #endif
29 #include <algorithm>
30 #include <cmath>
31 #include <cstdio>
32 #include <cstring>
33 #include <iostream>
34 #include <limits>
35 #include <new>
36 #include <stdexcept>
37 
38 #include "th_map.h"
39 
full_colour_renderer(int iWidth,int iHeight)40 full_colour_renderer::full_colour_renderer(int iWidth, int iHeight)
41     : width(iWidth), height(iHeight) {
42   x = 0;
43   y = 0;
44 }
45 
46 namespace {
47 
48 //! Convert a colour to an equivalent grey scale level.
49 /*!
50     @param iOpacity Opacity of the pixel.
51     @param iR Red colour intensity.
52     @param iG Green colour intensity.
53     @param iB Blue colour intensity.
54     @return 32bpp colour pixel in grey scale.
55  */
makeGreyScale(uint8_t iOpacity,uint8_t iR,uint8_t iG,uint8_t iB)56 inline uint32_t makeGreyScale(uint8_t iOpacity, uint8_t iR, uint8_t iG,
57                               uint8_t iB) {
58   // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
59   // 0.2126*R + 0.7152*G + 0.0722*B
60   // 0.2126 * 65536 = 13932.9536 -> 1393
61   // 0.7152 * 65536 = 46871.3472
62   // 0.0722 * 65536 =  4731.6992 -> 4732
63   // 13933 + 46871 + 4732 = 65536 = 2**16
64   uint8_t iGrey =
65       static_cast<uint8_t>((13933 * iR + 46871 * iG + 4732 * iB) >> 16);
66   return palette::pack_argb(iOpacity, iGrey, iGrey, iGrey);
67 }
68 
69 //! Convert a colour by swapping red and blue channel.
70 /*!
71     @param iOpacity Opacity of the pixel.
72     @param iR Red colour intensity.
73     @param iG Green colour intensity.
74     @param iB Blue colour intensity.
75     @return 32bpp colour pixel with red and blue swapped.
76  */
makeSwapRedBlue(uint8_t iOpacity,uint8_t iR,uint8_t iG,uint8_t iB)77 inline uint32_t makeSwapRedBlue(uint8_t iOpacity, uint8_t iR, uint8_t iG,
78                                 uint8_t iB) {
79   // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
80   // The Y factor for red is 0.2126, and for blue 0.0722. This means red is
81   // about 3 times stronger than blue. Simple swapping channels will thus
82   // distort the balance. This code compensates for that by computing red  =
83   // blue * 0.0722 / 0.2126 = blue * 1083 / 3189 blue = red  * 0.2126 / 0.0722
84   // = red  * 1063 / 361 (clipped at max blue, 255)
85   uint8_t iNewRed = static_cast<uint8_t>(iB * 1083 / 3189);
86   int iNewBlue = iR * 1063 / 361;
87   if (iNewBlue > 255) iNewBlue = 255;
88   return palette::pack_argb(iOpacity, iNewRed, iG,
89                             static_cast<uint8_t>(iNewBlue));
90 }
91 
convert_6bit_to_8bit_colour_component(uint8_t colour_component)92 uint8_t convert_6bit_to_8bit_colour_component(uint8_t colour_component) {
93   constexpr uint8_t mask_6bit = 0x3F;
94   return static_cast<uint8_t>(std::lround(
95       (colour_component & mask_6bit) * static_cast<double>(0xFF) / mask_6bit));
96 }
97 
98 //! Get the enclosing rect of an SDL_Rect scaled by the given scale factor.
99 //  Scaling the rect is location dependent, as non-integer scale factors
100 //  require that same size rects be +/-1 pixel different in size depending
101 //  on location.
102 /*!
103     @param rect Pointer to the SDL_Rect to be scaled.
104     @param scale_factor Scale to be applied to the rectangle.
105     @param[out] dst_rect Enclosing SDL_Rect of rect scaled by scale_factor.
106  */
getEnclosingScaleRect(const SDL_Rect * rect,double scale_factor,SDL_Rect * dst_rect)107 void getEnclosingScaleRect(const SDL_Rect* rect, double scale_factor,
108                            SDL_Rect* dst_rect) {
109   // When scaling a rect, we use the scaled position of the bottom-right corner
110   // to determine the size depending on where on the screen the rect is. Compute
111   // width and height first so that we can support when rect == dst_rect.
112   int dst_x = static_cast<int>(scale_factor * rect->x);
113   int dst_y = static_cast<int>(scale_factor * rect->y);
114   dst_rect->w =
115       static_cast<int>(ceil(scale_factor * (rect->x + rect->w))) - dst_x;
116   dst_rect->h =
117       static_cast<int>(ceil(scale_factor * (rect->y + rect->h))) - dst_y;
118   dst_rect->x = dst_x;
119   dst_rect->y = dst_y;
120 }
121 
122 }  // namespace
123 
palette()124 palette::palette() { colour_count = 0; }
125 
load_from_th_file(const uint8_t * pData,size_t iDataLength)126 bool palette::load_from_th_file(const uint8_t* pData, size_t iDataLength) {
127   if (iDataLength != 256 * 3) return false;
128 
129   colour_count = static_cast<int>(iDataLength / 3);
130   for (int i = 0; i < colour_count; ++i, pData += 3) {
131     uint8_t iR = convert_6bit_to_8bit_colour_component(pData[0]);
132     uint8_t iG = convert_6bit_to_8bit_colour_component(pData[1]);
133     uint8_t iB = convert_6bit_to_8bit_colour_component(pData[2]);
134     uint32_t iColour = pack_argb(0xFF, iR, iG, iB);
135     // Remap magenta to transparent
136     if (iColour == pack_argb(0xFF, 0xFF, 0x00, 0xFF))
137       iColour = pack_argb(0x00, 0x00, 0x00, 0x00);
138     colour_index_to_argb_map[i] = iColour;
139   }
140 
141   return true;
142 }
143 
set_entry(int iEntry,uint8_t iR,uint8_t iG,uint8_t iB)144 bool palette::set_entry(int iEntry, uint8_t iR, uint8_t iG, uint8_t iB) {
145   if (iEntry < 0 || iEntry >= colour_count) return false;
146   uint32_t iColour = pack_argb(0xFF, iR, iG, iB);
147   // Remap magenta to transparent
148   if (iColour == pack_argb(0xFF, 0xFF, 0x00, 0xFF))
149     iColour = pack_argb(0x00, 0x00, 0x00, 0x00);
150   colour_index_to_argb_map[iEntry] = iColour;
151   return true;
152 }
153 
get_colour_count() const154 int palette::get_colour_count() const { return colour_count; }
155 
get_argb_data() const156 const uint32_t* palette::get_argb_data() const {
157   return colour_index_to_argb_map;
158 }
159 
decode_image(const uint8_t * pImg,const palette * pPalette,uint32_t iSpriteFlags)160 void full_colour_renderer::decode_image(const uint8_t* pImg,
161                                         const palette* pPalette,
162                                         uint32_t iSpriteFlags) {
163   if (width <= 0) {
164     throw std::logic_error("width cannot be <= 0 when decoding an image");
165   }
166   if (height <= 0) {
167     throw std::logic_error("height cannot be <= 0 when decoding an image");
168   }
169 
170   iSpriteFlags &= thdf_alt32_mask;
171 
172   const uint32_t* pColours = pPalette->get_argb_data();
173   for (;;) {
174     uint8_t iType = *pImg++;
175     size_t iLength = iType & 63;
176     switch (iType >> 6) {
177       case 0:  // Fixed fully opaque 32bpp pixels
178         while (iLength > 0) {
179           uint32_t iColour;
180           if (iSpriteFlags == thdf_alt32_blue_red_swap)
181             iColour = makeSwapRedBlue(0xFF, pImg[0], pImg[1], pImg[2]);
182           else if (iSpriteFlags == thdf_alt32_grey_scale)
183             iColour = makeGreyScale(0xFF, pImg[0], pImg[1], pImg[2]);
184           else
185             iColour = palette::pack_argb(0xFF, pImg[0], pImg[1], pImg[2]);
186           push_pixel(iColour);
187           pImg += 3;
188           iLength--;
189         }
190         break;
191 
192       case 1:  // Fixed partially transparent 32bpp pixels
193       {
194         uint8_t iOpacity = *pImg++;
195         while (iLength > 0) {
196           uint32_t iColour;
197           if (iSpriteFlags == thdf_alt32_blue_red_swap)
198             iColour = makeSwapRedBlue(0xFF, pImg[0], pImg[1], pImg[2]);
199           else if (iSpriteFlags == thdf_alt32_grey_scale)
200             iColour = makeGreyScale(iOpacity, pImg[0], pImg[1], pImg[2]);
201           else
202             iColour = palette::pack_argb(iOpacity, pImg[0], pImg[1], pImg[2]);
203           push_pixel(iColour);
204           pImg += 3;
205           iLength--;
206         }
207         break;
208       }
209 
210       case 2:  // Fixed fully transparent pixels
211       {
212         static const uint32_t iTransparent = palette::pack_argb(0, 0, 0, 0);
213         while (iLength > 0) {
214           push_pixel(iTransparent);
215           iLength--;
216         }
217         break;
218       }
219 
220       case 3:  // Recolour layer
221       {
222         uint8_t iTable = *pImg++;
223         pImg++;  // Skip reading the opacity for now.
224         if (iTable == 0xFF) {
225           // Legacy sprite data. Use the palette to recolour the
226           // layer. Note that the iOpacity is ignored here.
227           while (iLength > 0) {
228             push_pixel(pColours[*pImg++]);
229             iLength--;
230           }
231         } else {
232           // TODO: Add proper recolour layers, where RGB comes from
233           // table 'iTable' at index *pImg (iLength times), and
234           // opacity comes from the byte after the iTable byte.
235           //
236           // For now just draw black pixels, so it won't go unnoticed.
237           while (iLength > 0) {
238             uint32_t iColour = palette::pack_argb(0xFF, 0, 0, 0);
239             push_pixel(iColour);
240             iLength--;
241           }
242         }
243         break;
244       }
245     }
246 
247     if (y >= height) break;
248   }
249   if (y != height || x != 0) {
250     throw std::logic_error("Image data does not match given dimensions");
251   }
252 }
253 
full_colour_storing(uint32_t * pDest,int iWidth,int iHeight)254 full_colour_storing::full_colour_storing(uint32_t* pDest, int iWidth,
255                                          int iHeight)
256     : full_colour_renderer(iWidth, iHeight) {
257   destination = pDest;
258 }
259 
store_argb(uint32_t pixel)260 void full_colour_storing::store_argb(uint32_t pixel) { *destination++ = pixel; }
261 
wx_storing(uint8_t * pRGBData,uint8_t * pAData,int iWidth,int iHeight)262 wx_storing::wx_storing(uint8_t* pRGBData, uint8_t* pAData, int iWidth,
263                        int iHeight)
264     : full_colour_renderer(iWidth, iHeight) {
265   rgb_data = pRGBData;
266   alpha_data = pAData;
267 }
268 
store_argb(uint32_t pixel)269 void wx_storing::store_argb(uint32_t pixel) {
270   rgb_data[0] = palette::get_red(pixel);
271   rgb_data[1] = palette::get_green(pixel);
272   rgb_data[2] = palette::get_blue(pixel);
273   rgb_data += 3;
274 
275   *alpha_data++ = palette::get_alpha(pixel);
276 }
277 
render_target()278 render_target::render_target()
279     : window(nullptr),
280       renderer(nullptr),
281       zoom_texture(nullptr),
282       pixel_format(nullptr),
283       blue_filter_active(false),
284       game_cursor(nullptr),
285       global_scale_factor(1.0),
286       width(-1),
287       height(-1),
288       scale_bitmaps(false),
289       apply_opengl_clip_fix(false),
290       direct_zoom(false) {}
291 
~render_target()292 render_target::~render_target() { destroy(); }
293 
create(const render_target_creation_params * pParams)294 bool render_target::create(const render_target_creation_params* pParams) {
295   if (renderer != nullptr) return false;
296 
297   direct_zoom = pParams->direct_zoom;
298 
299   if (direct_zoom) {
300     // When scaling rendering, linear introduces semi-transparent gaps at
301     // transparent edges within tiles. Using nearest ensures that we can scale
302     // angled transparent tile edges without seams.
303     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
304   } else {
305     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
306   }
307 
308   pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
309   window =
310       SDL_CreateWindow("CorsixTH", SDL_WINDOWPOS_UNDEFINED,
311                        SDL_WINDOWPOS_UNDEFINED, pParams->width, pParams->height,
312                        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
313   if (!window) {
314     return false;
315   }
316 
317   Uint32 iRendererFlags =
318       (pParams->present_immediate ? 0 : SDL_RENDERER_PRESENTVSYNC);
319   renderer = SDL_CreateRenderer(window, -1, iRendererFlags);
320 
321   SDL_RendererInfo info;
322   SDL_GetRendererInfo(renderer, &info);
323   supports_target_textures = (info.flags & SDL_RENDERER_TARGETTEXTURE) != 0;
324 
325   SDL_version sdlVersion;
326   SDL_GetVersion(&sdlVersion);
327   apply_opengl_clip_fix = std::strncmp(info.name, "opengl", 6) == 0 &&
328                           sdlVersion.major == 2 && sdlVersion.minor == 0 &&
329                           sdlVersion.patch < 4;
330 
331   return update(pParams);
332 }
333 
update(const render_target_creation_params * pParams)334 bool render_target::update(const render_target_creation_params* pParams) {
335   if (window == nullptr) {
336     return false;
337   }
338 
339   bool bUpdateSize = (width != pParams->width) || (height != pParams->height);
340   width = pParams->width;
341   height = pParams->height;
342 
343   bool bIsFullscreen =
344       ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) ==
345        SDL_WINDOW_FULLSCREEN_DESKTOP);
346   if (bIsFullscreen != pParams->fullscreen) {
347     SDL_SetWindowFullscreen(
348         window, (pParams->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
349   }
350 
351   if (bUpdateSize || bIsFullscreen != pParams->fullscreen) {
352     SDL_SetWindowSize(window, width, height);
353   }
354 
355   if (bUpdateSize) {
356     SDL_RenderSetLogicalSize(renderer, width, height);
357   }
358 
359   return true;
360 }
361 
destroy()362 void render_target::destroy() {
363   if (pixel_format) {
364     SDL_FreeFormat(pixel_format);
365     pixel_format = nullptr;
366   }
367 
368   if (zoom_texture) {
369     SDL_DestroyTexture(zoom_texture);
370     zoom_texture = nullptr;
371   }
372 
373   if (renderer) {
374     SDL_DestroyRenderer(renderer);
375     renderer = nullptr;
376   }
377 
378   if (window) {
379     SDL_DestroyWindow(window);
380     window = nullptr;
381   }
382 }
383 
set_scale_factor(double fScale,scaled_items eWhatToScale)384 bool render_target::set_scale_factor(double fScale, scaled_items eWhatToScale) {
385   flush_zoom_buffer();
386   scale_bitmaps = false;
387 
388   if (fScale <= 0.000) {
389     return false;
390   } else if (eWhatToScale == scaled_items::all && direct_zoom) {
391     global_scale_factor = fScale;
392     return true;
393   } else if (eWhatToScale == scaled_items::all && supports_target_textures) {
394     // Draw everything from now until the next scale to zoom_texture
395     // with the appropriate virtual size, which will be copied scaled to
396     // fit the window.
397     int virtWidth = static_cast<int>(width / fScale);
398     int virtHeight = static_cast<int>(height / fScale);
399 
400     zoom_texture =
401         SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
402                           SDL_TEXTUREACCESS_TARGET, virtWidth, virtHeight);
403 
404     SDL_RenderSetLogicalSize(renderer, virtWidth, virtHeight);
405     if (SDL_SetRenderTarget(renderer, zoom_texture) != 0) {
406       std::cout << "Warning: Could not render to zoom texture - "
407                 << SDL_GetError() << std::endl;
408 
409       SDL_RenderSetLogicalSize(renderer, width, height);
410       SDL_DestroyTexture(zoom_texture);
411       zoom_texture = nullptr;
412       return false;
413     }
414 
415     // Clear the new texture to transparent/black.
416     SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_TRANSPARENT);
417     SDL_RenderClear(renderer);
418 
419     return true;
420   } else if (0.999 <= fScale && fScale <= 1.001) {
421     return true;
422   } else if (eWhatToScale == scaled_items::bitmaps) {
423     scale_bitmaps = true;
424     bitmap_scale_factor = fScale;
425 
426     return true;
427   }
428   return false;
429 }
430 
set_caption(const char * sCaption)431 void render_target::set_caption(const char* sCaption) {
432   SDL_SetWindowTitle(window, sCaption);
433 }
434 
get_renderer_details() const435 const char* render_target::get_renderer_details() const {
436   SDL_RendererInfo info = {};
437   SDL_GetRendererInfo(renderer, &info);
438   return info.name;
439 }
440 
get_last_error()441 const char* render_target::get_last_error() { return SDL_GetError(); }
442 
start_frame()443 bool render_target::start_frame() {
444   fill_black();
445   return true;
446 }
447 
end_frame()448 bool render_target::end_frame() {
449   flush_zoom_buffer();
450 
451   // End the frame by adding the cursor and possibly a filter.
452   if (game_cursor) {
453     game_cursor->draw(this, cursor_x, cursor_y);
454   }
455   if (blue_filter_active) {
456     SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
457     SDL_SetRenderDrawColor(renderer, 51, 51, 255,
458                            128);  // r=0.2, g=0.2, b=1, a=0.5 .
459     SDL_RenderFillRect(renderer, nullptr);
460   }
461 
462   SDL_RenderPresent(renderer);
463   return true;
464 }
465 
fill_black()466 bool render_target::fill_black() {
467   SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
468   SDL_RenderClear(renderer);
469 
470   return true;
471 }
472 
set_blue_filter_active(bool bActivate)473 void render_target::set_blue_filter_active(bool bActivate) {
474   blue_filter_active = bActivate;
475 }
476 
477 // Actiate and Deactivate SDL function to capture mouse to window
set_window_grab(bool bActivate)478 void render_target::set_window_grab(bool bActivate) {
479   SDL_SetWindowGrab(window, bActivate ? SDL_TRUE : SDL_FALSE);
480 }
481 
fill_rect(uint32_t iColour,int iX,int iY,int iW,int iH)482 bool render_target::fill_rect(uint32_t iColour, int iX, int iY, int iW,
483                               int iH) {
484   SDL_Rect rcDest = {iX, iY, iW, iH};
485   getEnclosingScaleRect(&rcDest, global_scale_factor, &rcDest);
486 
487   Uint8 r, g, b, a;
488   SDL_GetRGBA(iColour, pixel_format, &r, &g, &b, &a);
489 
490   SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
491   SDL_SetRenderDrawColor(renderer, r, g, b, a);
492   SDL_RenderFillRect(renderer, &rcDest);
493 
494   return true;
495 }
496 
get_clip_rect(clip_rect * pRect) const497 void render_target::get_clip_rect(clip_rect* pRect) const {
498   SDL_RenderGetClipRect(renderer, reinterpret_cast<SDL_Rect*>(pRect));
499   // SDL returns empty rect when clipping is disabled -> return full rect for
500   // CTH
501   if (SDL_RectEmpty(pRect)) {
502     pRect->x = pRect->y = 0;
503     pRect->w = width;
504     pRect->h = height;
505   }
506 
507   if (apply_opengl_clip_fix) {
508     int renderWidth, renderHeight;
509     SDL_RenderGetLogicalSize(renderer, &renderWidth, &renderHeight);
510     pRect->y = renderHeight - pRect->y - pRect->h;
511   }
512 
513   getEnclosingScaleRect(pRect, 1.0 / global_scale_factor, pRect);
514 }
515 
set_clip_rect(const clip_rect * pRect)516 void render_target::set_clip_rect(const clip_rect* pRect) {
517   // Full clip rect for CTH means clipping disabled
518   if (pRect == nullptr || (pRect->w == width && pRect->h == height)) {
519     SDL_RenderSetClipRect(renderer, nullptr);
520     return;
521   }
522 
523   SDL_Rect SDLRect;
524   getEnclosingScaleRect(pRect, global_scale_factor, &SDLRect);
525 
526   // For some reason, SDL treats an empty rect (h or w <= 0) as if you turned
527   // off clipping, so we replace it with a rect that's outside our viewport.
528   const SDL_Rect rcBogus = {-2, -2, 1, 1};
529   if (SDL_RectEmpty(&SDLRect)) {
530     SDLRect = rcBogus;
531   }
532 
533   if (apply_opengl_clip_fix) {
534     int renderWidth, renderHeight;
535     SDL_RenderGetLogicalSize(renderer, &renderWidth, &renderHeight);
536     SDLRect.y = renderHeight - SDLRect.y - SDLRect.h;
537   }
538 
539   SDL_RenderSetClipRect(renderer, &SDLRect);
540 }
541 
get_width() const542 int render_target::get_width() const {
543   int w;
544   SDL_RenderGetLogicalSize(renderer, &w, nullptr);
545   return w;
546 }
547 
get_height() const548 int render_target::get_height() const {
549   int h;
550   SDL_RenderGetLogicalSize(renderer, nullptr, &h);
551   return h;
552 }
553 
start_nonoverlapping_draws()554 void render_target::start_nonoverlapping_draws() {
555   // SDL has no optimisations for drawing lots of non-overlapping sprites
556 }
557 
finish_nonoverlapping_draws()558 void render_target::finish_nonoverlapping_draws() {
559   // SDL has no optimisations for drawing lots of non-overlapping sprites
560 }
561 
set_cursor(cursor * pCursor)562 void render_target::set_cursor(cursor* pCursor) { game_cursor = pCursor; }
563 
set_cursor_position(int iX,int iY)564 void render_target::set_cursor_position(int iX, int iY) {
565   cursor_x = iX;
566   cursor_y = iY;
567 }
568 
take_screenshot(const char * sFile)569 bool render_target::take_screenshot(const char* sFile) {
570   int width = 0, height = 0;
571   if (SDL_GetRendererOutputSize(renderer, &width, &height) == -1) return false;
572 
573   // Create a window-sized surface, RGB format (0 Rmask means RGB.)
574   SDL_Surface* pRgbSurface =
575       SDL_CreateRGBSurface(0, width, height, 24, 0, 0, 0, 0);
576   if (pRgbSurface == nullptr) return false;
577 
578   int readStatus = -1;
579   if (SDL_LockSurface(pRgbSurface) != -1) {
580     // Ask the renderer to (slowly) fill the surface with renderer
581     // output data.
582     readStatus =
583         SDL_RenderReadPixels(renderer, nullptr, pRgbSurface->format->format,
584                              pRgbSurface->pixels, pRgbSurface->pitch);
585     SDL_UnlockSurface(pRgbSurface);
586 
587     if (readStatus != -1) SDL_SaveBMP(pRgbSurface, sFile);
588   }
589 
590   SDL_FreeSurface(pRgbSurface);
591 
592   return (readStatus != -1);
593 }
594 
should_scale_bitmaps(double * pFactor)595 bool render_target::should_scale_bitmaps(double* pFactor) {
596   if (!scale_bitmaps) return false;
597   if (pFactor) *pFactor = bitmap_scale_factor;
598   return true;
599 }
600 
flush_zoom_buffer()601 void render_target::flush_zoom_buffer() {
602   if (zoom_texture == nullptr) {
603     return;
604   }
605 
606   SDL_SetRenderTarget(renderer, nullptr);
607   SDL_RenderSetLogicalSize(renderer, width, height);
608   SDL_SetTextureBlendMode(zoom_texture, SDL_BLENDMODE_BLEND);
609   SDL_RenderCopy(renderer, zoom_texture, nullptr, nullptr);
610   SDL_DestroyTexture(zoom_texture);
611   zoom_texture = nullptr;
612 }
613 
614 namespace {
615 
616 //! Convert legacy 8bpp sprite data to recoloured 32bpp data, using special
617 //! recolour table 0xFF.
618 /*!
619     @param pPixelData Legacy 8bpp pixels.
620     @param iPixelDataLength Number of pixels in the \a pPixelData.
621     @return Converted 32bpp pixel data, if succeeded else nullptr is returned.
622    Caller should free the returned memory.
623  */
convertLegacySprite(const uint8_t * pPixelData,size_t iPixelDataLength)624 uint8_t* convertLegacySprite(const uint8_t* pPixelData,
625                              size_t iPixelDataLength) {
626   // Recolour blocks are 63 pixels long.
627   // XXX To reduce the size of the 32bpp data, transparent pixels can be
628   // stored more compactly.
629   size_t iNumFilled = iPixelDataLength / 63;
630   size_t iRemaining = iPixelDataLength - iNumFilled * 63;
631   size_t iNewSize =
632       iNumFilled * (3 + 63) + ((iRemaining > 0) ? 3 + iRemaining : 0);
633   uint8_t* pData = new uint8_t[iNewSize];
634 
635   uint8_t* pDest = pData;
636   while (iPixelDataLength > 0) {
637     size_t iLength = (iPixelDataLength >= 63) ? 63 : iPixelDataLength;
638     *pDest++ =
639         static_cast<uint8_t>(iLength + 0xC0);  // Recolour layer type of block.
640     *pDest++ = 0xFF;  // Use special table 0xFF (which uses the palette as
641                       // table).
642     *pDest++ = 0xFF;  // Non-transparent.
643     std::memcpy(pDest, pPixelData, iLength);
644     pDest += iLength;
645     pPixelData += iLength;
646     iPixelDataLength -= iLength;
647   }
648   return pData;
649 }
650 
651 }  // namespace
652 
create_palettized_texture(int iWidth,int iHeight,const uint8_t * pPixels,const palette * pPalette,uint32_t iSpriteFlags) const653 SDL_Texture* render_target::create_palettized_texture(
654     int iWidth, int iHeight, const uint8_t* pPixels, const palette* pPalette,
655     uint32_t iSpriteFlags) const {
656   uint32_t* pARGBPixels = new uint32_t[iWidth * iHeight];
657 
658   full_colour_storing oRenderer(pARGBPixels, iWidth, iHeight);
659   oRenderer.decode_image(pPixels, pPalette, iSpriteFlags);
660 
661   SDL_Texture* pTexture = create_texture(iWidth, iHeight, pARGBPixels);
662   delete[] pARGBPixels;
663   return pTexture;
664 }
665 
create_texture(int iWidth,int iHeight,const uint32_t * pPixels) const666 SDL_Texture* render_target::create_texture(int iWidth, int iHeight,
667                                            const uint32_t* pPixels) const {
668   SDL_Texture* pTexture =
669       SDL_CreateTexture(renderer, pixel_format->format,
670                         SDL_TEXTUREACCESS_STATIC, iWidth, iHeight);
671 
672   if (pTexture == nullptr) {
673     throw std::runtime_error(SDL_GetError());
674   }
675 
676   int err = 0;
677   err = SDL_UpdateTexture(pTexture, nullptr, pPixels,
678                           static_cast<int>(sizeof(*pPixels) * iWidth));
679   if (err < 0) {
680     throw std::runtime_error(SDL_GetError());
681   }
682 
683   err = SDL_SetTextureBlendMode(pTexture, SDL_BLENDMODE_BLEND);
684   if (err < 0) {
685     throw std::runtime_error(SDL_GetError());
686   }
687 
688   err = SDL_SetTextureColorMod(pTexture, 0xFF, 0xFF, 0xFF);
689   if (err < 0) {
690     throw std::runtime_error(SDL_GetError());
691   }
692 
693   err = SDL_SetTextureAlphaMod(pTexture, 0xFF);
694   if (err < 0) {
695     throw std::runtime_error(SDL_GetError());
696   }
697 
698   return pTexture;
699 }
700 
draw(SDL_Texture * pTexture,const SDL_Rect * prcSrcRect,const SDL_Rect * prcDstRect,int iFlags)701 void render_target::draw(SDL_Texture* pTexture, const SDL_Rect* prcSrcRect,
702                          const SDL_Rect* prcDstRect, int iFlags) {
703   SDL_SetTextureAlphaMod(pTexture, 0xFF);
704   if (iFlags & thdf_alpha_50) {
705     SDL_SetTextureAlphaMod(pTexture, 0x80);
706   } else if (iFlags & thdf_alpha_75) {
707     SDL_SetTextureAlphaMod(pTexture, 0x40);
708   }
709 
710   int iSDLFlip = SDL_FLIP_NONE;
711   if (iFlags & thdf_flip_horizontal) iSDLFlip |= SDL_FLIP_HORIZONTAL;
712   if (iFlags & thdf_flip_vertical) iSDLFlip |= SDL_FLIP_VERTICAL;
713 
714   SDL_Rect scaledDstRect;
715   getEnclosingScaleRect(prcDstRect, global_scale_factor, &scaledDstRect);
716   if (iSDLFlip != 0) {
717     SDL_RenderCopyEx(renderer, pTexture, prcSrcRect, &scaledDstRect, 0, nullptr,
718                      (SDL_RendererFlip)iSDLFlip);
719   } else {
720     SDL_RenderCopy(renderer, pTexture, prcSrcRect, &scaledDstRect);
721   }
722 }
723 
draw_line(line * pLine,int iX,int iY)724 void render_target::draw_line(line* pLine, int iX, int iY) {
725   SDL_SetRenderDrawColor(renderer, pLine->red, pLine->green, pLine->blue,
726                          pLine->alpha);
727 
728   double lastX, lastY;
729   lastX = pLine->first_operation->x;
730   lastY = pLine->first_operation->y;
731 
732   line::line_operation* op =
733       (line::line_operation*)(pLine->first_operation->next);
734   while (op) {
735     if (op->type == line::line_operation_type::line) {
736       SDL_RenderDrawLine(
737           renderer, static_cast<int>(lastX + iX), static_cast<int>(lastY + iY),
738           static_cast<int>(op->x + iX), static_cast<int>(op->y + iY));
739     }
740 
741     lastX = op->x;
742     lastY = op->y;
743 
744     op = (line::line_operation*)(op->next);
745   }
746 }
747 
raw_bitmap()748 raw_bitmap::raw_bitmap() {
749   texture = nullptr;
750   bitmap_palette = nullptr;
751   target = nullptr;
752   width = 0;
753   height = 0;
754 }
755 
~raw_bitmap()756 raw_bitmap::~raw_bitmap() {
757   if (texture) {
758     SDL_DestroyTexture(texture);
759   }
760 }
761 
set_palette(const palette * pPalette)762 void raw_bitmap::set_palette(const palette* pPalette) {
763   bitmap_palette = pPalette;
764 }
765 
load_from_th_file(const uint8_t * pPixelData,size_t iPixelDataLength,int iWidth,render_target * pEventualCanvas)766 void raw_bitmap::load_from_th_file(const uint8_t* pPixelData,
767                                    size_t iPixelDataLength, int iWidth,
768                                    render_target* pEventualCanvas) {
769   if (pEventualCanvas == nullptr) {
770     throw std::invalid_argument("pEventualCanvas cannot be null");
771   }
772 
773   uint8_t* converted_sprite = convertLegacySprite(pPixelData, iPixelDataLength);
774 
775   int iHeight = static_cast<int>(iPixelDataLength) / iWidth;
776   texture = pEventualCanvas->create_palettized_texture(
777       iWidth, iHeight, converted_sprite, bitmap_palette, thdf_alt32_plain);
778   delete[] converted_sprite;
779 
780   width = iWidth;
781   height = iHeight;
782   target = pEventualCanvas;
783 }
784 
785 namespace {
786 
787 /**
788  * Test whether the loaded full colour sprite loads correctly.
789  * @param pData Data of the sprite.
790  * @param iDataLength Length of the sprite data.
791  * @param iWidth Width of the sprite.
792  * @param iHeight Height of the sprite.
793  * @return Whether the sprite loads correctly (at the end of the sprite, all
794  * data is used).
795  */
testSprite(const uint8_t * pData,size_t iDataLength,int iWidth,int iHeight)796 bool testSprite(const uint8_t* pData, size_t iDataLength, int iWidth,
797                 int iHeight) {
798   if (iWidth <= 0 || iHeight <= 0) return true;
799 
800   size_t iCount = iWidth * iHeight;
801   while (iCount > 0) {
802     if (iDataLength < 1) return false;
803     iDataLength--;
804     uint8_t iType = *pData++;
805 
806     size_t iLength = iType & 63;
807     switch (iType >> 6) {
808       case 0:  // Fixed fully opaque 32bpp pixels
809         if (iCount < iLength || iDataLength < iLength * 3) return false;
810         iCount -= iLength;
811         iDataLength -= iLength * 3;
812         pData += iLength * 3;
813         break;
814 
815       case 1:  // Fixed partially transparent 32bpp pixels
816         if (iDataLength < 1) return false;
817         iDataLength--;
818         pData++;  // Opacity byte.
819 
820         if (iCount < iLength || iDataLength < iLength * 3) return false;
821         iCount -= iLength;
822         iDataLength -= iLength * 3;
823         pData += iLength * 3;
824         break;
825 
826       case 2:  // Fixed fully transparent pixels
827         if (iCount < iLength) return false;
828         iCount -= iLength;
829         break;
830 
831       case 3:  // Recolour layer
832         if (iDataLength < 2) return false;
833         iDataLength -= 2;
834         pData += 2;  // Table number, opacity byte.
835 
836         if (iCount < iLength || iDataLength < iLength) return false;
837         iCount -= iLength;
838         iDataLength -= iLength;
839         pData += iLength;
840         break;
841     }
842   }
843   return iDataLength == 0;
844 }
845 
846 }  // namespace
847 
draw(render_target * pCanvas,int iX,int iY)848 void raw_bitmap::draw(render_target* pCanvas, int iX, int iY) {
849   draw(pCanvas, iX, iY, 0, 0, width, height);
850 }
851 
draw(render_target * pCanvas,int iX,int iY,int iSrcX,int iSrcY,int iWidth,int iHeight)852 void raw_bitmap::draw(render_target* pCanvas, int iX, int iY, int iSrcX,
853                       int iSrcY, int iWidth, int iHeight) {
854   double fScaleFactor;
855   if (texture == nullptr) return;
856 
857   if (!pCanvas->should_scale_bitmaps(&fScaleFactor)) {
858     fScaleFactor = 1;
859   }
860 
861   const SDL_Rect rcSrc = {iSrcX, iSrcY, iWidth, iHeight};
862   const SDL_Rect rcDest = {iX, iY, static_cast<int>(iWidth * fScaleFactor),
863                            static_cast<int>(iHeight * fScaleFactor)};
864 
865   pCanvas->draw(texture, &rcSrc, &rcDest, 0);
866 }
867 
sprite_sheet()868 sprite_sheet::sprite_sheet() {
869   sprites = nullptr;
870   palette = nullptr;
871   target = nullptr;
872   sprite_count = 0;
873 }
874 
~sprite_sheet()875 sprite_sheet::~sprite_sheet() { _freeSprites(); }
876 
_freeSingleSprite(size_t iNumber)877 void sprite_sheet::_freeSingleSprite(size_t iNumber) {
878   if (iNumber >= sprite_count) return;
879 
880   if (sprites[iNumber].texture != nullptr) {
881     SDL_DestroyTexture(sprites[iNumber].texture);
882     sprites[iNumber].texture = nullptr;
883   }
884   if (sprites[iNumber].alt_texture != nullptr) {
885     SDL_DestroyTexture(sprites[iNumber].alt_texture);
886     sprites[iNumber].alt_texture = nullptr;
887   }
888   if (sprites[iNumber].data != nullptr) {
889     delete[] sprites[iNumber].data;
890     sprites[iNumber].data = nullptr;
891   }
892 }
893 
_freeSprites()894 void sprite_sheet::_freeSprites() {
895   for (size_t i = 0; i < sprite_count; ++i) _freeSingleSprite(i);
896 
897   delete[] sprites;
898   sprites = nullptr;
899   sprite_count = 0;
900 }
901 
set_palette(const::palette * pPalette)902 void sprite_sheet::set_palette(const ::palette* pPalette) {
903   palette = pPalette;
904 }
905 
set_sprite_count(size_t iCount,render_target * pCanvas)906 bool sprite_sheet::set_sprite_count(size_t iCount, render_target* pCanvas) {
907   _freeSprites();
908 
909   if (pCanvas == nullptr) return false;
910   target = pCanvas;
911 
912   sprite_count = iCount;
913   sprites = new (std::nothrow) sprite[sprite_count];
914   if (sprites == nullptr) {
915     sprite_count = 0;
916     return false;
917   }
918 
919   for (size_t i = 0; i < sprite_count; i++) {
920     sprite& spr = sprites[i];
921     spr.texture = nullptr;
922     spr.alt_texture = nullptr;
923     spr.data = nullptr;
924     spr.alt_palette_map = nullptr;
925     spr.sprite_flags = thdf_alt32_plain;
926     spr.width = 0;
927     spr.height = 0;
928   }
929 
930   return true;
931 }
932 
load_from_th_file(const uint8_t * pTableData,size_t iTableDataLength,const uint8_t * pChunkData,size_t iChunkDataLength,bool bComplexChunks,render_target * pCanvas)933 bool sprite_sheet::load_from_th_file(const uint8_t* pTableData,
934                                      size_t iTableDataLength,
935                                      const uint8_t* pChunkData,
936                                      size_t iChunkDataLength,
937                                      bool bComplexChunks,
938                                      render_target* pCanvas) {
939   _freeSprites();
940   if (pCanvas == nullptr) return false;
941 
942   size_t iCount = iTableDataLength / sizeof(th_sprite_properties);
943   if (!set_sprite_count(iCount, pCanvas)) return false;
944 
945   for (size_t i = 0; i < sprite_count; ++i) {
946     sprite* pSprite = sprites + i;
947     const th_sprite_properties* pTHSprite =
948         reinterpret_cast<const th_sprite_properties*>(pTableData) + i;
949 
950     pSprite->texture = nullptr;
951     pSprite->alt_texture = nullptr;
952     pSprite->data = nullptr;
953     pSprite->alt_palette_map = nullptr;
954     pSprite->width = pTHSprite->width;
955     pSprite->height = pTHSprite->height;
956 
957     if (pSprite->width == 0 || pSprite->height == 0) continue;
958 
959     {
960       uint8_t* pData = new uint8_t[pSprite->width * pSprite->height];
961       chunk_renderer oRenderer(pSprite->width, pSprite->height, pData);
962       int iDataLen = static_cast<int>(iChunkDataLength) -
963                      static_cast<int>(pTHSprite->position);
964       if (iDataLen < 0) iDataLen = 0;
965       oRenderer.decode_chunks(pChunkData + pTHSprite->position, iDataLen,
966                               bComplexChunks);
967       pData = oRenderer.take_data();
968       pSprite->data =
969           convertLegacySprite(pData, pSprite->width * pSprite->height);
970       delete[] pData;
971     }
972   }
973   return true;
974 }
975 
set_sprite_data(size_t iSprite,const uint8_t * pData,bool bTakeData,size_t iDataLength,int iWidth,int iHeight)976 bool sprite_sheet::set_sprite_data(size_t iSprite, const uint8_t* pData,
977                                    bool bTakeData, size_t iDataLength,
978                                    int iWidth, int iHeight) {
979   if (iSprite >= sprite_count) return false;
980 
981   if (!testSprite(pData, iDataLength, iWidth, iHeight)) {
982     std::printf("Sprite number %zu has a bad encoding, skipping", iSprite);
983     return false;
984   }
985 
986   _freeSingleSprite(iSprite);
987   sprite* pSprite = sprites + iSprite;
988   if (bTakeData) {
989     pSprite->data = pData;
990   } else {
991     uint8_t* pNewData = new (std::nothrow) uint8_t[iDataLength];
992     if (pNewData == nullptr) return false;
993 
994     std::memcpy(pNewData, pData, iDataLength);
995     pSprite->data = pNewData;
996   }
997 
998   pSprite->width = iWidth;
999   pSprite->height = iHeight;
1000   return true;
1001 }
1002 
set_sprite_alt_palette_map(size_t iSprite,const uint8_t * pMap,uint32_t iAlt32)1003 void sprite_sheet::set_sprite_alt_palette_map(size_t iSprite,
1004                                               const uint8_t* pMap,
1005                                               uint32_t iAlt32) {
1006   if (iSprite >= sprite_count) return;
1007 
1008   sprite* pSprite = sprites + iSprite;
1009   if (pSprite->alt_palette_map != pMap) {
1010     pSprite->alt_palette_map = pMap;
1011     pSprite->sprite_flags = iAlt32;
1012     if (pSprite->alt_texture) {
1013       SDL_DestroyTexture(pSprite->alt_texture);
1014       pSprite->alt_texture = nullptr;
1015     }
1016   }
1017 }
1018 
get_sprite_count() const1019 size_t sprite_sheet::get_sprite_count() const { return sprite_count; }
1020 
get_sprite_size(size_t iSprite,int * pWidth,int * pHeight) const1021 bool sprite_sheet::get_sprite_size(size_t iSprite, int* pWidth,
1022                                    int* pHeight) const {
1023   if (iSprite >= sprite_count) return false;
1024   if (pWidth != nullptr) *pWidth = sprites[iSprite].width;
1025   if (pHeight != nullptr) *pHeight = sprites[iSprite].height;
1026   return true;
1027 }
1028 
get_sprite_size_unchecked(size_t iSprite,int * pWidth,int * pHeight) const1029 void sprite_sheet::get_sprite_size_unchecked(size_t iSprite, int* pWidth,
1030                                              int* pHeight) const {
1031   *pWidth = sprites[iSprite].width;
1032   *pHeight = sprites[iSprite].height;
1033 }
1034 
get_sprite_average_colour(size_t iSprite,argb_colour * pColour) const1035 bool sprite_sheet::get_sprite_average_colour(size_t iSprite,
1036                                              argb_colour* pColour) const {
1037   if (iSprite >= sprite_count) return false;
1038   const sprite* pSprite = sprites + iSprite;
1039   int iCountTotal = 0;
1040   int iUsageCounts[256] = {0};
1041   for (long i = 0; i < pSprite->width * pSprite->height; ++i) {
1042     uint8_t cPalIndex = pSprite->data[i];
1043     uint32_t iColour = palette->get_argb_data()[cPalIndex];
1044     if ((iColour >> 24) == 0) continue;
1045     // Grant higher score to pixels with high or low intensity (helps avoid
1046     // grey fonts)
1047     int iR = palette::get_red(iColour);
1048     int iG = palette::get_green(iColour);
1049     int iB = palette::get_blue(iColour);
1050     uint8_t cIntensity = static_cast<uint8_t>((iR + iG + iB) / 3);
1051     int iScore = 1 + std::max(0, 3 - ((255 - cIntensity) / 32)) +
1052                  std::max(0, 3 - (cIntensity / 32));
1053     iUsageCounts[cPalIndex] += iScore;
1054     iCountTotal += iScore;
1055   }
1056   if (iCountTotal == 0) return false;
1057   int iHighestCountIndex = 0;
1058   for (int i = 0; i < 256; ++i) {
1059     if (iUsageCounts[i] > iUsageCounts[iHighestCountIndex])
1060       iHighestCountIndex = i;
1061   }
1062   *pColour = palette->get_argb_data()[iHighestCountIndex];
1063   return true;
1064 }
1065 
draw_sprite(render_target * pCanvas,size_t iSprite,int iX,int iY,uint32_t iFlags)1066 void sprite_sheet::draw_sprite(render_target* pCanvas, size_t iSprite, int iX,
1067                                int iY, uint32_t iFlags) {
1068   if (iSprite >= sprite_count || pCanvas == nullptr || pCanvas != target)
1069     return;
1070   sprite& sprite = sprites[iSprite];
1071 
1072   // Find or create the texture
1073   SDL_Texture* pTexture = sprite.texture;
1074   if (!pTexture) {
1075     if (sprite.data == nullptr) return;
1076 
1077     uint32_t iSprFlags =
1078         (sprite.sprite_flags & ~thdf_alt32_mask) | thdf_alt32_plain;
1079     pTexture = target->create_palettized_texture(
1080         sprite.width, sprite.height, sprite.data, palette, iSprFlags);
1081     sprite.texture = pTexture;
1082   }
1083   if (iFlags & thdf_alt_palette) {
1084     pTexture = sprite.alt_texture;
1085     if (!pTexture) {
1086       pTexture = _makeAltBitmap(&sprite);
1087       if (!pTexture) return;
1088     }
1089   }
1090 
1091   SDL_Rect rcSrc = {0, 0, sprite.width, sprite.height};
1092   SDL_Rect rcDest = {iX, iY, sprite.width, sprite.height};
1093 
1094   pCanvas->draw(pTexture, &rcSrc, &rcDest, iFlags);
1095 }
1096 
wx_draw_sprite(size_t iSprite,uint8_t * pRGBData,uint8_t * pAData)1097 void sprite_sheet::wx_draw_sprite(size_t iSprite, uint8_t* pRGBData,
1098                                   uint8_t* pAData) {
1099   if (iSprite >= sprite_count || pRGBData == nullptr || pAData == nullptr)
1100     return;
1101   sprite* pSprite = sprites + iSprite;
1102 
1103   wx_storing oRenderer(pRGBData, pAData, pSprite->width, pSprite->height);
1104   oRenderer.decode_image(pSprite->data, palette, pSprite->sprite_flags);
1105 }
1106 
_makeAltBitmap(sprite * pSprite)1107 SDL_Texture* sprite_sheet::_makeAltBitmap(sprite* pSprite) {
1108   const uint32_t* pPalette = palette->get_argb_data();
1109 
1110   if (!pSprite->alt_palette_map)  // Use normal palette.
1111   {
1112     uint32_t iSprFlags =
1113         (pSprite->sprite_flags & ~thdf_alt32_mask) | thdf_alt32_plain;
1114     pSprite->alt_texture = target->create_palettized_texture(
1115         pSprite->width, pSprite->height, pSprite->data, palette, iSprFlags);
1116   } else if (!pPalette)  // Draw alternative palette, but no palette set (ie
1117                          // 32bpp image).
1118   {
1119     pSprite->alt_texture = target->create_palettized_texture(
1120         pSprite->width, pSprite->height, pSprite->data, palette,
1121         pSprite->sprite_flags);
1122   } else  // Paletted image, build recolour palette.
1123   {
1124     ::palette oPalette;
1125     for (int iColour = 0; iColour < 255; iColour++) {
1126       oPalette.set_argb(iColour, pPalette[pSprite->alt_palette_map[iColour]]);
1127     }
1128     oPalette.set_argb(255,
1129                       pPalette[255]);  // Colour 0xFF doesn't get remapped.
1130 
1131     pSprite->alt_texture = target->create_palettized_texture(
1132         pSprite->width, pSprite->height, pSprite->data, &oPalette,
1133         pSprite->sprite_flags);
1134   }
1135 
1136   return pSprite->alt_texture;
1137 }
1138 
1139 namespace {
1140 
1141 /**
1142  * Get the colour data of pixel \a iPixelNumber (\a iWidth * y + x)
1143  * @param pImg 32bpp image data.
1144  * @param iWidth Width of the image.
1145  * @param iHeight Height of the image.
1146  * @param pPalette Palette of the image, or \c nullptr.
1147  * @param iPixelNumber Number of the pixel to retrieve.
1148  */
get32BppPixel(const uint8_t * pImg,int iWidth,int iHeight,const::palette * pPalette,size_t iPixelNumber)1149 uint32_t get32BppPixel(const uint8_t* pImg, int iWidth, int iHeight,
1150                        const ::palette* pPalette, size_t iPixelNumber) {
1151   if (iWidth <= 0 || iHeight <= 0 ||
1152       iPixelNumber >= static_cast<size_t>(iWidth) * iHeight) {
1153     return palette::pack_argb(0, 0, 0, 0);
1154   }
1155 
1156   for (;;) {
1157     uint8_t iType = *pImg++;
1158     size_t iLength = iType & 63;
1159     switch (iType >> 6) {
1160       case 0:  // Fixed fully opaque 32bpp pixels
1161         if (iPixelNumber >= iLength) {
1162           pImg += 3 * iLength;
1163           iPixelNumber -= iLength;
1164           break;
1165         }
1166 
1167         while (iLength > 0) {
1168           if (iPixelNumber == 0)
1169             return palette::pack_argb(0xFF, pImg[0], pImg[1], pImg[2]);
1170 
1171           iPixelNumber--;
1172           pImg += 3;
1173           iLength--;
1174         }
1175         break;
1176 
1177       case 1:  // Fixed partially transparent 32bpp pixels
1178       {
1179         uint8_t iOpacity = *pImg++;
1180         if (iPixelNumber >= iLength) {
1181           pImg += 3 * iLength;
1182           iPixelNumber -= iLength;
1183           break;
1184         }
1185 
1186         while (iLength > 0) {
1187           if (iPixelNumber == 0)
1188             return palette::pack_argb(iOpacity, pImg[0], pImg[1], pImg[2]);
1189 
1190           iPixelNumber--;
1191           pImg += 3;
1192           iLength--;
1193         }
1194         break;
1195       }
1196 
1197       case 2:  // Fixed fully transparent pixels
1198       {
1199         if (iPixelNumber >= iLength) {
1200           iPixelNumber -= iLength;
1201           break;
1202         }
1203 
1204         return palette::pack_argb(0, 0, 0, 0);
1205       }
1206 
1207       case 3:  // Recolour layer
1208       {
1209         uint8_t iTable = *pImg++;
1210         pImg++;  // Skip reading the opacity for now.
1211         if (iPixelNumber >= iLength) {
1212           pImg += iLength;
1213           iPixelNumber -= iLength;
1214           break;
1215         }
1216 
1217         if (iTable == 0xFF && pPalette != nullptr) {
1218           // Legacy sprite data. Use the palette to recolour the
1219           // layer. Note that the iOpacity is ignored here.
1220           const uint32_t* pColours = pPalette->get_argb_data();
1221           return pColours[pImg[iPixelNumber]];
1222         } else {
1223           // TODO: Add proper recolour layers, where RGB comes from
1224           // table 'iTable' at index *pImg (iLength times), and
1225           // opacity comes from the byte after the iTable byte.
1226           //
1227           // For now just draw black pixels, so it won't go unnoticed.
1228           return palette::pack_argb(0xFF, 0, 0, 0);
1229         }
1230       }
1231     }
1232   }
1233 }
1234 
1235 }  // namespace
1236 
hit_test_sprite(size_t iSprite,int iX,int iY,uint32_t iFlags) const1237 bool sprite_sheet::hit_test_sprite(size_t iSprite, int iX, int iY,
1238                                    uint32_t iFlags) const {
1239   if (iX < 0 || iY < 0 || iSprite >= sprite_count) return false;
1240 
1241   sprite& sprite = sprites[iSprite];
1242   int iWidth = sprite.width;
1243   int iHeight = sprite.height;
1244   if (iX >= iWidth || iY >= iHeight) return false;
1245   if (iFlags & thdf_flip_horizontal) iX = iWidth - iX - 1;
1246   if (iFlags & thdf_flip_vertical) iY = iHeight - iY - 1;
1247 
1248   uint32_t iCol =
1249       get32BppPixel(sprite.data, iWidth, iHeight, palette, iY * iWidth + iX);
1250   return palette::get_alpha(iCol) != 0;
1251 }
1252 
cursor()1253 cursor::cursor() {
1254   bitmap = nullptr;
1255   hotspot_x = 0;
1256   hotspot_y = 0;
1257   hidden_cursor = nullptr;
1258 }
1259 
~cursor()1260 cursor::~cursor() {
1261   SDL_FreeSurface(bitmap);
1262   SDL_FreeCursor(hidden_cursor);
1263 }
1264 
create_from_sprite(sprite_sheet * pSheet,size_t iSprite,int iHotspotX,int iHotspotY)1265 bool cursor::create_from_sprite(sprite_sheet* pSheet, size_t iSprite,
1266                                 int iHotspotX, int iHotspotY) {
1267 #if 0
1268     SDL_FreeSurface(m_pBitmap);
1269     m_pBitmap = nullptr;
1270 
1271     if(pSheet == nullptr || iSprite >= pSheet->getSpriteCount())
1272         return false;
1273     SDL_Surface *pSprite = pSheet->_getSpriteBitmap(iSprite, 0);
1274     if(pSprite == nullptr || (m_pBitmap = SDL_DisplayFormat(pSprite)) == nullptr)
1275         return false;
1276     m_iHotspotX = iHotspotX;
1277     m_iHotspotY = iHotspotY;
1278     return true;
1279 #else
1280   return false;
1281 #endif
1282 }
1283 
use(render_target * pTarget)1284 void cursor::use(render_target* pTarget) {
1285 #if 0
1286     //SDL_ShowCursor(0) is buggy in fullscreen until 1.3 (they say)
1287     //  use transparent cursor for same effect
1288     uint8_t uData = 0;
1289     m_pCursorHidden = SDL_CreateCursor(&uData, &uData, 8, 1, 0, 0);
1290     SDL_SetCursor(m_pCursorHidden);
1291     pTarget->setCursor(this);
1292 #endif
1293 }
1294 
set_position(render_target * pTarget,int iX,int iY)1295 bool cursor::set_position(render_target* pTarget, int iX, int iY) {
1296 #if 0
1297     pTarget->setCursorPosition(iX, iY);
1298     return true;
1299 #else
1300   return false;
1301 #endif
1302 }
1303 
draw(render_target * pCanvas,int iX,int iY)1304 void cursor::draw(render_target* pCanvas, int iX, int iY) {
1305 #if 0
1306     SDL_Rect rcDest;
1307     rcDest.x = (Sint16)(iX - m_iHotspotX);
1308     rcDest.y = (Sint16)(iY - m_iHotspotY);
1309     SDL_BlitSurface(m_pBitmap, nullptr, pCanvas->getRawSurface(), &rcDest);
1310 #endif
1311 }
1312 
line()1313 line::line() { initialize(); }
1314 
~line()1315 line::~line() {
1316   line_operation* op = first_operation;
1317   while (op) {
1318     line_operation* next = (line_operation*)(op->next);
1319     delete (op);
1320     op = next;
1321   }
1322 }
1323 
initialize()1324 void line::initialize() {
1325   width = 1;
1326   red = 0;
1327   green = 0;
1328   blue = 0;
1329   alpha = 255;
1330 
1331   // We start at 0,0
1332   first_operation = new line_operation(line_operation_type::move, 0, 0);
1333   current_operation = first_operation;
1334 }
1335 
move_to(double fX,double fY)1336 void line::move_to(double fX, double fY) {
1337   line_operation* previous = current_operation;
1338   current_operation = new line_operation(line_operation_type::move, fX, fY);
1339   previous->next = current_operation;
1340 }
1341 
line_to(double fX,double fY)1342 void line::line_to(double fX, double fY) {
1343   line_operation* previous = current_operation;
1344   current_operation = new line_operation(line_operation_type::line, fX, fY);
1345   previous->next = current_operation;
1346 }
1347 
set_width(double pLineWidth)1348 void line::set_width(double pLineWidth) { width = pLineWidth; }
1349 
set_colour(uint8_t iR,uint8_t iG,uint8_t iB,uint8_t iA)1350 void line::set_colour(uint8_t iR, uint8_t iG, uint8_t iB, uint8_t iA) {
1351   red = iR;
1352   green = iG;
1353   blue = iB;
1354   alpha = iA;
1355 }
1356 
draw(render_target * pCanvas,int iX,int iY)1357 void line::draw(render_target* pCanvas, int iX, int iY) {
1358   pCanvas->draw_line(this, iX, iY);
1359 }
1360 
persist(lua_persist_writer * pWriter) const1361 void line::persist(lua_persist_writer* pWriter) const {
1362   pWriter->write_uint(static_cast<uint32_t>(red));
1363   pWriter->write_uint(static_cast<uint32_t>(green));
1364   pWriter->write_uint(static_cast<uint32_t>(blue));
1365   pWriter->write_uint(static_cast<uint32_t>(alpha));
1366   pWriter->write_float(width);
1367 
1368   line_operation* op = (line_operation*)(first_operation->next);
1369   uint32_t numOps = 0;
1370   for (; op; numOps++) {
1371     op = (line_operation*)(op->next);
1372   }
1373 
1374   pWriter->write_uint(numOps);
1375 
1376   op = (line_operation*)(first_operation->next);
1377   while (op) {
1378     pWriter->write_uint(static_cast<uint32_t>(op->type));
1379     pWriter->write_float<double>(op->x);
1380     pWriter->write_float(op->y);
1381 
1382     op = (line_operation*)(op->next);
1383   }
1384 }
1385 
depersist(lua_persist_reader * pReader)1386 void line::depersist(lua_persist_reader* pReader) {
1387   initialize();
1388 
1389   pReader->read_uint(red);
1390   pReader->read_uint(green);
1391   pReader->read_uint(blue);
1392   pReader->read_uint(alpha);
1393   pReader->read_float(width);
1394 
1395   uint32_t numOps = 0;
1396   pReader->read_uint(numOps);
1397 
1398   for (uint32_t i = 0; i < numOps; i++) {
1399     // Initialize to invalid in case the read fails.
1400     uint32_t type_val = std::numeric_limits<uint32_t>::max();
1401     double fX = std::nan("");
1402     double fY = std::nan("");
1403     pReader->read_uint(type_val);
1404     pReader->read_float(fX);
1405     pReader->read_float(fY);
1406 
1407     if (std::isnan(fX) || std::isnan(fY)) {
1408       return;
1409     }
1410 
1411     if (type_val == static_cast<uint32_t>(line_operation_type::move)) {
1412       move_to(fX, fY);
1413     } else if (type_val == static_cast<uint32_t>(line_operation_type::line)) {
1414       line_to(fX, fY);
1415     }
1416   }
1417 }
1418 
1419 #ifdef CORSIX_TH_USE_FREETYPE2
is_monochrome() const1420 bool freetype_font::is_monochrome() const { return false; }
1421 
free_texture(cached_text * pCacheEntry) const1422 void freetype_font::free_texture(cached_text* pCacheEntry) const {
1423   if (pCacheEntry->texture != nullptr) {
1424     SDL_DestroyTexture(pCacheEntry->texture);
1425     pCacheEntry->texture = nullptr;
1426   }
1427 }
1428 
make_texture(render_target * pEventualCanvas,cached_text * pCacheEntry) const1429 void freetype_font::make_texture(render_target* pEventualCanvas,
1430                                  cached_text* pCacheEntry) const {
1431   uint32_t* pPixels = new uint32_t[pCacheEntry->width * pCacheEntry->height];
1432   std::memset(pPixels, 0,
1433               pCacheEntry->width * pCacheEntry->height * sizeof(uint32_t));
1434   uint8_t* pInRow = pCacheEntry->data;
1435   uint32_t* pOutRow = pPixels;
1436   uint32_t iColBase = colour & 0xFFFFFF;
1437   for (int iY = 0; iY < pCacheEntry->height;
1438        ++iY, pOutRow += pCacheEntry->width, pInRow += pCacheEntry->width) {
1439     for (int iX = 0; iX < pCacheEntry->width; ++iX) {
1440       pOutRow[iX] = (static_cast<uint32_t>(pInRow[iX]) << 24) | iColBase;
1441     }
1442   }
1443 
1444   pCacheEntry->texture = pEventualCanvas->create_texture(
1445       pCacheEntry->width, pCacheEntry->height, pPixels);
1446   delete[] pPixels;
1447 }
1448 
draw_texture(render_target * pCanvas,cached_text * pCacheEntry,int iX,int iY) const1449 void freetype_font::draw_texture(render_target* pCanvas,
1450                                  cached_text* pCacheEntry, int iX,
1451                                  int iY) const {
1452   if (pCacheEntry->texture == nullptr) return;
1453 
1454   SDL_Rect rcDest = {iX, iY, pCacheEntry->width, pCacheEntry->height};
1455   pCanvas->draw(pCacheEntry->texture, nullptr, &rcDest, 0);
1456 }
1457 
1458 #endif  // CORSIX_TH_USE_FREETYPE2
1459