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