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