The Battle for Wesnoth  1.19.25+dev
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 
39 static 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 
48 bool 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 
61 surface 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.
99 surface scale_surface(const surface &surf, int w, int h)
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 
405 void 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);
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 
446 void 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 
464 void sepia_image(surface& nsurf)
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 
483 void 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 
517 void 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 
530 {
531  if(surf == nullptr)
532  return;
533 
534  // we blur it, and reuse the neutral surface created by the blur function
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 
636 void 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 
667 void 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 
685 void 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 
694 void 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 
708 bool 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 
756 bool 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 
790 void 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 
838 void blur_surface(surface& surf, rect rect, int depth)
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 
954 void 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 
1153 void 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. */
1181 surface 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.
1283 surface 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 
1317 void flip_surface(surface& nsurf)
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 
1340 void flop_surface(surface& nsurf)
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 
1392 void apply_surface_opacity(surface& surf, float opacity)
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);
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 
1416 namespace
1417 {
1418 template<typename Range>
1419 bool 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  */
1433 auto 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
double g
Definition: astarsearch.cpp:63
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
Definition: color_range.hpp:24
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1287
std::size_t i
Definition: function.cpp:1031
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.
Definition: utils_simd.hpp:38
auto pixel_span(const surface &surf)
Returns a read-only view over to surf's underlying pixel array.
Definition: surface.hpp:74
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
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.
Definition: utils_simd.cpp:354
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.
Definition: utils_simd.cpp:337
std::size_t apply_surface_opacity_simd(surface &surf, const uint8_t alpha_mod)
Applies an alpha modification to the whole surface using SIMD.
Definition: utils_simd.cpp:320
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.
Definition: utils_simd.cpp:302
SIMD-accelerated helper functions for image manipulation.
#define e
#define h
#define b