The Battle for Wesnoth  1.19.19+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 <SDL2/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  SDL_version sdl_version;
44  SDL_GetVersion(&sdl_version);
45  return version_info(sdl_version.major, sdl_version.minor, sdl_version.patch);
46 }
47 
48 bool sdl::runtime_at_least(uint8_t major, uint8_t minor, uint8_t patch)
49 {
50  SDL_version ver;
51  SDL_GetVersion(&ver);
52  if(ver.major < major) return false;
53  if(ver.major > major) return true;
54  // major version equal
55  if(ver.minor < minor) return false;
56  if(ver.minor > minor) return true;
57  // major and minor version equal
58  if(ver.patch < patch) return false;
59  return true;
60 }
61 
62 surface scale_surface_xbrz(const surface & surf, std::size_t z)
63 {
64  if(surf == nullptr)
65  return nullptr;
66 
67  if (z > xbrz::SCALE_FACTOR_MAX) {
68  PLAIN_LOG << "Cannot use xbrz scaling with zoom factor > " << xbrz::SCALE_FACTOR_MAX;
69  z = 1;
70  }
71 
72  if (z == 1) {
73  surface temp = surf; // TODO: no temp surface
74  return temp;
75  }
76 
77  surface dst(surf->w *z, surf->h * z);
78 
79  if (z == 0) {
80  PLAIN_LOG << "Create an empty image";
81  return dst;
82  }
83 
84  if(surf == nullptr || dst == nullptr) {
85  PLAIN_LOG << "Could not create surface to scale onto";
86  return nullptr;
87  }
88 
89  {
90  const_surface_lock src_lock(surf);
91  surface_lock dst_lock(dst);
92 
93  xbrz::scale(z, src_lock.pixels(), dst_lock.pixels(), surf->w, surf->h, xbrz::ColorFormat::ARGB);
94  }
95 
96  return dst;
97 }
98 
99 // NOTE: Don't pass this function 0 scaling arguments.
100 surface scale_surface(const surface &surf, int w, int h)
101 {
102  if(surf == nullptr)
103  return nullptr;
104 
105  if(w == surf->w && h == surf->h) {
106  return surf;
107  }
108  assert(w >= 0);
109  assert(h >= 0);
110 
111  surface dst(w,h);
112 
113  if (w == 0 || h ==0) {
114  PLAIN_LOG << "Create an empty image";
115  return dst;
116  }
117 
118  if(surf == nullptr || dst == nullptr) {
119  PLAIN_LOG << "Could not create surface to scale onto";
120  return nullptr;
121  }
122 
123  {
124  const_surface_lock src_lock(surf);
125  surface_lock dst_lock(dst);
126 
127  const uint32_t* const src_pixels = src_lock.pixels();
128  uint32_t* const dst_pixels = dst_lock.pixels();
129 
130  int32_t xratio = fixed_point_divide(surf->w,w);
131  int32_t yratio = fixed_point_divide(surf->h,h);
132 
133  int32_t ysrc = 0;
134  for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
135  int32_t xsrc = 0;
136  for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
137  const int xsrcint = fixed_point_to_int(xsrc);
138  const int ysrcint = fixed_point_to_int(ysrc);
139 
140  const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
141  uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
142  const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
143  const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
144 
145  uint8_t r,g,b,a;
146  uint32_t rr,gg,bb,aa, temp;
147 
148  uint32_t pix[4], bilin[4];
149 
150  // This next part is the fixed point
151  // equivalent of "take everything to
152  // the right of the decimal point."
153  // These fundamental weights decide
154  // the contributions from various
155  // input pixels. The labels assume
156  // that the upper left corner of the
157  // screen ("northeast") is 0,0 but the
158  // code should still be consistent if
159  // the graphics origin is actually
160  // somewhere else.
161  //
162  // That is, the bilin array holds the
163  // "geometric" weights. I.E. If I'm scaling
164  // a 2 x 2 block a 10 x 10 block, then for
165  // pixel (2,2) of output, the upper left
166  // pixel should be 10:1 more influential than
167  // the upper right, and also 10:1 more influential
168  // than lower left, and 100:1 more influential
169  // than lower right.
170 
171  const int32_t e = 0x000000FF & xsrc;
172  const int32_t s = 0x000000FF & ysrc;
173  const int32_t n = 0xFF - s;
174  // Not called "w" to avoid hiding a function parameter
175  // (would cause a compiler warning in MSVC2015 with /W4)
176  const int32_t we = 0xFF - e;
177 
178  pix[0] = *src_word; // northwest
179  pix[1] = *(src_word + dx); // northeast
180  pix[2] = *(src_word + dy); // southwest
181  pix[3] = *(src_word + dx + dy); // southeast
182 
183  bilin[0] = n*we;
184  bilin[1] = n*e;
185  bilin[2] = s*we;
186  bilin[3] = s*e;
187 
188  int loc;
189  rr = bb = gg = aa = 0;
190  for (loc=0; loc<4; loc++) {
191  a = pix[loc] >> 24;
192  r = pix[loc] >> 16;
193  g = pix[loc] >> 8;
194  b = pix[loc] >> 0;
195 
196  //We also have to implement weighting by alpha for the RGB components
197  //If a unit has some parts solid and some parts translucent,
198  //i.e. a red cloak but a dark shadow, then when we scale in
199  //the shadow shouldn't appear to become red at the edges.
200  //This part also smoothly interpolates between alpha=0 being
201  //transparent and having no contribution, vs being opaque.
202  temp = (a * bilin[loc]);
203  rr += r * temp;
204  gg += g * temp;
205  bb += b * temp;
206  aa += temp;
207  }
208 
209  a = aa >> (16); // we average the alphas, they don't get weighted by any other factor besides bilin
210  if (a != 0) {
211  rr /= a; // finish alpha weighting: divide by sum of alphas
212  gg /= a;
213  bb /= a;
214  }
215  r = rr >> (16); // now shift over by 16 for the bilin part
216  g = gg >> (16);
217  b = bb >> (16);
218  *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
219  }
220  }
221  }
222 
223  return dst;
224 }
225 
227 {
228  if(surf == nullptr)
229  return nullptr;
230 
231  if(w == surf->w && h == surf->h) {
232  return surf;
233  }
234  assert(w >= 0);
235  assert(h >= 0);
236 
237  surface dst(w,h);
238 
239  if(surf == nullptr || dst == nullptr) {
240  PLAIN_LOG << "Could not create surface to scale onto";
241  return nullptr;
242  }
243 
244  {
245  const_surface_lock src_lock(surf);
246  surface_lock dst_lock(dst);
247 
248  const uint32_t* const src_pixels = src_lock.pixels();
249  uint32_t* const dst_pixels = dst_lock.pixels();
250 
251  int32_t xratio = fixed_point_divide(surf->w,w);
252  int32_t yratio = fixed_point_divide(surf->h,h);
253 
254  int32_t ysrc = 0;
255  for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
256  int32_t xsrc = 0;
257  for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
258  const int xsrcint = fixed_point_to_int(xsrc);
259  const int ysrcint = fixed_point_to_int(ysrc);
260 
261  const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
262  uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
263  const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
264  const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
265 
266  uint8_t r,g,b,a;
267  uint32_t rr,gg,bb,aa;
268  uint16_t avg_r, avg_g, avg_b;
269  uint32_t pix[4], bilin[4];
270 
271  // This next part is the fixed point
272  // equivalent of "take everything to
273  // the right of the decimal point."
274  // These fundamental weights decide
275  // the contributions from various
276  // input pixels. The labels assume
277  // that the upper left corner of the
278  // screen ("northeast") is 0,0 but the
279  // code should still be consistent if
280  // the graphics origin is actually
281  // somewhere else.
282 
283  const int32_t east = 0x000000FF & xsrc;
284  const int32_t south = 0x000000FF & ysrc;
285  const int32_t north = 0xFF - south;
286  const int32_t west = 0xFF - east;
287 
288  pix[0] = *src_word; // northwest
289  pix[1] = *(src_word + dx); // northeast
290  pix[2] = *(src_word + dy); // southwest
291  pix[3] = *(src_word + dx + dy); // southeast
292 
293  bilin[0] = north*west;
294  bilin[1] = north*east;
295  bilin[2] = south*west;
296  bilin[3] = south*east;
297 
298  // Scope out the neighboorhood, see
299  // what the pixel values are like.
300 
301  int count = 0;
302  avg_r = avg_g = avg_b = 0;
303  int loc;
304  for (loc=0; loc<4; loc++) {
305  a = pix[loc] >> 24;
306  r = pix[loc] >> 16;
307  g = pix[loc] >> 8;
308  b = pix[loc] >> 0;
309  if (a != 0) {
310  avg_r += r;
311  avg_g += g;
312  avg_b += b;
313  count++;
314  }
315  }
316  if (count>0) {
317  avg_r /= count;
318  avg_b /= count;
319  avg_g /= count;
320  }
321 
322  // Perform modified bilinear interpolation.
323  // Don't trust any color information from
324  // an RGBA sample when the alpha channel
325  // is set to fully transparent.
326  //
327  // Some of the input images are hex tiles,
328  // created using a hexagon shaped alpha channel
329  // that is either set to full-on or full-off.
330 
331  rr = gg = bb = aa = 0;
332  for (loc=0; loc<4; loc++) {
333  a = pix[loc] >> 24;
334  r = pix[loc] >> 16;
335  g = pix[loc] >> 8;
336  b = pix[loc] >> 0;
337  if (a == 0) {
338  r = static_cast<uint8_t>(avg_r);
339  g = static_cast<uint8_t>(avg_g);
340  b = static_cast<uint8_t>(avg_b);
341  }
342  rr += r * bilin[loc];
343  gg += g * bilin[loc];
344  bb += b * bilin[loc];
345  aa += a * bilin[loc];
346  }
347  r = rr >> 16;
348  g = gg >> 16;
349  b = bb >> 16;
350  a = aa >> 16;
351  *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
352  }
353  }
354  }
355 
356  return dst;
357 }
358 
360 {
361  if(surf == nullptr) {
362  return nullptr;
363  }
364  if(w == surf->w && h == surf->h) {
365  return surf;
366  }
367 
368  assert(w >= 0);
369  assert(h >= 0);
370  surface dst(w, h);
371  if(dst == nullptr) {
372  PLAIN_LOG << "Could not create surface to scale onto";
373  return nullptr;
374  }
375 
376  if(w == 0 || h == 0) {
377  PLAIN_LOG << "Creating an empty image";
378  return dst;
379  }
380 
381  {
382  const_surface_lock src_lock(surf);
383  surface_lock dst_lock(dst);
384 
385  const uint32_t* const src_pixels = src_lock.pixels();
386  uint32_t* const dst_pixels = dst_lock.pixels();
387 
388  const int src_w = surf->w;
389  const int src_h = surf->h;
390 
391  const float xratio = static_cast<float>(src_w) / static_cast<float>(w);
392  const float yratio = static_cast<float>(src_h) / static_cast<float>(h);
393  for(int ydst = 0; ydst != h; ++ydst) {
394  for(int xdst = 0; xdst != w; ++xdst) {
395  // Project dst pixel to a single corresponding src pixel by scale and simply take it
396  const int xsrc = std::floor(static_cast<float>(xdst) * xratio);
397  const int ysrc = std::floor(static_cast<float>(ydst) * yratio);
398  dst_pixels[ydst * dst->w + xdst] = src_pixels[ysrc * src_w + xsrc];
399  }
400  }
401  }
402 
403  return dst;
404 }
405 
406 void adjust_surface_color(surface& nsurf, int red, int green, int blue)
407 {
408  if(nsurf && (red != 0 || green != 0 || blue != 0)) {
409  surface_lock lock(nsurf);
410 
411  for(auto& pixel : lock.pixel_span()) {
412  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
413 
414  r = std::clamp(static_cast<int>(r) + red, 0, 255);
415  g = std::clamp(static_cast<int>(g) + green, 0, 255);
416  b = std::clamp(static_cast<int>(b) + blue, 0, 255);
417 
418  pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
419  }
420  }
421 }
422 
424 {
425  if(nsurf) {
426  surface_lock lock(nsurf);
427 
428  for(auto& pixel : lock.pixel_span()) {
429  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
430 
431  // Use the correct formula for RGB to grayscale conversion.
432  // Ok, this is no big deal :)
433  // The correct formula being:
434  // gray=0.299red+0.587green+0.114blue
435  const uint8_t avg = static_cast<uint8_t>((
436  77 * static_cast<uint16_t>(r) +
437  150 * static_cast<uint16_t>(g) +
438  29 * static_cast<uint16_t>(b) ) / 256);
439 
440  pixel = (alpha << 24) | (avg << 16) | (avg << 8) | avg;
441  }
442  }
443 }
444 
445 void monochrome_image(surface& nsurf, const int threshold)
446 {
447  if(nsurf) {
448  surface_lock lock(nsurf);
449 
450  for(auto& pixel : lock.pixel_span()) {
451  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
452 
453  // first convert the pixel to grayscale
454  // if the resulting value is above the threshold make it black
455  // else make it white
456  uint8_t result = static_cast<uint8_t>(0.299 * r + 0.587 * g + 0.114 * b) > threshold ? 255 : 0;
457 
458  pixel = (alpha << 24) | (result << 16) | (result << 8) | result;
459  }
460  }
461 }
462 
463 void sepia_image(surface& nsurf)
464 {
465  if(nsurf) {
466  surface_lock lock(nsurf);
467 
468  for(auto& pixel : lock.pixel_span()) {
469  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
470 
471  // this is the formula for applying a sepia effect
472  // that can be found on various web sites
473  uint8_t outR = std::min(255, static_cast<int>((r * 0.393) + (g * 0.769) + (b * 0.189)));
474  uint8_t outG = std::min(255, static_cast<int>((r * 0.349) + (g * 0.686) + (b * 0.168)));
475  uint8_t outB = std::min(255, static_cast<int>((r * 0.272) + (g * 0.534) + (b * 0.131)));
476 
477  pixel = (alpha << 24) | (outR << 16) | (outG << 8) | (outB);
478  }
479  }
480 }
481 
482 void negative_image(surface& nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
483 {
484  if(nsurf) {
485  surface_lock lock(nsurf);
486 
487  for(auto& pixel : lock.pixel_span()) {
488  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
489 
490  // invert he channel only if its value is greater than the supplied threshold
491  // this can be used for solarization effects
492  // for a full negative effect, use a value of -1
493  // 255 is a no-op value (doesn't do anything, since a uint8_t cannot contain a greater value than that)
494  uint8_t newR = r > thresholdR ? 255 - r : r;
495  uint8_t newG = g > thresholdG ? 255 - g : g;
496  uint8_t newB = b > thresholdB ? 255 - b : b;
497 
498  pixel = (alpha << 24) | (newR << 16) | (newG << 8) | (newB);
499  }
500  }
501 }
502 
504 {
505  if(nsurf) {
506  surface_lock lock(nsurf);
507 
508  for(auto& pixel : lock.pixel_span()) {
509  uint8_t alpha = pixel >> 24;
510 
511  pixel = (0xff << 24) | (alpha << 16) | (alpha << 8) | alpha;
512  }
513  }
514 }
515 
516 void wipe_alpha(surface& nsurf)
517 {
518  if(nsurf) {
519  surface_lock lock(nsurf);
520 
521  for(auto& pixel : lock.pixel_span()) {
522  pixel = 0xff000000 | pixel;
523  }
524  }
525 }
526 
527 
529 {
530  if(surf == nullptr)
531  return;
532 
533  // we blur it, and reuse the neutral surface created by the blur function
535 
536  {
537  surface_lock lock(surf);
538 
539  for(auto& pixel : lock.pixel_span()) {
540  uint8_t alpha = pixel >> 24;
541 
542  // increase alpha and color in black (RGB=0)
543  // with some stupid optimization for handling maximum values
544  if(alpha < 255 / 4) {
545  pixel = (alpha * 4) << 24;
546  } else {
547  pixel = 0xFF000000; // we hit the maximum
548  }
549  }
550  }
551 }
552 
554 {
555  if(nsurf) {
556  surface_lock lock(nsurf);
557 
558  for(auto& pixel : lock.pixel_span()) {
559  auto [red, green, blue, alpha] = color_t::from_argb_bytes(pixel);
560  uint8_t newRed, newGreen, newBlue, newAlpha;
561 
562  switch (r) {
563  case RED:
564  newRed = red;
565  break;
566  case GREEN:
567  newRed = green;
568  break;
569  case BLUE:
570  newRed = blue;
571  break;
572  case ALPHA:
573  newRed = alpha;
574  break;
575  default:
576  return;
577  }
578 
579  switch (g) {
580  case RED:
581  newGreen = red;
582  break;
583  case GREEN:
584  newGreen = green;
585  break;
586  case BLUE:
587  newGreen = blue;
588  break;
589  case ALPHA:
590  newGreen = alpha;
591  break;
592  default:
593  return;
594  }
595 
596  switch (b) {
597  case RED:
598  newBlue = red;
599  break;
600  case GREEN:
601  newBlue = green;
602  break;
603  case BLUE:
604  newBlue = blue;
605  break;
606  case ALPHA:
607  newBlue = alpha;
608  break;
609  default:
610  return;
611  }
612 
613  switch (a) {
614  case RED:
615  newAlpha = red;
616  break;
617  case GREEN:
618  newAlpha = green;
619  break;
620  case BLUE:
621  newAlpha = blue;
622  break;
623  case ALPHA:
624  newAlpha = alpha;
625  break;
626  default:
627  return;
628  }
629 
630  pixel = (newAlpha << 24) | (newRed << 16) | (newGreen << 8) | newBlue;
631  }
632  }
633 }
634 
635 void recolor_image(surface& nsurf, const color_mapping& map_rgb)
636 {
637  if(nsurf == nullptr)
638  return;
639 
640  if(map_rgb.empty()) {
641  return;
642  }
643 
644  surface_lock lock(nsurf);
645 
646  for(auto& pixel : lock.pixel_span()) {
647  auto color = color_t::from_argb_bytes(pixel);
648 
649  // Palette uses only RGB channels, so remove alpha
650  uint8_t old_alpha = color.a;
651  color.a = ALPHA_OPAQUE;
652 
653  auto iter = map_rgb.find(color);
654  if(iter == map_rgb.end()) {
655  continue;
656  }
657 
658  // Set new color, restore alpha
659  color = iter->second;
660  color.a = old_alpha;
661 
662  pixel = color.to_argb_bytes();
663  }
664 }
665 
666 void brighten_image(surface& nsurf, int32_t amount)
667 {
668  if(nsurf) {
669  surface_lock lock(nsurf);
670 
671  if (amount < 0) amount = 0;
672  for(auto& pixel : lock.pixel_span()) {
673  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
674 
675  r = std::min<unsigned>(fixed_point_multiply(r, amount), 255);
676  g = std::min<unsigned>(fixed_point_multiply(g, amount), 255);
677  b = std::min<unsigned>(fixed_point_multiply(b, amount), 255);
678 
679  pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
680  }
681  }
682 }
683 
684 void adjust_surface_alpha(surface& surf, uint8_t alpha_mod)
685 {
686  if(surf == nullptr) {
687  return;
688  }
689 
690  SDL_SetSurfaceAlphaMod(surf, alpha_mod);
691 }
692 
693 void adjust_surface_alpha_add(surface& nsurf, int amount)
694 {
695  if(nsurf) {
696  surface_lock lock(nsurf);
697 
698  for(auto& pixel : lock.pixel_span()) {
699  auto [r, g, b, alpha] = color_t::from_argb_bytes(pixel);
700 
701  alpha = uint8_t(std::clamp(static_cast<int>(alpha) + amount, 0, 255));
702  pixel = (alpha << 24) + (r << 16) + (g << 8) + b;
703  }
704  }
705 }
706 
707 bool mask_surface(surface& nsurf, const surface& nmask, const std::string& filename)
708 {
709  if(nsurf == nullptr) {
710  return true;
711  }
712  if(nmask == nullptr) {
713  return false;
714  }
715 
716  if (nsurf->w != nmask->w) {
717  // we don't support efficiently different width.
718  // (different height is not a real problem)
719  // This function is used on all hexes and usually only for that
720  // so better keep it simple and efficient for the normal case
721  std::stringstream ss;
722  ss << "Detected an image with bad dimensions: ";
723  if(!filename.empty()) ss << filename << ": ";
724  ss << nsurf->w << "x" << nsurf->h;
725  PLAIN_LOG << ss.str();
726  PLAIN_LOG << "It will not be masked, please use: "<< nmask->w << "x" << nmask->h;
727  return false;
728  }
729 
730  uint32_t cumulative_alpha{0};
731  {
732  surface_lock lock(nsurf);
733  const_surface_lock mlock(nmask);
734 
735  utils::span surf_pixels = lock.pixel_span();
736  utils::span mask_pixels = mlock.pixel_span();
737 
738  // Note: any pixels outside the range of the smaller surface are ignored.
739  const auto sentinel = std::min(surf_pixels.size(), mask_pixels.size());
740 
741  for(std::size_t i = 0; i < sentinel; ++i) {
742  const uint32_t surf_alpha = surf_pixels[i] & SDL_ALPHA_MASK;
743  const uint32_t mask_alpha = mask_pixels[i] & SDL_ALPHA_MASK;
744 
745  const auto min_alpha = std::min(surf_alpha, mask_alpha);
746 
747  // Clear the alpha bits before writing the new alpha value.
748  surf_pixels[i] &= ~SDL_ALPHA_MASK;
749  surf_pixels[i] |= min_alpha;
750 
751  // This will quickly saturate the leftmost 8 bits,
752  // but we only care whether the final result is 0.
753  cumulative_alpha |= min_alpha;
754  }
755  }
756 
757  return cumulative_alpha == 0;
758 }
759 
760 bool in_mask_surface(const surface& nsurf, const surface& nmask)
761 {
762  if(nsurf == nullptr) {
763  return false;
764  }
765  if(nmask == nullptr){
766  return true;
767  }
768 
769  if (nsurf->w != nmask->w || nsurf->h != nmask->h ) {
770  // not same size, consider it doesn't fit
771  return false;
772  }
773 
774  const_surface_lock lock(nsurf);
775  const_surface_lock mlock(nmask);
776 
777  utils::span surf_pixels = lock.pixel_span();
778  utils::span mask_pixels = mlock.pixel_span();
779 
780  // Note: unlike in mask_surface, both ranges here have the same size.
781  for(std::size_t i = 0; i < surf_pixels.size(); ++i) {
782  const uint32_t surf_alpha = surf_pixels[i] & SDL_ALPHA_MASK;
783  const uint32_t mask_alpha = mask_pixels[i] & SDL_ALPHA_MASK;
784 
785  // A visible pixel (non-zero alpha) which the mask would otherwise hide.
786  if(surf_alpha && mask_alpha == 0) {
787  return false;
788  }
789  }
790 
791  return true;
792 }
793 
794 void light_surface(surface& nsurf, const surface &lightmap)
795 {
796  if(nsurf == nullptr) {
797  return;
798  }
799  if(lightmap == nullptr) {
800  return;
801  }
802 
803  if (nsurf->w != lightmap->w) {
804  // we don't support efficiently different width.
805  // (different height is not a real problem)
806  // This function is used on all hexes and usually only for that
807  // so better keep it simple and efficient for the normal case
808  PLAIN_LOG << "Detected an image with bad dimensions: " << nsurf->w << "x" << nsurf->h;
809  PLAIN_LOG << "It will not be lighted, please use: "<< lightmap->w << "x" << lightmap->h;
810  return;
811  }
812  {
813  surface_lock lock(nsurf);
814  const_surface_lock llock(lightmap);
815 
816  uint32_t* beg = lock.pixels();
817  uint32_t* end = beg + nsurf.area();
818  const uint32_t* lbeg = llock.pixels();
819  const uint32_t* lend = lbeg + lightmap.area();
820 
821  while(beg != end && lbeg != lend) {
822  auto [lr, lg, lb, la] = color_t::from_argb_bytes(*lbeg);
823  auto [r, g, b, alpha] = color_t::from_argb_bytes(*beg);
824 
825  int dr = (static_cast<int>(lr) - 128) * 2;
826  int dg = (static_cast<int>(lg) - 128) * 2;
827  int db = (static_cast<int>(lb) - 128) * 2;
828 
829  //note that r + dr will promote r to int (needed to avoid uint8_t math)
830  r = std::clamp(r + dr, 0, 255);
831  g = std::clamp(g + dg, 0, 255);
832  b = std::clamp(b + db, 0, 255);
833 
834  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
835 
836  ++beg;
837  ++lbeg;
838  }
839  }
840 }
841 
842 void blur_surface(surface& surf, rect rect, int depth)
843 {
844  if(surf == nullptr) {
845  return;
846  }
847 
848  const int max_blur = 256;
849  if(depth > max_blur) {
850  depth = max_blur;
851  }
852 
853  uint32_t queue[max_blur];
854  const uint32_t* end_queue = queue + max_blur;
855 
856  const uint32_t ff = 0xff;
857 
858  const unsigned pixel_offset = rect.y * surf->w + rect.x;
859 
860  surface_lock lock(surf);
861  for(int y = 0; y < rect.h; ++y) {
862  const uint32_t* front = &queue[0];
863  uint32_t* back = &queue[0];
864  uint32_t red = 0, green = 0, blue = 0, avg = 0;
865  uint32_t* p = lock.pixels() + pixel_offset + y * surf->w;
866  for(int x = 0; x <= depth && x < rect.w; ++x, ++p) {
867  red += ((*p) >> 16)&0xFF;
868  green += ((*p) >> 8)&0xFF;
869  blue += (*p)&0xFF;
870  ++avg;
871  *back++ = *p;
872  if(back == end_queue) {
873  back = &queue[0];
874  }
875  }
876 
877  p = lock.pixels() + pixel_offset + y * surf->w;
878  for(int x = 0; x < rect.w; ++x, ++p) {
879  *p = 0xFF000000
880  | (std::min(red/avg,ff) << 16)
881  | (std::min(green/avg,ff) << 8)
882  | std::min(blue/avg,ff);
883 
884  if(x >= depth) {
885  red -= ((*front) >> 16)&0xFF;
886  green -= ((*front) >> 8)&0xFF;
887  blue -= *front&0xFF;
888  --avg;
889  ++front;
890  if(front == end_queue) {
891  front = &queue[0];
892  }
893  }
894 
895  if(x + depth+1 < rect.w) {
896  uint32_t* q = p + depth+1;
897  red += ((*q) >> 16)&0xFF;
898  green += ((*q) >> 8)&0xFF;
899  blue += (*q)&0xFF;
900  ++avg;
901  *back++ = *q;
902  if(back == end_queue) {
903  back = &queue[0];
904  }
905  }
906  }
907  }
908 
909  for(int x = 0; x < rect.w; ++x) {
910  const uint32_t* front = &queue[0];
911  uint32_t* back = &queue[0];
912  uint32_t red = 0, green = 0, blue = 0, avg = 0;
913  uint32_t* p = lock.pixels() + pixel_offset + x;
914  for(int y = 0; y <= depth && y < rect.h; ++y, p += surf->w) {
915  red += ((*p) >> 16)&0xFF;
916  green += ((*p) >> 8)&0xFF;
917  blue += *p&0xFF;
918  ++avg;
919  *back++ = *p;
920  if(back == end_queue) {
921  back = &queue[0];
922  }
923  }
924 
925  p = lock.pixels() + pixel_offset + x;
926  for(int y = 0; y < rect.h; ++y, p += surf->w) {
927  *p = 0xFF000000
928  | (std::min(red/avg,ff) << 16)
929  | (std::min(green/avg,ff) << 8)
930  | std::min(blue/avg,ff);
931 
932  if(y >= depth) {
933  red -= ((*front) >> 16)&0xFF;
934  green -= ((*front) >> 8)&0xFF;
935  blue -= *front&0xFF;
936  --avg;
937  ++front;
938  if(front == end_queue) {
939  front = &queue[0];
940  }
941  }
942 
943  if(y + depth+1 < rect.h) {
944  uint32_t* q = p + (depth+1)*surf->w;
945  red += ((*q) >> 16)&0xFF;
946  green += ((*q) >> 8)&0xFF;
947  blue += (*q)&0xFF;
948  ++avg;
949  *back++ = *q;
950  if(back == end_queue) {
951  back = &queue[0];
952  }
953  }
954  }
955  }
956 }
957 
958 void blur_alpha_surface(surface& res, int depth)
959 {
960  if(res == nullptr) {
961  return;
962  }
963 
964  const int max_blur = 256;
965  if(depth > max_blur) {
966  depth = max_blur;
967  }
968 
969  struct Pixel{
970  uint8_t alpha;
971  uint8_t red;
972  uint8_t green;
973  uint8_t blue;
974  Pixel(uint32_t* p)
975  : alpha(((*p) >> 24)&0xFF)
976  , red(((*p) >> 16)&0xFF)
977  , green(((*p) >> 8)&0xFF)
978  , blue((*p)&0xFF) {}
979  };
980  struct Average{
981  uint32_t alpha;
982  uint32_t red;
983  uint32_t green;
984  uint32_t blue;
985  Average() : alpha(), red(), green(), blue()
986  {}
987  Average& operator+=(const Pixel& pix){
988  red += pix.alpha * pix.red;
989  green += pix.alpha * pix.green;
990  blue += pix.alpha * pix.blue;
991  alpha += pix.alpha;
992  return *this;
993  }
994  Average& operator-=(const Pixel& pix){
995  red -= pix.alpha * pix.red;
996  green -= pix.alpha * pix.green;
997  blue -= pix.alpha * pix.blue;
998  alpha -= pix.alpha;
999  return *this;
1000  }
1001  uint32_t operator()(unsigned num){
1002  const uint32_t ff = 0xff;
1003  if(!alpha){
1004  return 0;
1005  }
1006  return (std::min(alpha/num,ff) << 24)
1007  | (std::min(red/alpha,ff) << 16)
1008  | (std::min(green/alpha,ff) << 8)
1009  | std::min(blue/alpha,ff);
1010  }
1011  };
1012 
1013  boost::circular_buffer<Pixel> queue(depth*2+1);
1014 
1015  surface_lock lock(res);
1016  int x, y;
1017  // Iterate over rows, blurring each row horizontally
1018  for(y = 0; y < res->h; ++y) {
1019  // Sum of pixel values stored here
1020  Average avg;
1021 
1022  // Preload the first depth+1 pixels
1023  uint32_t* p = lock.pixels() + y*res->w;
1024  for(x = 0; x <= depth && x < res->w; ++x, ++p) {
1025  assert(!queue.full());
1026  queue.push_back(Pixel{p});
1027  avg += queue.back();
1028  }
1029 
1030  // This is the actual inner loop
1031  p = lock.pixels() + y*res->w;
1032  for(x = 0; x < res->w; ++x, ++p) {
1033  // Write the current average
1034  const uint32_t num = queue.size();
1035  *p = avg(num);
1036 
1037  // Unload earlier pixels that are now too far away
1038  if(x >= depth) {
1039  avg -= queue.front();
1040  assert(!queue.empty());
1041  queue.pop_front();
1042  }
1043 
1044  // Add new pixels
1045  if(x + depth+1 < res->w) {
1046  uint32_t* q = p + depth+1;
1047  assert(!queue.full());
1048  queue.push_back(Pixel{q});
1049  avg += queue.back();
1050  }
1051  }
1052  assert(static_cast<int>(queue.size()) == std::min(depth, res->w));
1053  queue.clear();
1054  }
1055 
1056  // Iterate over columns, blurring each column vertically
1057  for(x = 0; x < res->w; ++x) {
1058  // Sum of pixel values stored here
1059  Average avg;
1060 
1061  // Preload the first depth+1 pixels
1062  uint32_t* p = lock.pixels() + x;
1063  for(y = 0; y <= depth && y < res->h; ++y, p += res->w) {
1064  assert(!queue.full());
1065  queue.push_back(Pixel{p});
1066  avg += queue.back();
1067  }
1068 
1069  // This is the actual inner loop
1070  p = lock.pixels() + x;
1071  for(y = 0; y < res->h; ++y, p += res->w) {
1072  // Write the current average
1073  const uint32_t num = queue.size();
1074  *p = avg(num);
1075 
1076  // Unload earlier pixels that are now too far away
1077  if(y >= depth) {
1078  avg -= queue.front();
1079  assert(!queue.empty());
1080  queue.pop_front();
1081  }
1082 
1083  // Add new pixels
1084  if(y + depth+1 < res->h) {
1085  uint32_t* q = p + (depth+1)*res->w;
1086  assert(!queue.full());
1087  queue.push_back(Pixel{q});
1088  avg += queue.back();
1089  }
1090  }
1091  assert(static_cast<int>(queue.size()) == std::min(depth, res->h));
1092  queue.clear();
1093  }
1094 }
1095 
1097 {
1098  if(surf == nullptr)
1099  return nullptr;
1100 
1101  surface res(r.w, r.h);
1102 
1103  if(res == nullptr) {
1104  PLAIN_LOG << "Could not create a new surface in cut_surface()";
1105  return nullptr;
1106  }
1107 
1108  std::size_t sbpp = surf->format->BytesPerPixel;
1109  std::size_t spitch = surf->pitch;
1110  std::size_t rbpp = res->format->BytesPerPixel;
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:103
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:112
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:48
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:503
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:635
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:226
void blur_surface(surface &surf, rect rect, int depth)
Cross-fades a surface in place.
Definition: utils.cpp:842
void wipe_alpha(surface &nsurf)
Definition: utils.cpp:516
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1096
void brighten_image(surface &nsurf, int32_t amount)
Definition: utils.cpp:666
void adjust_surface_alpha(surface &surf, uint8_t alpha_mod)
Definition: utils.cpp:684
void sepia_image(surface &nsurf)
Definition: utils.cpp:463
void adjust_surface_alpha_add(surface &nsurf, int amount)
Definition: utils.cpp:693
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:553
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:406
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:482
void shadow_image(surface &surf, int scale)
create an heavy shadow of the image, by blurring, increasing alpha and darkening
Definition: utils.cpp:528
static lg::log_domain log_display("display")
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:794
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:62
bool in_mask_surface(const surface &nsurf, const surface &nmask)
Check if a surface fit into a mask.
Definition: utils.cpp:760
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:359
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:958
void greyscale_image(surface &nsurf)
Definition: utils.cpp:423
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:100
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:445
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:707
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