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