The Battle for Wesnoth  1.19.16+dev
image_modifications.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2025
3  by Iris Morelle <shadowm2006@gmail.com>
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 #include "image_modifications.hpp"
17 
18 #include <utility>
19 
20 #include "color.hpp"
21 #include "config.hpp"
22 #include "game_config.hpp"
23 #include "picture.hpp"
24 #include "lexical_cast.hpp"
25 #include "log.hpp"
27 #include "team.hpp"
28 #include "utils/from_chars.hpp"
29 
30 #include "formula/formula.hpp"
32 
33 #define GETTEXT_DOMAIN "wesnoth-lib"
34 
35 static lg::log_domain log_display("display");
36 #define ERR_DP LOG_STREAM(err, log_display)
37 
38 namespace image {
39 
40 void modification_queue::push(std::unique_ptr<modification>&& mod)
41 {
42  priorities_[mod->priority()].push_back(std::move(mod));
43 }
44 
46 {
47  auto top_pair = priorities_.begin();
48  auto& mod_list = top_pair->second;
49 
50  // Erase the top element.
51  mod_list.erase(mod_list.begin());
52 
53  // We need to keep the map clean.
54  if(mod_list.empty()) {
55  priorities_.erase(top_pair);
56  }
57 }
58 
59 std::size_t modification_queue::size() const
60 {
61  std::size_t count = 0;
62  for(const auto& [priority, mods] : priorities_) {
63  count += mods.size();
64  }
65 
66  return count;
67 }
68 
70 {
71  return *priorities_.begin()->second.front();
72 }
73 
74 
75 namespace {
76 
77 /** A function used to parse modification arguments */
78 using mod_parser = std::function<std::unique_ptr<modification>(std::string_view)>;
79 
80 /** A map of all registered mod parsers
81  *
82  * The mapping is between the modification name and the parser function pointer
83  * An example of an entry would be "TC" -> &parse_TC_mod
84  */
85 std::map<std::string, mod_parser, std::less<>> mod_parsers;
86 
87 /** Decodes a single modification using an appropriate mod_parser
88  *
89  * @param encoded_mod A string representing a single modification
90  *
91  * @return A pointer to the decoded modification object
92  * @retval nullptr if the string is invalid or a parser isn't found
93  */
94 std::unique_ptr<modification> decode_modification(const std::string& encoded_mod)
95 {
96  const std::vector<std::string> split = utils::parenthetical_split(encoded_mod);
97 
98  if(split.size() != 2) {
99  ERR_DP << "error parsing image modifications: " << encoded_mod;
100  return nullptr;
101  }
102 
103  const std::string& mod_type = split[0];
104  const std::string& args = split[1];
105 
106  if(const auto parser = mod_parsers.find(mod_type); parser != mod_parsers.end()) {
107  return std::invoke(parser->second, args);
108  } else {
109  ERR_DP << "unknown image function in path: " << mod_type;
110  return nullptr;
111  }
112 }
113 
114 } // end anon namespace
115 
116 
117 modification::imod_exception::imod_exception(const std::stringstream& message_stream)
118  : message(message_stream.str())
119 {
120  this->store();
121 }
122 
124  : message(message)
125 {
126  this->store();
127 }
128 
129 /** Decodes the modification string
130  *
131  * Important:
132  * It creates new objects which need to be deleted after use
133  *
134  * @param encoded_mods A string representing any number of modifications
135  *
136  * @return A modification_queue filled with decoded modification pointers
137  */
138 modification_queue modification::decode(const std::string& encoded_mods)
139 {
140  modification_queue mods;
141 
142  for(const std::string& encoded_mod : utils::parenthetical_split(encoded_mods, '~')) {
143  if(auto mod = decode_modification(encoded_mod)) {
144  mods.push(std::move(mod));
145  }
146  }
147 
148  return mods;
149 }
150 
152 {
153  recolor_image(src, rc_map_);
154 }
155 
157 {
158  if(horiz_ && vert_ ) {
159  // Slightly faster than doing both a flip and a flop.
161  } else if(horiz_) {
162  flip_surface(src);
163  } else if(vert_) {
164  flop_surface(src);
165  }
166 }
167 
169 {
170  // Convert the number of degrees to the interval [0,360].
171  const int normalized = degrees_ >= 0 ?
172  degrees_ - 360 * (degrees_ / 360) :
173  degrees_ + 360 * (1 + (-degrees_) / 360); // In case compilers disagree as to what -90/360 is.
174 
175  switch ( normalized )
176  {
177  case 0:
178  return;
179  case 90:
180  src = rotate_90_surface(src, true);
181  return;
182  case 180:
184  return;
185  case 270:
186  src = rotate_90_surface(src, false);
187  return;
188  case 360:
189  return;
190  }
191 
192  src = rotate_any_surface(src, normalized, zoom_, offset_);
193 }
194 
196 {
198 }
199 
201 {
203  if(src_rect.w == src->w && src_rect.h == src->h) {
204  return;
205  }
206 
207  if(surface cropped = get_surface_portion(src, src_rect)) {
208  src = cropped;
209  } else {
210  ERR_DP << "Failed to either crop or scale the surface";
211  }
212 }
213 
215 {
216  monochrome_image(src, threshold_);
217 }
218 
220 {
221  sepia_image(src);
222 }
223 
225 {
226  negative_image(src, red_, green_, blue_);
227 }
228 
230 {
232 }
233 
235 {
236  wipe_alpha(src);
237 }
238 
239 // TODO: Is this useful enough to move into formula/callable_objects?
241 {
242 public:
243  pixel_callable(std::size_t index, color_t clr, int w, int h)
244  : color_callable(clr), coord(), w(w), h(h)
245  {
246  coord.x = index % w;
247  coord.y = index / w;
248  }
249 
250  void get_inputs(wfl::formula_input_vector& inputs) const override
251  {
252  color_callable::get_inputs(inputs);
253  add_input(inputs, "x");
254  add_input(inputs, "y");
255  add_input(inputs, "u");
256  add_input(inputs, "v");
257  add_input(inputs, "height");
258  add_input(inputs, "width");
259  }
260 
261  wfl::variant get_value(const std::string& key) const override
262  {
263  using wfl::variant;
264  if(key == "x") {
265  return variant(coord.x);
266  } else if(key == "y") {
267  return variant(coord.y);
268  } else if(key == "width") {
269  return variant(w);
270  } else if(key == "height") {
271  return variant(h);
272  } else if(key == "u") {
273  return variant(coord.x / static_cast<float>(w));
274  } else if(key == "v") {
275  return variant(coord.y / static_cast<float>(h));
276  }
277 
278  return color_callable::get_value(key);
279  }
280 
281 private:
283  int w, h;
284 };
285 
287 {
288  if(src) {
289  wfl::formula new_alpha(formula_);
290 
291  surface_lock lock(src);
292  std::size_t index{0};
293 
294  for(auto& pixel : lock.pixel_span()) {
295  auto color = color_t::from_argb_bytes(pixel);
296 
297  auto px = pixel_callable{index++, color, src->w, src->h};
298  color.a = std::min<unsigned>(new_alpha.evaluate(px).as_int(), 255);
299 
300  pixel = color.to_argb_bytes();
301  }
302  }
303 }
304 
306 {
307  if(src) {
308  wfl::formula new_r(formulas_[0]);
309  wfl::formula new_g(formulas_[1]);
310  wfl::formula new_b(formulas_[2]);
311  wfl::formula new_a(formulas_[3]);
312 
313  surface_lock lock(src);
314  std::size_t index{0};
315 
316  for(auto& pixel : lock.pixel_span()) {
317  auto color = color_t::from_argb_bytes(pixel);
318 
319  auto px = pixel_callable{index++, color, src->w, src->h};
320  color.r = std::min<unsigned>(new_r.evaluate(px).as_int(), 255);
321  color.g = std::min<unsigned>(new_g.evaluate(px).as_int(), 255);
322  color.b = std::min<unsigned>(new_b.evaluate(px).as_int(), 255);
323  color.a = std::min<unsigned>(new_a.evaluate(px).as_int(), 255);
324 
325  pixel = color.to_argb_bytes();
326  }
327  }
328 }
329 
331 {
332  rect area = slice_;
333  if(area.w == 0) {
334  area.w = src->w;
335  }
336 
337  if(area.h == 0) {
338  area.h = src->h;
339  }
340 
341  src = cut_surface(src, area);
342 }
343 
345 {
346  if(x_ >= src->w) {
347  std::stringstream sstr;
348  sstr << "~BLIT(): x-coordinate '"
349  << x_ << "' larger than destination image's width '"
350  << src->w << "' no blitting performed.\n";
351 
352  throw imod_exception(sstr);
353  }
354 
355  if(y_ >= src->h) {
356  std::stringstream sstr;
357  sstr << "~BLIT(): y-coordinate '"
358  << y_ << "' larger than destination image's height '"
359  << src->h << "' no blitting performed.\n";
360 
361  throw imod_exception(sstr);
362  }
363 
364  if(surf_->w + x_ < 0) {
365  std::stringstream sstr;
366  sstr << "~BLIT(): offset and width '"
367  << x_ + surf_->w << "' less than zero no blitting performed.\n";
368 
369  throw imod_exception(sstr);
370  }
371 
372  if(surf_->h + y_ < 0) {
373  std::stringstream sstr;
374  sstr << "~BLIT(): offset and height '"
375  << y_ + surf_->h << "' less than zero no blitting performed.\n";
376 
377  throw imod_exception(sstr);
378  }
379 
380  rect r {x_, y_, 0, 0};
381  sdl_blit(surf_, nullptr, src, &r);
382 }
383 
385 {
386  if(src->w == mask_->w && src->h == mask_->h && x_ == 0 && y_ == 0) {
387  mask_surface(src, mask_);
388  return;
389  }
390 
391  rect r {x_, y_, 0, 0};
392  surface new_mask(src->w, src->h);
393  sdl_blit(mask_, nullptr, new_mask, &r);
394  mask_surface(src, new_mask);
395 }
396 
398 {
399  if(src == nullptr) { return; }
400 
401  // light_surface wants a neutral surface having same dimensions
402  if(surf_->w != src->w || surf_->h != src->h) {
403  light_surface(src, scale_surface(surf_, src->w, src->h));
404  } else {
405  light_surface(src, surf_);
406  }
407 }
408 
410 {
411  point size = target_size_;
412 
413  if(size.x <= 0) {
414  size.x = src->w;
415  } else if(flags_ & X_BY_FACTOR) {
416  size.x = src->w * (static_cast<double>(size.x) / 100);
417  }
418 
419  if(size.y <= 0) {
420  size.y = src->h;
421  } else if(flags_ & Y_BY_FACTOR) {
422  size.y = src->h * (static_cast<double>(size.y) / 100);
423  }
424 
425  if(flags_ & PRESERVE_ASPECT_RATIO) {
426  const auto ratio = std::min(
427  static_cast<long double>(size.x) / src->w,
428  static_cast<long double>(size.y) / src->h
429  );
430 
431  size = {
432  static_cast<int>(src->w * ratio),
433  static_cast<int>(src->h * ratio)
434  };
435  }
436 
437  if(flags_ & SCALE_SHARP) {
439  } else {
441  }
442 }
443 
445 {
446  if(z_ != 1) {
447  src = scale_surface_xbrz(src, z_);
448  }
449 }
450 
452 {
453  // Calculate the new dimensions of the padded surface
454  const int new_w = src->w + left_ + right_;
455  const int new_h = src->h + top_ + bottom_;
456 
457  // Create a new transparent surface with the calculated dimensions
458  surface padded(new_w, new_h);
459 
460  // Define the destination rectangle for the original image
461  rect dstrect{
462  left_,
463  top_,
464  0, // These two values are ignored by SDL_BlitSurface, so we set them to 0.
465  0 // The function always blits the entire source surface.
466  };
467 
468  SDL_BlendMode original_blend_mode;
469  SDL_GetSurfaceBlendMode(src, &original_blend_mode);
470  // Set blend mode to none to prevent blending
471  SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
472 
473  // Blit the original surface onto the new, padded surface
474  SDL_BlitSurface(src, nullptr, padded, &dstrect);
475 
476  SDL_SetSurfaceBlendMode(padded, original_blend_mode);
477 
478  src = padded;
479 }
480 
481 /*
482  * The Opacity IPF doesn't seem to work with surface-wide alpha and instead needs per-pixel alpha.
483  * If this is needed anywhere else it can be moved back to sdl/utils.*pp.
484  */
486 {
487  if(src) {
488  uint8_t alpha_mod = float_to_color(opacity_);
489 
490  surface_lock lock(src);
491  uint32_t* beg = lock.pixels();
492  uint32_t* end = beg + src.area();
493 
494  while(beg != end) {
495  uint8_t alpha = (*beg) >> 24;
496 
497  if(alpha) {
498  uint8_t r, g, b;
499  r = (*beg) >> 16;
500  g = (*beg) >> 8;
501  b = (*beg);
502 
503  alpha = color_multiply(alpha, alpha_mod);
504  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
505  }
506 
507  ++beg;
508  }
509  }
510 }
511 
513 {
514  if((r_ != 0 || g_ != 0 || b_ != 0)) {
515  adjust_surface_color(src, r_, g_, b_);
516  }
517 }
518 
520 {
521  blend_surface(src, static_cast<double>(a_), color_t(r_, g_, b_));
522 }
523 
525 {
526  blur_alpha_surface(src, depth_);
527 }
528 
530 {
531  surface ret = src.clone();
532  SDL_FillRect(ret, nullptr, SDL_MapRGBA(ret->format, color_.r, color_.g, color_.b, color_.a));
533  sdl_blit(src, nullptr, ret, nullptr);
534  src = ret;
535 }
536 
538 {
539  swap_channels_image(src, red_, green_, blue_, alpha_);
540 }
541 
542 namespace {
543 
544 struct parse_mod_registration
545 {
546  parse_mod_registration(const char* name, mod_parser parser)
547  {
548  mod_parsers[name] = std::move(parser);
549  }
550 };
551 
552 /** A macro for automatic modification parser registration
553  *
554  * It automatically registers the created parser in the mod_parsers map
555  * It should be used just like a function header (look at the uses below)
556  * It should only be used within an anonymous namespace
557  *
558  * @param type The modification type to be registered (unquoted)
559  * @param args_var The name for the string argument provided
560  */
561 #define REGISTER_MOD_PARSER(type, args_var) \
562  static std::unique_ptr<modification> parse_##type##_mod(std::string_view); \
563  static parse_mod_registration parse_##type##_mod_registration_aux(#type, &parse_##type##_mod); \
564  static std::unique_ptr<modification> parse_##type##_mod(std::string_view args_var) \
565 
566 // Color-range-based recoloring
567 REGISTER_MOD_PARSER(TC, args)
568 {
569  const auto params = utils::split_view(args,',');
570 
571  if(params.size() < 2) {
572  ERR_DP << "too few arguments passed to the ~TC() function";
573 
574  return nullptr;
575  }
576 
577  const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
578  if(side_n < 1) {
579  ERR_DP << "Invalid side (" << params[0] << ") passed to the ~TC() function";
580  return nullptr;
581  }
582 
583  //
584  // Pass argseters for RC functor
585  //
586  if(!game_config::tc_info(params[1]).size()){
587  ERR_DP << "could not load TC info for '" << params[1] << "' palette";
588  ERR_DP << "bailing out from TC";
589 
590  return nullptr;
591  }
592 
593  color_range_map rc_map;
594  try {
595  const color_range& new_color = team::get_side_color_range(side_n);
596  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
597 
598  rc_map = recolor_range(new_color,old_color);
599  } catch(const config::error& e) {
600  ERR_DP << "caught config::error while processing TC: " << e.message;
601  ERR_DP << "bailing out from TC";
602 
603  return nullptr;
604  }
605 
606  return std::make_unique<rc_modification>(rc_map);
607 }
608 
609 // Team-color-based color range selection and recoloring
610 REGISTER_MOD_PARSER(RC, args)
611 {
612  const auto recolor_params = utils::split_view(args,'>');
613 
614  if(recolor_params.size() <= 1) {
615  return nullptr;
616  }
617 
618  //
619  // recolor source palette to color range
620  //
621  color_range_map rc_map;
622  try {
623  const color_range& new_color = game_config::color_info(recolor_params[1]);
624  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
625 
626  rc_map = recolor_range(new_color,old_color);
627  } catch (const config::error& e) {
628  ERR_DP
629  << "caught config::error while processing color-range RC: "
630  << e.message;
631  ERR_DP << "bailing out from RC";
632  rc_map.clear();
633  }
634 
635  return std::make_unique<rc_modification>(rc_map);
636 }
637 
638 // Palette switch
639 REGISTER_MOD_PARSER(PAL, args)
640 {
641  const auto remap_params = utils::split_view(args,'>');
642 
643  if(remap_params.size() < 2) {
644  ERR_DP << "not enough arguments passed to the ~PAL() function: " << args;
645 
646  return nullptr;
647  }
648 
649  try {
650  color_range_map rc_map;
651  const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
652  const std::vector<color_t>& new_palette =game_config::tc_info(remap_params[1]);
653 
654  for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
655  rc_map[old_palette[i]] = new_palette[i];
656  }
657 
658  return std::make_unique<rc_modification>(rc_map);
659  } catch(const config::error& e) {
660  ERR_DP
661  << "caught config::error while processing PAL function: "
662  << e.message;
663  ERR_DP
664  << "bailing out from PAL";
665 
666  return nullptr;
667  }
668 }
669 
670 // Flip/flop
671 REGISTER_MOD_PARSER(FL, args)
672 {
673  bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
674  bool vert = (args.find("vert") != std::string::npos);
675 
676  return std::make_unique<fl_modification>(horiz, vert);
677 }
678 
679 // Rotations
680 REGISTER_MOD_PARSER(ROTATE, args)
681 {
682  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
683 
684  switch(slice_params.size()) {
685  case 0:
686  return std::make_unique<rotate_modification>();
687  case 1:
688  return std::make_unique<rotate_modification>(
689  utils::from_chars<int>(slice_params[0]).value_or(0));
690  case 2:
691  return std::make_unique<rotate_modification>(
692  utils::from_chars<int>(slice_params[0]).value_or(0),
693  utils::from_chars<int>(slice_params[1]).value_or(0));
694  case 3:
695  return std::make_unique<rotate_modification>(
696  utils::from_chars<int>(slice_params[0]).value_or(0),
697  utils::from_chars<int>(slice_params[1]).value_or(0),
698  utils::from_chars<int>(slice_params[2]).value_or(0));
699  }
700  return nullptr;
701 }
702 
703 // Grayscale
705 {
706  return std::make_unique<gs_modification>();
707 }
708 
709 // crop transparent padding
710 REGISTER_MOD_PARSER(CROP_TRANSPARENCY, )
711 {
712  return std::make_unique<crop_transparency_modification>();
713 }
714 
715 // TODO: should this be made a more general util function?
716 bool in_range(int val, int min, int max)
717 {
718  return min <= val && val <= max;
719 }
720 
721 // Black and white
722 REGISTER_MOD_PARSER(BW, args)
723 {
724  const auto params = utils::split_view(args, ',');
725 
726  if(params.size() != 1) {
727  ERR_DP << "~BW() requires exactly one argument";
728  return nullptr;
729  }
730 
731  // TODO: maybe get this directly as uint8_t?
732  const auto threshold = utils::from_chars<int>(params[0]);
733  if(!threshold) {
734  ERR_DP << "unsupported argument in ~BW() function";
735  return nullptr;
736  }
737 
738  if(!in_range(*threshold, 0, 255)) {
739  ERR_DP << "~BW() argument out of range 0 - 255";
740  return nullptr;
741  }
742 
743  return std::make_unique<bw_modification>(*threshold);
744 }
745 
746 // Sepia
747 REGISTER_MOD_PARSER(SEPIA, )
748 {
749  return std::make_unique<sepia_modification>();
750 }
751 
752 // Negative
753 REGISTER_MOD_PARSER(NEG, args)
754 {
755  const auto params = utils::split_view(args, ',');
756 
757  switch(params.size()) {
758  case 0:
759  // apparently -1 may be a magic number but this is the threshold
760  // value required to fully invert a channel
761  return std::make_unique<negative_modification>(-1, -1, -1);
762 
763  case 1: {
764  const auto threshold = utils::from_chars<int>(params[0]);
765 
766  if(threshold && in_range(*threshold, -1, 255)) {
767  return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
768  } else {
769  ERR_DP << "unsupported argument value in ~NEG() function";
770  return nullptr;
771  }
772  }
773 
774  case 3: {
775  const auto thR = utils::from_chars<int>(params[0]);
776  const auto thG = utils::from_chars<int>(params[1]);
777  const auto thB = utils::from_chars<int>(params[2]);
778 
779  if(thR && thG && thB && in_range(*thR, -1, 255) && in_range(*thG, -1, 255) && in_range(*thB, -1, 255)) {
780  return std::make_unique<negative_modification>(*thR, *thG, *thB);
781  } else {
782  ERR_DP << "unsupported argument value in ~NEG() function";
783  return nullptr;
784  }
785  }
786 
787  default:
788  ERR_DP << "~NEG() requires 0, 1 or 3 arguments";
789  return nullptr;
790  }
791 }
792 
793 // Plot Alpha
794 REGISTER_MOD_PARSER(PLOT_ALPHA, )
795 {
796  return std::make_unique<plot_alpha_modification>();
797 }
798 
799 // Wipe Alpha
800 REGISTER_MOD_PARSER(WIPE_ALPHA, )
801 {
802  return std::make_unique<wipe_alpha_modification>();
803 }
804 
805 // Adjust Alpha
806 REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
807 {
808  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
809  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
810  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
811 
812  if(params.size() != 1) {
813  ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments";
814  return nullptr;
815  }
816 
817  return std::make_unique<adjust_alpha_modification>(params.at(0));
818 }
819 
820 // Adjust Channels
821 REGISTER_MOD_PARSER(CHAN, args)
822 {
823  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
824  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
825  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
826 
827  if(params.size() < 1 || params.size() > 4) {
828  ERR_DP << "~CHAN() requires 1 to 4 arguments";
829  return nullptr;
830  }
831 
832  return std::make_unique<adjust_channels_modification>(params);
833 }
834 
835 // Color-shift
836 REGISTER_MOD_PARSER(CS, args)
837 {
838  const auto factors = utils::split_view(args, ',');
839  const std::size_t s = factors.size();
840 
841  if(s == 0) {
842  ERR_DP << "no arguments passed to the ~CS() function";
843  return nullptr;
844  }
845 
846  int r = 0, g = 0, b = 0;
847 
848  r = utils::from_chars<int>(factors[0]).value_or(0);
849 
850  if(s > 1 ) {
851  g = utils::from_chars<int>(factors[1]).value_or(0);
852  }
853  if(s > 2 ) {
854  b = utils::from_chars<int>(factors[2]).value_or(0);
855  }
856 
857  return std::make_unique<cs_modification>(r, g , b);
858 }
859 
860 // Color blending
861 REGISTER_MOD_PARSER(BLEND, args)
862 {
863  const auto params = utils::split_view(args, ',');
864 
865  if(params.size() != 4) {
866  ERR_DP << "~BLEND() requires exactly 4 arguments";
867  return nullptr;
868  }
869 
870  float opacity = 0.0f;
871  const std::string_view& opacity_str = params[3];
872  const std::string_view::size_type p100_pos = opacity_str.find('%');
873 
874  if(p100_pos == std::string::npos)
875  opacity = lexical_cast_default<float>(opacity_str);
876  else {
877  // make multiplier
878  const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
879  opacity = lexical_cast_default<float>(parsed_field);
880  opacity /= 100.0f;
881  }
882 
883  return std::make_unique<blend_modification>(
884  utils::from_chars<int>(params[0]).value_or(0),
885  utils::from_chars<int>(params[1]).value_or(0),
886  utils::from_chars<int>(params[2]).value_or(0),
887  opacity);
888 }
889 
890 // Crop/slice
891 REGISTER_MOD_PARSER(CROP, args)
892 {
893  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
894  const std::size_t s = slice_params.size();
895 
896  if(s == 0 || (s == 1 && slice_params[0].empty())) {
897  ERR_DP << "no arguments passed to the ~CROP() function";
898  return nullptr;
899  }
900 
901  rect slice_rect { 0, 0, 0, 0 };
902 
903  slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
904 
905  if(s > 1) {
906  slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
907  }
908  if(s > 2) {
909  slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
910  }
911  if(s > 3) {
912  slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
913  }
914 
915  return std::make_unique<crop_modification>(slice_rect);
916 }
917 
918 static bool check_image(const image::locator& img, std::stringstream & message)
919 {
920  if(image::exists(img)) return true;
921  message << " image not found: '" << img.get_filename() << "'\n";
922  ERR_DP << message.str();
923  return false;
924 }
925 
926 // Blit
927 REGISTER_MOD_PARSER(BLIT, args)
928 {
929  std::vector<std::string> param = utils::parenthetical_split(args, ',');
930  const std::size_t s = param.size();
931 
932  if(s == 0 || (s == 1 && param[0].empty())){
933  ERR_DP << "no arguments passed to the ~BLIT() function";
934  return nullptr;
935  }
936 
937  if(s > 3){
938  ERR_DP << "too many arguments passed to the ~BLIT() function";
939  return nullptr;
940  }
941 
942  int x = 0, y = 0;
943 
944  if(s == 3) {
945  x = utils::from_chars<int>(param[1]).value_or(0);
946  y = utils::from_chars<int>(param[2]).value_or(0);
947  }
948 
949  const image::locator img(param[0]);
950  std::stringstream message;
951  message << "~BLIT():";
952  if(!check_image(img, message))
953  return nullptr;
955 
956  return std::make_unique<blit_modification>(surf, x, y);
957 }
958 
959 // Mask
960 REGISTER_MOD_PARSER(MASK, args)
961 {
962  std::vector<std::string> param = utils::parenthetical_split(args, ',');
963  const std::size_t s = param.size();
964 
965  if(s == 0 || (s == 1 && param[0].empty())){
966  ERR_DP << "no arguments passed to the ~MASK() function";
967  return nullptr;
968  }
969 
970  int x = 0, y = 0;
971 
972  if(s == 3) {
973  x = utils::from_chars<int>(param[1]).value_or(0);
974  y = utils::from_chars<int>(param[2]).value_or(0);
975  }
976 
977  if(x < 0 || y < 0) {
978  ERR_DP << "negative position arguments in ~MASK() function";
979  return nullptr;
980  }
981 
982  const image::locator img(param[0]);
983  std::stringstream message;
984  message << "~MASK():";
985  if(!check_image(img, message))
986  return nullptr;
988 
989  return std::make_unique<mask_modification>(surf, x, y);
990 }
991 
992 // Light
993 REGISTER_MOD_PARSER(L, args)
994 {
995  if(args.empty()){
996  ERR_DP << "no arguments passed to the ~L() function";
997  return nullptr;
998  }
999 
1000  surface surf = get_surface(std::string{args}); // FIXME: string_view for image::locator::value
1001  return std::make_unique<light_modification>(surf);
1002 }
1003 
1004 namespace
1005 {
1006 std::pair<int, bool> parse_scale_value(std::string_view arg)
1007 {
1008  if(const std::size_t pos = arg.rfind('%'); pos != std::string_view::npos) {
1009  return { utils::from_chars<int>(arg.substr(0, pos)).value_or(0), true };
1010  } else {
1011  return { utils::from_chars<int>(arg).value_or(0), false };
1012  }
1013 }
1014 
1015 /** Common helper function to parse scaling IPF inputs. */
1016 utils::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
1017 {
1018  const auto scale_params = utils::split_view(args, ',', utils::STRIP_SPACES);
1019  const std::size_t num_args = scale_params.size();
1020 
1021  if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
1022  return utils::nullopt;
1023  }
1024 
1025  uint8_t flags = 0;
1026  std::array<int, 2> parsed_sizes{0,0};
1027 
1028  for(unsigned i = 0; i < std::min<unsigned>(2, num_args); ++i) {
1029  const auto& [size, relative] = parse_scale_value(scale_params[i]);
1030 
1031  if(size < 0) {
1032  ERR_DP << "Negative size passed to scaling IPF. Original image dimension will be used instead";
1033  continue;
1034  }
1035 
1036  parsed_sizes[i] = size;
1037 
1038  if(relative) {
1040  }
1041  }
1042 
1043  return std::pair{point{parsed_sizes[0], parsed_sizes[1]}, flags};
1044 }
1045 
1046 } // namespace
1047 
1048 // Scale
1049 REGISTER_MOD_PARSER(SCALE, args)
1050 {
1051  if(auto params = parse_scale_args(args)) {
1053  return std::make_unique<scale_modification>(params->first, mode | params->second);
1054  } else {
1055  ERR_DP << "no arguments passed to the ~SCALE() function";
1056  return nullptr;
1057  }
1058 }
1059 
1060 REGISTER_MOD_PARSER(SCALE_SHARP, args)
1061 {
1062  if(auto params = parse_scale_args(args)) {
1064  return std::make_unique<scale_modification>(params->first, mode | params->second);
1065  } else {
1066  ERR_DP << "no arguments passed to the ~SCALE_SHARP() function";
1067  return nullptr;
1068  }
1069 }
1070 
1071 REGISTER_MOD_PARSER(SCALE_INTO, args)
1072 {
1073  if(auto params = parse_scale_args(args)) {
1075  return std::make_unique<scale_modification>(params->first, mode | params->second);
1076  } else {
1077  ERR_DP << "no arguments passed to the ~SCALE_INTO() function";
1078  return nullptr;
1079  }
1080 }
1081 
1082 REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1083 {
1084  if(auto params = parse_scale_args(args)) {
1086  return std::make_unique<scale_modification>(params->first, mode | params->second);
1087  } else {
1088  ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function";
1089  return nullptr;
1090  }
1091 }
1092 
1093 // xBRZ
1094 REGISTER_MOD_PARSER(XBRZ, args)
1095 {
1096  const int factor = std::clamp(utils::from_chars<int>(args).value_or(1), 1, 6);
1097  return std::make_unique<xbrz_modification>(factor);
1098 }
1099 
1100 // Pad
1101 REGISTER_MOD_PARSER(PAD, args)
1102 {
1103  int top = 0;
1104  int right = 0;
1105  int bottom = 0;
1106  int left = 0;
1107 
1108  // Check for the presence of an '=' sign to determine the parsing mode.
1109  if(args.find('=') != std::string_view::npos) {
1110  // --- Keyword-Argument Mode ---
1111  const auto params = utils::map_split(std::string{args}, ',', '='); // map_split needs a std::string
1112 
1113  // Map valid input strings to the corresponding integer reference
1114  const std::map<std::string, int*> alias_map = {
1115  {"top", &top},
1116  {"t", &top},
1117  {"right", &right},
1118  {"r", &right},
1119  {"bottom", &bottom},
1120  {"b", &bottom},
1121  {"left", &left},
1122  {"l", &left},
1123  };
1124 
1125  // Parse and assign values if keywords are valid
1126  for(const auto& [key, value] : params) {
1127  auto it = alias_map.find(key);
1128  if(it != alias_map.end()) {
1129  if(utils::optional padding = utils::from_chars<int>(value)) {
1130  *it->second = padding.value();
1131  } else {
1132  ERR_DP << "~PAD() keyword argument '" << key << "' requires a valid integer value. Received: '" << value << "'.";
1133  return nullptr;
1134  }
1135  } else {
1136  ERR_DP << "~PAD() found an unknown keyword: '" << key << "'. Valid keywords: top, t, right, r, bottom, b, left, l.";
1137  return nullptr;
1138  }
1139  }
1140  } else {
1141  // --- Numeric-Argument Mode ---
1142  const auto params = utils::split_view(args, ',');
1143  if(params.size() != 1) {
1144  ERR_DP << "~PAD() takes either 1 numeric argument for the padding on all sides or a comma separated list of '<keyword>=<number>' pairs with available keywords: top, t, right, r, bottom, b, left, l.";
1145  return nullptr;
1146  }
1147 
1148  // Single integer argument: apply to all sides
1149  if(utils::optional padding = utils::from_chars<int>(params[0])) {
1150  top = right = bottom = left = padding.value();
1151  } else {
1152  ERR_DP << "~PAD() numeric argument (pad all sides) requires a single valid integer. Received: '" << params[0] << "'.";
1153  return nullptr;
1154  }
1155  }
1156 
1157  return std::make_unique<pad_modification>(top, right, bottom, left);
1158 }
1159 
1160 // Gaussian-like blur
1161 REGISTER_MOD_PARSER(BL, args)
1162 {
1163  const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1164  return std::make_unique<bl_modification>(depth);
1165 }
1166 
1167 // Opacity-shift
1168 REGISTER_MOD_PARSER(O, args)
1169 {
1170  const std::string::size_type p100_pos = args.find('%');
1171  float num = 0.0f;
1172  if(p100_pos == std::string::npos) {
1173  num = lexical_cast_default<float, std::string_view>(args);
1174  } else {
1175  // make multiplier
1176  const std::string_view parsed_field = args.substr(0, p100_pos);
1177  num = lexical_cast_default<float, std::string_view>(parsed_field);
1178  num /= 100.0f;
1179  }
1180 
1181  return std::make_unique<o_modification>(num);
1182 }
1183 
1184 //
1185 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1186 // Hence they are at the end of the evaluation.
1187 //
1188 // Red component color-shift
1189 REGISTER_MOD_PARSER(R, args)
1190 {
1191  const int r = utils::from_chars<int>(args).value_or(0);
1192  return std::make_unique<cs_modification>(r, 0, 0);
1193 }
1194 
1195 // Green component color-shift
1196 REGISTER_MOD_PARSER(G, args)
1197 {
1198  const int g = utils::from_chars<int>(args).value_or(0);
1199  return std::make_unique<cs_modification>(0, g, 0);
1200 }
1201 
1202 // Blue component color-shift
1203 REGISTER_MOD_PARSER(B, args)
1204 {
1205  const int b = utils::from_chars<int>(args).value_or(0);
1206  return std::make_unique<cs_modification>(0, 0, b);
1207 }
1208 
1209 REGISTER_MOD_PARSER(NOP, )
1210 {
1211  return nullptr;
1212 }
1213 
1214 // Only used to tag terrain images which should not be color-shifted by ToD
1215 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1216 {
1217  return nullptr;
1218 }
1219 
1220 // Fake image function used by GUI2 portraits until
1221 // Mordante gets rid of it. *tsk* *tsk*
1222 REGISTER_MOD_PARSER(RIGHT, )
1223 {
1224  return nullptr;
1225 }
1226 
1227 // Add a background color.
1228 REGISTER_MOD_PARSER(BG, args)
1229 {
1230  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1231  const auto factors = utils::split_view(args, ',');
1232 
1233  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1234  c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1235  }
1236 
1237  return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1238 }
1239 
1240 // Channel swap
1241 REGISTER_MOD_PARSER(SWAP, args)
1242 {
1243  const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1244 
1245  // accept 3 arguments (rgb) or 4 (rgba)
1246  if(params.size() != 3 && params.size() != 4) {
1247  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1248  return nullptr;
1249  }
1250 
1251  channel redValue, greenValue, blueValue, alphaValue;
1252  // compare the parameter's value with the constants defined in the channels enum
1253  if(params[0] == "red") {
1254  redValue = RED;
1255  } else if(params[0] == "green") {
1256  redValue = GREEN;
1257  } else if(params[0] == "blue") {
1258  redValue = BLUE;
1259  } else if(params[0] == "alpha") {
1260  redValue = ALPHA;
1261  } else {
1262  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1263  return nullptr;
1264  }
1265 
1266  // wash, rinse and repeat for the other three channels
1267  if(params[1] == "red") {
1268  greenValue = RED;
1269  } else if(params[1] == "green") {
1270  greenValue = GREEN;
1271  } else if(params[1] == "blue") {
1272  greenValue = BLUE;
1273  } else if(params[1] == "alpha") {
1274  greenValue = ALPHA;
1275  } else {
1276  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1277  return nullptr;
1278  }
1279 
1280  if(params[2] == "red") {
1281  blueValue = RED;
1282  } else if(params[2] == "green") {
1283  blueValue = GREEN;
1284  } else if(params[2] == "blue") {
1285  blueValue = BLUE;
1286  } else if(params[2] == "alpha") {
1287  blueValue = ALPHA;
1288  } else {
1289  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1290  return nullptr;
1291  }
1292 
1293  // additional check: the params vector may not have a fourth elementh
1294  // if so, default to the same channel
1295  if(params.size() == 3) {
1296  alphaValue = ALPHA;
1297  } else {
1298  if(params[3] == "red") {
1299  alphaValue = RED;
1300  } else if(params[3] == "green") {
1301  alphaValue = GREEN;
1302  } else if(params[3] == "blue") {
1303  alphaValue = BLUE;
1304  } else if(params[3] == "alpha") {
1305  alphaValue = ALPHA;
1306  } else {
1307  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1308  return nullptr;
1309  }
1310  }
1311 
1312  return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1313 }
1314 
1315 } // end anon namespace
1316 
1317 } /* end namespace image */
double g
Definition: astarsearch.cpp:63
A color range definition is made of four reference RGB colors, used for calculating conversions from ...
Definition: color_range.hpp:49
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
A modified priority queue used to order image modifications.
void push(std::unique_ptr< modification > &&mod)
Adds mod to the queue.
const modification & top() const
Returns a const reference to the top element in the queue.
std::map< int, std::vector< std::unique_ptr< modification > >, std::greater< int > > priorities_
Map from a mod's priority() to the mods having that priority.
void pop()
Removes the top element from the queue.
std::size_t size() const
Returns the number of elements in the queue.
Base abstract class for an image-path modification.
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
pixel_callable(std::size_t index, color_t clr, int w, int h)
wfl::variant get_value(const std::string &key) const override
void get_inputs(wfl::formula_input_vector &inputs) const override
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
void store() const noexcept
Stores a copy the current exception to be rethrown.
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:71
pixel_t * pixels() const
Definition: surface.hpp:90
utils::span< pixel_t > pixel_span() const
Definition: surface.hpp:95
static const color_range get_side_color_range(int side)
Definition: team.cpp:931
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition: variant.cpp:291
constexpr uint8_t color_multiply(uint8_t n1, uint8_t n2)
Multiply two 8-bit colour values as if in the range [0.0,1.0].
Definition: color.hpp:330
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition: color.hpp:314
color_range_map recolor_range(const color_range &new_range, const std::vector< color_t > &old_rgb)
Converts a source palette using the specified color_range object.
Definition: color_range.cpp:91
std::unordered_map< color_t, color_t > color_range_map
Definition: color_range.hpp:30
Definitions for the interface to Wesnoth Markup Language (WML).
variant a_
Definition: function.cpp:821
std::size_t i
Definition: function.cpp:1032
variant b_
Definition: function.cpp:821
#define REGISTER_MOD_PARSER(type, args_var)
A macro for automatic modification parser registration.
static lg::log_domain log_display("display")
#define ERR_DP
New lexcical_cast header.
Standard logging facilities (interface).
const std::vector< color_t > & tc_info(std::string_view name)
const color_range & color_info(std::string_view name)
Functions to load and save images from/to disk.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:844
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:683
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:35
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
std::vector< std::string_view > split_view(std::string_view s, const char sep, const int flags)
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
std::vector< std::string > parenthetical_split(std::string_view val, const char separator, std::string_view left, std::string_view right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
std::vector< std::string > split(const config_attribute_value &val)
std::vector< formula_input > formula_input_vector
int w
Definition: pathfind.cpp:188
surface surf
Image.
rect src
Non-transparent portion of the surface to compose.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition: color.hpp:128
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Exception thrown by the operator() when an error occurs.
imod_exception(const std::stringstream &message_stream)
Constructor.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
constexpr int area() const
The area of this rectangle, in square pixels.
Definition: rect.hpp:103
mock_char c
static map_location::direction s
void mask_surface(surface &nsurf, const surface &nmask, bool *empty_result, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:706
surface get_surface_portion(const surface &src, rect &area)
Get a portion of the screen.
Definition: utils.cpp:1355
rect get_non_transparent_portion(const surface &nsurf)
Definition: utils.cpp:1399
void alpha_to_greyscale(surface &nsurf)
Definition: utils.cpp:502
surface scale_surface_legacy(const surface &surf, int w, int h)
Scale a surface using simple bilinear filtering (discarding rgb from source pixels with 0 alpha)
Definition: utils.cpp:225
void recolor_image(surface &nsurf, const color_range_map &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition: utils.cpp:634
void wipe_alpha(surface &nsurf)
Definition: utils.cpp:515
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1100
void sepia_image(surface &nsurf)
Definition: utils.cpp:462
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1159
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:552
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition: utils.cpp:405
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:481
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:798
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:61
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:358
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:962
void greyscale_image(surface &nsurf)
Definition: utils.cpp:422
void flop_surface(surface &nsurf)
Definition: utils.cpp:1339
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1289
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1249
void flip_surface(surface &nsurf)
Definition: utils.cpp:1323
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition: utils.cpp:99
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1187
void monochrome_image(surface &nsurf, const int threshold)
Definition: utils.cpp:444
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:43
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