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