The Battle for Wesnoth  1.19.25+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 /* The Opacity IPF doesn't seem to work with surface-wide alpha and instead needs per-pixel alpha. */
482 {
483  if(src) {
484  apply_surface_opacity(src, opacity_);
485  }
486 }
487 
489 {
490  if((r_ != 0 || g_ != 0 || b_ != 0)) {
491  adjust_surface_color(src, r_, g_, b_);
492  }
493 }
494 
496 {
497  blend_surface(src, static_cast<double>(a_), color_t(r_, g_, b_));
498 }
499 
501 {
502  blur_alpha_surface(src, depth_);
503 }
504 
506 {
507  surface ret = src.clone();
508  SDL_FillSurfaceRect(ret, nullptr, SDL_MapSurfaceRGBA(ret, color_.r, color_.g, color_.b, color_.a));
509  sdl_blit(src, nullptr, ret, nullptr);
510  src = ret;
511 }
512 
514 {
515  swap_channels_image(src, red_, green_, blue_, alpha_);
516 }
517 
518 namespace {
519 
520 struct parse_mod_registration
521 {
522  parse_mod_registration(const char* name, mod_parser parser)
523  {
524  mod_parsers[name] = std::move(parser);
525  }
526 };
527 
528 /** A macro for automatic modification parser registration
529  *
530  * It automatically registers the created parser in the mod_parsers map
531  * It should be used just like a function header (look at the uses below)
532  * It should only be used within an anonymous namespace
533  *
534  * @param type The modification type to be registered (unquoted)
535  * @param args_var The name for the string argument provided
536  */
537 #define REGISTER_MOD_PARSER(type, args_var) \
538  static std::unique_ptr<modification> parse_##type##_mod(std::string_view); \
539  static parse_mod_registration parse_##type##_mod_registration_aux(#type, &parse_##type##_mod); \
540  static std::unique_ptr<modification> parse_##type##_mod(std::string_view args_var) \
541 
542 // Color-range-based recoloring
543 REGISTER_MOD_PARSER(TC, args)
544 {
545  const auto params = utils::split_view(args,',');
546 
547  if(params.size() < 2) {
548  ERR_DP << "too few arguments passed to the ~TC() function";
549 
550  return nullptr;
551  }
552 
553  const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
554  if(side_n < 1) {
555  ERR_DP << "Invalid side (" << params[0] << ") passed to the ~TC() function";
556  return nullptr;
557  }
558 
559  //
560  // Pass argseters for RC functor
561  //
562  if(!game_config::tc_info(params[1]).size()){
563  ERR_DP << "could not load TC info for '" << params[1] << "' palette";
564  ERR_DP << "bailing out from TC";
565 
566  return nullptr;
567  }
568 
569  try {
570  const color_range& new_color = team::get_side_color_range(side_n);
571  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
572 
573  return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
574  } catch(const config::error& e) {
575  ERR_DP << "caught config::error while processing TC: " << e.message;
576  ERR_DP << "bailing out from TC";
577 
578  return nullptr;
579  }
580 }
581 
582 // Team-color-based color range selection and recoloring
583 REGISTER_MOD_PARSER(RC, args)
584 {
585  const auto recolor_params = utils::split_view(args,'>');
586 
587  if(recolor_params.size() <= 1) {
588  return nullptr;
589  }
590 
591  //
592  // recolor source palette to color range
593  //
594  try {
595  const color_range& new_color = game_config::color_info(recolor_params[1]);
596  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
597 
598  return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
599  } catch (const config::error& e) {
600  ERR_DP
601  << "caught config::error while processing color-range RC: "
602  << e.message;
603  ERR_DP << "bailing out from RC";
604  return nullptr;
605  }
606 }
607 
608 // Palette switch
609 REGISTER_MOD_PARSER(PAL, args)
610 {
611  const auto remap_params = utils::split_view(args,'>');
612 
613  if(remap_params.size() < 2) {
614  ERR_DP << "not enough arguments passed to the ~PAL() function: " << args;
615 
616  return nullptr;
617  }
618 
619  try {
620  color_mapping rc_map;
621  const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
622  const std::vector<color_t>& new_palette = game_config::tc_info(remap_params[1]);
623 
624  for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
625  rc_map[old_palette[i]] = new_palette[i];
626  }
627 
628  return std::make_unique<rc_modification>(std::move(rc_map));
629  } catch(const config::error& e) {
630  ERR_DP
631  << "caught config::error while processing PAL function: "
632  << e.message;
633  ERR_DP
634  << "bailing out from PAL";
635 
636  return nullptr;
637  }
638 }
639 
640 // Flip/flop
641 REGISTER_MOD_PARSER(FL, args)
642 {
643  bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
644  bool vert = (args.find("vert") != std::string::npos);
645 
646  return std::make_unique<fl_modification>(horiz, vert);
647 }
648 
649 // Rotations
650 REGISTER_MOD_PARSER(ROTATE, args)
651 {
652  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
653 
654  switch(slice_params.size()) {
655  case 0:
656  return std::make_unique<rotate_modification>();
657  case 1:
658  return std::make_unique<rotate_modification>(
659  utils::from_chars<int>(slice_params[0]).value_or(0));
660  case 2:
661  return std::make_unique<rotate_modification>(
662  utils::from_chars<int>(slice_params[0]).value_or(0),
663  utils::from_chars<int>(slice_params[1]).value_or(0));
664  case 3:
665  return std::make_unique<rotate_modification>(
666  utils::from_chars<int>(slice_params[0]).value_or(0),
667  utils::from_chars<int>(slice_params[1]).value_or(0),
668  utils::from_chars<int>(slice_params[2]).value_or(0));
669  }
670  return nullptr;
671 }
672 
673 // Grayscale
675 {
676  return std::make_unique<gs_modification>();
677 }
678 
679 // crop transparent padding
680 REGISTER_MOD_PARSER(CROP_TRANSPARENCY, )
681 {
682  return std::make_unique<crop_transparency_modification>();
683 }
684 
685 // TODO: should this be made a more general util function?
686 bool in_range(int val, int min, int max)
687 {
688  return min <= val && val <= max;
689 }
690 
691 // Black and white
692 REGISTER_MOD_PARSER(BW, args)
693 {
694  const auto params = utils::split_view(args, ',');
695 
696  if(params.size() != 1) {
697  ERR_DP << "~BW() requires exactly one argument";
698  return nullptr;
699  }
700 
701  // TODO: maybe get this directly as uint8_t?
702  const auto threshold = utils::from_chars<int>(params[0]);
703  if(!threshold) {
704  ERR_DP << "unsupported argument in ~BW() function";
705  return nullptr;
706  }
707 
708  if(!in_range(*threshold, 0, 255)) {
709  ERR_DP << "~BW() argument out of range 0 - 255";
710  return nullptr;
711  }
712 
713  return std::make_unique<bw_modification>(*threshold);
714 }
715 
716 // Sepia
717 REGISTER_MOD_PARSER(SEPIA, )
718 {
719  return std::make_unique<sepia_modification>();
720 }
721 
722 // Negative
723 REGISTER_MOD_PARSER(NEG, args)
724 {
725  const auto params = utils::split_view(args, ',');
726 
727  switch(params.size()) {
728  case 0:
729  // apparently -1 may be a magic number but this is the threshold
730  // value required to fully invert a channel
731  return std::make_unique<negative_modification>(-1, -1, -1);
732 
733  case 1: {
734  const auto threshold = utils::from_chars<int>(params[0]);
735 
736  if(threshold && in_range(*threshold, -1, 255)) {
737  return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
738  } else {
739  ERR_DP << "unsupported argument value in ~NEG() function";
740  return nullptr;
741  }
742  }
743 
744  case 3: {
745  const auto thR = utils::from_chars<int>(params[0]);
746  const auto thG = utils::from_chars<int>(params[1]);
747  const auto thB = utils::from_chars<int>(params[2]);
748 
749  if(thR && thG && thB && in_range(*thR, -1, 255) && in_range(*thG, -1, 255) && in_range(*thB, -1, 255)) {
750  return std::make_unique<negative_modification>(*thR, *thG, *thB);
751  } else {
752  ERR_DP << "unsupported argument value in ~NEG() function";
753  return nullptr;
754  }
755  }
756 
757  default:
758  ERR_DP << "~NEG() requires 0, 1 or 3 arguments";
759  return nullptr;
760  }
761 }
762 
763 // Plot Alpha
764 REGISTER_MOD_PARSER(PLOT_ALPHA, )
765 {
766  return std::make_unique<plot_alpha_modification>();
767 }
768 
769 // Wipe Alpha
770 REGISTER_MOD_PARSER(WIPE_ALPHA, )
771 {
772  return std::make_unique<wipe_alpha_modification>();
773 }
774 
775 // Adjust Alpha
776 REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
777 {
778  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
779  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
780  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
781 
782  if(params.size() != 1) {
783  ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments";
784  return nullptr;
785  }
786 
787  return std::make_unique<adjust_alpha_modification>(params.at(0));
788 }
789 
790 // Adjust Channels
791 REGISTER_MOD_PARSER(CHAN, args)
792 {
793  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
794  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
795  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
796 
797  if(params.size() < 1 || params.size() > 4) {
798  ERR_DP << "~CHAN() requires 1 to 4 arguments";
799  return nullptr;
800  }
801 
802  return std::make_unique<adjust_channels_modification>(params);
803 }
804 
805 // Color-shift
806 REGISTER_MOD_PARSER(CS, args)
807 {
808  const auto factors = utils::split_view(args, ',');
809  const std::size_t s = factors.size();
810 
811  if(s == 0) {
812  ERR_DP << "no arguments passed to the ~CS() function";
813  return nullptr;
814  }
815 
816  int r = 0, g = 0, b = 0;
817 
818  r = utils::from_chars<int>(factors[0]).value_or(0);
819 
820  if(s > 1 ) {
821  g = utils::from_chars<int>(factors[1]).value_or(0);
822  }
823  if(s > 2 ) {
824  b = utils::from_chars<int>(factors[2]).value_or(0);
825  }
826 
827  return std::make_unique<cs_modification>(r, g , b);
828 }
829 
830 // Color blending
831 REGISTER_MOD_PARSER(BLEND, args)
832 {
833  const auto params = utils::split_view(args, ',');
834 
835  if(params.size() != 4) {
836  ERR_DP << "~BLEND() requires exactly 4 arguments";
837  return nullptr;
838  }
839 
840  float opacity = 0.0f;
841  const std::string_view& opacity_str = params[3];
842  const std::string_view::size_type p100_pos = opacity_str.find('%');
843 
844  if(p100_pos == std::string::npos)
845  opacity = utils::from_chars<float>(opacity_str).value_or(0.0f);
846  else {
847  // make multiplier
848  const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
849  opacity = utils::from_chars<float>(parsed_field).value_or(0.0f);
850  opacity /= 100.0f;
851  }
852 
853  return std::make_unique<blend_modification>(
854  utils::from_chars<int>(params[0]).value_or(0),
855  utils::from_chars<int>(params[1]).value_or(0),
856  utils::from_chars<int>(params[2]).value_or(0),
857  opacity);
858 }
859 
860 // Crop/slice
861 REGISTER_MOD_PARSER(CROP, args)
862 {
863  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
864  const std::size_t s = slice_params.size();
865 
866  if(s == 0 || (s == 1 && slice_params[0].empty())) {
867  ERR_DP << "no arguments passed to the ~CROP() function";
868  return nullptr;
869  }
870 
871  rect slice_rect { 0, 0, 0, 0 };
872 
873  slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
874 
875  if(s > 1) {
876  slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
877  }
878  if(s > 2) {
879  slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
880  }
881  if(s > 3) {
882  slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
883  }
884 
885  return std::make_unique<crop_modification>(slice_rect);
886 }
887 
888 static bool check_image(const image::locator& img, std::stringstream & message)
889 {
890  if(image::exists(img)) return true;
891  message << " image not found: '" << img.get_filename() << "'\n";
892  ERR_DP << message.str();
893  return false;
894 }
895 
896 // Blit
897 REGISTER_MOD_PARSER(BLIT, args)
898 {
899  std::vector<std::string> param = utils::parenthetical_split(args, ',');
900  const std::size_t s = param.size();
901 
902  if(s == 0 || (s == 1 && param[0].empty())){
903  ERR_DP << "no arguments passed to the ~BLIT() function";
904  return nullptr;
905  }
906 
907  if(s > 3){
908  ERR_DP << "too many arguments passed to the ~BLIT() function";
909  return nullptr;
910  }
911 
912  int x = 0, y = 0;
913 
914  if(s == 3) {
915  x = utils::from_chars<int>(param[1]).value_or(0);
916  y = utils::from_chars<int>(param[2]).value_or(0);
917  }
918 
919  const image::locator img(param[0]);
920  std::stringstream message;
921  message << "~BLIT():";
922  if(!check_image(img, message))
923  return nullptr;
925 
926  return std::make_unique<blit_modification>(surf, x, y);
927 }
928 
929 // Mask
930 REGISTER_MOD_PARSER(MASK, args)
931 {
932  std::vector<std::string> param = utils::parenthetical_split(args, ',');
933  const std::size_t s = param.size();
934 
935  if(s == 0 || (s == 1 && param[0].empty())){
936  ERR_DP << "no arguments passed to the ~MASK() function";
937  return nullptr;
938  }
939 
940  int x = 0, y = 0;
941 
942  if(s == 3) {
943  x = utils::from_chars<int>(param[1]).value_or(0);
944  y = utils::from_chars<int>(param[2]).value_or(0);
945  }
946 
947  if(x < 0 || y < 0) {
948  ERR_DP << "negative position arguments in ~MASK() function";
949  return nullptr;
950  }
951 
952  const image::locator img(param[0]);
953  std::stringstream message;
954  message << "~MASK():";
955  if(!check_image(img, message))
956  return nullptr;
958 
959  return std::make_unique<mask_modification>(surf, x, y);
960 }
961 
962 // Light
963 REGISTER_MOD_PARSER(L, args)
964 {
965  if(args.empty()){
966  ERR_DP << "no arguments passed to the ~L() function";
967  return nullptr;
968  }
969 
970  surface surf = get_surface(std::string{args}); // FIXME: string_view for image::locator::value
971  return std::make_unique<light_modification>(surf);
972 }
973 
974 namespace
975 {
976 std::pair<int, bool> parse_scale_value(std::string_view arg)
977 {
978  if(const std::size_t pos = arg.rfind('%'); pos != std::string_view::npos) {
979  return { utils::from_chars<int>(arg.substr(0, pos)).value_or(0), true };
980  } else {
981  return { utils::from_chars<int>(arg).value_or(0), false };
982  }
983 }
984 
985 /** Common helper function to parse scaling IPF inputs. */
986 utils::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
987 {
988  const auto scale_params = utils::split_view(args, ',', utils::STRIP_SPACES);
989  const std::size_t num_args = scale_params.size();
990 
991  if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
992  return utils::nullopt;
993  }
994 
995  uint8_t flags = 0;
996  std::array<int, 2> parsed_sizes{0,0};
997 
998  for(unsigned i = 0; i < std::min<unsigned>(2, num_args); ++i) {
999  const auto& [size, relative] = parse_scale_value(scale_params[i]);
1000 
1001  if(size < 0) {
1002  ERR_DP << "Negative size passed to scaling IPF. Original image dimension will be used instead";
1003  continue;
1004  }
1005 
1006  parsed_sizes[i] = size;
1007 
1008  if(relative) {
1010  }
1011  }
1012 
1013  return std::pair{point{parsed_sizes[0], parsed_sizes[1]}, flags};
1014 }
1015 
1016 } // namespace
1017 
1018 // Scale
1019 REGISTER_MOD_PARSER(SCALE, args)
1020 {
1021  if(auto params = parse_scale_args(args)) {
1023  return std::make_unique<scale_modification>(params->first, mode | params->second);
1024  } else {
1025  ERR_DP << "no arguments passed to the ~SCALE() function";
1026  return nullptr;
1027  }
1028 }
1029 
1030 REGISTER_MOD_PARSER(SCALE_SHARP, args)
1031 {
1032  if(auto params = parse_scale_args(args)) {
1034  return std::make_unique<scale_modification>(params->first, mode | params->second);
1035  } else {
1036  ERR_DP << "no arguments passed to the ~SCALE_SHARP() function";
1037  return nullptr;
1038  }
1039 }
1040 
1041 REGISTER_MOD_PARSER(SCALE_INTO, args)
1042 {
1043  if(auto params = parse_scale_args(args)) {
1045  return std::make_unique<scale_modification>(params->first, mode | params->second);
1046  } else {
1047  ERR_DP << "no arguments passed to the ~SCALE_INTO() function";
1048  return nullptr;
1049  }
1050 }
1051 
1052 REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1053 {
1054  if(auto params = parse_scale_args(args)) {
1056  return std::make_unique<scale_modification>(params->first, mode | params->second);
1057  } else {
1058  ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function";
1059  return nullptr;
1060  }
1061 }
1062 
1063 // xBRZ
1064 REGISTER_MOD_PARSER(XBRZ, args)
1065 {
1066  const int factor = std::clamp(utils::from_chars<int>(args).value_or(1), 1, 6);
1067  return std::make_unique<xbrz_modification>(factor);
1068 }
1069 
1070 // Pad
1071 REGISTER_MOD_PARSER(PAD, args)
1072 {
1073  int top = 0;
1074  int right = 0;
1075  int bottom = 0;
1076  int left = 0;
1077 
1078  // Check for the presence of an '=' sign to determine the parsing mode.
1079  if(args.find('=') != std::string_view::npos) {
1080  // --- Keyword-Argument Mode ---
1081  const auto params = utils::map_split(std::string{args}, ',', '='); // map_split needs a std::string
1082 
1083  // Map valid input strings to the corresponding integer reference
1084  const std::map<std::string, int*> alias_map = {
1085  {"top", &top},
1086  {"t", &top},
1087  {"right", &right},
1088  {"r", &right},
1089  {"bottom", &bottom},
1090  {"b", &bottom},
1091  {"left", &left},
1092  {"l", &left},
1093  };
1094 
1095  // Parse and assign values if keywords are valid
1096  for(const auto& [key, value] : params) {
1097  auto it = alias_map.find(key);
1098  if(it != alias_map.end()) {
1099  if(utils::optional padding = utils::from_chars<int>(value)) {
1100  *it->second = padding.value();
1101  } else {
1102  ERR_DP << "~PAD() keyword argument '" << key << "' requires a valid integer value. Received: '" << value << "'.";
1103  return nullptr;
1104  }
1105  } else {
1106  ERR_DP << "~PAD() found an unknown keyword: '" << key << "'. Valid keywords: top, t, right, r, bottom, b, left, l.";
1107  return nullptr;
1108  }
1109  }
1110  } else {
1111  // --- Numeric-Argument Mode ---
1112  const auto params = utils::split_view(args, ',');
1113  if(params.size() != 1) {
1114  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.";
1115  return nullptr;
1116  }
1117 
1118  // Single integer argument: apply to all sides
1119  if(utils::optional padding = utils::from_chars<int>(params[0])) {
1120  top = right = bottom = left = padding.value();
1121  } else {
1122  ERR_DP << "~PAD() numeric argument (pad all sides) requires a single valid integer. Received: '" << params[0] << "'.";
1123  return nullptr;
1124  }
1125  }
1126 
1127  return std::make_unique<pad_modification>(top, right, bottom, left);
1128 }
1129 
1130 // Gaussian-like blur
1131 REGISTER_MOD_PARSER(BL, args)
1132 {
1133  const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1134  return std::make_unique<bl_modification>(depth);
1135 }
1136 
1137 // Opacity-shift
1138 REGISTER_MOD_PARSER(O, args)
1139 {
1140  const std::string::size_type p100_pos = args.find('%');
1141  float num = 0.0f;
1142  if(p100_pos == std::string::npos) {
1143  num = utils::from_chars<float>(args).value_or(0.0f);
1144  } else {
1145  // make multiplier
1146  const std::string_view parsed_field = args.substr(0, p100_pos);
1147  num = utils::from_chars<float>(parsed_field).value_or(0.0f);
1148  num /= 100.0f;
1149  }
1150 
1151  return std::make_unique<o_modification>(num);
1152 }
1153 
1154 //
1155 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1156 // Hence they are at the end of the evaluation.
1157 //
1158 // Red component color-shift
1159 REGISTER_MOD_PARSER(R, args)
1160 {
1161  const int r = utils::from_chars<int>(args).value_or(0);
1162  return std::make_unique<cs_modification>(r, 0, 0);
1163 }
1164 
1165 // Green component color-shift
1166 REGISTER_MOD_PARSER(G, args)
1167 {
1168  const int g = utils::from_chars<int>(args).value_or(0);
1169  return std::make_unique<cs_modification>(0, g, 0);
1170 }
1171 
1172 // Blue component color-shift
1173 REGISTER_MOD_PARSER(B, args)
1174 {
1175  const int b = utils::from_chars<int>(args).value_or(0);
1176  return std::make_unique<cs_modification>(0, 0, b);
1177 }
1178 
1179 REGISTER_MOD_PARSER(NOP, )
1180 {
1181  return nullptr;
1182 }
1183 
1184 // Only used to tag terrain images which should not be color-shifted by ToD
1185 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1186 {
1187  return nullptr;
1188 }
1189 
1190 // Fake image function used by GUI2 portraits until
1191 // Mordante gets rid of it. *tsk* *tsk*
1192 REGISTER_MOD_PARSER(RIGHT, )
1193 {
1194  return nullptr;
1195 }
1196 
1197 // Add a background color.
1198 REGISTER_MOD_PARSER(BG, args)
1199 {
1200  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1201  const auto factors = utils::split_view(args, ',');
1202 
1203  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1204  c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1205  }
1206 
1207  return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1208 }
1209 
1210 // Channel swap
1211 REGISTER_MOD_PARSER(SWAP, args)
1212 {
1213  const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1214 
1215  // accept 3 arguments (rgb) or 4 (rgba)
1216  if(params.size() != 3 && params.size() != 4) {
1217  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1218  return nullptr;
1219  }
1220 
1221  channel redValue, greenValue, blueValue, alphaValue;
1222  // compare the parameter's value with the constants defined in the channels enum
1223  if(params[0] == "red") {
1224  redValue = RED;
1225  } else if(params[0] == "green") {
1226  redValue = GREEN;
1227  } else if(params[0] == "blue") {
1228  redValue = BLUE;
1229  } else if(params[0] == "alpha") {
1230  redValue = ALPHA;
1231  } else {
1232  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1233  return nullptr;
1234  }
1235 
1236  // wash, rinse and repeat for the other three channels
1237  if(params[1] == "red") {
1238  greenValue = RED;
1239  } else if(params[1] == "green") {
1240  greenValue = GREEN;
1241  } else if(params[1] == "blue") {
1242  greenValue = BLUE;
1243  } else if(params[1] == "alpha") {
1244  greenValue = ALPHA;
1245  } else {
1246  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1247  return nullptr;
1248  }
1249 
1250  if(params[2] == "red") {
1251  blueValue = RED;
1252  } else if(params[2] == "green") {
1253  blueValue = GREEN;
1254  } else if(params[2] == "blue") {
1255  blueValue = BLUE;
1256  } else if(params[2] == "alpha") {
1257  blueValue = ALPHA;
1258  } else {
1259  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1260  return nullptr;
1261  }
1262 
1263  // additional check: the params vector may not have a fourth elementh
1264  // if so, default to the same channel
1265  if(params.size() == 3) {
1266  alphaValue = ALPHA;
1267  } else {
1268  if(params[3] == "red") {
1269  alphaValue = RED;
1270  } else if(params[3] == "green") {
1271  alphaValue = GREEN;
1272  } else if(params[3] == "blue") {
1273  alphaValue = BLUE;
1274  } else if(params[3] == "alpha") {
1275  alphaValue = ALPHA;
1276  } else {
1277  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1278  return nullptr;
1279  }
1280  }
1281 
1282  return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1283 }
1284 
1285 } // end anon namespace
1286 
1287 } /* 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
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
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:855
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
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:1356
void alpha_to_greyscale(surface &nsurf)
Definition: utils.cpp:504
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:636
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 wipe_alpha(surface &nsurf)
Definition: utils.cpp:517
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1092
void sepia_image(surface &nsurf)
Definition: utils.cpp:464
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1153
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:554
rect get_non_transparent_portion(const surface &surf)
Definition: utils.cpp:1440
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:483
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:790
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:954
void greyscale_image(surface &nsurf)
Definition: utils.cpp:424
void apply_surface_opacity(surface &surf, float opacity)
Applies an opacity modification to a surface.
Definition: utils.cpp:1392
void flop_surface(surface &nsurf)
Definition: utils.cpp:1340
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1283
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1243
void flip_surface(surface &nsurf)
Definition: utils.cpp:1317
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:1181
void monochrome_image(surface &nsurf, const int threshold)
Definition: utils.cpp:446
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask to the source surface by calculating the minimum alpha channel value for every corresp...
Definition: utils.cpp:708
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