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