The Battle for Wesnoth  1.19.23+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  try {
593  const color_range& new_color = team::get_side_color_range(side_n);
594  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
595 
596  return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
597  } catch(const config::error& e) {
598  ERR_DP << "caught config::error while processing TC: " << e.message;
599  ERR_DP << "bailing out from TC";
600 
601  return nullptr;
602  }
603 }
604 
605 // Team-color-based color range selection and recoloring
606 REGISTER_MOD_PARSER(RC, args)
607 {
608  const auto recolor_params = utils::split_view(args,'>');
609 
610  if(recolor_params.size() <= 1) {
611  return nullptr;
612  }
613 
614  //
615  // recolor source palette to color range
616  //
617  try {
618  const color_range& new_color = game_config::color_info(recolor_params[1]);
619  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
620 
621  return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
622  } catch (const config::error& e) {
623  ERR_DP
624  << "caught config::error while processing color-range RC: "
625  << e.message;
626  ERR_DP << "bailing out from RC";
627  return nullptr;
628  }
629 }
630 
631 // Palette switch
632 REGISTER_MOD_PARSER(PAL, args)
633 {
634  const auto remap_params = utils::split_view(args,'>');
635 
636  if(remap_params.size() < 2) {
637  ERR_DP << "not enough arguments passed to the ~PAL() function: " << args;
638 
639  return nullptr;
640  }
641 
642  try {
643  color_mapping rc_map;
644  const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
645  const std::vector<color_t>& new_palette = game_config::tc_info(remap_params[1]);
646 
647  for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
648  rc_map[old_palette[i]] = new_palette[i];
649  }
650 
651  return std::make_unique<rc_modification>(std::move(rc_map));
652  } catch(const config::error& e) {
653  ERR_DP
654  << "caught config::error while processing PAL function: "
655  << e.message;
656  ERR_DP
657  << "bailing out from PAL";
658 
659  return nullptr;
660  }
661 }
662 
663 // Flip/flop
664 REGISTER_MOD_PARSER(FL, args)
665 {
666  bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
667  bool vert = (args.find("vert") != std::string::npos);
668 
669  return std::make_unique<fl_modification>(horiz, vert);
670 }
671 
672 // Rotations
673 REGISTER_MOD_PARSER(ROTATE, args)
674 {
675  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
676 
677  switch(slice_params.size()) {
678  case 0:
679  return std::make_unique<rotate_modification>();
680  case 1:
681  return std::make_unique<rotate_modification>(
682  utils::from_chars<int>(slice_params[0]).value_or(0));
683  case 2:
684  return std::make_unique<rotate_modification>(
685  utils::from_chars<int>(slice_params[0]).value_or(0),
686  utils::from_chars<int>(slice_params[1]).value_or(0));
687  case 3:
688  return std::make_unique<rotate_modification>(
689  utils::from_chars<int>(slice_params[0]).value_or(0),
690  utils::from_chars<int>(slice_params[1]).value_or(0),
691  utils::from_chars<int>(slice_params[2]).value_or(0));
692  }
693  return nullptr;
694 }
695 
696 // Grayscale
698 {
699  return std::make_unique<gs_modification>();
700 }
701 
702 // crop transparent padding
703 REGISTER_MOD_PARSER(CROP_TRANSPARENCY, )
704 {
705  return std::make_unique<crop_transparency_modification>();
706 }
707 
708 // TODO: should this be made a more general util function?
709 bool in_range(int val, int min, int max)
710 {
711  return min <= val && val <= max;
712 }
713 
714 // Black and white
715 REGISTER_MOD_PARSER(BW, args)
716 {
717  const auto params = utils::split_view(args, ',');
718 
719  if(params.size() != 1) {
720  ERR_DP << "~BW() requires exactly one argument";
721  return nullptr;
722  }
723 
724  // TODO: maybe get this directly as uint8_t?
725  const auto threshold = utils::from_chars<int>(params[0]);
726  if(!threshold) {
727  ERR_DP << "unsupported argument in ~BW() function";
728  return nullptr;
729  }
730 
731  if(!in_range(*threshold, 0, 255)) {
732  ERR_DP << "~BW() argument out of range 0 - 255";
733  return nullptr;
734  }
735 
736  return std::make_unique<bw_modification>(*threshold);
737 }
738 
739 // Sepia
740 REGISTER_MOD_PARSER(SEPIA, )
741 {
742  return std::make_unique<sepia_modification>();
743 }
744 
745 // Negative
746 REGISTER_MOD_PARSER(NEG, args)
747 {
748  const auto params = utils::split_view(args, ',');
749 
750  switch(params.size()) {
751  case 0:
752  // apparently -1 may be a magic number but this is the threshold
753  // value required to fully invert a channel
754  return std::make_unique<negative_modification>(-1, -1, -1);
755 
756  case 1: {
757  const auto threshold = utils::from_chars<int>(params[0]);
758 
759  if(threshold && in_range(*threshold, -1, 255)) {
760  return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
761  } else {
762  ERR_DP << "unsupported argument value in ~NEG() function";
763  return nullptr;
764  }
765  }
766 
767  case 3: {
768  const auto thR = utils::from_chars<int>(params[0]);
769  const auto thG = utils::from_chars<int>(params[1]);
770  const auto thB = utils::from_chars<int>(params[2]);
771 
772  if(thR && thG && thB && in_range(*thR, -1, 255) && in_range(*thG, -1, 255) && in_range(*thB, -1, 255)) {
773  return std::make_unique<negative_modification>(*thR, *thG, *thB);
774  } else {
775  ERR_DP << "unsupported argument value in ~NEG() function";
776  return nullptr;
777  }
778  }
779 
780  default:
781  ERR_DP << "~NEG() requires 0, 1 or 3 arguments";
782  return nullptr;
783  }
784 }
785 
786 // Plot Alpha
787 REGISTER_MOD_PARSER(PLOT_ALPHA, )
788 {
789  return std::make_unique<plot_alpha_modification>();
790 }
791 
792 // Wipe Alpha
793 REGISTER_MOD_PARSER(WIPE_ALPHA, )
794 {
795  return std::make_unique<wipe_alpha_modification>();
796 }
797 
798 // Adjust Alpha
799 REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
800 {
801  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
802  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
803  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
804 
805  if(params.size() != 1) {
806  ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments";
807  return nullptr;
808  }
809 
810  return std::make_unique<adjust_alpha_modification>(params.at(0));
811 }
812 
813 // Adjust Channels
814 REGISTER_MOD_PARSER(CHAN, args)
815 {
816  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
817  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
818  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
819 
820  if(params.size() < 1 || params.size() > 4) {
821  ERR_DP << "~CHAN() requires 1 to 4 arguments";
822  return nullptr;
823  }
824 
825  return std::make_unique<adjust_channels_modification>(params);
826 }
827 
828 // Color-shift
829 REGISTER_MOD_PARSER(CS, args)
830 {
831  const auto factors = utils::split_view(args, ',');
832  const std::size_t s = factors.size();
833 
834  if(s == 0) {
835  ERR_DP << "no arguments passed to the ~CS() function";
836  return nullptr;
837  }
838 
839  int r = 0, g = 0, b = 0;
840 
841  r = utils::from_chars<int>(factors[0]).value_or(0);
842 
843  if(s > 1 ) {
844  g = utils::from_chars<int>(factors[1]).value_or(0);
845  }
846  if(s > 2 ) {
847  b = utils::from_chars<int>(factors[2]).value_or(0);
848  }
849 
850  return std::make_unique<cs_modification>(r, g , b);
851 }
852 
853 // Color blending
854 REGISTER_MOD_PARSER(BLEND, args)
855 {
856  const auto params = utils::split_view(args, ',');
857 
858  if(params.size() != 4) {
859  ERR_DP << "~BLEND() requires exactly 4 arguments";
860  return nullptr;
861  }
862 
863  float opacity = 0.0f;
864  const std::string_view& opacity_str = params[3];
865  const std::string_view::size_type p100_pos = opacity_str.find('%');
866 
867  if(p100_pos == std::string::npos)
868  opacity = utils::from_chars<float>(opacity_str).value_or(0.0f);
869  else {
870  // make multiplier
871  const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
872  opacity = utils::from_chars<float>(parsed_field).value_or(0.0f);
873  opacity /= 100.0f;
874  }
875 
876  return std::make_unique<blend_modification>(
877  utils::from_chars<int>(params[0]).value_or(0),
878  utils::from_chars<int>(params[1]).value_or(0),
879  utils::from_chars<int>(params[2]).value_or(0),
880  opacity);
881 }
882 
883 // Crop/slice
884 REGISTER_MOD_PARSER(CROP, args)
885 {
886  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
887  const std::size_t s = slice_params.size();
888 
889  if(s == 0 || (s == 1 && slice_params[0].empty())) {
890  ERR_DP << "no arguments passed to the ~CROP() function";
891  return nullptr;
892  }
893 
894  rect slice_rect { 0, 0, 0, 0 };
895 
896  slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
897 
898  if(s > 1) {
899  slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
900  }
901  if(s > 2) {
902  slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
903  }
904  if(s > 3) {
905  slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
906  }
907 
908  return std::make_unique<crop_modification>(slice_rect);
909 }
910 
911 static bool check_image(const image::locator& img, std::stringstream & message)
912 {
913  if(image::exists(img)) return true;
914  message << " image not found: '" << img.get_filename() << "'\n";
915  ERR_DP << message.str();
916  return false;
917 }
918 
919 // Blit
920 REGISTER_MOD_PARSER(BLIT, args)
921 {
922  std::vector<std::string> param = utils::parenthetical_split(args, ',');
923  const std::size_t s = param.size();
924 
925  if(s == 0 || (s == 1 && param[0].empty())){
926  ERR_DP << "no arguments passed to the ~BLIT() function";
927  return nullptr;
928  }
929 
930  if(s > 3){
931  ERR_DP << "too many arguments passed to the ~BLIT() function";
932  return nullptr;
933  }
934 
935  int x = 0, y = 0;
936 
937  if(s == 3) {
938  x = utils::from_chars<int>(param[1]).value_or(0);
939  y = utils::from_chars<int>(param[2]).value_or(0);
940  }
941 
942  const image::locator img(param[0]);
943  std::stringstream message;
944  message << "~BLIT():";
945  if(!check_image(img, message))
946  return nullptr;
948 
949  return std::make_unique<blit_modification>(surf, x, y);
950 }
951 
952 // Mask
953 REGISTER_MOD_PARSER(MASK, args)
954 {
955  std::vector<std::string> param = utils::parenthetical_split(args, ',');
956  const std::size_t s = param.size();
957 
958  if(s == 0 || (s == 1 && param[0].empty())){
959  ERR_DP << "no arguments passed to the ~MASK() function";
960  return nullptr;
961  }
962 
963  int x = 0, y = 0;
964 
965  if(s == 3) {
966  x = utils::from_chars<int>(param[1]).value_or(0);
967  y = utils::from_chars<int>(param[2]).value_or(0);
968  }
969 
970  if(x < 0 || y < 0) {
971  ERR_DP << "negative position arguments in ~MASK() function";
972  return nullptr;
973  }
974 
975  const image::locator img(param[0]);
976  std::stringstream message;
977  message << "~MASK():";
978  if(!check_image(img, message))
979  return nullptr;
981 
982  return std::make_unique<mask_modification>(surf, x, y);
983 }
984 
985 // Light
986 REGISTER_MOD_PARSER(L, args)
987 {
988  if(args.empty()){
989  ERR_DP << "no arguments passed to the ~L() function";
990  return nullptr;
991  }
992 
993  surface surf = get_surface(std::string{args}); // FIXME: string_view for image::locator::value
994  return std::make_unique<light_modification>(surf);
995 }
996 
997 namespace
998 {
999 std::pair<int, bool> parse_scale_value(std::string_view arg)
1000 {
1001  if(const std::size_t pos = arg.rfind('%'); pos != std::string_view::npos) {
1002  return { utils::from_chars<int>(arg.substr(0, pos)).value_or(0), true };
1003  } else {
1004  return { utils::from_chars<int>(arg).value_or(0), false };
1005  }
1006 }
1007 
1008 /** Common helper function to parse scaling IPF inputs. */
1009 utils::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
1010 {
1011  const auto scale_params = utils::split_view(args, ',', utils::STRIP_SPACES);
1012  const std::size_t num_args = scale_params.size();
1013 
1014  if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
1015  return utils::nullopt;
1016  }
1017 
1018  uint8_t flags = 0;
1019  std::array<int, 2> parsed_sizes{0,0};
1020 
1021  for(unsigned i = 0; i < std::min<unsigned>(2, num_args); ++i) {
1022  const auto& [size, relative] = parse_scale_value(scale_params[i]);
1023 
1024  if(size < 0) {
1025  ERR_DP << "Negative size passed to scaling IPF. Original image dimension will be used instead";
1026  continue;
1027  }
1028 
1029  parsed_sizes[i] = size;
1030 
1031  if(relative) {
1033  }
1034  }
1035 
1036  return std::pair{point{parsed_sizes[0], parsed_sizes[1]}, flags};
1037 }
1038 
1039 } // namespace
1040 
1041 // Scale
1042 REGISTER_MOD_PARSER(SCALE, args)
1043 {
1044  if(auto params = parse_scale_args(args)) {
1046  return std::make_unique<scale_modification>(params->first, mode | params->second);
1047  } else {
1048  ERR_DP << "no arguments passed to the ~SCALE() function";
1049  return nullptr;
1050  }
1051 }
1052 
1053 REGISTER_MOD_PARSER(SCALE_SHARP, args)
1054 {
1055  if(auto params = parse_scale_args(args)) {
1057  return std::make_unique<scale_modification>(params->first, mode | params->second);
1058  } else {
1059  ERR_DP << "no arguments passed to the ~SCALE_SHARP() function";
1060  return nullptr;
1061  }
1062 }
1063 
1064 REGISTER_MOD_PARSER(SCALE_INTO, args)
1065 {
1066  if(auto params = parse_scale_args(args)) {
1068  return std::make_unique<scale_modification>(params->first, mode | params->second);
1069  } else {
1070  ERR_DP << "no arguments passed to the ~SCALE_INTO() function";
1071  return nullptr;
1072  }
1073 }
1074 
1075 REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1076 {
1077  if(auto params = parse_scale_args(args)) {
1079  return std::make_unique<scale_modification>(params->first, mode | params->second);
1080  } else {
1081  ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function";
1082  return nullptr;
1083  }
1084 }
1085 
1086 // xBRZ
1087 REGISTER_MOD_PARSER(XBRZ, args)
1088 {
1089  const int factor = std::clamp(utils::from_chars<int>(args).value_or(1), 1, 6);
1090  return std::make_unique<xbrz_modification>(factor);
1091 }
1092 
1093 // Pad
1094 REGISTER_MOD_PARSER(PAD, args)
1095 {
1096  int top = 0;
1097  int right = 0;
1098  int bottom = 0;
1099  int left = 0;
1100 
1101  // Check for the presence of an '=' sign to determine the parsing mode.
1102  if(args.find('=') != std::string_view::npos) {
1103  // --- Keyword-Argument Mode ---
1104  const auto params = utils::map_split(std::string{args}, ',', '='); // map_split needs a std::string
1105 
1106  // Map valid input strings to the corresponding integer reference
1107  const std::map<std::string, int*> alias_map = {
1108  {"top", &top},
1109  {"t", &top},
1110  {"right", &right},
1111  {"r", &right},
1112  {"bottom", &bottom},
1113  {"b", &bottom},
1114  {"left", &left},
1115  {"l", &left},
1116  };
1117 
1118  // Parse and assign values if keywords are valid
1119  for(const auto& [key, value] : params) {
1120  auto it = alias_map.find(key);
1121  if(it != alias_map.end()) {
1122  if(utils::optional padding = utils::from_chars<int>(value)) {
1123  *it->second = padding.value();
1124  } else {
1125  ERR_DP << "~PAD() keyword argument '" << key << "' requires a valid integer value. Received: '" << value << "'.";
1126  return nullptr;
1127  }
1128  } else {
1129  ERR_DP << "~PAD() found an unknown keyword: '" << key << "'. Valid keywords: top, t, right, r, bottom, b, left, l.";
1130  return nullptr;
1131  }
1132  }
1133  } else {
1134  // --- Numeric-Argument Mode ---
1135  const auto params = utils::split_view(args, ',');
1136  if(params.size() != 1) {
1137  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.";
1138  return nullptr;
1139  }
1140 
1141  // Single integer argument: apply to all sides
1142  if(utils::optional padding = utils::from_chars<int>(params[0])) {
1143  top = right = bottom = left = padding.value();
1144  } else {
1145  ERR_DP << "~PAD() numeric argument (pad all sides) requires a single valid integer. Received: '" << params[0] << "'.";
1146  return nullptr;
1147  }
1148  }
1149 
1150  return std::make_unique<pad_modification>(top, right, bottom, left);
1151 }
1152 
1153 // Gaussian-like blur
1154 REGISTER_MOD_PARSER(BL, args)
1155 {
1156  const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1157  return std::make_unique<bl_modification>(depth);
1158 }
1159 
1160 // Opacity-shift
1161 REGISTER_MOD_PARSER(O, args)
1162 {
1163  const std::string::size_type p100_pos = args.find('%');
1164  float num = 0.0f;
1165  if(p100_pos == std::string::npos) {
1166  num = utils::from_chars<float>(args).value_or(0.0f);
1167  } else {
1168  // make multiplier
1169  const std::string_view parsed_field = args.substr(0, p100_pos);
1170  num = utils::from_chars<float>(parsed_field).value_or(0.0f);
1171  num /= 100.0f;
1172  }
1173 
1174  return std::make_unique<o_modification>(num);
1175 }
1176 
1177 //
1178 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1179 // Hence they are at the end of the evaluation.
1180 //
1181 // Red component color-shift
1182 REGISTER_MOD_PARSER(R, args)
1183 {
1184  const int r = utils::from_chars<int>(args).value_or(0);
1185  return std::make_unique<cs_modification>(r, 0, 0);
1186 }
1187 
1188 // Green component color-shift
1189 REGISTER_MOD_PARSER(G, args)
1190 {
1191  const int g = utils::from_chars<int>(args).value_or(0);
1192  return std::make_unique<cs_modification>(0, g, 0);
1193 }
1194 
1195 // Blue component color-shift
1196 REGISTER_MOD_PARSER(B, args)
1197 {
1198  const int b = utils::from_chars<int>(args).value_or(0);
1199  return std::make_unique<cs_modification>(0, 0, b);
1200 }
1201 
1202 REGISTER_MOD_PARSER(NOP, )
1203 {
1204  return nullptr;
1205 }
1206 
1207 // Only used to tag terrain images which should not be color-shifted by ToD
1208 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1209 {
1210  return nullptr;
1211 }
1212 
1213 // Fake image function used by GUI2 portraits until
1214 // Mordante gets rid of it. *tsk* *tsk*
1215 REGISTER_MOD_PARSER(RIGHT, )
1216 {
1217  return nullptr;
1218 }
1219 
1220 // Add a background color.
1221 REGISTER_MOD_PARSER(BG, args)
1222 {
1223  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1224  const auto factors = utils::split_view(args, ',');
1225 
1226  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1227  c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1228  }
1229 
1230  return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1231 }
1232 
1233 // Channel swap
1234 REGISTER_MOD_PARSER(SWAP, args)
1235 {
1236  const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1237 
1238  // accept 3 arguments (rgb) or 4 (rgba)
1239  if(params.size() != 3 && params.size() != 4) {
1240  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1241  return nullptr;
1242  }
1243 
1244  channel redValue, greenValue, blueValue, alphaValue;
1245  // compare the parameter's value with the constants defined in the channels enum
1246  if(params[0] == "red") {
1247  redValue = RED;
1248  } else if(params[0] == "green") {
1249  redValue = GREEN;
1250  } else if(params[0] == "blue") {
1251  redValue = BLUE;
1252  } else if(params[0] == "alpha") {
1253  redValue = ALPHA;
1254  } else {
1255  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1256  return nullptr;
1257  }
1258 
1259  // wash, rinse and repeat for the other three channels
1260  if(params[1] == "red") {
1261  greenValue = RED;
1262  } else if(params[1] == "green") {
1263  greenValue = GREEN;
1264  } else if(params[1] == "blue") {
1265  greenValue = BLUE;
1266  } else if(params[1] == "alpha") {
1267  greenValue = ALPHA;
1268  } else {
1269  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1270  return nullptr;
1271  }
1272 
1273  if(params[2] == "red") {
1274  blueValue = RED;
1275  } else if(params[2] == "green") {
1276  blueValue = GREEN;
1277  } else if(params[2] == "blue") {
1278  blueValue = BLUE;
1279  } else if(params[2] == "alpha") {
1280  blueValue = ALPHA;
1281  } else {
1282  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1283  return nullptr;
1284  }
1285 
1286  // additional check: the params vector may not have a fourth elementh
1287  // if so, default to the same channel
1288  if(params.size() == 3) {
1289  alphaValue = ALPHA;
1290  } else {
1291  if(params[3] == "red") {
1292  alphaValue = RED;
1293  } else if(params[3] == "green") {
1294  alphaValue = GREEN;
1295  } else if(params[3] == "blue") {
1296  alphaValue = BLUE;
1297  } else if(params[3] == "alpha") {
1298  alphaValue = ALPHA;
1299  } else {
1300  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1301  return nullptr;
1302  }
1303  }
1304 
1305  return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1306 }
1307 
1308 } // end anon namespace
1309 
1310 } /* 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:43
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:98
pixel_t * pixels() const
Definition: surface.hpp:117
utils::span< pixel_t > pixel_span() const
Definition: surface.hpp:122
static 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:336
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:290
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:274
color_mapping generate_color_mapping(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_mapping
Definition: color_range.hpp:24
Definitions for the interface to Wesnoth Markup Language (WML).
variant a_
Definition: function.cpp:821
std::size_t i
Definition: function.cpp:1031
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:851
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:691
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:37
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:51
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition: color.hpp:102
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
surface get_surface_portion(const surface &src, rect &area)
Get a portion of the screen.
Definition: utils.cpp:1351
void alpha_to_greyscale(surface &nsurf)
Definition: utils.cpp:503
void recolor_image(surface &nsurf, const color_mapping &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition: utils.cpp:635
surface scale_surface_legacy(const surface &surf, int w, int h)
Scale a surface using simple bilinear filtering (discarding rgb from source pixels with 0 alpha)
Definition: utils.cpp:226
void wipe_alpha(surface &nsurf)
Definition: utils.cpp:516
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1096
void sepia_image(surface &nsurf)
Definition: utils.cpp:463
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1155
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:553
rect get_non_transparent_portion(const surface &surf)
Definition: utils.cpp:1411
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition: utils.cpp:406
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:482
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:794
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:62
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:359
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:958
void greyscale_image(surface &nsurf)
Definition: utils.cpp:423
void flop_surface(surface &nsurf)
Definition: utils.cpp:1335
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1285
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1245
void flip_surface(surface &nsurf)
Definition: utils.cpp:1319
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition: utils.cpp:100
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1183
void monochrome_image(surface &nsurf, const int threshold)
Definition: utils.cpp:445
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:707
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