The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
utils.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
3 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16/**
17 * @file
18 * Support-routines for the SDL-graphics-library.
19 */
20
21#include "sdl/rect.hpp"
22#include "sdl/utils.hpp"
23#include "color.hpp"
24#include "log.hpp"
25#include "utils_simd.hpp"
26#include "xBRZ/xbrz.hpp"
27
28#include <algorithm>
29#include <cassert>
30#include <cstring>
31#include "utils/ranges.hpp"
32#include "utils/span.hpp"
33
34#include <SDL3/SDL_version.h>
35
36#include <boost/circular_buffer.hpp>
37#include <boost/math/constants/constants.hpp>
38
39static lg::log_domain log_display("display");
40#define ERR_DP LOG_STREAM(err, log_display)
41
43{
44 int linked_sdl_version = SDL_GetVersion();
45 return version_info(SDL_VERSIONNUM_MAJOR(linked_sdl_version), SDL_VERSIONNUM_MINOR(linked_sdl_version), SDL_VERSIONNUM_MICRO(linked_sdl_version));
46}
47
48bool sdl::runtime_at_least(uint8_t major, uint8_t minor, uint8_t patch)
49{
50 int linked_sdl_version = SDL_GetVersion();
51 if(SDL_VERSIONNUM_MAJOR(linked_sdl_version) < major) return false;
52 if(SDL_VERSIONNUM_MAJOR(linked_sdl_version) > major) return true;
53 // major version equal
54 if(SDL_VERSIONNUM_MINOR(linked_sdl_version) < minor) return false;
55 if(SDL_VERSIONNUM_MINOR(linked_sdl_version) > minor) return true;
56 // major and minor version equal
57 if(SDL_VERSIONNUM_MICRO(linked_sdl_version) < patch) return false;
58 return true;
59}
60
61surface scale_surface_xbrz(const surface & surf, std::size_t z)
62{
63 if(surf == nullptr)
64 return nullptr;
65
66 if (z > xbrz::SCALE_FACTOR_MAX) {
67 PLAIN_LOG << "Cannot use xbrz scaling with zoom factor > " << xbrz::SCALE_FACTOR_MAX;
68 z = 1;
69 }
70
71 if (z == 1) {
72 surface temp = surf; // TODO: no temp surface
73 return temp;
74 }
75
76 surface dst(surf->w *z, surf->h * z);
77
78 if (z == 0) {
79 PLAIN_LOG << "Create an empty image";
80 return dst;
81 }
82
83 if(surf == nullptr || dst == nullptr) {
84 PLAIN_LOG << "Could not create surface to scale onto";
85 return nullptr;
86 }
87
88 {
89 const_surface_lock src_lock(surf);
90 surface_lock dst_lock(dst);
91
92 xbrz::scale(z, src_lock.pixels(), dst_lock.pixels(), surf->w, surf->h, xbrz::ColorFormat::ARGB);
93 }
94
95 return dst;
96}
97
98// NOTE: Don't pass this function 0 scaling arguments.
100{
101 if(surf == nullptr)
102 return nullptr;
103
104 if(w == surf->w && h == surf->h) {
105 return surf;
106 }
107 assert(w >= 0);
108 assert(h >= 0);
109
110 surface dst(w,h);
111
112 if (w == 0 || h ==0) {
113 PLAIN_LOG << "Create an empty image";
114 return dst;
115 }
116
117 if(surf == nullptr || dst == nullptr) {
118 PLAIN_LOG << "Could not create surface to scale onto";
119 return nullptr;
120 }
121
122 {
123 const_surface_lock src_lock(surf);
124 surface_lock dst_lock(dst);
125
126 const uint32_t* const src_pixels = src_lock.pixels();
127 uint32_t* const dst_pixels = dst_lock.pixels();
128
129 int32_t xratio = fixed_point_divide(surf->w,w);
130 int32_t yratio = fixed_point_divide(surf->h,h);
131
132 int32_t ysrc = 0;
133 for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
134 int32_t xsrc = 0;
135 for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
136 const int xsrcint = fixed_point_to_int(xsrc);
137 const int ysrcint = fixed_point_to_int(ysrc);
138
139 const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
140 uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
141 const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
142 const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
143
144 uint8_t r,g,b,a;
145 uint32_t rr,gg,bb,aa, temp;
146
147 uint32_t pix[4], bilin[4];
148
149 // This next part is the fixed point
150 // equivalent of "take everything to
151 // the right of the decimal point."
152 // These fundamental weights decide
153 // the contributions from various
154 // input pixels. The labels assume
155 // that the upper left corner of the
156 // screen ("northeast") is 0,0 but the
157 // code should still be consistent if
158 // the graphics origin is actually
159 // somewhere else.
160 //
161 // That is, the bilin array holds the
162 // "geometric" weights. I.E. If I'm scaling
163 // a 2 x 2 block a 10 x 10 block, then for
164 // pixel (2,2) of output, the upper left
165 // pixel should be 10:1 more influential than
166 // the upper right, and also 10:1 more influential
167 // than lower left, and 100:1 more influential
168 // than lower right.
169
170 const int32_t e = 0x000000FF & xsrc;
171 const int32_t s = 0x000000FF & ysrc;
172 const int32_t n = 0xFF - s;
173 // Not called "w" to avoid hiding a function parameter
174 // (would cause a compiler warning in MSVC2015 with /W4)
175 const int32_t we = 0xFF - e;
176
177 pix[0] = *src_word; // northwest
178 pix[1] = *(src_word + dx); // northeast
179 pix[2] = *(src_word + dy); // southwest
180 pix[3] = *(src_word + dx + dy); // southeast
181
182 bilin[0] = n*we;
183 bilin[1] = n*e;
184 bilin[2] = s*we;
185 bilin[3] = s*e;
186
187 int loc;
188 rr = bb = gg = aa = 0;
189 for (loc=0; loc<4; loc++) {
190 a = pix[loc] >> 24;
191 r = pix[loc] >> 16;
192 g = pix[loc] >> 8;
193 b = pix[loc] >> 0;
194
195 //We also have to implement weighting by alpha for the RGB components
196 //If a unit has some parts solid and some parts translucent,
197 //i.e. a red cloak but a dark shadow, then when we scale in
198 //the shadow shouldn't appear to become red at the edges.
199 //This part also smoothly interpolates between alpha=0 being
200 //transparent and having no contribution, vs being opaque.
201 temp = (a * bilin[loc]);
202 rr += r * temp;
203 gg += g * temp;
204 bb += b * temp;
205 aa += temp;
206 }
207
208 a = aa >> (16); // we average the alphas, they don't get weighted by any other factor besides bilin
209 if (a != 0) {
210 rr /= a; // finish alpha weighting: divide by sum of alphas
211 gg /= a;
212 bb /= a;
213 }
214 r = rr >> (16); // now shift over by 16 for the bilin part
215 g = gg >> (16);
216 b = bb >> (16);
217 *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
218 }
219 }
220 }
221
222 return dst;
223}
224
226{
227 if(surf == nullptr)
228 return nullptr;
229
230 if(w == surf->w && h == surf->h) {
231 return surf;
232 }
233 assert(w >= 0);
234 assert(h >= 0);
235
236 surface dst(w,h);
237
238 if(surf == nullptr || dst == nullptr) {
239 PLAIN_LOG << "Could not create surface to scale onto";
240 return nullptr;
241 }
242
243 {
244 const_surface_lock src_lock(surf);
245 surface_lock dst_lock(dst);
246
247 const uint32_t* const src_pixels = src_lock.pixels();
248 uint32_t* const dst_pixels = dst_lock.pixels();
249
250 int32_t xratio = fixed_point_divide(surf->w,w);
251 int32_t yratio = fixed_point_divide(surf->h,h);
252
253 int32_t ysrc = 0;
254 for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
255 int32_t xsrc = 0;
256 for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
257 const int xsrcint = fixed_point_to_int(xsrc);
258 const int ysrcint = fixed_point_to_int(ysrc);
259
260 const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
261 uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
262 const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
263 const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
264
265 uint8_t r,g,b,a;
266 uint32_t rr,gg,bb,aa;
267 uint16_t avg_r, avg_g, avg_b;
268 uint32_t pix[4], bilin[4];
269
270 // This next part is the fixed point
271 // equivalent of "take everything to
272 // the right of the decimal point."
273 // These fundamental weights decide
274 // the contributions from various
275 // input pixels. The labels assume
276 // that the upper left corner of the
277 // screen ("northeast") is 0,0 but the
278 // code should still be consistent if
279 // the graphics origin is actually
280 // somewhere else.
281
282 const int32_t east = 0x000000FF & xsrc;
283 const int32_t south = 0x000000FF & ysrc;
284 const int32_t north = 0xFF - south;
285 const int32_t west = 0xFF - east;
286
287 pix[0] = *src_word; // northwest
288 pix[1] = *(src_word + dx); // northeast
289 pix[2] = *(src_word + dy); // southwest
290 pix[3] = *(src_word + dx + dy); // southeast
291
292 bilin[0] = north*west;
293 bilin[1] = north*east;
294 bilin[2] = south*west;
295 bilin[3] = south*east;
296
297 // Scope out the neighboorhood, see
298 // what the pixel values are like.
299
300 int count = 0;
301 avg_r = avg_g = avg_b = 0;
302 int loc;
303 for (loc=0; loc<4; loc++) {
304 a = pix[loc] >> 24;
305 r = pix[loc] >> 16;
306 g = pix[loc] >> 8;
307 b = pix[loc] >> 0;
308 if (a != 0) {
309 avg_r += r;
310 avg_g += g;
311 avg_b += b;
312 count++;
313 }
314 }
315 if (count>0) {
316 avg_r /= count;
317 avg_b /= count;
318 avg_g /= count;
319 }
320
321 // Perform modified bilinear interpolation.
322 // Don't trust any color information from
323 // an RGBA sample when the alpha channel
324 // is set to fully transparent.
325 //
326 // Some of the input images are hex tiles,
327 // created using a hexagon shaped alpha channel
328 // that is either set to full-on or full-off.
329
330 rr = gg = bb = aa = 0;
331 for (loc=0; loc<4; loc++) {
332 a = pix[loc] >> 24;
333 r = pix[loc] >> 16;
334 g = pix[loc] >> 8;
335 b = pix[loc] >> 0;
336 if (a == 0) {
337 r = static_cast<uint8_t>(avg_r);
338 g = static_cast<uint8_t>(avg_g);
339 b = static_cast<uint8_t>(avg_b);
340 }
341 rr += r * bilin[loc];
342 gg += g * bilin[loc];
343 bb += b * bilin[loc];
344 aa += a * bilin[loc];
345 }
346 r = rr >> 16;
347 g = gg >> 16;
348 b = bb >> 16;
349 a = aa >> 16;
350 *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
351 }
352 }
353 }
354
355 return dst;
356}
357
359{
360 if(surf == nullptr) {
361 return nullptr;
362 }
363 if(w == surf->w && h == surf->h) {
364 return surf;
365 }
366
367 assert(w >= 0);
368 assert(h >= 0);
369 surface dst(w, h);
370 if(dst == nullptr) {
371 PLAIN_LOG << "Could not create surface to scale onto";
372 return nullptr;
373 }
374
375 if(w == 0 || h == 0) {
376 PLAIN_LOG << "Creating an empty image";
377 return dst;
378 }
379
380 {
381 const_surface_lock src_lock(surf);
382 surface_lock dst_lock(dst);
383
384 const uint32_t* const src_pixels = src_lock.pixels();
385 uint32_t* const dst_pixels = dst_lock.pixels();
386
387 const int src_w = surf->w;
388 const int src_h = surf->h;
389
390 const float xratio = static_cast<float>(src_w) / static_cast<float>(w);
391 const float yratio = static_cast<float>(src_h) / static_cast<float>(h);
392 for(int ydst = 0; ydst != h; ++ydst) {
393 for(int xdst = 0; xdst != w; ++xdst) {
394 // Project dst pixel to a single corresponding src pixel by scale and simply take it
395 const int xsrc = std::floor(static_cast<float>(xdst) * xratio);
396 const int ysrc = std::floor(static_cast<float>(ydst) * yratio);
397 dst_pixels[ydst * dst->w + xdst] = src_pixels[ysrc * src_w + xsrc];
398 }
399 }
400 }
401
402 return dst;
403}
404
405void adjust_surface_color(surface& nsurf, int red, int green, int blue)
406{
407 if(nsurf && (red != 0 || green != 0 || blue != 0)) {
408 // Attempt SIMD
409 std::size_t simd_processed = simd::is_enabled() ? adjust_surface_color_simd(nsurf, red, green, blue) : 0;
410
411 // SCALAR fallback: process remaining pixels (either all pixels if SIMD unavailable, or just the remainder)
412 surface_lock lock(nsurf);
413 utils::span pixel_span = lock.pixel_span();
414 for(auto& pixel : pixel_span.subspan(simd_processed)) {
415 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
416 r = std::clamp(static_cast<int>(r) + red, 0, 255);
417 g = std::clamp(static_cast<int>(g) + green, 0, 255);
418 b = std::clamp(static_cast<int>(b) + blue, 0, 255);
419 pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
420 }
421 }
422}
423
425{
426 if(nsurf) {
427 surface_lock lock(nsurf);
428
429 for(auto& pixel : lock.pixel_span()) {
430 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
431
432 // Use the correct formula for RGB to grayscale conversion.
433 // Ok, this is no big deal :)
434 // The correct formula being:
435 // gray=0.299red+0.587green+0.114blue
436 const uint8_t avg = static_cast<uint8_t>((
437 77 * static_cast<uint16_t>(r) +
438 150 * static_cast<uint16_t>(g) +
439 29 * static_cast<uint16_t>(b) ) / 256);
440
441 pixel = (alpha << 24) | (avg << 16) | (avg << 8) | avg;
442 }
443 }
444}
445
446void monochrome_image(surface& nsurf, const int threshold)
447{
448 if(nsurf) {
449 surface_lock lock(nsurf);
450
451 for(auto& pixel : lock.pixel_span()) {
452 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
453
454 // first convert the pixel to grayscale
455 // if the resulting value is above the threshold make it black
456 // else make it white
457 uint8_t result = static_cast<uint8_t>(0.299 * r + 0.587 * g + 0.114 * b) > threshold ? 255 : 0;
458
459 pixel = (alpha << 24) | (result << 16) | (result << 8) | result;
460 }
461 }
462}
463
465{
466 if(nsurf) {
467 surface_lock lock(nsurf);
468
469 for(auto& pixel : lock.pixel_span()) {
470 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
471
472 // this is the formula for applying a sepia effect
473 // that can be found on various web sites
474 uint8_t outR = std::min(255, static_cast<int>((r * 0.393) + (g * 0.769) + (b * 0.189)));
475 uint8_t outG = std::min(255, static_cast<int>((r * 0.349) + (g * 0.686) + (b * 0.168)));
476 uint8_t outB = std::min(255, static_cast<int>((r * 0.272) + (g * 0.534) + (b * 0.131)));
477
478 pixel = (alpha << 24) | (outR << 16) | (outG << 8) | (outB);
479 }
480 }
481}
482
483void negative_image(surface& nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
484{
485 if(nsurf) {
486 surface_lock lock(nsurf);
487
488 for(auto& pixel : lock.pixel_span()) {
489 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
490
491 // invert he channel only if its value is greater than the supplied threshold
492 // this can be used for solarization effects
493 // for a full negative effect, use a value of -1
494 // 255 is a no-op value (doesn't do anything, since a uint8_t cannot contain a greater value than that)
495 uint8_t newR = r > thresholdR ? 255 - r : r;
496 uint8_t newG = g > thresholdG ? 255 - g : g;
497 uint8_t newB = b > thresholdB ? 255 - b : b;
498
499 pixel = (alpha << 24) | (newR << 16) | (newG << 8) | (newB);
500 }
501 }
502}
503
505{
506 if(nsurf) {
507 surface_lock lock(nsurf);
508
509 for(auto& pixel : lock.pixel_span()) {
510 uint8_t alpha = pixel >> 24;
511
512 pixel = (0xff << 24) | (alpha << 16) | (alpha << 8) | alpha;
513 }
514 }
515}
516
517void wipe_alpha(surface& nsurf)
518{
519 if(nsurf) {
520 surface_lock lock(nsurf);
521
522 for(auto& pixel : lock.pixel_span()) {
523 pixel = 0xff000000 | pixel;
524 }
525 }
526}
527
528
529void shadow_image(surface& surf, int scale)
530{
531 if(surf == nullptr)
532 return;
533
534 // we blur it, and reuse the neutral surface created by the blur function
535 blur_alpha_surface(surf, 2*scale);
536
537 {
538 surface_lock lock(surf);
539
540 for(auto& pixel : lock.pixel_span()) {
541 uint8_t alpha = pixel >> 24;
542
543 // increase alpha and color in black (RGB=0)
544 // with some stupid optimization for handling maximum values
545 if(alpha < 255 / 4) {
546 pixel = (alpha * 4) << 24;
547 } else {
548 pixel = 0xFF000000; // we hit the maximum
549 }
550 }
551 }
552}
553
555{
556 if(nsurf) {
557 surface_lock lock(nsurf);
558
559 for(auto& pixel : lock.pixel_span()) {
560 auto [red, green, blue, alpha] = color_t::from_argb_bytes(pixel);
561 uint8_t newRed, newGreen, newBlue, newAlpha;
562
563 switch (r) {
564 case RED:
565 newRed = red;
566 break;
567 case GREEN:
568 newRed = green;
569 break;
570 case BLUE:
571 newRed = blue;
572 break;
573 case ALPHA:
574 newRed = alpha;
575 break;
576 default:
577 return;
578 }
579
580 switch (g) {
581 case RED:
582 newGreen = red;
583 break;
584 case GREEN:
585 newGreen = green;
586 break;
587 case BLUE:
588 newGreen = blue;
589 break;
590 case ALPHA:
591 newGreen = alpha;
592 break;
593 default:
594 return;
595 }
596
597 switch (b) {
598 case RED:
599 newBlue = red;
600 break;
601 case GREEN:
602 newBlue = green;
603 break;
604 case BLUE:
605 newBlue = blue;
606 break;
607 case ALPHA:
608 newBlue = alpha;
609 break;
610 default:
611 return;
612 }
613
614 switch (a) {
615 case RED:
616 newAlpha = red;
617 break;
618 case GREEN:
619 newAlpha = green;
620 break;
621 case BLUE:
622 newAlpha = blue;
623 break;
624 case ALPHA:
625 newAlpha = alpha;
626 break;
627 default:
628 return;
629 }
630
631 pixel = (newAlpha << 24) | (newRed << 16) | (newGreen << 8) | newBlue;
632 }
633 }
634}
635
636void recolor_image(surface& nsurf, const color_mapping& map_rgb)
637{
638 if(nsurf == nullptr)
639 return;
640
641 if(map_rgb.empty()) {
642 return;
643 }
644
645 surface_lock lock(nsurf);
646
647 for(auto& pixel : lock.pixel_span()) {
648 auto color = color_t::from_argb_bytes(pixel);
649
650 // Palette uses only RGB channels, so remove alpha
651 uint8_t old_alpha = color.a;
652 color.a = ALPHA_OPAQUE;
653
654 auto iter = map_rgb.find(color);
655 if(iter == map_rgb.end()) {
656 continue;
657 }
658
659 // Set new color, restore alpha
660 color = iter->second;
661 color.a = old_alpha;
662
663 pixel = color.to_argb_bytes();
664 }
665}
666
667void brighten_image(surface& nsurf, int32_t amount)
668{
669 if(nsurf) {
670 surface_lock lock(nsurf);
671
672 if (amount < 0) amount = 0;
673 for(auto& pixel : lock.pixel_span()) {
674 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
675
676 r = std::min<unsigned>(fixed_point_multiply(r, amount), 255);
677 g = std::min<unsigned>(fixed_point_multiply(g, amount), 255);
678 b = std::min<unsigned>(fixed_point_multiply(b, amount), 255);
679
680 pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
681 }
682 }
683}
684
685void adjust_surface_alpha(surface& surf, uint8_t alpha_mod)
686{
687 if(surf == nullptr) {
688 return;
689 }
690
691 SDL_SetSurfaceAlphaMod(surf, alpha_mod);
692}
693
694void adjust_surface_alpha_add(surface& nsurf, int amount)
695{
696 if(nsurf) {
697 surface_lock lock(nsurf);
698
699 for(auto& pixel : lock.pixel_span()) {
700 auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
701
702 alpha = uint8_t(std::clamp(static_cast<int>(alpha) + amount, 0, 255));
703 pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
704 }
705 }
706}
707
708bool mask_surface(surface& nsurf, const surface& nmask, const std::string& filename)
709{
710 if(nsurf == nullptr) {
711 return true;
712 }
713 if(nmask == nullptr) {
714 return false;
715 }
716
717 if(nsurf->w != nmask->w) {
718 // we don't support efficiently different width.
719 // (different height is not a real problem)
720 // This function is used on all hexes and usually only for that
721 // so better keep it simple and efficient for the normal case
722 std::stringstream ss;
723 ss << "Detected an image with bad dimensions: ";
724 if(!filename.empty()) ss << filename << ": ";
725 ss << nsurf->w << "x" << nsurf->h;
726 PLAIN_LOG << ss.str();
727 PLAIN_LOG << "It will not be masked, please use: "<< nmask->w << "x" << nmask->h;
728 return false;
729 }
730
731 uint32_t cumulative_alpha{0};
732 {
733 // Attempt SIMD
734 bool simd_empty = true;
735 std::size_t simd_processed = simd::is_enabled() ? mask_surface_simd(nsurf, nmask, simd_empty) : 0;
736 if(simd_processed > 0) {
737 cumulative_alpha = simd_empty ? 0 : 1;
738 }
739
740 // SCALAR fallback: process remaining pixels (either all pixels if SIMD unavailable, or just the remainder)
741 surface_lock lock(nsurf);
742 const_surface_lock mlock(nmask);
743 const utils::span surf_px = lock.pixel_span();
744 const utils::span mask_px = mlock.pixel_span();
745 const utils::span surf_sub = surf_px.subspan(simd_processed);
746 const utils::span mask_sub = mask_px.subspan(simd_processed);
747 for(std::size_t i = 0; i < surf_sub.size(); ++i) {
748 const uint32_t min_alpha = std::min(surf_sub[i] & SDL_ALPHA_MASK, mask_sub[i] & SDL_ALPHA_MASK);
749 surf_sub[i] = (surf_sub[i] & ~SDL_ALPHA_MASK) | min_alpha;
750 cumulative_alpha |= min_alpha;
751 }
752 }
753 return cumulative_alpha == 0;
754}
755
756bool in_mask_surface(const surface& nsurf, const surface& nmask)
757{
758 if(nsurf == nullptr) {
759 return false;
760 }
761 if(nmask == nullptr){
762 return true;
763 }
764
765 if (nsurf->w != nmask->w || nsurf->h != nmask->h ) {
766 // not same size, consider it doesn't fit
767 return false;
768 }
769
770 const_surface_lock lock(nsurf);
771 const_surface_lock mlock(nmask);
772
773 utils::span surf_pixels = lock.pixel_span();
774 utils::span mask_pixels = mlock.pixel_span();
775
776 // Note: unlike in mask_surface, both ranges here have the same size.
777 for(std::size_t i = 0; i < surf_pixels.size(); ++i) {
778 const uint32_t surf_alpha = surf_pixels[i] & SDL_ALPHA_MASK;
779 const uint32_t mask_alpha = mask_pixels[i] & SDL_ALPHA_MASK;
780
781 // A visible pixel (non-zero alpha) which the mask would otherwise hide.
782 if(surf_alpha && mask_alpha == 0) {
783 return false;
784 }
785 }
786
787 return true;
788}
789
790void light_surface(surface& nsurf, const surface &lightmap)
791{
792 if(nsurf == nullptr) {
793 return;
794 }
795 if(lightmap == nullptr) {
796 return;
797 }
798
799 if (nsurf->w != lightmap->w) {
800 // we don't support efficiently different width.
801 // (different height is not a real problem)
802 // This function is used on all hexes and usually only for that
803 // so better keep it simple and efficient for the normal case
804 PLAIN_LOG << "Detected an image with bad dimensions: " << nsurf->w << "x" << nsurf->h;
805 PLAIN_LOG << "It will not be lighted, please use: "<< lightmap->w << "x" << lightmap->h;
806 return;
807 }
808 {
809 surface_lock lock(nsurf);
810 const_surface_lock llock(lightmap);
811
812 uint32_t* beg = lock.pixels();
813 uint32_t* end = beg + nsurf.area();
814 const uint32_t* lbeg = llock.pixels();
815 const uint32_t* lend = lbeg + lightmap.area();
816
817 while(beg != end && lbeg != lend) {
818 auto [lr, lg, lb, la] = color_t::from_argb_bytes(*lbeg);
819 auto [r, g, b, alpha] = color_t::from_argb_bytes(*beg);
820
821 int dr = (static_cast<int>(lr) - 128) * 2;
822 int dg = (static_cast<int>(lg) - 128) * 2;
823 int db = (static_cast<int>(lb) - 128) * 2;
824
825 //note that r + dr will promote r to int (needed to avoid uint8_t math)
826 r = std::clamp(r + dr, 0, 255);
827 g = std::clamp(g + dg, 0, 255);
828 b = std::clamp(b + db, 0, 255);
829
830 *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
831
832 ++beg;
833 ++lbeg;
834 }
835 }
836}
837
839{
840 if(surf == nullptr) {
841 return;
842 }
843
844 const int max_blur = 256;
845 if(depth > max_blur) {
846 depth = max_blur;
847 }
848
849 uint32_t queue[max_blur];
850 const uint32_t* end_queue = queue + max_blur;
851
852 const uint32_t ff = 0xff;
853
854 const unsigned pixel_offset = rect.y * surf->w + rect.x;
855
856 surface_lock lock(surf);
857 for(int y = 0; y < rect.h; ++y) {
858 const uint32_t* front = &queue[0];
859 uint32_t* back = &queue[0];
860 uint32_t red = 0, green = 0, blue = 0, avg = 0;
861 uint32_t* p = lock.pixels() + pixel_offset + y * surf->w;
862 for(int x = 0; x <= depth && x < rect.w; ++x, ++p) {
863 red += ((*p) >> 16)&0xFF;
864 green += ((*p) >> 8)&0xFF;
865 blue += (*p)&0xFF;
866 ++avg;
867 *back++ = *p;
868 if(back == end_queue) {
869 back = &queue[0];
870 }
871 }
872
873 p = lock.pixels() + pixel_offset + y * surf->w;
874 for(int x = 0; x < rect.w; ++x, ++p) {
875 *p = 0xFF000000
876 | (std::min(red/avg,ff) << 16)
877 | (std::min(green/avg,ff) << 8)
878 | std::min(blue/avg,ff);
879
880 if(x >= depth) {
881 red -= ((*front) >> 16)&0xFF;
882 green -= ((*front) >> 8)&0xFF;
883 blue -= *front&0xFF;
884 --avg;
885 ++front;
886 if(front == end_queue) {
887 front = &queue[0];
888 }
889 }
890
891 if(x + depth+1 < rect.w) {
892 uint32_t* q = p + depth+1;
893 red += ((*q) >> 16)&0xFF;
894 green += ((*q) >> 8)&0xFF;
895 blue += (*q)&0xFF;
896 ++avg;
897 *back++ = *q;
898 if(back == end_queue) {
899 back = &queue[0];
900 }
901 }
902 }
903 }
904
905 for(int x = 0; x < rect.w; ++x) {
906 const uint32_t* front = &queue[0];
907 uint32_t* back = &queue[0];
908 uint32_t red = 0, green = 0, blue = 0, avg = 0;
909 uint32_t* p = lock.pixels() + pixel_offset + x;
910 for(int y = 0; y <= depth && y < rect.h; ++y, p += surf->w) {
911 red += ((*p) >> 16)&0xFF;
912 green += ((*p) >> 8)&0xFF;
913 blue += *p&0xFF;
914 ++avg;
915 *back++ = *p;
916 if(back == end_queue) {
917 back = &queue[0];
918 }
919 }
920
921 p = lock.pixels() + pixel_offset + x;
922 for(int y = 0; y < rect.h; ++y, p += surf->w) {
923 *p = 0xFF000000
924 | (std::min(red/avg,ff) << 16)
925 | (std::min(green/avg,ff) << 8)
926 | std::min(blue/avg,ff);
927
928 if(y >= depth) {
929 red -= ((*front) >> 16)&0xFF;
930 green -= ((*front) >> 8)&0xFF;
931 blue -= *front&0xFF;
932 --avg;
933 ++front;
934 if(front == end_queue) {
935 front = &queue[0];
936 }
937 }
938
939 if(y + depth+1 < rect.h) {
940 uint32_t* q = p + (depth+1)*surf->w;
941 red += ((*q) >> 16)&0xFF;
942 green += ((*q) >> 8)&0xFF;
943 blue += (*q)&0xFF;
944 ++avg;
945 *back++ = *q;
946 if(back == end_queue) {
947 back = &queue[0];
948 }
949 }
950 }
951 }
952}
953
954void blur_alpha_surface(surface& res, int depth)
955{
956 if(res == nullptr) {
957 return;
958 }
959
960 const int max_blur = 256;
961 if(depth > max_blur) {
962 depth = max_blur;
963 }
964
965 struct Pixel{
966 uint8_t alpha;
967 uint8_t red;
968 uint8_t green;
969 uint8_t blue;
970 Pixel(uint32_t* p)
971 : alpha(((*p) >> 24)&0xFF)
972 , red(((*p) >> 16)&0xFF)
973 , green(((*p) >> 8)&0xFF)
974 , blue((*p)&0xFF) {}
975 };
976 struct Average{
977 uint32_t alpha;
978 uint32_t red;
979 uint32_t green;
980 uint32_t blue;
981 Average() : alpha(), red(), green(), blue()
982 {}
983 Average& operator+=(const Pixel& pix){
984 red += pix.alpha * pix.red;
985 green += pix.alpha * pix.green;
986 blue += pix.alpha * pix.blue;
987 alpha += pix.alpha;
988 return *this;
989 }
990 Average& operator-=(const Pixel& pix){
991 red -= pix.alpha * pix.red;
992 green -= pix.alpha * pix.green;
993 blue -= pix.alpha * pix.blue;
994 alpha -= pix.alpha;
995 return *this;
996 }
997 uint32_t operator()(unsigned num){
998 const uint32_t ff = 0xff;
999 if(!alpha){
1000 return 0;
1001 }
1002 return (std::min(alpha/num,ff) << 24)
1003 | (std::min(red/alpha,ff) << 16)
1004 | (std::min(green/alpha,ff) << 8)
1005 | std::min(blue/alpha,ff);
1006 }
1007 };
1008
1009 boost::circular_buffer<Pixel> queue(depth*2+1);
1010
1011 surface_lock lock(res);
1012 int x, y;
1013 // Iterate over rows, blurring each row horizontally
1014 for(y = 0; y < res->h; ++y) {
1015 // Sum of pixel values stored here
1016 Average avg;
1017
1018 // Preload the first depth+1 pixels
1019 uint32_t* p = lock.pixels() + y*res->w;
1020 for(x = 0; x <= depth && x < res->w; ++x, ++p) {
1021 assert(!queue.full());
1022 queue.push_back(Pixel{p});
1023 avg += queue.back();
1024 }
1025
1026 // This is the actual inner loop
1027 p = lock.pixels() + y*res->w;
1028 for(x = 0; x < res->w; ++x, ++p) {
1029 // Write the current average
1030 const uint32_t num = queue.size();
1031 *p = avg(num);
1032
1033 // Unload earlier pixels that are now too far away
1034 if(x >= depth) {
1035 avg -= queue.front();
1036 assert(!queue.empty());
1037 queue.pop_front();
1038 }
1039
1040 // Add new pixels
1041 if(x + depth+1 < res->w) {
1042 uint32_t* q = p + depth+1;
1043 assert(!queue.full());
1044 queue.push_back(Pixel{q});
1045 avg += queue.back();
1046 }
1047 }
1048 assert(static_cast<int>(queue.size()) == std::min(depth, res->w));
1049 queue.clear();
1050 }
1051
1052 // Iterate over columns, blurring each column vertically
1053 for(x = 0; x < res->w; ++x) {
1054 // Sum of pixel values stored here
1055 Average avg;
1056
1057 // Preload the first depth+1 pixels
1058 uint32_t* p = lock.pixels() + x;
1059 for(y = 0; y <= depth && y < res->h; ++y, p += res->w) {
1060 assert(!queue.full());
1061 queue.push_back(Pixel{p});
1062 avg += queue.back();
1063 }
1064
1065 // This is the actual inner loop
1066 p = lock.pixels() + x;
1067 for(y = 0; y < res->h; ++y, p += res->w) {
1068 // Write the current average
1069 const uint32_t num = queue.size();
1070 *p = avg(num);
1071
1072 // Unload earlier pixels that are now too far away
1073 if(y >= depth) {
1074 avg -= queue.front();
1075 assert(!queue.empty());
1076 queue.pop_front();
1077 }
1078
1079 // Add new pixels
1080 if(y + depth+1 < res->h) {
1081 uint32_t* q = p + (depth+1)*res->w;
1082 assert(!queue.full());
1083 queue.push_back(Pixel{q});
1084 avg += queue.back();
1085 }
1086 }
1087 assert(static_cast<int>(queue.size()) == std::min(depth, res->h));
1088 queue.clear();
1089 }
1090}
1091
1093{
1094 if(surf == nullptr)
1095 return nullptr;
1096
1097 surface res(r.w, r.h);
1098
1099 if(res == nullptr) {
1100 PLAIN_LOG << "Could not create a new surface in cut_surface()";
1101 return nullptr;
1102 }
1103
1104 const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(surf->format);
1105
1106 std::size_t sbpp = details->bytes_per_pixel;
1107 std::size_t spitch = surf->pitch;
1108 std::size_t rbpp = details->bytes_per_pixel;
1109 std::size_t rpitch = res->pitch;
1110
1111 // compute the areas to copy
1112 rect src_rect = r;
1113 rect dst_rect { 0, 0, r.w, r.h };
1114
1115 if (src_rect.x < 0) {
1116 if (src_rect.x + src_rect.w <= 0)
1117 return res;
1118 dst_rect.x -= src_rect.x;
1119 dst_rect.w += src_rect.x;
1120 src_rect.w += src_rect.x;
1121 src_rect.x = 0;
1122 }
1123 if (src_rect.y < 0) {
1124 if (src_rect.y + src_rect.h <= 0)
1125 return res;
1126 dst_rect.y -= src_rect.y;
1127 dst_rect.h += src_rect.y;
1128 src_rect.h += src_rect.y;
1129 src_rect.y = 0;
1130 }
1131
1132 if(src_rect.x >= surf->w || src_rect.y >= surf->h)
1133 return res;
1134
1135 const_surface_lock slock(surf);
1136 surface_lock rlock(res);
1137
1138 const uint8_t* src = reinterpret_cast<const uint8_t *>(slock.pixels());
1139 uint8_t* dest = reinterpret_cast<uint8_t *>(rlock.pixels());
1140
1141 for(int y = 0; y < src_rect.h && (src_rect.y + y) < surf->h; ++y) {
1142 const uint8_t* line_src = src + (src_rect.y + y) * spitch + src_rect.x * sbpp;
1143 uint8_t* line_dest = dest + (dst_rect.y + y) * rpitch + dst_rect.x * rbpp;
1144 std::size_t size = src_rect.w + src_rect.x <= surf->w ? src_rect.w : surf->w - src_rect.x;
1145
1146 assert(rpitch >= src_rect.w * rbpp);
1147 memcpy(line_dest, line_src, size * rbpp);
1148 }
1149
1150 return res;
1151}
1152
1153void blend_surface(surface& nsurf, const double amount, const color_t color)
1154{
1155 if(nsurf) {
1156 surface_lock lock(nsurf);
1157
1158 uint16_t ratio = amount * 256;
1159 const uint16_t red = ratio * color.r;
1160 const uint16_t green = ratio * color.g;
1161 const uint16_t blue = ratio * color.b;
1162 ratio = 256 - ratio;
1163
1164 for(auto& pixel : lock.pixel_span()) {
1165 auto [r, g, b, a] = color_t::from_argb_bytes(pixel);
1166
1167 r = (ratio * r + red) >> 8;
1168 g = (ratio * g + green) >> 8;
1169 b = (ratio * b + blue) >> 8;
1170
1171 pixel = (a << 24) | (r << 16) | (g << 8) | b;
1172 }
1173 }
1174}
1175
1176/* Simplified RotSprite algorithm.
1177 * http://en.wikipedia.org/wiki/Image_scaling#RotSprite
1178 * Lifted from: http://github.com/salmonmoose/SpriteRotator
1179 * 1) Zoom the source image by a certain factor.
1180 * 2) Scan the zoomed source image at every step=offset and put it in the result. */
1181surface rotate_any_surface(const surface& surf, float angle, int zoom, int offset)
1182{
1183 int src_w, src_h, dst_w, dst_h;
1184 float min_x, min_y, sine, cosine;
1185 {
1186 float max_x, max_y;
1187 // convert angle to radiant (angle * 2 * PI) / 360
1188 const float radians = angle * boost::math::constants::pi<float>() / 180;
1189 cosine = std::cos(radians);
1190 sine = std::sin(radians);
1191 // calculate the size of the dst image
1192 src_w = surf->w * zoom;
1193 src_h = surf->h * zoom;
1194 /* See http://en.wikipedia.org/wiki/Rotation_(mathematics) */
1195 const float point_1x = src_h * -sine;
1196 const float point_1y = src_h * cosine;
1197 const float point_2x = src_w * cosine - src_h * sine;
1198 const float point_2y = src_h * cosine + src_w * sine;
1199 const float point_3x = src_w * cosine;
1200 const float point_3y = src_w * sine;
1201 /* After the rotation, the new image has different dimensions.
1202 * E.g.: The maximum height equals the former diagonal in case the angle is 45, 135, 225 or 315 degree.
1203 * See http://en.wikipedia.org/wiki/File:Rotation_illustration2.svg to get the idea. */
1204 min_x = std::min(0.0F, std::min(point_1x, std::min(point_2x, point_3x)));
1205 min_y = std::min(0.0F, std::min(point_1y, std::min(point_2y, point_3y)));
1206 max_x = (angle > 90 && angle < 180) ? 0 : std::max(point_1x, std::max(point_2x, point_3x));
1207 max_y = (angle > 180 && angle < 270) ? 0 : std::max(point_1y, std::max(point_2y, point_3y));
1208 dst_w = static_cast<int>(ceil(std::abs(max_x) - min_x)) / zoom;
1209 dst_h = static_cast<int>(ceil(std::abs(max_y) - min_y)) / zoom;
1210 }
1211 surface dst(dst_w, dst_h);
1212 {
1213 surface_lock dst_lock(dst);
1214 uint32_t* const dst_pixels = dst_lock.pixels();
1215
1216 const surface src = scale_surface(surf, src_w, src_h);
1217 const_surface_lock src_lock(src);
1218 const uint32_t* const src_pixels = src_lock.pixels();
1219
1220 const float scale = 1.f / zoom;
1221 const int max_x = dst_w * zoom;
1222 const int max_y = dst_h * zoom;
1223 /* Loop through the zoomed src image,
1224 * take every pixel in steps with offset distance and place it in the dst image. */
1225 for (int x = 0; x < max_x; x += offset)
1226 for (int y = 0; y < max_y; y += offset) {
1227 // calculate the src pixel that fits in the dst
1228 const float source_x = (x + min_x)*cosine + (y + min_y)*sine;
1229 const float source_y = (y + min_y)*cosine - (x + min_x)*sine;
1230 // if the pixel exists on the src surface
1231 if (source_x >= 0 && source_x < src_w && source_y >= 0 && source_y < src_h) {
1232 // get it from the src surface and place it on the dst surface
1233 dst_pixels[int((y * scale)) * dst->w + int((x * scale))] =
1234 src_pixels[int(source_y) * src->w + int(source_x)];
1235 }
1236 }
1237 }
1238
1239 return dst;
1240}
1241
1242// Rotates a surface 180 degrees.
1244{
1245 if ( surf == nullptr )
1246 return nullptr;
1247
1248 surface nsurf = surf.clone();
1249
1250 if ( nsurf == nullptr ) {
1251 PLAIN_LOG << "could not make neutral surface...";
1252 return nullptr;
1253 }
1254
1255 {// Code block to limit the scope of the surface lock.
1256 surface_lock lock(nsurf);
1257 uint32_t* const pixels = lock.pixels();
1258
1259 // Swap pixels in the upper half of the image with
1260 // those in the lower half.
1261 for (int y=0; y != nsurf->h/2; ++y) {
1262 for(int x=0; x != nsurf->w; ++x) {
1263 const int index1 = y*nsurf->w + x;
1264 const int index2 = (nsurf->h-y)*nsurf->w - x - 1;
1265 std::swap(pixels[index1],pixels[index2]);
1266 }
1267 }
1268
1269 if ( is_odd(nsurf->h) ) {
1270 // The middle row still needs to be processed.
1271 for (int x=0; x != nsurf->w/2; ++x) {
1272 const int index1 = (nsurf->h/2)*nsurf->w + x;
1273 const int index2 = (nsurf->h/2)*nsurf->w + (nsurf->w - x - 1);
1274 std::swap(pixels[index1],pixels[index2]);
1275 }
1276 }
1277 }
1278
1279 return nsurf;
1280}
1281
1282// Rotates a surface 90 degrees, either clockwise or counter-clockwise.
1283surface rotate_90_surface(const surface& surf, bool clockwise)
1284{
1285 if(surf == nullptr)
1286 return surf;
1287
1288 surface dst(surf->h, surf->w); // Flipped dimensions.
1289
1290 if ( surf == nullptr || dst == nullptr ) {
1291 PLAIN_LOG << "could not make neutral surface...";
1292 return nullptr;
1293 }
1294
1295 {
1296 const_surface_lock src_lock(surf);
1297 surface_lock dst_lock(dst);
1298
1299 const uint32_t* const src_pixels = src_lock.pixels();
1300 uint32_t* const dst_pixels = dst_lock.pixels();
1301
1302 // Copy the pixels.
1303 for(int y = 0; y != surf->h; ++y) {
1304 for ( int x = 0; x != surf->w; ++x ) {
1305 const int src_index = y*surf->w + x;
1306 const int dst_index = clockwise ?
1307 x*dst->w + (dst->w-1-y) :
1308 (dst->h-1-x)*dst->w + y;
1309 dst_pixels[dst_index] = src_pixels[src_index];
1310 }
1311 }
1312 }
1313
1314 return dst;
1315}
1316
1318{
1319 if(nsurf) {
1320
1321 // Attempt SIMD
1322 std::size_t simd_x_processed = simd::is_enabled() ? flip_image_simd(nsurf) : 0;
1323
1324 // SCALAR fallback: process remaining columns (either all if SIMD unavailable, or just the remainder)
1325 surface_lock lock(nsurf);
1326 uint32_t* const pixels = lock.pixels();
1327 const std::size_t width = static_cast<std::size_t>(nsurf->w);
1328 const std::size_t height = static_cast<std::size_t>(nsurf->h);
1329 const std::size_t half_width = width / 2;
1330 for(std::size_t y = 0; y < height; ++y) {
1331 for(std::size_t x = simd_x_processed; x < half_width; ++x) {
1332 const std::size_t index1 = y * width + x;
1333 const std::size_t index2 = y * width + (width - x - 1);
1334 std::swap(pixels[index1], pixels[index2]);
1335 }
1336 }
1337 }
1338}
1339
1341{
1342 if(nsurf) {
1343 surface_lock lock(nsurf);
1344 uint32_t* const pixels = lock.pixels();
1345
1346 for(int x = 0; x != nsurf->w; ++x) {
1347 for(int y = 0; y != nsurf->h/2; ++y) {
1348 const int index1 = y*nsurf->w + x;
1349 const int index2 = (nsurf->h-y-1)*nsurf->w + x;
1350 std::swap(pixels[index1],pixels[index2]);
1351 }
1352 }
1353 }
1354}
1355
1357{
1358 if (src == nullptr) {
1359 return nullptr;
1360 }
1361
1362 // Check if there is something in the portion
1363 if(area.x >= src->w || area.y >= src->h || area.x + area.w < 0 || area.y + area.h < 0) {
1364 return nullptr;
1365 }
1366
1367 if(area.x + area.w > src->w) {
1368 area.w = src->w - area.x;
1369 }
1370 if(area.y + area.h > src->h) {
1371 area.h = src->h - area.y;
1372 }
1373
1374 // use same format as the source (almost always the screen)
1375 surface dst(area.w, area.h);
1376
1377 if(dst == nullptr) {
1378 PLAIN_LOG << "Could not create a new surface in get_surface_portion()";
1379 return nullptr;
1380 }
1381
1382 // Blit to dst with BLENDMODE_NONE, then reset src blend mode.
1383 SDL_BlendMode src_blend;
1384 SDL_GetSurfaceBlendMode(src, &src_blend);
1385 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
1386 SDL_BlitSurface(src, &area, dst, nullptr);
1387 SDL_SetSurfaceBlendMode(src, src_blend);
1388
1389 return dst;
1390}
1391
1393{
1394 if(surf == nullptr) return;
1395
1396 // Convert float opacity to integer modifier (0-255)
1397 uint8_t alpha_mod = float_to_color(opacity);
1398 if(alpha_mod == 255) return;
1399
1400 // Attempt SIMD
1401 std::size_t simd_processed = simd::is_enabled() ? apply_surface_opacity_simd(surf, alpha_mod) : 0;
1402
1403 // SCALAR fallback: process remaining pixels (either all pixels if SIMD unavailable, or just the remainder)
1404 surface_lock lock(surf);
1405 utils::span pixel_span = lock.pixel_span();
1406 for(auto& pixel : pixel_span.subspan(simd_processed)) {
1407 uint8_t alpha = pixel >> 24;
1408 if(alpha) {
1409 auto [r, g, b, old_alpha] = color_t::from_argb_bytes(pixel);
1410 alpha = color_multiply(old_alpha, alpha_mod);
1411 pixel = (alpha << 24) | (r << 16) | (g << 8) | b;
1412 }
1413 }
1414}
1415
1416namespace
1417{
1418template<typename Range>
1419bool contains_non_transparent_pixel(const Range& span)
1420{
1421 return std::any_of(span.begin(), span.end(),
1422 [](uint32_t pixel) { return (pixel & SDL_ALPHA_MASK) != 0; });
1423}
1424
1425/**
1426 * Calculates the inclusive distance between two array indices.
1427 *
1428 * For example, two adjacent columns of pixels should have a
1429 * distance of two even though their indices are one apart.
1430 *
1431 * @pre @a i2 > @a i1
1432 */
1433auto cover_distance(int i1, int i2)
1434{
1435 return (i2 - i1) + 1;
1436}
1437
1438} // namespace
1439
1441{
1442 auto lock = const_surface_lock{surf};
1443 utils::span pixels = lock.pixel_span();
1444
1445 const auto row_is_not_transparent = [&](std::size_t y) {
1446 utils::span row_span = pixels.subspan(y * surf->w, surf->w);
1447 return contains_non_transparent_pixel(row_span);
1448 };
1449
1450 const auto column_is_not_transparent = [&](std::size_t x) {
1451 // Striding ahead by width yields all pixels in the x'th column.
1452 utils::span offset = pixels.subspan(x);
1453 auto column_span = offset | utils::views::stride(surf->w);
1454 return contains_non_transparent_pixel(column_span);
1455 };
1456
1457 rect res;
1458
1459 // Find the first non-transparent row from the top.
1460 for(int y = 0; y < surf->h; ++y) {
1461 if(row_is_not_transparent(y)) {
1462 res.y = y;
1463 break;
1464 }
1465 }
1466
1467 // Find the first non-transparent row from the bottom.
1468 for(int y = surf->h - 1; y >= res.y; --y) {
1469 if(row_is_not_transparent(y)) {
1470 res.h = cover_distance(res.y, y);
1471 break;
1472 }
1473 }
1474
1475 // Discard fully transparent top and bottom rows.
1476 pixels = pixels.subspan(
1477 static_cast<std::size_t>(res.y) * surf->w,
1478 static_cast<std::size_t>(res.h) * surf->w);
1479
1480 // Find the first non-transparent column from the left.
1481 for(int x = 0; x < surf->w; ++x) {
1482 if(column_is_not_transparent(x)) {
1483 res.x = x;
1484 break;
1485 }
1486 }
1487
1488 // Find the first non-transparent column from the right.
1489 for(int x = surf->w - 1; x >= res.x; --x) {
1490 if(column_is_not_transparent(x)) {
1491 res.w = cover_distance(res.x, x);
1492 break;
1493 }
1494 }
1495
1496 return res;
1497}
map_location loc
Definition move.cpp:172
constexpr std::enable_if< C==dynamic_extent, span< T, detail::span_sub< E, O >::value > >::type subspan() const
Definition span.hpp:328
constexpr size_type size() const noexcept
Definition span.hpp:355
Helper class for pinning SDL surfaces into memory.
Definition surface.hpp:98
pixel_t * pixels() const
Definition surface.hpp:117
utils::span< pixel_t > pixel_span() const
Definition surface.hpp:122
point size() const
Dimensions of the surface.
Definition surface.cpp:106
surface clone() const
Creates a new, duplicate surface in memory using the 'neutral' pixel format.
Definition surface.cpp:97
std::size_t area() const
Total area of the surface in square pixels.
Definition surface.cpp:115
Represents version numbers.
constexpr uint8_t ALPHA_OPAQUE
Definition color.hpp:37
constexpr uint32_t SDL_ALPHA_MASK
Definition color.hpp:27
constexpr uint8_t color_multiply(uint8_t n1, uint8_t n2)
Multiply two 8-bit colour values as if in the range [0.0,1.0].
Definition color.hpp:290
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition color.hpp:274
std::unordered_map< color_t, color_t > color_mapping
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
constexpr int fixed_point_to_int(int32_t n)
If positive, just bit shift.
Definition math.hpp:217
constexpr bool is_odd(T num)
Definition math.hpp:34
constexpr unsigned fixed_point_multiply(int32_t n1, int32_t n2)
Definition math.hpp:195
constexpr int32_t fixed_point_divide(int n1, int n2)
Definition math.hpp:205
Definition pump.hpp:41
version_info get_version()
Returns the runtime SDL version.
Definition utils.cpp:42
bool runtime_at_least(uint8_t major, uint8_t minor=0, uint8_t patch=0)
Returns true if the runtime SDL version is at or greater than the specified version,...
Definition utils.cpp:48
bool is_enabled()
Checks if SIMD hardware acceleration is allowed/enabeled.
constexpr auto stride
Definition ranges.hpp:57
void scale(size_t factor, const uint32_t *src, uint32_t *trg, int srcWidth, int srcHeight, ColorFormat colFmt, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Definition xbrz.cpp:1175
const int SCALE_FACTOR_MAX
Definition xbrz.hpp:50
int w
Definition pathfind.cpp:188
Contains the SDL_Rect helper code.
rect dst
Location on the final composed sheet.
surface surf
Image.
rect src
Non-transparent portion of the surface to compose.
std::string filename
Filename.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition color.hpp:102
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
mock_party p
static map_location::direction n
static map_location::direction s
surface get_surface_portion(const surface &src, rect &area)
Get a portion of the screen.
Definition utils.cpp:1356
void alpha_to_greyscale(surface &nsurf)
Definition utils.cpp:504
void recolor_image(surface &nsurf, const color_mapping &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition utils.cpp:636
surface scale_surface_legacy(const surface &surf, int w, int h)
Scale a surface using simple bilinear filtering (discarding rgb from source pixels with 0 alpha)
Definition utils.cpp:225
void blur_surface(surface &surf, rect rect, int depth)
Cross-fades a surface in place.
Definition utils.cpp:838
void wipe_alpha(surface &nsurf)
Definition utils.cpp:517
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition utils.cpp:1092
void brighten_image(surface &nsurf, int32_t amount)
Definition utils.cpp:667
void adjust_surface_alpha(surface &surf, uint8_t alpha_mod)
Definition utils.cpp:685
void sepia_image(surface &nsurf)
Definition utils.cpp:464
void adjust_surface_alpha_add(surface &nsurf, int amount)
Definition utils.cpp:694
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition utils.cpp:1153
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition utils.cpp:554
rect get_non_transparent_portion(const surface &surf)
Definition utils.cpp:1440
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition utils.cpp:405
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition utils.cpp:483
void shadow_image(surface &surf, int scale)
create an heavy shadow of the image, by blurring, increasing alpha and darkening
Definition utils.cpp:529
static lg::log_domain log_display("display")
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition utils.cpp:790
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition utils.cpp:61
bool in_mask_surface(const surface &nsurf, const surface &nmask)
Checks if the source surface is entirely covered by the visible area of the mask.
Definition utils.cpp:756
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition utils.cpp:358
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition utils.cpp:954
void greyscale_image(surface &nsurf)
Definition utils.cpp:424
void apply_surface_opacity(surface &surf, float opacity)
Applies an opacity modification to a surface.
Definition utils.cpp:1392
void flop_surface(surface &nsurf)
Definition utils.cpp:1340
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition utils.cpp:1283
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition utils.cpp:1243
void flip_surface(surface &nsurf)
Definition utils.cpp:1317
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition utils.cpp:99
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition utils.cpp:1181
void monochrome_image(surface &nsurf, const int threshold)
Definition utils.cpp:446
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask to the source surface by calculating the minimum alpha channel value for every corresp...
Definition utils.cpp:708
channel
Definition utils.hpp:103
@ BLUE
Definition utils.hpp:103
@ ALPHA
Definition utils.hpp:103
@ GREEN
Definition utils.hpp:103
@ RED
Definition utils.hpp:103
std::size_t flip_image_simd(surface &surf)
Flips each row of pixels (horizontal mirror) using SIMD.
std::size_t adjust_surface_color_simd(surface &surf, const int r, const int g, const int b)
Adjusts the color channels of a surface using saturated SIMD arithmetic.
std::size_t apply_surface_opacity_simd(surface &surf, const uint8_t alpha_mod)
Applies an alpha modification to the whole surface using SIMD.
std::size_t mask_surface_simd(surface &surf, const surface &mask, bool &empty)
Modifies the alpha channel of the surface pixels based on the mask.
SIMD-accelerated helper functions for image manipulation.
#define i
#define g
#define e
#define h
#define b