The Battle for Wesnoth  1.19.11+dev
image_modifications.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2025
3  by Iris Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "image_modifications.hpp"
17 
18 #include <utility>
19 
20 #include "color.hpp"
21 #include "config.hpp"
22 #include "game_config.hpp"
23 #include "picture.hpp"
24 #include "lexical_cast.hpp"
25 #include "log.hpp"
27 #include "team.hpp"
28 #include "utils/from_chars.hpp"
29 
30 #include "formula/formula.hpp"
31 #include "formula/callable_objects.hpp"
32 
33 #define GETTEXT_DOMAIN "wesnoth-lib"
34 
35 static lg::log_domain log_display("display");
36 #define ERR_DP LOG_STREAM(err, log_display)
37 
38 namespace image {
39 
40 void modification_queue::push(std::unique_ptr<modification>&& mod)
41 {
42  priorities_[mod->priority()].push_back(std::move(mod));
43 }
44 
46 {
47  auto top_pair = priorities_.begin();
48  auto& mod_list = top_pair->second;
49 
50  // Erase the top element.
51  mod_list.erase(mod_list.begin());
52 
53  // We need to keep the map clean.
54  if(mod_list.empty()) {
55  priorities_.erase(top_pair);
56  }
57 }
58 
59 std::size_t modification_queue::size() const
60 {
61  std::size_t count = 0;
62  for(const auto& [priority, mods] : priorities_) {
63  count += mods.size();
64  }
65 
66  return count;
67 }
68 
70 {
71  return *priorities_.begin()->second.front();
72 }
73 
74 
75 namespace {
76 
77 /** A function used to parse modification arguments */
78 using mod_parser = std::function<std::unique_ptr<modification>(std::string_view)>;
79 
80 /** A map of all registered mod parsers
81  *
82  * The mapping is between the modification name and the parser function pointer
83  * An example of an entry would be "TC" -> &parse_TC_mod
84  */
85 std::map<std::string, mod_parser, std::less<>> mod_parsers;
86 
87 /** Decodes a single modification using an appropriate mod_parser
88  *
89  * @param encoded_mod A string representing a single modification
90  *
91  * @return A pointer to the decoded modification object
92  * @retval nullptr if the string is invalid or a parser isn't found
93  */
94 std::unique_ptr<modification> decode_modification(const std::string& encoded_mod)
95 {
96  const std::vector<std::string> split = utils::parenthetical_split(encoded_mod);
97 
98  if(split.size() != 2) {
99  ERR_DP << "error parsing image modifications: " << encoded_mod;
100  return nullptr;
101  }
102 
103  const std::string& mod_type = split[0];
104  const std::string& args = split[1];
105 
106  if(const auto parser = mod_parsers.find(mod_type); parser != mod_parsers.end()) {
107  return std::invoke(parser->second, args);
108  } else {
109  ERR_DP << "unknown image function in path: " << mod_type;
110  return nullptr;
111  }
112 }
113 
114 } // end anon namespace
115 
116 
117 modification::imod_exception::imod_exception(const std::stringstream& message_stream)
118  : message(message_stream.str())
119 {
120  this->store();
121 }
122 
124  : message(message)
125 {
126  this->store();
127 }
128 
129 /** Decodes the modification string
130  *
131  * Important:
132  * It creates new objects which need to be deleted after use
133  *
134  * @param encoded_mods A string representing any number of modifications
135  *
136  * @return A modification_queue filled with decoded modification pointers
137  */
138 modification_queue modification::decode(const std::string& encoded_mods)
139 {
140  modification_queue mods;
141 
142  for(const std::string& encoded_mod : utils::parenthetical_split(encoded_mods, '~')) {
143  if(auto mod = decode_modification(encoded_mod)) {
144  mods.push(std::move(mod));
145  }
146  }
147 
148  return mods;
149 }
150 
152 {
153  recolor_image(src, rc_map_);
154 }
155 
157 {
158  if(horiz_ && vert_ ) {
159  // Slightly faster than doing both a flip and a flop.
161  } else if(horiz_) {
162  flip_surface(src);
163  } else if(vert_) {
164  flop_surface(src);
165  }
166 }
167 
169 {
170  // Convert the number of degrees to the interval [0,360].
171  const int normalized = degrees_ >= 0 ?
172  degrees_ - 360 * (degrees_ / 360) :
173  degrees_ + 360 * (1 + (-degrees_) / 360); // In case compilers disagree as to what -90/360 is.
174 
175  switch ( normalized )
176  {
177  case 0:
178  return;
179  case 90:
180  src = rotate_90_surface(src, true);
181  return;
182  case 180:
184  return;
185  case 270:
186  src = rotate_90_surface(src, false);
187  return;
188  case 360:
189  return;
190  }
191 
192  src = rotate_any_surface(src, normalized, zoom_, offset_);
193 }
194 
196 {
198 }
199 
201 {
203  if(src_rect.w == src->w && src_rect.h == src->h) {
204  return;
205  }
206 
207  if(surface cropped = get_surface_portion(src, src_rect)) {
208  src = cropped;
209  } else {
210  ERR_DP << "Failed to either crop or scale the surface";
211  }
212 }
213 
215 {
216  monochrome_image(src, threshold_);
217 }
218 
220 {
221  sepia_image(src);
222 }
223 
225 {
226  negative_image(src, red_, green_, blue_);
227 }
228 
230 {
232 }
233 
235 {
236  wipe_alpha(src);
237 }
238 
239 // TODO: Is this useful enough to move into formula/callable_objects?
241 {
242 public:
243  pixel_callable(std::size_t index, color_t clr, int w, int h)
244  : color_callable(clr), coord(), w(w), h(h)
245  {
246  coord.x = index % w;
247  coord.y = index / w;
248  }
249 
250  void get_inputs(wfl::formula_input_vector& inputs) const override
251  {
252  color_callable::get_inputs(inputs);
253  add_input(inputs, "x");
254  add_input(inputs, "y");
255  add_input(inputs, "u");
256  add_input(inputs, "v");
257  add_input(inputs, "height");
258  add_input(inputs, "width");
259  }
260 
261  wfl::variant get_value(const std::string& key) const override
262  {
263  using wfl::variant;
264  if(key == "x") {
265  return variant(coord.x);
266  } else if(key == "y") {
267  return variant(coord.y);
268  } else if(key == "width") {
269  return variant(w);
270  } else if(key == "height") {
271  return variant(h);
272  } else if(key == "u") {
273  return variant(coord.x / static_cast<float>(w));
274  } else if(key == "v") {
275  return variant(coord.y / static_cast<float>(h));
276  }
277 
278  return color_callable::get_value(key);
279  }
280 
281 private:
282  SDL_Point coord;
283  int w, h;
284 };
285 
287 {
288  if(src) {
289  wfl::formula new_alpha(formula_);
290 
291  surface_lock lock(src);
292  std::size_t index{0};
293 
294  for(auto& pixel : lock.pixel_span()) {
295  auto color = color_t::from_argb_bytes(pixel);
296 
297  auto px = pixel_callable{index++, color, src->w, src->h};
298  color.a = std::min<unsigned>(new_alpha.evaluate(px).as_int(), 255);
299 
300  pixel = color.to_argb_bytes();
301  }
302  }
303 }
304 
306 {
307  if(src) {
308  wfl::formula new_r(formulas_[0]);
309  wfl::formula new_g(formulas_[1]);
310  wfl::formula new_b(formulas_[2]);
311  wfl::formula new_a(formulas_[3]);
312 
313  surface_lock lock(src);
314  std::size_t index{0};
315 
316  for(auto& pixel : lock.pixel_span()) {
317  auto color = color_t::from_argb_bytes(pixel);
318 
319  auto px = pixel_callable{index++, color, src->w, src->h};
320  color.r = std::min<unsigned>(new_r.evaluate(px).as_int(), 255);
321  color.g = std::min<unsigned>(new_g.evaluate(px).as_int(), 255);
322  color.b = std::min<unsigned>(new_b.evaluate(px).as_int(), 255);
323  color.a = std::min<unsigned>(new_a.evaluate(px).as_int(), 255);
324 
325  pixel = color.to_argb_bytes();
326  }
327  }
328 }
329 
331 {
332  SDL_Rect area = slice_;
333  if(area.w == 0) {
334  area.w = src->w;
335  }
336 
337  if(area.h == 0) {
338  area.h = src->h;
339  }
340 
341  src = cut_surface(src, area);
342 }
343 
345 {
346  if(x_ >= src->w) {
347  std::stringstream sstr;
348  sstr << "~BLIT(): x-coordinate '"
349  << x_ << "' larger than destination image's width '"
350  << src->w << "' no blitting performed.\n";
351 
352  throw imod_exception(sstr);
353  }
354 
355  if(y_ >= src->h) {
356  std::stringstream sstr;
357  sstr << "~BLIT(): y-coordinate '"
358  << y_ << "' larger than destination image's height '"
359  << src->h << "' no blitting performed.\n";
360 
361  throw imod_exception(sstr);
362  }
363 
364  if(surf_->w + x_ < 0) {
365  std::stringstream sstr;
366  sstr << "~BLIT(): offset and width '"
367  << x_ + surf_->w << "' less than zero no blitting performed.\n";
368 
369  throw imod_exception(sstr);
370  }
371 
372  if(surf_->h + y_ < 0) {
373  std::stringstream sstr;
374  sstr << "~BLIT(): offset and height '"
375  << y_ + surf_->h << "' less than zero no blitting performed.\n";
376 
377  throw imod_exception(sstr);
378  }
379 
380  SDL_Rect r {x_, y_, 0, 0};
381  sdl_blit(surf_, nullptr, src, &r);
382 }
383 
385 {
386  if(src->w == mask_->w && src->h == mask_->h && x_ == 0 && y_ == 0) {
387  mask_surface(src, mask_);
388  return;
389  }
390 
391  SDL_Rect r {x_, y_, 0, 0};
392  surface new_mask(src->w, src->h);
393  sdl_blit(mask_, nullptr, new_mask, &r);
394  mask_surface(src, new_mask);
395 }
396 
398 {
399  if(src == nullptr) { return; }
400 
401  // light_surface wants a neutral surface having same dimensions
402  if(surf_->w != src->w || surf_->h != src->h) {
403  light_surface(src, scale_surface(surf_, src->w, src->h));
404  } else {
405  light_surface(src, surf_);
406  }
407 }
408 
410 {
411  point size = target_size_;
412 
413  if(size.x <= 0) {
414  size.x = src->w;
415  } else if(flags_ & X_BY_FACTOR) {
416  size.x = src->w * (static_cast<double>(size.x) / 100);
417  }
418 
419  if(size.y <= 0) {
420  size.y = src->h;
421  } else if(flags_ & Y_BY_FACTOR) {
422  size.y = src->h * (static_cast<double>(size.y) / 100);
423  }
424 
425  if(flags_ & PRESERVE_ASPECT_RATIO) {
426  const auto ratio = std::min(
427  static_cast<long double>(size.x) / src->w,
428  static_cast<long double>(size.y) / src->h
429  );
430 
431  size = {
432  static_cast<int>(src->w * ratio),
433  static_cast<int>(src->h * ratio)
434  };
435  }
436 
437  if(flags_ & SCALE_SHARP) {
439  } else {
441  }
442 }
443 
445 {
446  if(z_ != 1) {
447  src = scale_surface_xbrz(src, z_);
448  }
449 }
450 
451 /*
452  * The Opacity IPF doesn't seem to work with surface-wide alpha and instead needs per-pixel alpha.
453  * If this is needed anywhere else it can be moved back to sdl/utils.*pp.
454  */
456 {
457  if(src) {
458  uint8_t alpha_mod = float_to_color(opacity_);
459 
460  surface_lock lock(src);
461  uint32_t* beg = lock.pixels();
462  uint32_t* end = beg + src.area();
463 
464  while(beg != end) {
465  uint8_t alpha = (*beg) >> 24;
466 
467  if(alpha) {
468  uint8_t r, g, b;
469  r = (*beg) >> 16;
470  g = (*beg) >> 8;
471  b = (*beg);
472 
473  alpha = color_multiply(alpha, alpha_mod);
474  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
475  }
476 
477  ++beg;
478  }
479  }
480 }
481 
483 {
484  if((r_ != 0 || g_ != 0 || b_ != 0)) {
485  adjust_surface_color(src, r_, g_, b_);
486  }
487 }
488 
490 {
491  blend_surface(src, static_cast<double>(a_), color_t(r_, g_, b_));
492 }
493 
495 {
496  blur_alpha_surface(src, depth_);
497 }
498 
500 {
501  surface ret = src.clone();
502  SDL_FillRect(ret, nullptr, SDL_MapRGBA(ret->format, color_.r, color_.g, color_.b, color_.a));
503  sdl_blit(src, nullptr, ret, nullptr);
504  src = ret;
505 }
506 
508 {
509  swap_channels_image(src, red_, green_, blue_, alpha_);
510 }
511 
512 namespace {
513 
514 struct parse_mod_registration
515 {
516  parse_mod_registration(const char* name, mod_parser parser)
517  {
518  mod_parsers[name] = std::move(parser);
519  }
520 };
521 
522 /** A macro for automatic modification parser registration
523  *
524  * It automatically registers the created parser in the mod_parsers map
525  * It should be used just like a function header (look at the uses below)
526  * It should only be used within an anonymous namespace
527  *
528  * @param type The modification type to be registered (unquoted)
529  * @param args_var The name for the string argument provided
530  */
531 #define REGISTER_MOD_PARSER(type, args_var) \
532  static std::unique_ptr<modification> parse_##type##_mod(std::string_view); \
533  static parse_mod_registration parse_##type##_mod_registration_aux(#type, &parse_##type##_mod); \
534  static std::unique_ptr<modification> parse_##type##_mod(std::string_view args_var) \
535 
536 // Color-range-based recoloring
537 REGISTER_MOD_PARSER(TC, args)
538 {
539  const auto params = utils::split_view(args,',');
540 
541  if(params.size() < 2) {
542  ERR_DP << "too few arguments passed to the ~TC() function";
543 
544  return nullptr;
545  }
546 
547  const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
548  if(side_n < 1) {
549  ERR_DP << "Invalid side (" << params[0] << ") passed to the ~TC() function";
550  return nullptr;
551  }
552 
553  //
554  // Pass argseters for RC functor
555  //
556  if(!game_config::tc_info(params[1]).size()){
557  ERR_DP << "could not load TC info for '" << params[1] << "' palette";
558  ERR_DP << "bailing out from TC";
559 
560  return nullptr;
561  }
562 
563  color_range_map rc_map;
564  try {
565  const color_range& new_color = team::get_side_color_range(side_n);
566  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
567 
568  rc_map = recolor_range(new_color,old_color);
569  } catch(const config::error& e) {
570  ERR_DP << "caught config::error while processing TC: " << e.message;
571  ERR_DP << "bailing out from TC";
572 
573  return nullptr;
574  }
575 
576  return std::make_unique<rc_modification>(rc_map);
577 }
578 
579 // Team-color-based color range selection and recoloring
580 REGISTER_MOD_PARSER(RC, args)
581 {
582  const auto recolor_params = utils::split_view(args,'>');
583 
584  if(recolor_params.size() <= 1) {
585  return nullptr;
586  }
587 
588  //
589  // recolor source palette to color range
590  //
591  color_range_map rc_map;
592  try {
593  const color_range& new_color = game_config::color_info(recolor_params[1]);
594  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
595 
596  rc_map = recolor_range(new_color,old_color);
597  } catch (const config::error& e) {
598  ERR_DP
599  << "caught config::error while processing color-range RC: "
600  << e.message;
601  ERR_DP << "bailing out from RC";
602  rc_map.clear();
603  }
604 
605  return std::make_unique<rc_modification>(rc_map);
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_range_map 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>(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 = lexical_cast_default<float>(opacity_str);
846  else {
847  // make multiplier
848  const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
849  opacity = lexical_cast_default<float>(parsed_field);
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  SDL_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 // Gaussian-like blur
1071 REGISTER_MOD_PARSER(BL, args)
1072 {
1073  const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1074  return std::make_unique<bl_modification>(depth);
1075 }
1076 
1077 // Opacity-shift
1078 REGISTER_MOD_PARSER(O, args)
1079 {
1080  const std::string::size_type p100_pos = args.find('%');
1081  float num = 0.0f;
1082  if(p100_pos == std::string::npos) {
1083  num = lexical_cast_default<float, std::string_view>(args);
1084  } else {
1085  // make multiplier
1086  const std::string_view parsed_field = args.substr(0, p100_pos);
1087  num = lexical_cast_default<float, std::string_view>(parsed_field);
1088  num /= 100.0f;
1089  }
1090 
1091  return std::make_unique<o_modification>(num);
1092 }
1093 
1094 //
1095 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1096 // Hence they are at the end of the evaluation.
1097 //
1098 // Red component color-shift
1099 REGISTER_MOD_PARSER(R, args)
1100 {
1101  const int r = utils::from_chars<int>(args).value_or(0);
1102  return std::make_unique<cs_modification>(r, 0, 0);
1103 }
1104 
1105 // Green component color-shift
1106 REGISTER_MOD_PARSER(G, args)
1107 {
1108  const int g = utils::from_chars<int>(args).value_or(0);
1109  return std::make_unique<cs_modification>(0, g, 0);
1110 }
1111 
1112 // Blue component color-shift
1113 REGISTER_MOD_PARSER(B, args)
1114 {
1115  const int b = utils::from_chars<int>(args).value_or(0);
1116  return std::make_unique<cs_modification>(0, 0, b);
1117 }
1118 
1119 REGISTER_MOD_PARSER(NOP, )
1120 {
1121  return nullptr;
1122 }
1123 
1124 // Only used to tag terrain images which should not be color-shifted by ToD
1125 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1126 {
1127  return nullptr;
1128 }
1129 
1130 // Fake image function used by GUI2 portraits until
1131 // Mordante gets rid of it. *tsk* *tsk*
1132 REGISTER_MOD_PARSER(RIGHT, )
1133 {
1134  return nullptr;
1135 }
1136 
1137 // Add a background color.
1138 REGISTER_MOD_PARSER(BG, args)
1139 {
1140  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1141  const auto factors = utils::split_view(args, ',');
1142 
1143  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1144  c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1145  }
1146 
1147  return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1148 }
1149 
1150 // Channel swap
1151 REGISTER_MOD_PARSER(SWAP, args)
1152 {
1153  const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1154 
1155  // accept 3 arguments (rgb) or 4 (rgba)
1156  if(params.size() != 3 && params.size() != 4) {
1157  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1158  return nullptr;
1159  }
1160 
1161  channel redValue, greenValue, blueValue, alphaValue;
1162  // compare the parameter's value with the constants defined in the channels enum
1163  if(params[0] == "red") {
1164  redValue = RED;
1165  } else if(params[0] == "green") {
1166  redValue = GREEN;
1167  } else if(params[0] == "blue") {
1168  redValue = BLUE;
1169  } else if(params[0] == "alpha") {
1170  redValue = ALPHA;
1171  } else {
1172  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1173  return nullptr;
1174  }
1175 
1176  // wash, rinse and repeat for the other three channels
1177  if(params[1] == "red") {
1178  greenValue = RED;
1179  } else if(params[1] == "green") {
1180  greenValue = GREEN;
1181  } else if(params[1] == "blue") {
1182  greenValue = BLUE;
1183  } else if(params[1] == "alpha") {
1184  greenValue = ALPHA;
1185  } else {
1186  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1187  return nullptr;
1188  }
1189 
1190  if(params[2] == "red") {
1191  blueValue = RED;
1192  } else if(params[2] == "green") {
1193  blueValue = GREEN;
1194  } else if(params[2] == "blue") {
1195  blueValue = BLUE;
1196  } else if(params[2] == "alpha") {
1197  blueValue = ALPHA;
1198  } else {
1199  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1200  return nullptr;
1201  }
1202 
1203  // additional check: the params vector may not have a fourth elementh
1204  // if so, default to the same channel
1205  if(params.size() == 3) {
1206  alphaValue = ALPHA;
1207  } else {
1208  if(params[3] == "red") {
1209  alphaValue = RED;
1210  } else if(params[3] == "green") {
1211  alphaValue = GREEN;
1212  } else if(params[3] == "blue") {
1213  alphaValue = BLUE;
1214  } else if(params[3] == "alpha") {
1215  alphaValue = ALPHA;
1216  } else {
1217  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1218  return nullptr;
1219  }
1220  }
1221 
1222  return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1223 }
1224 
1225 } // end anon namespace
1226 
1227 } /* 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:60
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.
pixel_callable(std::size_t index, color_t clr, int w, int h)
wfl::variant get_value(const std::string &key) const override
void get_inputs(wfl::formula_input_vector &inputs) const override
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
void store() const noexcept
Stores a copy the current exception to be rethrown.
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:71
pixel_t * pixels() const
Definition: surface.hpp:90
utils::span< pixel_t > pixel_span() const
Definition: surface.hpp:95
static const color_range get_side_color_range(int side)
Definition: team.cpp:937
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() 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:330
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition: color.hpp:314
color_range_map recolor_range(const color_range &new_range, const std::vector< color_t > &old_rgb)
Converts a source palette using the specified color_range object.
Definition: color_range.cpp:91
std::unordered_map< color_t, color_t > color_range_map
Definition: color_range.hpp:30
Definitions for the interface to Wesnoth Markup Language (WML).
variant a_
Definition: function.cpp:819
std::size_t i
Definition: function.cpp:1030
variant b_
Definition: function.cpp:819
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:832
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:665
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:31
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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::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
surface surf
Image.
rect src
Non-transparent portion of the surface to compose.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition: color.hpp:128
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Exception thrown by the operator() when an error occurs.
imod_exception(const std::stringstream &message_stream)
Constructor.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
constexpr int area() const
The area of this rectangle, in square pixels.
Definition: rect.hpp:103
mock_char c
static map_location::direction s
void mask_surface(surface &nsurf, const surface &nmask, bool *empty_result, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:706
rect get_non_transparent_portion(const surface &nsurf)
Definition: utils.cpp:1399
void alpha_to_greyscale(surface &nsurf)
Definition: utils.cpp:502
surface scale_surface_legacy(const surface &surf, int w, int h)
Scale a surface using simple bilinear filtering (discarding rgb from source pixels with 0 alpha)
Definition: utils.cpp:225
void recolor_image(surface &nsurf, const color_range_map &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition: utils.cpp:634
void wipe_alpha(surface &nsurf)
Definition: utils.cpp:515
void sepia_image(surface &nsurf)
Definition: utils.cpp:462
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1159
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:552
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition: utils.cpp:405
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1100
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:481
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:798
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:1355
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:61
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:358
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:962
void greyscale_image(surface &nsurf)
Definition: utils.cpp:422
void flop_surface(surface &nsurf)
Definition: utils.cpp:1339
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1289
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1249
void flip_surface(surface &nsurf)
Definition: utils.cpp:1323
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition: utils.cpp:99
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1187
void monochrome_image(surface &nsurf, const int threshold)
Definition: utils.cpp:444
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:44
channel
Definition: utils.hpp:104
@ BLUE
Definition: utils.hpp:104
@ ALPHA
Definition: utils.hpp:104
@ GREEN
Definition: utils.hpp:104
@ RED
Definition: utils.hpp:104
#define e
#define h
#define b