The Battle for Wesnoth  1.19.0+dev
utils.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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/utils.hpp"
22 #include "color.hpp"
23 #include "log.hpp"
24 #include "xBRZ/xbrz.hpp"
25 
26 #include <algorithm>
27 #include <cassert>
28 #include <cstring>
29 
30 #include <boost/circular_buffer.hpp>
31 #include <boost/math/constants/constants.hpp>
32 
33 static lg::log_domain log_display("display");
34 #define ERR_DP LOG_STREAM(err, log_display)
35 
37 {
38  SDL_version sdl_version;
39  SDL_GetVersion(&sdl_version);
40  return version_info(sdl_version.major, sdl_version.minor, sdl_version.patch);
41 }
42 
43 bool sdl::runtime_at_least(uint8_t major, uint8_t minor, uint8_t patch)
44 {
45  SDL_version ver;
46  SDL_GetVersion(&ver);
47  if(ver.major < major) return false;
48  if(ver.major > major) return true;
49  // major version equal
50  if(ver.minor < minor) return false;
51  if(ver.minor > minor) return true;
52  // major and minor version equal
53  if(ver.patch < patch) return false;
54  return true;
55 }
56 
57 surface scale_surface_xbrz(const surface & surf, std::size_t z)
58 {
59  if(surf == nullptr)
60  return nullptr;
61 
62  if (z > 5) {
63  PLAIN_LOG << "Cannot use xbrz scaling with zoom factor > 5.";
64  z = 1;
65  }
66 
67  if (z == 1) {
68  surface temp = surf; // TODO: no temp surface
69  return temp;
70  }
71 
72  surface dst(surf->w *z, surf->h * z);
73 
74  if (z == 0) {
75  PLAIN_LOG << "Create an empty image";
76  return dst;
77  }
78 
79  if(surf == nullptr || dst == nullptr) {
80  PLAIN_LOG << "Could not create surface to scale onto";
81  return nullptr;
82  }
83 
84  {
85  const_surface_lock src_lock(surf);
86  surface_lock dst_lock(dst);
87 
88  xbrz::scale(z, src_lock.pixels(), dst_lock.pixels(), surf->w, surf->h);
89  }
90 
91  return dst;
92 }
93 
94 surface scale_surface_nn (const surface & surf, int w, int h)
95 {
96  // Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
97  assert(SDL_ALPHA_TRANSPARENT==0);
98 
99  if (surf == nullptr)
100  return nullptr;
101 
102  if(w == surf->w && h == surf->h) {
103  return surf;
104  }
105  assert(w >= 0);
106  assert(h >= 0);
107 
108  surface dst(w,h);
109 
110  if (w == 0 || h ==0) {
111  PLAIN_LOG << "Create an empty image";
112  return dst;
113  }
114 
115  if(surf == nullptr || dst == nullptr) {
116  PLAIN_LOG << "Could not create surface to scale onto";
117  return nullptr;
118  }
119 
120  {
121  const_surface_lock src_lock(surf);
122  surface_lock dst_lock(dst);
123 
124  xbrz::nearestNeighborScale(src_lock.pixels(), surf->w, surf->h, dst_lock.pixels(), w, h);
125  }
126 
127  return dst;
128 }
129 
130 // NOTE: Don't pass this function 0 scaling arguments.
131 surface scale_surface(const surface &surf, int w, int h)
132 {
133  // Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
134  assert(SDL_ALPHA_TRANSPARENT==0);
135 
136  if(surf == nullptr)
137  return nullptr;
138 
139  if(w == surf->w && h == surf->h) {
140  return surf;
141  }
142  assert(w >= 0);
143  assert(h >= 0);
144 
145  surface dst(w,h);
146 
147  if (w == 0 || h ==0) {
148  PLAIN_LOG << "Create an empty image";
149  return dst;
150  }
151 
152  if(surf == nullptr || dst == nullptr) {
153  PLAIN_LOG << "Could not create surface to scale onto";
154  return nullptr;
155  }
156 
157  {
158  const_surface_lock src_lock(surf);
159  surface_lock dst_lock(dst);
160 
161  const uint32_t* const src_pixels = src_lock.pixels();
162  uint32_t* const dst_pixels = dst_lock.pixels();
163 
164  int32_t xratio = fixed_point_divide(surf->w,w);
165  int32_t yratio = fixed_point_divide(surf->h,h);
166 
167  int32_t ysrc = 0;
168  for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
169  int32_t xsrc = 0;
170  for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
171  const int xsrcint = fixed_point_to_int(xsrc);
172  const int ysrcint = fixed_point_to_int(ysrc);
173 
174  const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
175  uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
176  const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
177  const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
178 
179  uint8_t r,g,b,a;
180  uint32_t rr,gg,bb,aa, temp;
181 
182  uint32_t pix[4], bilin[4];
183 
184  // This next part is the fixed point
185  // equivalent of "take everything to
186  // the right of the decimal point."
187  // These fundamental weights decide
188  // the contributions from various
189  // input pixels. The labels assume
190  // that the upper left corner of the
191  // screen ("northeast") is 0,0 but the
192  // code should still be consistent if
193  // the graphics origin is actually
194  // somewhere else.
195  //
196  // That is, the bilin array holds the
197  // "geometric" weights. I.E. If I'm scaling
198  // a 2 x 2 block a 10 x 10 block, then for
199  // pixel (2,2) of output, the upper left
200  // pixel should be 10:1 more influential than
201  // the upper right, and also 10:1 more influential
202  // than lower left, and 100:1 more influential
203  // than lower right.
204 
205  const int32_t e = 0x000000FF & xsrc;
206  const int32_t s = 0x000000FF & ysrc;
207  const int32_t n = 0xFF - s;
208  // Not called "w" to avoid hiding a function parameter
209  // (would cause a compiler warning in MSVC2015 with /W4)
210  const int32_t we = 0xFF - e;
211 
212  pix[0] = *src_word; // northwest
213  pix[1] = *(src_word + dx); // northeast
214  pix[2] = *(src_word + dy); // southwest
215  pix[3] = *(src_word + dx + dy); // southeast
216 
217  bilin[0] = n*we;
218  bilin[1] = n*e;
219  bilin[2] = s*we;
220  bilin[3] = s*e;
221 
222  int loc;
223  rr = bb = gg = aa = 0;
224  for (loc=0; loc<4; loc++) {
225  a = pix[loc] >> 24;
226  r = pix[loc] >> 16;
227  g = pix[loc] >> 8;
228  b = pix[loc] >> 0;
229 
230  //We also have to implement weighting by alpha for the RGB components
231  //If a unit has some parts solid and some parts translucent,
232  //i.e. a red cloak but a dark shadow, then when we scale in
233  //the shadow shouldn't appear to become red at the edges.
234  //This part also smoothly interpolates between alpha=0 being
235  //transparent and having no contribution, vs being opaque.
236  temp = (a * bilin[loc]);
237  rr += r * temp;
238  gg += g * temp;
239  bb += b * temp;
240  aa += temp;
241  }
242 
243  a = aa >> (16); // we average the alphas, they don't get weighted by any other factor besides bilin
244  if (a != 0) {
245  rr /= a; // finish alpha weighting: divide by sum of alphas
246  gg /= a;
247  bb /= a;
248  }
249  r = rr >> (16); // now shift over by 16 for the bilin part
250  g = gg >> (16);
251  b = bb >> (16);
252  *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
253  }
254  }
255  }
256 
257  return dst;
258 }
259 
260 surface scale_surface_legacy(const surface &surf, int w, int h)
261 {
262  // Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
263  assert(SDL_ALPHA_TRANSPARENT==0);
264 
265  if(surf == nullptr)
266  return nullptr;
267 
268  if(w == surf->w && h == surf->h) {
269  return surf;
270  }
271  assert(w >= 0);
272  assert(h >= 0);
273 
274  surface dst(w,h);
275 
276  if(surf == nullptr || dst == nullptr) {
277  PLAIN_LOG << "Could not create surface to scale onto";
278  return nullptr;
279  }
280 
281  {
282  const_surface_lock src_lock(surf);
283  surface_lock dst_lock(dst);
284 
285  const uint32_t* const src_pixels = src_lock.pixels();
286  uint32_t* const dst_pixels = dst_lock.pixels();
287 
288  int32_t xratio = fixed_point_divide(surf->w,w);
289  int32_t yratio = fixed_point_divide(surf->h,h);
290 
291  int32_t ysrc = 0;
292  for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
293  int32_t xsrc = 0;
294  for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
295  const int xsrcint = fixed_point_to_int(xsrc);
296  const int ysrcint = fixed_point_to_int(ysrc);
297 
298  const uint32_t* const src_word = src_pixels + ysrcint*surf->w + xsrcint;
299  uint32_t* const dst_word = dst_pixels + ydst*dst->w + xdst;
300  const int dx = (xsrcint + 1 < surf->w) ? 1 : 0;
301  const int dy = (ysrcint + 1 < surf->h) ? surf->w : 0;
302 
303  uint8_t r,g,b,a;
304  uint32_t rr,gg,bb,aa;
305  uint16_t avg_r, avg_g, avg_b;
306  uint32_t pix[4], bilin[4];
307 
308  // This next part is the fixed point
309  // equivalent of "take everything to
310  // the right of the decimal point."
311  // These fundamental weights decide
312  // the contributions from various
313  // input pixels. The labels assume
314  // that the upper left corner of the
315  // screen ("northeast") is 0,0 but the
316  // code should still be consistent if
317  // the graphics origin is actually
318  // somewhere else.
319 
320  const int32_t east = 0x000000FF & xsrc;
321  const int32_t south = 0x000000FF & ysrc;
322  const int32_t north = 0xFF - south;
323  const int32_t west = 0xFF - east;
324 
325  pix[0] = *src_word; // northwest
326  pix[1] = *(src_word + dx); // northeast
327  pix[2] = *(src_word + dy); // southwest
328  pix[3] = *(src_word + dx + dy); // southeast
329 
330  bilin[0] = north*west;
331  bilin[1] = north*east;
332  bilin[2] = south*west;
333  bilin[3] = south*east;
334 
335  // Scope out the neighboorhood, see
336  // what the pixel values are like.
337 
338  int count = 0;
339  avg_r = avg_g = avg_b = 0;
340  int loc;
341  for (loc=0; loc<4; loc++) {
342  a = pix[loc] >> 24;
343  r = pix[loc] >> 16;
344  g = pix[loc] >> 8;
345  b = pix[loc] >> 0;
346  if (a != 0) {
347  avg_r += r;
348  avg_g += g;
349  avg_b += b;
350  count++;
351  }
352  }
353  if (count>0) {
354  avg_r /= count;
355  avg_b /= count;
356  avg_g /= count;
357  }
358 
359  // Perform modified bilinear interpolation.
360  // Don't trust any color information from
361  // an RGBA sample when the alpha channel
362  // is set to fully transparent.
363  //
364  // Some of the input images are hex tiles,
365  // created using a hexagon shaped alpha channel
366  // that is either set to full-on or full-off.
367 
368  rr = gg = bb = aa = 0;
369  for (loc=0; loc<4; loc++) {
370  a = pix[loc] >> 24;
371  r = pix[loc] >> 16;
372  g = pix[loc] >> 8;
373  b = pix[loc] >> 0;
374  if (a == 0) {
375  r = static_cast<uint8_t>(avg_r);
376  g = static_cast<uint8_t>(avg_g);
377  b = static_cast<uint8_t>(avg_b);
378  }
379  rr += r * bilin[loc];
380  gg += g * bilin[loc];
381  bb += b * bilin[loc];
382  aa += a * bilin[loc];
383  }
384  r = rr >> 16;
385  g = gg >> 16;
386  b = bb >> 16;
387  a = aa >> 16;
388  *dst_word = (a << 24) + (r << 16) + (g << 8) + b;
389  }
390  }
391  }
392 
393  return dst;
394 }
395 
396 
397 surface scale_surface_sharp(const surface& surf, int w, int h)
398 {
399  // Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
400  assert(SDL_ALPHA_TRANSPARENT == 0);
401 
402  if(surf == nullptr) {
403  return nullptr;
404  }
405  if(w == surf->w && h == surf->h) {
406  return surf;
407  }
408 
409  assert(w >= 0);
410  assert(h >= 0);
411  surface dst(w, h);
412  if(dst == nullptr) {
413  PLAIN_LOG << "Could not create surface to scale onto";
414  return nullptr;
415  }
416 
417  if(w == 0 || h == 0) {
418  PLAIN_LOG << "Creating an empty image";
419  return dst;
420  }
421 
422  {
423  const_surface_lock src_lock(surf);
424  surface_lock dst_lock(dst);
425 
426  const uint32_t* const src_pixels = src_lock.pixels();
427  uint32_t* const dst_pixels = dst_lock.pixels();
428 
429  const int src_w = surf->w;
430  const int src_h = surf->h;
431 
432  const float xratio = static_cast<float>(src_w) / static_cast<float>(w);
433  const float yratio = static_cast<float>(src_h) / static_cast<float>(h);
434  for(int ydst = 0; ydst != h; ++ydst) {
435  for(int xdst = 0; xdst != w; ++xdst) {
436  // Project dst pixel to a single corresponding src pixel by scale and simply take it
437  const int xsrc = std::floor(static_cast<float>(xdst) * xratio);
438  const int ysrc = std::floor(static_cast<float>(ydst) * yratio);
439  dst_pixels[ydst * dst->w + xdst] = src_pixels[ysrc * src_w + xsrc];
440  }
441  }
442  }
443 
444  return dst;
445 }
446 
447 surface adjust_surface_color(const surface &surf, int red, int green, int blue)
448 {
449  if(surf == nullptr)
450  return nullptr;
451 
452  if((red == 0 && green == 0 && blue == 0)) {
453  surface temp = surf; // TODO: remove temp surface
454  return temp;
455  }
456 
457  surface nsurf = surf.clone();
458 
459  if(nsurf == nullptr) {
460  PLAIN_LOG << "failed to make neutral surface";
461  return nullptr;
462  }
463 
464  {
465  surface_lock lock(nsurf);
466  uint32_t* beg = lock.pixels();
467  uint32_t* end = beg + nsurf->w*surf->h;
468 
469  while(beg != end) {
470  uint8_t alpha = (*beg) >> 24;
471 
472  if(alpha) {
473  uint8_t r, g, b;
474  r = (*beg) >> 16;
475  g = (*beg) >> 8;
476  b = (*beg) >> 0;
477 
478  r = std::max<int>(0,std::min<int>(255,static_cast<int>(r)+red));
479  g = std::max<int>(0,std::min<int>(255,static_cast<int>(g)+green));
480  b = std::max<int>(0,std::min<int>(255,static_cast<int>(b)+blue));
481 
482  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
483  }
484 
485  ++beg;
486  }
487  }
488 
489  return nsurf;
490 }
491 
493 {
494  if(surf == nullptr)
495  return nullptr;
496 
497  surface nsurf = surf.clone();
498  if(nsurf == nullptr) {
499  PLAIN_LOG << "failed to make neutral surface";
500  return nullptr;
501  }
502 
503  {
504  surface_lock lock(nsurf);
505  uint32_t* beg = lock.pixels();
506  uint32_t* end = beg + nsurf->w*surf->h;
507 
508  while(beg != end) {
509  uint8_t alpha = (*beg) >> 24;
510 
511  if(alpha) {
512  uint8_t r, g, b;
513  r = (*beg) >> 16;
514  g = (*beg) >> 8;
515  b = (*beg);
516  //const uint8_t avg = (red+green+blue)/3;
517 
518  // Use the correct formula for RGB to grayscale conversion.
519  // Ok, this is no big deal :)
520  // The correct formula being:
521  // gray=0.299red+0.587green+0.114blue
522  const uint8_t avg = static_cast<uint8_t>((
523  77 * static_cast<uint16_t>(r) +
524  150 * static_cast<uint16_t>(g) +
525  29 * static_cast<uint16_t>(b) ) / 256);
526 
527  *beg = (alpha << 24) | (avg << 16) | (avg << 8) | avg;
528  }
529 
530  ++beg;
531  }
532  }
533 
534  return nsurf;
535 }
536 
537 surface monochrome_image(const surface &surf, const int threshold)
538 {
539  if(surf == nullptr)
540  return nullptr;
541 
542  surface nsurf = surf.clone();
543  if(nsurf == nullptr) {
544  PLAIN_LOG << "failed to make neutral surface";
545  return nullptr;
546  }
547 
548  {
549  surface_lock lock(nsurf);
550  uint32_t* beg = lock.pixels();
551  uint32_t* end = beg + nsurf->w*surf->h;
552 
553  while(beg != end) {
554  uint8_t alpha = (*beg) >> 24;
555 
556  if(alpha) {
557  uint8_t r, g, b, result;
558  r = (*beg) >> 16;
559  g = (*beg) >> 8;
560  b = (*beg);
561 
562  // first convert the pixel to grayscale
563  // if the resulting value is above the threshold make it black
564  // else make it white
565  result = static_cast<uint8_t>(0.299 * r + 0.587 * g + 0.114 * b) > threshold ? 255 : 0;
566 
567  *beg = (alpha << 24) | (result << 16) | (result << 8) | result;
568  }
569 
570  ++beg;
571  }
572  }
573 
574  return nsurf;
575 }
576 
578 {
579  if(surf == nullptr)
580  return nullptr;
581 
582  surface nsurf = surf.clone();
583  if(nsurf == nullptr) {
584  PLAIN_LOG << "failed to make neutral surface";
585  return nullptr;
586  }
587 
588  {
589  surface_lock lock(nsurf);
590  uint32_t* beg = lock.pixels();
591  uint32_t* end = beg + nsurf->w*surf->h;
592 
593  while(beg != end) {
594  uint8_t alpha = (*beg) >> 24;
595 
596  if(alpha) {
597  uint8_t r, g, b;
598  r = (*beg) >> 16;
599  g = (*beg) >> 8;
600  b = (*beg);
601 
602  // this is the formula for applying a sepia effect
603  // that can be found on various web sites
604  // for example here: https://software.intel.com/sites/default/files/article/346220/sepiafilter-intelcilkplus.pdf
605  uint8_t outRed = std::min(255, static_cast<int>((r * 0.393) + (g * 0.769) + (b * 0.189)));
606  uint8_t outGreen = std::min(255, static_cast<int>((r * 0.349) + (g * 0.686) + (b * 0.168)));
607  uint8_t outBlue = std::min(255, static_cast<int>((r * 0.272) + (g * 0.534) + (b * 0.131)));
608 
609  *beg = (alpha << 24) | (outRed << 16) | (outGreen << 8) | (outBlue);
610  }
611 
612  ++beg;
613  }
614  }
615 
616  return nsurf;
617 }
618 
619 surface negative_image(const surface &surf, const int thresholdR, const int thresholdG, const int thresholdB)
620 {
621  if(surf == nullptr)
622  return nullptr;
623 
624  surface nsurf = surf.clone();
625  if(nsurf == nullptr) {
626  PLAIN_LOG << "failed to make neutral surface";
627  return nullptr;
628  }
629 
630  {
631  surface_lock lock(nsurf);
632  uint32_t* beg = lock.pixels();
633  uint32_t* end = beg + nsurf->w*surf->h;
634 
635  while(beg != end) {
636  uint8_t alpha = (*beg) >> 24;
637 
638  if(alpha) {
639  uint8_t r, g, b, newR, newG, newB;
640  r = (*beg) >> 16;
641  g = (*beg) >> 8;
642  b = (*beg);
643 
644  // invert he channel only if its value is greater than the supplied threshold
645  // this can be used for solarization effects
646  // for a full negative effect, use a value of -1
647  // 255 is a no-op value (doesn't do anything, since a uint8_t cannot contain a greater value than that)
648  newR = r > thresholdR ? 255 - r : r;
649  newG = g > thresholdG ? 255 - g : g;
650  newB = b > thresholdB ? 255 - b : b;
651 
652  *beg = (alpha << 24) | (newR << 16) | (newG << 8) | (newB);
653  }
654 
655  ++beg;
656  }
657  }
658 
659  return nsurf;
660 }
661 
663 {
664  if(surf == nullptr)
665  return nullptr;
666 
667  surface nsurf = surf.clone();
668  if(nsurf == nullptr) {
669  PLAIN_LOG << "failed to make neutral surface";
670  return nullptr;
671  }
672 
673  {
674  surface_lock lock(nsurf);
675  uint32_t* beg = lock.pixels();
676  uint32_t* end = beg + nsurf->w*surf->h;
677 
678  while(beg != end) {
679  uint8_t alpha = (*beg) >> 24;
680 
681  *beg = (0xff << 24) | (alpha << 16) | (alpha << 8) | alpha;
682 
683  ++beg;
684  }
685  }
686 
687  return nsurf;
688 }
689 
691 {
692  if(surf == nullptr)
693  return nullptr;
694 
695  surface nsurf = surf.clone();
696  if(nsurf == nullptr) {
697  PLAIN_LOG << "failed to make neutral surface";
698  return nullptr;
699  }
700 
701  {
702  surface_lock lock(nsurf);
703  uint32_t* beg = lock.pixels();
704  uint32_t* end = beg + nsurf->w*surf->h;
705 
706  while(beg != end) {
707 
708  *beg = 0xff000000 | *beg;
709 
710  ++beg;
711  }
712  }
713 
714  return nsurf;
715 }
716 
717 
719 {
720  if(surf == nullptr)
721  return nullptr;
722 
723  // we blur it, and reuse the neutral surface created by the blur function
724  surface nsurf (blur_alpha_surface(surf, 2*scale));
725 
726  if(nsurf == nullptr) {
727  PLAIN_LOG << "failed to blur the shadow surface";
728  return nullptr;
729  }
730 
731  {
732  surface_lock lock(nsurf);
733  uint32_t* beg = lock.pixels();
734  uint32_t* end = beg + nsurf->w*surf->h;
735 
736  while(beg != end) {
737  uint8_t alpha = (*beg) >> 24;
738 
739  if(alpha) {
740  // increase alpha and color in black (RGB=0)
741  // with some stupid optimization for handling maximum values
742  if (alpha < 255/4)
743  *beg = (alpha*4) << 24;
744  else
745  *beg = 0xFF000000; // we hit the maximum
746  }
747 
748  ++beg;
749  }
750  }
751 
752  return nsurf;
753 }
754 
756  if(surf == nullptr)
757  return nullptr;
758 
759  surface nsurf = surf.clone();
760  if(nsurf == nullptr) {
761  PLAIN_LOG << "failed to make neutral surface";
762  return nullptr;
763  }
764 
765  {
766  surface_lock lock(nsurf);
767  uint32_t* beg = lock.pixels();
768  uint32_t* end = beg + nsurf->w*surf->h;
769 
770  while(beg != end) {
771  uint8_t alpha = (*beg) >> 24;
772 
773  if(alpha) {
774  uint8_t red, green, blue, newRed, newGreen, newBlue, newAlpha;
775  red = (*beg) >> 16;
776  green = (*beg) >> 8;
777  blue = (*beg);
778 
779  switch (r) {
780  case RED:
781  newRed = red;
782  break;
783  case GREEN:
784  newRed = green;
785  break;
786  case BLUE:
787  newRed = blue;
788  break;
789  case ALPHA:
790  newRed = alpha;
791  break;
792  default:
793  return nullptr;
794  }
795 
796  switch (g) {
797  case RED:
798  newGreen = red;
799  break;
800  case GREEN:
801  newGreen = green;
802  break;
803  case BLUE:
804  newGreen = blue;
805  break;
806  case ALPHA:
807  newGreen = alpha;
808  break;
809  default:
810  return nullptr;
811  }
812 
813  switch (b) {
814  case RED:
815  newBlue = red;
816  break;
817  case GREEN:
818  newBlue = green;
819  break;
820  case BLUE:
821  newBlue = blue;
822  break;
823  case ALPHA:
824  newBlue = alpha;
825  break;
826  default:
827  return nullptr;
828  }
829 
830  switch (a) {
831  case RED:
832  newAlpha = red;
833  break;
834  case GREEN:
835  newAlpha = green;
836  break;
837  case BLUE:
838  newAlpha = blue;
839  break;
840  case ALPHA:
841  newAlpha = alpha;
842  break;
843  default:
844  return nullptr;
845  }
846 
847  *beg = (newAlpha << 24) | (newRed << 16) | (newGreen << 8) | newBlue;
848  }
849 
850  ++beg;
851  }
852  }
853 
854  return nsurf;
855 }
856 
858 {
859  if(surf == nullptr)
860  return nullptr;
861 
862  if(map_rgb.empty()) {
863  return surf;
864  }
865 
866  surface nsurf = surf.clone();
867  if(nsurf == nullptr) {
868  PLAIN_LOG << "failed to make neutral surface";
869  return nullptr;
870  }
871 
872  surface_lock lock(nsurf);
873  uint32_t* beg = lock.pixels();
874  uint32_t* end = beg + nsurf->w*surf->h;
875 
876  while(beg != end) {
877  uint8_t alpha = (*beg) >> 24;
878 
879  // Don't recolor invisible pixels.
880  if(alpha) {
881  // Palette use only RGB channels, so remove alpha
882  uint32_t oldrgb = (*beg) | 0xFF000000;
883 
884  auto i = map_rgb.find(color_t::from_argb_bytes(oldrgb));
885  if(i != map_rgb.end()) {
886  *beg = (alpha << 24) | (i->second.to_argb_bytes() & 0x00FFFFFF);
887  }
888  }
889 
890  ++beg;
891  }
892 
893  return nsurf;
894 }
895 
896 surface brighten_image(const surface &surf, int32_t amount)
897 {
898  if(surf == nullptr) {
899  return nullptr;
900  }
901 
902  surface nsurf = surf.clone();
903 
904  if(nsurf == nullptr) {
905  PLAIN_LOG << "could not make neutral surface...";
906  return nullptr;
907  }
908 
909  {
910  surface_lock lock(nsurf);
911  uint32_t* beg = lock.pixels();
912  uint32_t* end = beg + nsurf->w*surf->h;
913 
914  if (amount < 0) amount = 0;
915  while(beg != end) {
916  uint8_t alpha = (*beg) >> 24;
917 
918  if(alpha) {
919  uint8_t r, g, b;
920  r = (*beg) >> 16;
921  g = (*beg) >> 8;
922  b = (*beg);
923 
924  r = std::min<unsigned>(fixed_point_multiply(r, amount),255);
925  g = std::min<unsigned>(fixed_point_multiply(g, amount),255);
926  b = std::min<unsigned>(fixed_point_multiply(b, amount),255);
927 
928  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
929  }
930 
931  ++beg;
932  }
933  }
934 
935  return nsurf;
936 }
937 
938 void adjust_surface_alpha(surface& surf, uint8_t alpha_mod)
939 {
940  if(surf == nullptr) {
941  return;
942  }
943 
944  SDL_SetSurfaceAlphaMod(surf, alpha_mod);
945 }
946 
947 surface adjust_surface_alpha_add(const surface &surf, int amount)
948 {
949  if(surf== nullptr) {
950  return nullptr;
951  }
952 
953  surface nsurf = surf.clone();
954 
955  if(nsurf == nullptr) {
956  PLAIN_LOG << "could not make neutral surface...";
957  return nullptr;
958  }
959 
960  {
961  surface_lock lock(nsurf);
962  uint32_t* beg = lock.pixels();
963  uint32_t* end = beg + nsurf->w*surf->h;
964 
965  while(beg != end) {
966  uint8_t alpha = (*beg) >> 24;
967 
968  if(alpha) {
969  uint8_t r, g, b;
970  r = (*beg) >> 16;
971  g = (*beg) >> 8;
972  b = (*beg);
973 
974  alpha = uint8_t(std::max<int>(0,std::min<int>(255,static_cast<int>(alpha) + amount)));
975  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
976  }
977 
978  ++beg;
979  }
980  }
981 
982  return nsurf;
983 }
984 
985 surface mask_surface(const surface &surf, const surface &mask, bool* empty_result, const std::string& filename)
986 {
987  if(surf == nullptr) {
988  *empty_result = true;
989  return nullptr;
990  }
991  if(mask == nullptr) {
992  return surf;
993  }
994 
995  surface nsurf = surf.clone();
996  surface nmask = mask.clone();
997 
998  if(nsurf == nullptr || nmask == nullptr) {
999  PLAIN_LOG << "could not make neutral surface...";
1000  return nullptr;
1001  }
1002  if (nsurf->w != nmask->w) {
1003  // we don't support efficiently different width.
1004  // (different height is not a real problem)
1005  // This function is used on all hexes and usually only for that
1006  // so better keep it simple and efficient for the normal case
1007  std::stringstream ss;
1008  ss << "Detected an image with bad dimensions: ";
1009  if(!filename.empty()) ss << filename << ": ";
1010  ss << nsurf->w << "x" << nsurf->h;
1011  PLAIN_LOG << ss.str();
1012  PLAIN_LOG << "It will not be masked, please use: "<< nmask->w << "x" << nmask->h;
1013  return nsurf;
1014  }
1015 
1016  bool empty = true;
1017  {
1018  surface_lock lock(nsurf);
1019  const_surface_lock mlock(nmask);
1020 
1021  uint32_t* beg = lock.pixels();
1022  uint32_t* end = beg + nsurf->w*surf->h;
1023  const uint32_t* mbeg = mlock.pixels();
1024  const uint32_t* mend = mbeg + nmask->w*nmask->h;
1025 
1026  while(beg != end && mbeg != mend) {
1027  uint8_t alpha = (*beg) >> 24;
1028 
1029  if(alpha) {
1030  uint8_t r, g, b;
1031  r = (*beg) >> 16;
1032  g = (*beg) >> 8;
1033  b = (*beg);
1034 
1035  uint8_t malpha = (*mbeg) >> 24;
1036  if (alpha > malpha) {
1037  alpha = malpha;
1038  }
1039  if(alpha)
1040  empty = false;
1041 
1042  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
1043  }
1044 
1045  ++beg;
1046  ++mbeg;
1047  }
1048  }
1049  if(empty_result)
1050  *empty_result = empty;
1051 
1052  return nsurf;
1053 }
1054 
1055 bool in_mask_surface(const surface &surf, const surface &mask)
1056 {
1057  if(surf == nullptr) {
1058  return false;
1059  }
1060  if(mask == nullptr){
1061  return true;
1062  }
1063 
1064  if (surf->w != mask->w || surf->h != mask->h ) {
1065  // not same size, consider it doesn't fit
1066  return false;
1067  }
1068 
1069  surface nsurf = surf.clone();
1070  surface nmask = mask.clone();
1071 
1072  if(nsurf == nullptr || nmask == nullptr) {
1073  PLAIN_LOG << "could not make neutral surface...";
1074  return false;
1075  }
1076 
1077  {
1078  surface_lock lock(nsurf);
1079  const_surface_lock mlock(nmask);
1080 
1081  const uint32_t* mbeg = mlock.pixels();
1082  const uint32_t* mend = mbeg + nmask->w*nmask->h;
1083  uint32_t* beg = lock.pixels();
1084  // no need for 'end', because both surfaces have same size
1085 
1086  while(mbeg != mend) {
1087  uint8_t malpha = (*mbeg) >> 24;
1088  if(malpha == 0) {
1089  uint8_t alpha = (*beg) >> 24;
1090  if (alpha)
1091  return false;
1092  }
1093  ++mbeg;
1094  ++beg;
1095  }
1096  }
1097 
1098  return true;
1099 }
1100 
1101 surface light_surface(const surface &surf, const surface &lightmap)
1102 {
1103  if(surf == nullptr) {
1104  return nullptr;
1105  }
1106  if(lightmap == nullptr) {
1107  return surf;
1108  }
1109 
1110  surface nsurf = surf.clone();
1111 
1112  if(nsurf == nullptr) {
1113  PLAIN_LOG << "could not make neutral surface...";
1114  return nullptr;
1115  }
1116  if (nsurf->w != lightmap->w) {
1117  // we don't support efficiently different width.
1118  // (different height is not a real problem)
1119  // This function is used on all hexes and usually only for that
1120  // so better keep it simple and efficient for the normal case
1121  PLAIN_LOG << "Detected an image with bad dimensions: " << nsurf->w << "x" << nsurf->h;
1122  PLAIN_LOG << "It will not be lighted, please use: "<< lightmap->w << "x" << lightmap->h;
1123  return nsurf;
1124  }
1125  {
1126  surface_lock lock(nsurf);
1127  const_surface_lock llock(lightmap);
1128 
1129  uint32_t* beg = lock.pixels();
1130  uint32_t* end = beg + nsurf->w * nsurf->h;
1131  const uint32_t* lbeg = llock.pixels();
1132  const uint32_t* lend = lbeg + lightmap->w * lightmap->h;
1133 
1134  while(beg != end && lbeg != lend) {
1135  uint8_t alpha = (*beg) >> 24;
1136  if(alpha) {
1137  uint8_t lr, lg, lb;
1138 
1139  lr = (*lbeg) >> 16;
1140  lg = (*lbeg) >> 8;
1141  lb = (*lbeg);
1142 
1143  uint8_t r, g, b;
1144  r = (*beg) >> 16;
1145  g = (*beg) >> 8;
1146  b = (*beg);
1147 
1148  int dr = (static_cast<int>(lr) - 128) * 2;
1149  int dg = (static_cast<int>(lg) - 128) * 2;
1150  int db = (static_cast<int>(lb) - 128) * 2;
1151  //note that r + dr will promote r to int (needed to avoid uint8_t math)
1152  r = std::max<int>(0,std::min<int>(255, r + dr));
1153  g = std::max<int>(0,std::min<int>(255, g + dg));
1154  b = std::max<int>(0,std::min<int>(255, b + db));
1155 
1156  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
1157  }
1158  ++beg;
1159  ++lbeg;
1160  }
1161  }
1162 
1163  return nsurf;
1164 }
1165 
1166 
1167 surface blur_surface(const surface &surf, int depth)
1168 {
1169  if(surf == nullptr) {
1170  return nullptr;
1171  }
1172 
1173  surface res = surf.clone();
1174 
1175  if(res == nullptr) {
1176  PLAIN_LOG << "could not make neutral surface...";
1177  return nullptr;
1178  }
1179 
1180  SDL_Rect rect {0, 0, surf->w, surf->h};
1181  blur_surface(res, rect, depth);
1182 
1183  return res;
1184 }
1185 
1186 void blur_surface(surface& surf, SDL_Rect rect, int depth)
1187 {
1188  if(surf == nullptr) {
1189  return;
1190  }
1191 
1192  const int max_blur = 256;
1193  if(depth > max_blur) {
1194  depth = max_blur;
1195  }
1196 
1197  uint32_t queue[max_blur];
1198  const uint32_t* end_queue = queue + max_blur;
1199 
1200  const uint32_t ff = 0xff;
1201 
1202  const unsigned pixel_offset = rect.y * surf->w + rect.x;
1203 
1204  surface_lock lock(surf);
1205  for(int y = 0; y < rect.h; ++y) {
1206  const uint32_t* front = &queue[0];
1207  uint32_t* back = &queue[0];
1208  uint32_t red = 0, green = 0, blue = 0, avg = 0;
1209  uint32_t* p = lock.pixels() + pixel_offset + y * surf->w;
1210  for(int x = 0; x <= depth && x < rect.w; ++x, ++p) {
1211  red += ((*p) >> 16)&0xFF;
1212  green += ((*p) >> 8)&0xFF;
1213  blue += (*p)&0xFF;
1214  ++avg;
1215  *back++ = *p;
1216  if(back == end_queue) {
1217  back = &queue[0];
1218  }
1219  }
1220 
1221  p = lock.pixels() + pixel_offset + y * surf->w;
1222  for(int x = 0; x < rect.w; ++x, ++p) {
1223  *p = 0xFF000000
1224  | (std::min(red/avg,ff) << 16)
1225  | (std::min(green/avg,ff) << 8)
1226  | std::min(blue/avg,ff);
1227 
1228  if(x >= depth) {
1229  red -= ((*front) >> 16)&0xFF;
1230  green -= ((*front) >> 8)&0xFF;
1231  blue -= *front&0xFF;
1232  --avg;
1233  ++front;
1234  if(front == end_queue) {
1235  front = &queue[0];
1236  }
1237  }
1238 
1239  if(x + depth+1 < rect.w) {
1240  uint32_t* q = p + depth+1;
1241  red += ((*q) >> 16)&0xFF;
1242  green += ((*q) >> 8)&0xFF;
1243  blue += (*q)&0xFF;
1244  ++avg;
1245  *back++ = *q;
1246  if(back == end_queue) {
1247  back = &queue[0];
1248  }
1249  }
1250  }
1251  }
1252 
1253  for(int x = 0; x < rect.w; ++x) {
1254  const uint32_t* front = &queue[0];
1255  uint32_t* back = &queue[0];
1256  uint32_t red = 0, green = 0, blue = 0, avg = 0;
1257  uint32_t* p = lock.pixels() + pixel_offset + x;
1258  for(int y = 0; y <= depth && y < rect.h; ++y, p += surf->w) {
1259  red += ((*p) >> 16)&0xFF;
1260  green += ((*p) >> 8)&0xFF;
1261  blue += *p&0xFF;
1262  ++avg;
1263  *back++ = *p;
1264  if(back == end_queue) {
1265  back = &queue[0];
1266  }
1267  }
1268 
1269  p = lock.pixels() + pixel_offset + x;
1270  for(int y = 0; y < rect.h; ++y, p += surf->w) {
1271  *p = 0xFF000000
1272  | (std::min(red/avg,ff) << 16)
1273  | (std::min(green/avg,ff) << 8)
1274  | std::min(blue/avg,ff);
1275 
1276  if(y >= depth) {
1277  red -= ((*front) >> 16)&0xFF;
1278  green -= ((*front) >> 8)&0xFF;
1279  blue -= *front&0xFF;
1280  --avg;
1281  ++front;
1282  if(front == end_queue) {
1283  front = &queue[0];
1284  }
1285  }
1286 
1287  if(y + depth+1 < rect.h) {
1288  uint32_t* q = p + (depth+1)*surf->w;
1289  red += ((*q) >> 16)&0xFF;
1290  green += ((*q) >> 8)&0xFF;
1291  blue += (*q)&0xFF;
1292  ++avg;
1293  *back++ = *q;
1294  if(back == end_queue) {
1295  back = &queue[0];
1296  }
1297  }
1298  }
1299  }
1300 }
1301 
1302 surface blur_alpha_surface(const surface &surf, int depth)
1303 {
1304  if(surf == nullptr) {
1305  return nullptr;
1306  }
1307 
1308  surface res = surf.clone();
1309 
1310  if(res == nullptr) {
1311  PLAIN_LOG << "could not make neutral surface...";
1312  return nullptr;
1313  }
1314 
1315  const int max_blur = 256;
1316  if(depth > max_blur) {
1317  depth = max_blur;
1318  }
1319 
1320  struct Pixel{
1321  uint8_t alpha;
1322  uint8_t red;
1323  uint8_t green;
1324  uint8_t blue;
1325  Pixel(uint32_t* p)
1326  : alpha(((*p) >> 24)&0xFF)
1327  , red(((*p) >> 16)&0xFF)
1328  , green(((*p) >> 8)&0xFF)
1329  , blue((*p)&0xFF) {}
1330  };
1331  struct Average{
1332  uint32_t alpha;
1333  uint32_t red;
1334  uint32_t green;
1335  uint32_t blue;
1336  Average() : alpha(), red(), green(), blue()
1337  {}
1338  Average& operator+=(const Pixel& pix){
1339  red += pix.alpha * pix.red;
1340  green += pix.alpha * pix.green;
1341  blue += pix.alpha * pix.blue;
1342  alpha += pix.alpha;
1343  return *this;
1344  }
1345  Average& operator-=(const Pixel& pix){
1346  red -= pix.alpha * pix.red;
1347  green -= pix.alpha * pix.green;
1348  blue -= pix.alpha * pix.blue;
1349  alpha -= pix.alpha;
1350  return *this;
1351  }
1352  uint32_t operator()(unsigned num){
1353  const uint32_t ff = 0xff;
1354  if(!alpha){
1355  return 0;
1356  }
1357  return (std::min(alpha/num,ff) << 24)
1358  | (std::min(red/alpha,ff) << 16)
1359  | (std::min(green/alpha,ff) << 8)
1360  | std::min(blue/alpha,ff);
1361  }
1362  };
1363 
1364  boost::circular_buffer<Pixel> queue(depth*2+1);
1365 
1366  surface_lock lock(res);
1367  int x, y;
1368  // Iterate over rows, blurring each row horizontally
1369  for(y = 0; y < res->h; ++y) {
1370  // Sum of pixel values stored here
1371  Average avg;
1372 
1373  // Preload the first depth+1 pixels
1374  uint32_t* p = lock.pixels() + y*res->w;
1375  for(x = 0; x <= depth && x < res->w; ++x, ++p) {
1376  assert(!queue.full());
1377  queue.push_back(Pixel{p});
1378  avg += queue.back();
1379  }
1380 
1381  // This is the actual inner loop
1382  p = lock.pixels() + y*res->w;
1383  for(x = 0; x < res->w; ++x, ++p) {
1384  // Write the current average
1385  const uint32_t num = queue.size();
1386  *p = avg(num);
1387 
1388  // Unload earlier pixels that are now too far away
1389  if(x >= depth) {
1390  avg -= queue.front();
1391  assert(!queue.empty());
1392  queue.pop_front();
1393  }
1394 
1395  // Add new pixels
1396  if(x + depth+1 < res->w) {
1397  uint32_t* q = p + depth+1;
1398  assert(!queue.full());
1399  queue.push_back(Pixel{q});
1400  avg += queue.back();
1401  }
1402  }
1403  assert(static_cast<int>(queue.size()) == std::min(depth, res->w));
1404  queue.clear();
1405  }
1406 
1407  // Iterate over columns, blurring each column vertically
1408  for(x = 0; x < res->w; ++x) {
1409  // Sum of pixel values stored here
1410  Average avg;
1411 
1412  // Preload the first depth+1 pixels
1413  uint32_t* p = lock.pixels() + x;
1414  for(y = 0; y <= depth && y < res->h; ++y, p += res->w) {
1415  assert(!queue.full());
1416  queue.push_back(Pixel{p});
1417  avg += queue.back();
1418  }
1419 
1420  // This is the actual inner loop
1421  p = lock.pixels() + x;
1422  for(y = 0; y < res->h; ++y, p += res->w) {
1423  // Write the current average
1424  const uint32_t num = queue.size();
1425  *p = avg(num);
1426 
1427  // Unload earlier pixels that are now too far away
1428  if(y >= depth) {
1429  avg -= queue.front();
1430  assert(!queue.empty());
1431  queue.pop_front();
1432  }
1433 
1434  // Add new pixels
1435  if(y + depth+1 < res->h) {
1436  uint32_t* q = p + (depth+1)*res->w;
1437  assert(!queue.full());
1438  queue.push_back(Pixel{q});
1439  avg += queue.back();
1440  }
1441  }
1442  assert(static_cast<int>(queue.size()) == std::min(depth, res->h));
1443  queue.clear();
1444  }
1445 
1446  return res;
1447 }
1448 
1449 surface cut_surface(const surface &surf, const SDL_Rect& r)
1450 {
1451  if(surf == nullptr)
1452  return nullptr;
1453 
1454  surface res(r.w, r.h);
1455 
1456  if(res == nullptr) {
1457  PLAIN_LOG << "Could not create a new surface in cut_surface()";
1458  return nullptr;
1459  }
1460 
1461  std::size_t sbpp = surf->format->BytesPerPixel;
1462  std::size_t spitch = surf->pitch;
1463  std::size_t rbpp = res->format->BytesPerPixel;
1464  std::size_t rpitch = res->pitch;
1465 
1466  // compute the areas to copy
1467  SDL_Rect src_rect = r;
1468  SDL_Rect dst_rect { 0, 0, r.w, r.h };
1469 
1470  if (src_rect.x < 0) {
1471  if (src_rect.x + src_rect.w <= 0)
1472  return res;
1473  dst_rect.x -= src_rect.x;
1474  dst_rect.w += src_rect.x;
1475  src_rect.w += src_rect.x;
1476  src_rect.x = 0;
1477  }
1478  if (src_rect.y < 0) {
1479  if (src_rect.y + src_rect.h <= 0)
1480  return res;
1481  dst_rect.y -= src_rect.y;
1482  dst_rect.h += src_rect.y;
1483  src_rect.h += src_rect.y;
1484  src_rect.y = 0;
1485  }
1486 
1487  if(src_rect.x >= surf->w || src_rect.y >= surf->h)
1488  return res;
1489 
1490  const_surface_lock slock(surf);
1491  surface_lock rlock(res);
1492 
1493  const uint8_t* src = reinterpret_cast<const uint8_t *>(slock.pixels());
1494  uint8_t* dest = reinterpret_cast<uint8_t *>(rlock.pixels());
1495 
1496  for(int y = 0; y < src_rect.h && (src_rect.y + y) < surf->h; ++y) {
1497  const uint8_t* line_src = src + (src_rect.y + y) * spitch + src_rect.x * sbpp;
1498  uint8_t* line_dest = dest + (dst_rect.y + y) * rpitch + dst_rect.x * rbpp;
1499  std::size_t size = src_rect.w + src_rect.x <= surf->w ? src_rect.w : surf->w - src_rect.x;
1500 
1501  assert(rpitch >= src_rect.w * rbpp);
1502  memcpy(line_dest, line_src, size * rbpp);
1503  }
1504 
1505  return res;
1506 }
1508  const surface &surf
1509  , const double amount
1510  , const color_t color)
1511 {
1512  if(surf== nullptr) {
1513  return nullptr;
1514  }
1515 
1516  surface nsurf = surf.clone();
1517 
1518  if(nsurf == nullptr) {
1519  PLAIN_LOG << "could not make neutral surface...";
1520  return nullptr;
1521  }
1522 
1523  {
1524  surface_lock lock(nsurf);
1525  uint32_t* beg = lock.pixels();
1526  uint32_t* end = beg + nsurf->w*surf->h;
1527 
1528  uint16_t ratio = amount * 256;
1529  const uint16_t red = ratio * color.r;
1530  const uint16_t green = ratio * color.g;
1531  const uint16_t blue = ratio * color.b;
1532  ratio = 256 - ratio;
1533 
1534  while(beg != end) {
1535  uint8_t a = static_cast<uint8_t>(*beg >> 24);
1536  uint8_t r = (ratio * static_cast<uint8_t>(*beg >> 16) + red) >> 8;
1537  uint8_t g = (ratio * static_cast<uint8_t>(*beg >> 8) + green) >> 8;
1538  uint8_t b = (ratio * static_cast<uint8_t>(*beg) + blue) >> 8;
1539 
1540  *beg = (a << 24) | (r << 16) | (g << 8) | b;
1541 
1542  ++beg;
1543  }
1544  }
1545 
1546  return nsurf;
1547 }
1548 
1549 /* Simplified RotSprite algorithm.
1550  * http://en.wikipedia.org/wiki/Image_scaling#RotSprite
1551  * Lifted from: http://github.com/salmonmoose/SpriteRotator
1552  * 1) Zoom the source image by a certain factor.
1553  * 2) Scan the zoomed source image at every step=offset and put it in the result. */
1554 surface rotate_any_surface(const surface& surf, float angle, int zoom, int offset)
1555 {
1556  int src_w, src_h, dst_w, dst_h;
1557  float min_x, min_y, sine, cosine;
1558  {
1559  float max_x, max_y;
1560  // convert angle to radiant (angle * 2 * PI) / 360
1561  const float radians = angle * boost::math::constants::pi<float>() / 180;
1562  cosine = std::cos(radians);
1563  sine = std::sin(radians);
1564  // calculate the size of the dst image
1565  src_w = surf->w * zoom;
1566  src_h = surf->h * zoom;
1567  /* See http://en.wikipedia.org/wiki/Rotation_(mathematics) */
1568  const float point_1x = src_h * -sine;
1569  const float point_1y = src_h * cosine;
1570  const float point_2x = src_w * cosine - src_h * sine;
1571  const float point_2y = src_h * cosine + src_w * sine;
1572  const float point_3x = src_w * cosine;
1573  const float point_3y = src_w * sine;
1574  /* After the rotation, the new image has different dimensions.
1575  * E.g.: The maximum height equals the former diagonal in case the angle is 45, 135, 225 or 315 degree.
1576  * See http://en.wikipedia.org/wiki/File:Rotation_illustration2.svg to get the idea. */
1577  min_x = std::min(0.0F, std::min(point_1x, std::min(point_2x, point_3x)));
1578  min_y = std::min(0.0F, std::min(point_1y, std::min(point_2y, point_3y)));
1579  max_x = (angle > 90 && angle < 180) ? 0 : std::max(point_1x, std::max(point_2x, point_3x));
1580  max_y = (angle > 180 && angle < 270) ? 0 : std::max(point_1y, std::max(point_2y, point_3y));
1581  dst_w = static_cast<int>(ceil(std::abs(max_x) - min_x)) / zoom;
1582  dst_h = static_cast<int>(ceil(std::abs(max_y) - min_y)) / zoom;
1583  }
1584  surface dst(dst_w, dst_h);
1585  {
1586  surface_lock dst_lock(dst);
1587  const surface src = scale_surface(surf, src_w, src_h);
1588  const_surface_lock src_lock(src);
1589  const float scale = 1.f / zoom;
1590  const int max_x = dst_w * zoom;
1591  const int max_y = dst_h * zoom;
1592  /* Loop through the zoomed src image,
1593  * take every pixel in steps with offset distance and place it in the dst image. */
1594  for (int x = 0; x < max_x; x += offset)
1595  for (int y = 0; y < max_y; y += offset) {
1596  // calculate the src pixel that fits in the dst
1597  const float source_x = (x + min_x)*cosine + (y + min_y)*sine;
1598  const float source_y = (y + min_y)*cosine - (x + min_x)*sine;
1599  // if the pixel exists on the src surface
1600  if (source_x >= 0 && source_x < src_w
1601  && source_y >= 0 && source_y < src_h)
1602  // get it from the src surface and place it on the dst surface
1603  put_pixel(dst, dst_lock, x*scale , y*scale, // multiply with scale
1604  get_pixel(src, src_lock, source_x, source_y));
1605  }
1606  }
1607 
1608  return dst;
1609 }
1610 
1611 void put_pixel(const surface& surf, surface_lock& surf_lock, int x, int y, uint32_t pixel)
1612 {
1613  const int bpp = surf->format->BytesPerPixel;
1614  /* dst is the address to the pixel we want to set */
1615  uint8_t* const dst = reinterpret_cast<uint8_t*>(surf_lock.pixels()) + y * surf->pitch + x * bpp;
1616  switch (bpp) {
1617  case 1:
1618  *dst = pixel;
1619  break;
1620  case 2:
1621  *reinterpret_cast<uint16_t*>(dst) = pixel;
1622  break;
1623  case 3:
1624 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
1625  dst[0] = (pixel >> 16) & 0xff;
1626  dst[1] = (pixel >> 8) & 0xff;
1627  dst[2] = pixel & 0xff;
1628 #else
1629  dst[0] = pixel & 0xff;
1630  dst[1] = (pixel >> 8) & 0xff;
1631  dst[2] = (pixel >> 16) & 0xff;
1632 #endif
1633  break;
1634  case 4:
1635  *reinterpret_cast<uint32_t*>(dst) = pixel;
1636  break;
1637  default:
1638  break;
1639  }
1640 }
1641 
1642 uint32_t get_pixel(const surface& surf, const const_surface_lock& surf_lock, int x, int y)
1643 {
1644  const int bpp = surf->format->BytesPerPixel;
1645  /* p is the address to the pixel we want to retrieve */
1646  const uint8_t* const src = reinterpret_cast<const uint8_t*>(surf_lock.pixels()) + y * surf->pitch + x * bpp;
1647  switch (bpp) {
1648  case 1:
1649  return *src;
1650  case 2:
1651  return *reinterpret_cast<const uint16_t*>(src);
1652  case 3:
1653 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
1654  return src[0] << 16 | src[1] << 8 | src[2];
1655 #else
1656  return src[0] | src[1] << 8 | src[2] << 16;
1657 #endif
1658  case 4:
1659  return *reinterpret_cast<const uint32_t*>(src);
1660  }
1661  return 0;
1662 }
1663 
1664 // Rotates a surface 180 degrees.
1666 {
1667  if ( surf == nullptr )
1668  return nullptr;
1669 
1670  // Work with a "neutral" surface.
1671  surface nsurf = surf.clone();
1672 
1673  if ( nsurf == nullptr ) {
1674  PLAIN_LOG << "could not make neutral surface...";
1675  return nullptr;
1676  }
1677 
1678  {// Code block to limit the scope of the surface lock.
1679  surface_lock lock(nsurf);
1680  uint32_t* const pixels = lock.pixels();
1681 
1682  // Swap pixels in the upper half of the image with
1683  // those in the lower half.
1684  for (int y=0; y != nsurf->h/2; ++y) {
1685  for(int x=0; x != nsurf->w; ++x) {
1686  const int index1 = y*nsurf->w + x;
1687  const int index2 = (nsurf->h-y)*nsurf->w - x - 1;
1688  std::swap(pixels[index1],pixels[index2]);
1689  }
1690  }
1691 
1692  if ( is_odd(nsurf->h) ) {
1693  // The middle row still needs to be processed.
1694  for (int x=0; x != nsurf->w/2; ++x) {
1695  const int index1 = (nsurf->h/2)*nsurf->w + x;
1696  const int index2 = (nsurf->h/2)*nsurf->w + (nsurf->w - x - 1);
1697  std::swap(pixels[index1],pixels[index2]);
1698  }
1699  }
1700  }
1701 
1702  return nsurf;
1703 }
1704 
1705 
1706 // Rotates a surface 90 degrees, either clockwise or counter-clockwise.
1707 surface rotate_90_surface(const surface &surf, bool clockwise)
1708 {
1709  if ( surf == nullptr )
1710  return nullptr;
1711 
1712  surface dst(surf->h, surf->w); // Flipped dimensions.
1713 
1714  if ( surf == nullptr || dst == nullptr ) {
1715  PLAIN_LOG << "could not make neutral surface...";
1716  return nullptr;
1717  }
1718 
1719  {// Code block to limit the scope of the surface locks.
1720  const_surface_lock src_lock(surf);
1721  surface_lock dst_lock(dst);
1722 
1723  const uint32_t* const src_pixels = src_lock.pixels();
1724  uint32_t* const dst_pixels = dst_lock.pixels();
1725 
1726  // Copy the pixels.
1727  for(int y = 0; y != surf->h; ++y) {
1728  for ( int x = 0; x != surf->w; ++x ) {
1729  const int src_index = y*surf->w + x;
1730  const int dst_index = clockwise ?
1731  x*dst->w + (dst->w-1-y) :
1732  (dst->h-1-x)*dst->w + y;
1733  dst_pixels[dst_index] = src_pixels[src_index];
1734  }
1735  }
1736  }
1737 
1738  return dst;
1739 }
1740 
1741 
1743 {
1744  if(surf == nullptr) {
1745  return nullptr;
1746  }
1747 
1748  surface nsurf = surf.clone();
1749 
1750  if(nsurf == nullptr) {
1751  PLAIN_LOG << "could not make neutral surface...";
1752  return nullptr;
1753  }
1754 
1755  {
1756  surface_lock lock(nsurf);
1757  uint32_t* const pixels = lock.pixels();
1758 
1759  for(int y = 0; y != nsurf->h; ++y) {
1760  for(int x = 0; x != nsurf->w/2; ++x) {
1761  const int index1 = y*nsurf->w + x;
1762  const int index2 = (y+1)*nsurf->w - x - 1;
1763  std::swap(pixels[index1],pixels[index2]);
1764  }
1765  }
1766  }
1767 
1768  return nsurf;
1769 }
1770 
1772 {
1773  if(surf == nullptr) {
1774  return nullptr;
1775  }
1776 
1777  surface nsurf = surf.clone();
1778 
1779  if(nsurf == nullptr) {
1780  PLAIN_LOG << "could not make neutral surface...";
1781  return nullptr;
1782  }
1783 
1784  {
1785  surface_lock lock(nsurf);
1786  uint32_t* const pixels = lock.pixels();
1787 
1788  for(int x = 0; x != nsurf->w; ++x) {
1789  for(int y = 0; y != nsurf->h/2; ++y) {
1790  const int index1 = y*nsurf->w + x;
1791  const int index2 = (nsurf->h-y-1)*surf->w + x;
1792  std::swap(pixels[index1],pixels[index2]);
1793  }
1794  }
1795  }
1796 
1797  return nsurf;
1798 }
1799 
1800 surface get_surface_portion(const surface &src, SDL_Rect &area)
1801 {
1802  if (src == nullptr) {
1803  return nullptr;
1804  }
1805 
1806  // Check if there is something in the portion
1807  if(area.x >= src->w || area.y >= src->h || area.x + area.w < 0 || area.y + area.h < 0) {
1808  return nullptr;
1809  }
1810 
1811  if(area.x + area.w > src->w) {
1812  area.w = src->w - area.x;
1813  }
1814  if(area.y + area.h > src->h) {
1815  area.h = src->h - area.y;
1816  }
1817 
1818  // use same format as the source (almost always the screen)
1819  surface dst(area.w, area.h);
1820 
1821  if(dst == nullptr) {
1822  PLAIN_LOG << "Could not create a new surface in get_surface_portion()";
1823  return nullptr;
1824  }
1825 
1826  // Blit to dst with BLENDMODE_NONE, then reset src blend mode.
1827  SDL_BlendMode src_blend;
1828  SDL_GetSurfaceBlendMode(src, &src_blend);
1829  SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
1830  SDL_BlitSurface(src, &area, dst, nullptr);
1831  SDL_SetSurfaceBlendMode(src, src_blend);
1832 
1833  return dst;
1834 }
1835 
1836 namespace {
1837 
1838 struct not_alpha
1839 {
1840  not_alpha() {}
1841 
1842  // we assume neutral format
1843  bool operator()(uint32_t pixel) const {
1844  uint8_t alpha = pixel >> 24;
1845  return alpha != 0x00;
1846  }
1847 };
1848 
1849 }
1851 {
1852  if(surf == nullptr)
1853  return nullptr;
1854 
1855  surface nsurf = surf.clone();
1856  if(nsurf == nullptr) {
1857  PLAIN_LOG << "failed to make neutral surface";
1858  return nullptr;
1859  }
1860 
1861  SDL_Rect res {0,0,0,0};
1862  const not_alpha calc;
1863 
1864  surface_lock lock(nsurf);
1865  const uint32_t* const pixels = lock.pixels();
1866 
1867  int n;
1868  for(n = 0; n != nsurf->h; ++n) {
1869  const uint32_t* const start_row = pixels + n*nsurf->w;
1870  const uint32_t* const end_row = start_row + nsurf->w;
1871 
1872  if(std::find_if(start_row,end_row,calc) != end_row)
1873  break;
1874  }
1875 
1876  res.y = n;
1877 
1878  for(n = 0; n != nsurf->h-res.y; ++n) {
1879  const uint32_t* const start_row = pixels + (nsurf->h-n-1)*surf->w;
1880  const uint32_t* const end_row = start_row + nsurf->w;
1881 
1882  if(std::find_if(start_row,end_row,calc) != end_row)
1883  break;
1884  }
1885 
1886  // The height is the height of the surface,
1887  // minus the distance from the top and
1888  // the distance from the bottom.
1889  res.h = nsurf->h - res.y - n;
1890 
1891  for(n = 0; n != nsurf->w; ++n) {
1892  int y;
1893  for(y = 0; y != nsurf->h; ++y) {
1894  const uint32_t pixel = pixels[y*nsurf->w + n];
1895  if(calc(pixel))
1896  break;
1897  }
1898 
1899  if(y != nsurf->h)
1900  break;
1901  }
1902 
1903  res.x = n;
1904 
1905  for(n = 0; n != nsurf->w-res.x; ++n) {
1906  int y;
1907  for(y = 0; y != nsurf->h; ++y) {
1908  const uint32_t pixel = pixels[y*nsurf->w + surf->w - n - 1];
1909  if(calc(pixel))
1910  break;
1911  }
1912 
1913  if(y != nsurf->h)
1914  break;
1915  }
1916 
1917  res.w = nsurf->w - res.x - n;
1918 
1919  surface cropped = get_surface_portion(nsurf, res);
1920  if(cropped && res.w > 0 && res.h > 0) {
1921  surface scaled = scale_surface(cropped, res.w, res.h);
1922  if(scaled) {
1923  return scaled;
1924  }
1925  }
1926 
1927  ERR_DP << "Failed to either crop or scale the surface";
1928  return nsurf;
1929 }
double g
Definition: astarsearch.cpp:63
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:123
pixel_t * pixels() const
Definition: surface.hpp:142
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
Represents version numbers.
std::unordered_map< color_t, color_t > color_range_map
Definition: color_range.hpp:30
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1347
std::size_t i
Definition: function.cpp:968
int w
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:295
constexpr int fixed_point_to_int(int32_t n)
If positive, just bit shift.
Definition: math.hpp:219
constexpr bool is_odd(T num)
Definition: math.hpp:36
constexpr unsigned fixed_point_multiply(int32_t n1, int32_t n2)
Definition: math.hpp:197
constexpr int32_t fixed_point_divide(int n1, int n2)
Definition: math.hpp:207
Definition: pump.hpp:41
version_info get_version()
Returns the runtime SDL version.
Definition: utils.cpp:36
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:43
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
void nearestNeighborScale(const uint32_t *src, int srcWidth, int srcHeight, uint32_t *trg, int trgWidth, int trgHeight)
Definition: xbrz.hpp:100
void scale(size_t factor, const uint32_t *src, uint32_t *trg, int srcWidth, int srcHeight, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Definition: xbrz.cpp:1189
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition: color.hpp:126
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
mock_party p
static map_location::DIRECTION n
static map_location::DIRECTION s
constexpr uint32_t red
Definition: test_sdl.cpp:24
constexpr uint32_t green
Definition: test_sdl.cpp:25
constexpr uint32_t blue
Definition: test_sdl.cpp:26
surface alpha_to_greyscale(const surface &surf)
Definition: utils.cpp:662
surface blur_alpha_surface(const surface &surf, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:1302
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:447
surface recolor_image(surface surf, const color_range_map &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition: utils.cpp:857
surface negative_image(const surface &surf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:619
surface swap_channels_image(const surface &surf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:755
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1101
surface shadow_image(const surface &surf, int scale)
create an heavy shadow of the image, by blurring, increasing alpha and darkening
Definition: utils.cpp:718
surface scale_surface_nn(const surface &surf, int w, int h)
Scale a surface using the nearest neighbor algorithm (provided by xBRZ lib)
Definition: utils.cpp:94
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:260
surface get_non_transparent_portion(const surface &surf)
Definition: utils.cpp:1850
void put_pixel(const surface &surf, surface_lock &surf_lock, int x, int y, uint32_t pixel)
Helper methods for setting/getting a single pixel in an image.
Definition: utils.cpp:1611
surface adjust_surface_alpha_add(const surface &surf, int amount)
Definition: utils.cpp:947
surface flip_surface(const surface &surf)
Definition: utils.cpp:1742
void adjust_surface_alpha(surface &surf, uint8_t alpha_mod)
Definition: utils.cpp:938
surface sepia_image(const surface &surf)
Definition: utils.cpp:577
surface blur_surface(const surface &surf, int depth)
Cross-fades a surface.
Definition: utils.cpp:1167
surface blend_surface(const surface &surf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1507
uint32_t get_pixel(const surface &surf, const const_surface_lock &surf_lock, int x, int y)
Definition: utils.cpp:1642
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1449
surface wipe_alpha(const surface &surf)
Definition: utils.cpp:690
static lg::log_domain log_display("display")
surface flop_surface(const surface &surf)
Definition: utils.cpp:1771
surface brighten_image(const surface &surf, int32_t amount)
Definition: utils.cpp:896
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:1800
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:57
#define ERR_DP
Definition: utils.cpp:34
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:397
surface monochrome_image(const surface &surf, const int threshold)
Definition: utils.cpp:537
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1707
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1665
surface greyscale_image(const surface &surf)
Definition: utils.cpp:492
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:131
surface mask_surface(const surface &surf, const surface &mask, bool *empty_result, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:985
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1554
bool in_mask_surface(const surface &surf, const surface &mask)
Check if a surface fit into a mask.
Definition: utils.cpp:1055
channel
Definition: utils.hpp:122
@ BLUE
Definition: utils.hpp:122
@ ALPHA
Definition: utils.hpp:122
@ GREEN
Definition: utils.hpp:122
@ RED
Definition: utils.hpp:122
#define e
#define h
#define a
#define b