The Battle for Wesnoth  1.19.0-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.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  // unchecked
155  return recolor_image(src, rc_map_);
156 }
157 
159 {
160  surface ret = src;
161 
162  if(horiz_ && vert_ ) {
163  // Slightly faster than doing both a flip and a flop.
164  ret = rotate_180_surface(ret);
165  } else if(horiz_) {
166  ret = flip_surface(ret);
167  } else if(vert_) {
168  ret = flop_surface(ret);
169  }
170 
171  return ret;
172 }
173 
175 {
176  // Convert the number of degrees to the interval [0,360].
177  const int normalized = degrees_ >= 0 ?
178  degrees_ - 360 * (degrees_ / 360) :
179  degrees_ + 360 * (1 + (-degrees_) / 360); // In case compilers disagree as to what -90/360 is.
180 
181  switch ( normalized )
182  {
183  case 0: return src;
184  case 90: return rotate_90_surface(src, true);
185  case 180: return rotate_180_surface(src);
186  case 270: return rotate_90_surface(src, false);
187  case 360: return src;
188  }
189 
190  return rotate_any_surface(src, normalized, zoom_, offset_);
191 }
192 
194 {
195  return greyscale_image(src);
196 }
197 
199 {
200  return get_non_transparent_portion(src);
201 }
202 
204 {
205  return monochrome_image(src, threshold_);
206 }
207 
209 {
210  return sepia_image(src);
211 }
212 
214 {
215  return negative_image(src, red_, green_, blue_);
216 }
217 
219 {
220  return alpha_to_greyscale(src);
221 }
222 
224 {
225  return wipe_alpha(src);
226 }
227 
228 // TODO: Is this useful enough to move into formula/callable_objects?
230 {
231 public:
232  pixel_callable(SDL_Point p, color_t clr, uint32_t w, uint32_t h)
233  : p(p), clr(clr), w(w), h(h)
234  {}
235 
236  void get_inputs(wfl::formula_input_vector& inputs) const override
237  {
238  add_input(inputs, "x");
239  add_input(inputs, "y");
240  add_input(inputs, "u");
241  add_input(inputs, "v");
242  add_input(inputs, "red");
243  add_input(inputs, "green");
244  add_input(inputs, "blue");
245  add_input(inputs, "alpha");
246  add_input(inputs, "height");
247  add_input(inputs, "width");
248  }
249 
250  wfl::variant get_value(const std::string& key) const override
251  {
252  using wfl::variant;
253  if(key == "x") {
254  return variant(p.x);
255  } else if(key == "y") {
256  return variant(p.y);
257  } else if(key == "red") {
258  return variant(clr.r);
259  } else if(key == "green") {
260  return variant(clr.g);
261  } else if(key == "blue") {
262  return variant(clr.b);
263  } else if(key == "alpha") {
264  return variant(clr.a);
265  } else if(key == "width") {
266  return variant(w);
267  } else if(key == "height") {
268  return variant(h);
269  } else if(key == "u") {
270  return variant(p.x / static_cast<float>(w));
271  } else if(key == "v") {
272  return variant(p.y / static_cast<float>(h));
273  }
274 
275  return variant();
276  }
277 
278 private:
279  SDL_Point p;
281  uint32_t w, h;
282 };
283 
285 {
286  if(src == nullptr) {
287  return nullptr;
288  }
289 
290  wfl::formula new_alpha(formula_);
291 
292  surface nsurf = src.clone();
293 
294  if(nsurf == nullptr) {
295  PLAIN_LOG << "could not make neutral surface...";
296  return nullptr;
297  }
298 
299  {
300  surface_lock lock(nsurf);
301  uint32_t* cur = lock.pixels();
302  uint32_t* const end = cur + nsurf->w * src->h;
303  uint32_t* const beg = cur;
304 
305  while(cur != end) {
306  color_t pixel;
307  pixel.a = (*cur) >> 24;
308  pixel.r = (*cur) >> 16;
309  pixel.g = (*cur) >> 8;
310  pixel.b = (*cur);
311 
312  int i = cur - beg;
313  SDL_Point p;
314  p.y = i / nsurf->w;
315  p.x = i % nsurf->w;
316 
317  pixel_callable px(p, pixel, nsurf->w, nsurf->h);
318  pixel.a = std::min<unsigned>(new_alpha.evaluate(px).as_int(), 255);
319  *cur = (pixel.a << 24) + (pixel.r << 16) + (pixel.g << 8) + pixel.b;
320 
321  ++cur;
322  }
323  }
324 
325  return nsurf;
326 }
327 
329 {
330  if(src == nullptr) {
331  return nullptr;
332  }
333 
334  wfl::formula new_red(formulas_[0]);
335  wfl::formula new_green(formulas_[1]);
336  wfl::formula new_blue(formulas_[2]);
337  wfl::formula new_alpha(formulas_[3]);
338 
339  surface nsurf = src.clone();
340 
341  if(nsurf == nullptr) {
342  PLAIN_LOG << "could not make neutral surface...";
343  return nullptr;
344  }
345 
346  {
347  surface_lock lock(nsurf);
348  uint32_t* cur = lock.pixels();
349  uint32_t* const end = cur + nsurf->w * src->h;
350  uint32_t* const beg = cur;
351 
352  while(cur != end) {
353  color_t pixel;
354  pixel.a = (*cur) >> 24;
355  pixel.r = (*cur) >> 16;
356  pixel.g = (*cur) >> 8;
357  pixel.b = (*cur);
358 
359  int i = cur - beg;
360  SDL_Point p;
361  p.y = i / nsurf->w;
362  p.x = i % nsurf->w;
363 
364  pixel_callable px(p, pixel, nsurf->w, nsurf->h);
365  pixel.r = std::min<unsigned>(new_red.evaluate(px).as_int(), 255);
366  pixel.g = std::min<unsigned>(new_green.evaluate(px).as_int(), 255);
367  pixel.b = std::min<unsigned>(new_blue.evaluate(px).as_int(), 255);
368  pixel.a = std::min<unsigned>(new_alpha.evaluate(px).as_int(), 255);
369  *cur = (pixel.a << 24) + (pixel.r << 16) + (pixel.g << 8) + pixel.b;
370 
371  ++cur;
372  }
373  }
374 
375  return nsurf;
376 }
377 
379 {
380  SDL_Rect area = slice_;
381  if(area.w == 0) {
382  area.w = src->w;
383  }
384 
385  if(area.h == 0) {
386  area.h = src->h;
387  }
388 
389  /*
390  * Unlike other image functions cut_surface does not convert the input
391  * surface to a neutral surface, nor does it convert its return surface
392  * to an optimised surface.
393  *
394  * Since it seems to work for most cases, rather change this caller instead
395  * of the function signature. (The issue was discovered in bug #20876).
396  */
397  return cut_surface(src, area);
398 }
399 
401 {
402  if(x_ >= src->w) {
403  std::stringstream sstr;
404  sstr << "~BLIT(): x-coordinate '"
405  << x_ << "' larger than destination image's width '"
406  << src->w << "' no blitting performed.\n";
407 
408  throw imod_exception(sstr);
409  }
410 
411  if(y_ >= src->h) {
412  std::stringstream sstr;
413  sstr << "~BLIT(): y-coordinate '"
414  << y_ << "' larger than destination image's height '"
415  << src->h << "' no blitting performed.\n";
416 
417  throw imod_exception(sstr);
418  }
419 
420  if(surf_->w + x_ < 0) {
421  std::stringstream sstr;
422  sstr << "~BLIT(): offset and width '"
423  << x_ + surf_->w << "' less than zero no blitting performed.\n";
424 
425  throw imod_exception(sstr);
426  }
427 
428  if(surf_->h + y_ < 0) {
429  std::stringstream sstr;
430  sstr << "~BLIT(): offset and height '"
431  << y_ + surf_->h << "' less than zero no blitting performed.\n";
432 
433  throw imod_exception(sstr);
434  }
435 
436  surface nsrc = src.clone();
437  SDL_Rect r {x_, y_, 0, 0};
438  sdl_blit(surf_, nullptr, nsrc, &r);
439  return nsrc;
440 }
441 
443 {
444  if(src->w == mask_->w && src->h == mask_->h && x_ == 0 && y_ == 0) {
445  return mask_surface(src, mask_);
446  }
447 
448  SDL_Rect r {x_, y_, 0, 0};
449  surface new_mask(src->w, src->h);
450  sdl_blit(mask_, nullptr, new_mask, &r);
451  return mask_surface(src, new_mask);
452 }
453 
455  if(src == nullptr) { return nullptr; }
456 
457  // light_surface wants a neutral surface having same dimensions
458  surface nsurf;
459  if(surf_->w != src->w || surf_->h != src->h) {
460  nsurf = scale_surface(surf_, src->w, src->h);
461  } else {
462  nsurf = surf_;
463  }
464 
465  return light_surface(src, nsurf);
466 }
467 
469 {
470  point size = target_size_;
471 
472  if(size.x <= 0) {
473  size.x = src->w;
474  } else if(flags_ & X_BY_FACTOR) {
475  size.x = src->w * (static_cast<double>(size.x) / 100);
476  }
477 
478  if(size.y <= 0) {
479  size.y = src->h;
480  } else if(flags_ & Y_BY_FACTOR) {
481  size.y = src->h * (static_cast<double>(size.y) / 100);
482  }
483 
484  if(flags_ & PRESERVE_ASPECT_RATIO) {
485  const auto ratio = std::min(
486  static_cast<long double>(size.x) / src->w,
487  static_cast<long double>(size.y) / src->h
488  );
489 
490  size = {
491  static_cast<int>(src->w * ratio),
492  static_cast<int>(src->h * ratio)
493  };
494  }
495 
496  if(flags_ & SCALE_SHARP) {
497  return scale_surface_sharp(src, size.x, size.y);
498  } else {
499  return scale_surface_legacy(src, size.x, size.y);
500  }
501 }
502 
504 {
505  if(z_ == 1) {
506  return src;
507  }
508 
509  return scale_surface_xbrz(src, z_);
510 }
511 
512 /*
513  * The Opacity IPF doesn't seem to work with surface-wide alpha and instead needs per-pixel alpha.
514  * If this is needed anywhere else it can be moved back to sdl/utils.*pp.
515  */
517 {
518  surface nsurf = src.clone();
519 
520  if(nsurf == nullptr) {
521  PLAIN_LOG << "could not make neutral surface...";
522  return nullptr;
523  }
524 
525  uint8_t alpha_mod = float_to_color(opacity_);
526 
527  {
528  surface_lock lock(nsurf);
529  uint32_t* beg = lock.pixels();
530  uint32_t* end = beg + nsurf->w * src->h;
531 
532  while(beg != end) {
533  uint8_t alpha = (*beg) >> 24;
534 
535  if(alpha) {
536  uint8_t r, g, b;
537  r = (*beg) >> 16;
538  g = (*beg) >> 8;
539  b = (*beg);
540 
541  alpha = color_multiply(alpha, alpha_mod);
542  *beg = (alpha << 24) + (r << 16) + (g << 8) + b;
543  }
544 
545  ++beg;
546  }
547  }
548 
549  return nsurf;
550 }
551 
553 {
554  return((r_ != 0 || g_ != 0 || b_ != 0)
555  ? adjust_surface_color(src, r_, g_, b_)
556  : src
557  );
558 }
559 
561 {
562  return blend_surface(src, static_cast<double>(a_), color_t(r_, g_, b_));
563 }
564 
566 {
567  return blur_alpha_surface(src, depth_);
568 }
569 
571 {
572  surface ret = src.clone();
573  SDL_FillRect(ret, nullptr, SDL_MapRGBA(ret->format, color_.r, color_.g,
574  color_.b, color_.a));
575  sdl_blit(src, nullptr, ret, nullptr);
576  return ret;
577 }
578 
580 {
581  return swap_channels_image(src, red_, green_, blue_, alpha_);
582 }
583 
584 namespace {
585 
586 struct parse_mod_registration
587 {
588  parse_mod_registration(const char* name, mod_parser parser)
589  {
590  mod_parsers[name] = parser;
591  }
592 };
593 
594 /** A macro for automatic modification parser registration
595  *
596  * It automatically registers the created parser in the mod_parsers map
597  * It should be used just like a function header (look at the uses below)
598  * It should only be used within an anonymous namespace
599  *
600  * @param type The modification type to be registered (unquoted)
601  * @param args_var The name for the string argument provided
602  */
603 #define REGISTER_MOD_PARSER(type, args_var) \
604  static std::unique_ptr<modification> parse_##type##_mod(std::string_view); \
605  static parse_mod_registration parse_##type##_mod_registration_aux(#type, &parse_##type##_mod); \
606  static std::unique_ptr<modification> parse_##type##_mod(std::string_view args_var) \
607 
608 // Color-range-based recoloring
609 REGISTER_MOD_PARSER(TC, args)
610 {
611  const auto params = utils::split_view(args,',');
612 
613  if(params.size() < 2) {
614  ERR_DP << "too few arguments passed to the ~TC() function";
615 
616  return nullptr;
617  }
618 
619  const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
620  if(side_n < 1) {
621  ERR_DP << "Invalid side (" << params[0] << ") passed to the ~TC() function";
622  return nullptr;
623  }
624 
625  //
626  // Pass argseters for RC functor
627  //
628  if(!game_config::tc_info(params[1]).size()){
629  ERR_DP << "could not load TC info for '" << params[1] << "' palette";
630  ERR_DP << "bailing out from TC";
631 
632  return nullptr;
633  }
634 
635  color_range_map rc_map;
636  try {
637  const color_range& new_color = team::get_side_color_range(side_n);
638  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
639 
640  rc_map = recolor_range(new_color,old_color);
641  } catch(const config::error& e) {
642  ERR_DP << "caught config::error while processing TC: " << e.message;
643  ERR_DP << "bailing out from TC";
644 
645  return nullptr;
646  }
647 
648  return std::make_unique<rc_modification>(rc_map);
649 }
650 
651 // Team-color-based color range selection and recoloring
652 REGISTER_MOD_PARSER(RC, args)
653 {
654  const auto recolor_params = utils::split_view(args,'>');
655 
656  if(recolor_params.size() <= 1) {
657  return nullptr;
658  }
659 
660  //
661  // recolor source palette to color range
662  //
663  color_range_map rc_map;
664  try {
665  const color_range& new_color = game_config::color_info(recolor_params[1]);
666  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
667 
668  rc_map = recolor_range(new_color,old_color);
669  } catch (const config::error& e) {
670  ERR_DP
671  << "caught config::error while processing color-range RC: "
672  << e.message;
673  ERR_DP << "bailing out from RC";
674  rc_map.clear();
675  }
676 
677  return std::make_unique<rc_modification>(rc_map);
678 }
679 
680 // Palette switch
681 REGISTER_MOD_PARSER(PAL, args)
682 {
683  const auto remap_params = utils::split_view(args,'>');
684 
685  if(remap_params.size() < 2) {
686  ERR_DP << "not enough arguments passed to the ~PAL() function: " << args;
687 
688  return nullptr;
689  }
690 
691  try {
692  color_range_map rc_map;
693  const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
694  const std::vector<color_t>& new_palette =game_config::tc_info(remap_params[1]);
695 
696  for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
697  rc_map[old_palette[i]] = new_palette[i];
698  }
699 
700  return std::make_unique<rc_modification>(rc_map);
701  } catch(const config::error& e) {
702  ERR_DP
703  << "caught config::error while processing PAL function: "
704  << e.message;
705  ERR_DP
706  << "bailing out from PAL";
707 
708  return nullptr;
709  }
710 }
711 
712 // Flip/flop
713 REGISTER_MOD_PARSER(FL, args)
714 {
715  bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
716  bool vert = (args.find("vert") != std::string::npos);
717 
718  return std::make_unique<fl_modification>(horiz, vert);
719 }
720 
721 // Rotations
722 REGISTER_MOD_PARSER(ROTATE, args)
723 {
724  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
725 
726  switch(slice_params.size()) {
727  case 0:
728  return std::make_unique<rotate_modification>();
729  case 1:
730  return std::make_unique<rotate_modification>(
731  utils::from_chars<int>(slice_params[0]).value_or(0));
732  case 2:
733  return std::make_unique<rotate_modification>(
734  utils::from_chars<int>(slice_params[0]).value_or(0),
735  utils::from_chars<int>(slice_params[1]).value_or(0));
736  case 3:
737  return std::make_unique<rotate_modification>(
738  utils::from_chars<int>(slice_params[0]).value_or(0),
739  utils::from_chars<int>(slice_params[1]).value_or(0),
740  utils::from_chars<int>(slice_params[2]).value_or(0));
741  }
742  return nullptr;
743 }
744 
745 // Grayscale
747 {
748  return std::make_unique<gs_modification>();
749 }
750 
751 // crop transparent padding
752 REGISTER_MOD_PARSER(CROP_TRANSPARENCY, )
753 {
754  return std::make_unique<crop_transparency_modification>();
755 }
756 
757 // TODO: should this be made a more general util function?
758 bool in_range(int val, int min, int max)
759 {
760  return min <= val && val <= max;
761 }
762 
763 // Black and white
764 REGISTER_MOD_PARSER(BW, args)
765 {
766  const auto params = utils::split_view(args, ',');
767 
768  if(params.size() != 1) {
769  ERR_DP << "~BW() requires exactly one argument";
770  return nullptr;
771  }
772 
773  // TODO: maybe get this directly as uint8_t?
774  const auto threshold = utils::from_chars<int>(params[0]);
775  if(!threshold) {
776  ERR_DP << "unsupported argument in ~BW() function";
777  return nullptr;
778  }
779 
780  if(!in_range(*threshold, 0, 255)) {
781  ERR_DP << "~BW() argument out of range 0 - 255";
782  return nullptr;
783  }
784 
785  return std::make_unique<bw_modification>(*threshold);
786 }
787 
788 // Sepia
789 REGISTER_MOD_PARSER(SEPIA, )
790 {
791  return std::make_unique<sepia_modification>();
792 }
793 
794 // Negative
795 REGISTER_MOD_PARSER(NEG, args)
796 {
797  const auto params = utils::split_view(args, ',');
798 
799  switch(params.size()) {
800  case 0:
801  // apparently -1 may be a magic number but this is the threshold
802  // value required to fully invert a channel
803  return std::make_unique<negative_modification>(-1, -1, -1);
804 
805  case 1: {
806  const auto threshold = utils::from_chars<int>(params[0]);
807 
808  if(threshold && in_range(*threshold, -1, 255)) {
809  return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
810  } else {
811  ERR_DP << "unsupported argument value in ~NEG() function";
812  return nullptr;
813  }
814  }
815 
816  case 3: {
817  const auto thR = utils::from_chars<int>(params[0]);
818  const auto thG = utils::from_chars<int>(params[1]);
819  const auto thB = utils::from_chars<int>(params[2]);
820 
821  if(thR && thG && thB && in_range(*thR, -1, 255) && in_range(*thG, -1, 255) && in_range(*thB, -1, 255)) {
822  return std::make_unique<negative_modification>(*thR, *thG, *thB);
823  } else {
824  ERR_DP << "unsupported argument value in ~NEG() function";
825  return nullptr;
826  }
827  }
828 
829  default:
830  ERR_DP << "~NEG() requires 0, 1 or 3 arguments";
831  return nullptr;
832  }
833 }
834 
835 // Plot Alpha
836 REGISTER_MOD_PARSER(PLOT_ALPHA, )
837 {
838  return std::make_unique<plot_alpha_modification>();
839 }
840 
841 // Wipe Alpha
842 REGISTER_MOD_PARSER(WIPE_ALPHA, )
843 {
844  return std::make_unique<wipe_alpha_modification>();
845 }
846 
847 // Adjust Alpha
848 REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
849 {
850  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
851  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
852  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
853 
854  if(params.size() != 1) {
855  ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments";
856  return nullptr;
857  }
858 
859  return std::make_unique<adjust_alpha_modification>(params.at(0));
860 }
861 
862 // Adjust Channels
863 REGISTER_MOD_PARSER(CHAN, args)
864 {
865  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
866  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
867  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
868 
869  if(params.size() < 1 || params.size() > 4) {
870  ERR_DP << "~CHAN() requires 1 to 4 arguments";
871  return nullptr;
872  }
873 
874  return std::make_unique<adjust_channels_modification>(params);
875 }
876 
877 // Color-shift
878 REGISTER_MOD_PARSER(CS, args)
879 {
880  const auto factors = utils::split_view(args, ',');
881  const std::size_t s = factors.size();
882 
883  if(s == 0) {
884  ERR_DP << "no arguments passed to the ~CS() function";
885  return nullptr;
886  }
887 
888  int r = 0, g = 0, b = 0;
889 
890  r = utils::from_chars<int>(factors[0]).value_or(0);
891 
892  if(s > 1 ) {
893  g = utils::from_chars<int>(factors[0]).value_or(0);
894  }
895  if(s > 2 ) {
896  b = utils::from_chars<int>(factors[0]).value_or(0);
897  }
898 
899  return std::make_unique<cs_modification>(r, g , b);
900 }
901 
902 // Color blending
903 REGISTER_MOD_PARSER(BLEND, args)
904 {
905  const auto params = utils::split_view(args, ',');
906 
907  if(params.size() != 4) {
908  ERR_DP << "~BLEND() requires exactly 4 arguments";
909  return nullptr;
910  }
911 
912  float opacity = 0.0f;
913  const std::string_view& opacity_str = params[3];
914  const std::string_view::size_type p100_pos = opacity_str.find('%');
915 
916  if(p100_pos == std::string::npos)
917  opacity = lexical_cast_default<float>(opacity_str);
918  else {
919  // make multiplier
920  const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
921  opacity = lexical_cast_default<float>(parsed_field);
922  opacity /= 100.0f;
923  }
924 
925  return std::make_unique<blend_modification>(
926  utils::from_chars<int>(params[0]).value_or(0),
927  utils::from_chars<int>(params[1]).value_or(0),
928  utils::from_chars<int>(params[2]).value_or(0),
929  opacity);
930 }
931 
932 // Crop/slice
933 REGISTER_MOD_PARSER(CROP, args)
934 {
935  const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
936  const std::size_t s = slice_params.size();
937 
938  if(s == 0 || (s == 1 && slice_params[0].empty())) {
939  ERR_DP << "no arguments passed to the ~CROP() function";
940  return nullptr;
941  }
942 
943  SDL_Rect slice_rect { 0, 0, 0, 0 };
944 
945  slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
946 
947  if(s > 1) {
948  slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
949  }
950  if(s > 2) {
951  slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
952  }
953  if(s > 3) {
954  slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
955  }
956 
957  return std::make_unique<crop_modification>(slice_rect);
958 }
959 
960 static bool check_image(const image::locator& img, std::stringstream & message)
961 {
962  if(img.file_exists()) return true;
963  message << " image not found: '" << img.get_filename() << "'\n";
964  ERR_DP << message.str();
965  return false;
966 }
967 
968 // Blit
969 REGISTER_MOD_PARSER(BLIT, args)
970 {
971  std::vector<std::string> param = utils::parenthetical_split(args, ',');
972  const std::size_t s = param.size();
973 
974  if(s == 0 || (s == 1 && param[0].empty())){
975  ERR_DP << "no arguments passed to the ~BLIT() function";
976  return nullptr;
977  }
978 
979  if(s > 3){
980  ERR_DP << "too many arguments passed to the ~BLIT() function";
981  return nullptr;
982  }
983 
984  int x = 0, y = 0;
985 
986  if(s == 3) {
987  x = utils::from_chars<int>(param[1]).value_or(0);
988  y = utils::from_chars<int>(param[2]).value_or(0);
989  }
990 
991  const image::locator img(param[0]);
992  std::stringstream message;
993  message << "~BLIT():";
994  if(!check_image(img, message))
995  return nullptr;
996  surface surf = get_surface(img);
997 
998  return std::make_unique<blit_modification>(surf, x, y);
999 }
1000 
1001 // Mask
1002 REGISTER_MOD_PARSER(MASK, args)
1003 {
1004  std::vector<std::string> param = utils::parenthetical_split(args, ',');
1005  const std::size_t s = param.size();
1006 
1007  if(s == 0 || (s == 1 && param[0].empty())){
1008  ERR_DP << "no arguments passed to the ~MASK() function";
1009  return nullptr;
1010  }
1011 
1012  int x = 0, y = 0;
1013 
1014  if(s == 3) {
1015  x = utils::from_chars<int>(param[1]).value_or(0);
1016  y = utils::from_chars<int>(param[2]).value_or(0);
1017  }
1018 
1019  if(x < 0 || y < 0) {
1020  ERR_DP << "negative position arguments in ~MASK() function";
1021  return nullptr;
1022  }
1023 
1024  const image::locator img(param[0]);
1025  std::stringstream message;
1026  message << "~MASK():";
1027  if(!check_image(img, message))
1028  return nullptr;
1029  surface surf = get_surface(img);
1030 
1031  return std::make_unique<mask_modification>(surf, x, y);
1032 }
1033 
1034 // Light
1035 REGISTER_MOD_PARSER(L, args)
1036 {
1037  if(args.empty()){
1038  ERR_DP << "no arguments passed to the ~L() function";
1039  return nullptr;
1040  }
1041 
1042  surface surf = get_surface(std::string{args}); // FIXME: string_view for image::locator::value
1043  return std::make_unique<light_modification>(surf);
1044 }
1045 
1046 namespace
1047 {
1048 std::pair<int, bool> parse_scale_value(std::string_view arg)
1049 {
1050  if(const std::size_t pos = arg.rfind('%'); pos != std::string_view::npos) {
1051  return { utils::from_chars<int>(arg.substr(0, pos)).value_or(0), true };
1052  } else {
1053  return { utils::from_chars<int>(arg).value_or(0), false };
1054  }
1055 }
1056 
1057 /** Common helper function to parse scaling IPF inputs. */
1058 std::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
1059 {
1060  const auto scale_params = utils::split_view(args, ',', utils::STRIP_SPACES);
1061  const std::size_t num_args = scale_params.size();
1062 
1063  if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
1064  return std::nullopt;
1065  }
1066 
1067  uint8_t flags = 0;
1068  std::array<int, 2> parsed_sizes{0,0};
1069 
1070  for(unsigned i = 0; i < std::min<unsigned>(2, num_args); ++i) {
1071  const auto& [size, relative] = parse_scale_value(scale_params[i]);
1072 
1073  if(size < 0) {
1074  ERR_DP << "Negative size passed to scaling IPF. Original image dimension will be used instead";
1075  continue;
1076  }
1077 
1078  parsed_sizes[i] = size;
1079 
1080  if(relative) {
1082  }
1083  }
1084 
1085  return std::pair{point{parsed_sizes[0], parsed_sizes[1]}, flags};
1086 }
1087 
1088 } // namespace
1089 
1090 // Scale
1091 REGISTER_MOD_PARSER(SCALE, args)
1092 {
1093  if(auto params = parse_scale_args(args)) {
1095  return std::make_unique<scale_modification>(params->first, mode | params->second);
1096  } else {
1097  ERR_DP << "no arguments passed to the ~SCALE() function";
1098  return nullptr;
1099  }
1100 }
1101 
1102 REGISTER_MOD_PARSER(SCALE_SHARP, args)
1103 {
1104  if(auto params = parse_scale_args(args)) {
1106  return std::make_unique<scale_modification>(params->first, mode | params->second);
1107  } else {
1108  ERR_DP << "no arguments passed to the ~SCALE_SHARP() function";
1109  return nullptr;
1110  }
1111 }
1112 
1113 REGISTER_MOD_PARSER(SCALE_INTO, args)
1114 {
1115  if(auto params = parse_scale_args(args)) {
1117  return std::make_unique<scale_modification>(params->first, mode | params->second);
1118  } else {
1119  ERR_DP << "no arguments passed to the ~SCALE_INTO() function";
1120  return nullptr;
1121  }
1122 }
1123 
1124 REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1125 {
1126  if(auto params = parse_scale_args(args)) {
1128  return std::make_unique<scale_modification>(params->first, mode | params->second);
1129  } else {
1130  ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function";
1131  return nullptr;
1132  }
1133 }
1134 
1135 // xBRZ
1136 REGISTER_MOD_PARSER(XBRZ, args)
1137 {
1138  int z = utils::from_chars<int>(args).value_or(0);
1139  if(z < 1 || z > 5) {
1140  z = 5; //only values 2 - 5 are permitted for xbrz scaling factors.
1141  }
1142 
1143  return std::make_unique<xbrz_modification>(z);
1144 }
1145 
1146 // Gaussian-like blur
1147 REGISTER_MOD_PARSER(BL, args)
1148 {
1149  const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1150  return std::make_unique<bl_modification>(depth);
1151 }
1152 
1153 // Opacity-shift
1154 REGISTER_MOD_PARSER(O, args)
1155 {
1156  const std::string::size_type p100_pos = args.find('%');
1157  float num = 0.0f;
1158  if(p100_pos == std::string::npos) {
1159  num = lexical_cast_default<float, std::string_view>(args);
1160  } else {
1161  // make multiplier
1162  const std::string_view parsed_field = args.substr(0, p100_pos);
1163  num = lexical_cast_default<float, std::string_view>(parsed_field);
1164  num /= 100.0f;
1165  }
1166 
1167  return std::make_unique<o_modification>(num);
1168 }
1169 
1170 //
1171 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1172 // Hence they are at the end of the evaluation.
1173 //
1174 // Red component color-shift
1175 REGISTER_MOD_PARSER(R, args)
1176 {
1177  const int r = utils::from_chars<int>(args).value_or(0);
1178  return std::make_unique<cs_modification>(r, 0, 0);
1179 }
1180 
1181 // Green component color-shift
1182 REGISTER_MOD_PARSER(G, args)
1183 {
1184  const int g = utils::from_chars<int>(args).value_or(0);
1185  return std::make_unique<cs_modification>(0, g, 0);
1186 }
1187 
1188 // Blue component color-shift
1189 REGISTER_MOD_PARSER(B, args)
1190 {
1191  const int b = utils::from_chars<int>(args).value_or(0);
1192  return std::make_unique<cs_modification>(0, 0, b);
1193 }
1194 
1195 REGISTER_MOD_PARSER(NOP, )
1196 {
1197  return nullptr;
1198 }
1199 
1200 // Only used to tag terrain images which should not be color-shifted by ToD
1201 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1202 {
1203  return nullptr;
1204 }
1205 
1206 // Fake image function used by GUI2 portraits until
1207 // Mordante gets rid of it. *tsk* *tsk*
1208 REGISTER_MOD_PARSER(RIGHT, )
1209 {
1210  return nullptr;
1211 }
1212 
1213 // Add a background color.
1214 REGISTER_MOD_PARSER(BG, args)
1215 {
1216  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1217  const auto factors = utils::split_view(args, ',');
1218 
1219  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1220  c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1221  }
1222 
1223  return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1224 }
1225 
1226 // Channel swap
1227 REGISTER_MOD_PARSER(SWAP, args)
1228 {
1229  const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1230 
1231  // accept 3 arguments (rgb) or 4 (rgba)
1232  if(params.size() != 3 && params.size() != 4) {
1233  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1234  return nullptr;
1235  }
1236 
1237  channel redValue, greenValue, blueValue, alphaValue;
1238  // compare the parameter's value with the constants defined in the channels enum
1239  if(params[0] == "red") {
1240  redValue = RED;
1241  } else if(params[0] == "green") {
1242  redValue = GREEN;
1243  } else if(params[0] == "blue") {
1244  redValue = BLUE;
1245  } else if(params[0] == "alpha") {
1246  redValue = ALPHA;
1247  } else {
1248  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1249  return nullptr;
1250  }
1251 
1252  // wash, rinse and repeat for the other three channels
1253  if(params[1] == "red") {
1254  greenValue = RED;
1255  } else if(params[1] == "green") {
1256  greenValue = GREEN;
1257  } else if(params[1] == "blue") {
1258  greenValue = BLUE;
1259  } else if(params[1] == "alpha") {
1260  greenValue = ALPHA;
1261  } else {
1262  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1263  return nullptr;
1264  }
1265 
1266  if(params[2] == "red") {
1267  blueValue = RED;
1268  } else if(params[2] == "green") {
1269  blueValue = GREEN;
1270  } else if(params[2] == "blue") {
1271  blueValue = BLUE;
1272  } else if(params[2] == "alpha") {
1273  blueValue = ALPHA;
1274  } else {
1275  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1276  return nullptr;
1277  }
1278 
1279  // additional check: the params vector may not have a fourth elementh
1280  // if so, default to the same channel
1281  if(params.size() == 3) {
1282  alphaValue = ALPHA;
1283  } else {
1284  if(params[3] == "red") {
1285  alphaValue = RED;
1286  } else if(params[3] == "green") {
1287  alphaValue = GREEN;
1288  } else if(params[3] == "blue") {
1289  alphaValue = BLUE;
1290  } else if(params[3] == "alpha") {
1291  alphaValue = ALPHA;
1292  } else {
1293  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1294  return nullptr;
1295  }
1296  }
1297 
1298  return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1299 }
1300 
1301 } // end anon namespace
1302 
1303 } /* 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 surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
Generic locator abstracting the location of an image.
Definition: picture.hpp:63
bool file_exists() const
Tests whether the file the locator points at exists.
Definition: picture.cpp:563
const std::string & get_filename() const
Definition: picture.hpp:85
virtual surface operator()(const surface &src) const
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 surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
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 surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual surface operator()(const surface &src) const
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:123
pixel_t * pixels() const
Definition: surface.hpp:142
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
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:29
std::unordered_map< color_t, color_t > color_range_map
Definition: color_range.hpp:30
variant a_
Definition: function.cpp:757
std::size_t i
Definition: function.cpp:968
variant b_
Definition: function.cpp:757
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).
#define PLAIN_LOG
Definition: log.hpp:295
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.
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:672
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
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
virtual surface operator()(const surface &src) const
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 surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
Holds a 2D point.
Definition: point.hpp:25
mock_char c
mock_party p
static map_location::DIRECTION s
surface alpha_to_greyscale(const surface &surf)
Definition: utils.cpp:662
surface blur_alpha_surface(const surface &surf, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:1302
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:447
surface recolor_image(surface surf, const color_range_map &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition: utils.cpp:857
surface negative_image(const surface &surf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:619
surface swap_channels_image(const surface &surf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:755
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1101
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:260
surface get_non_transparent_portion(const surface &surf)
Definition: utils.cpp:1850
surface flip_surface(const surface &surf)
Definition: utils.cpp:1742
surface sepia_image(const surface &surf)
Definition: utils.cpp:577
surface blend_surface(const surface &surf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1507
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1449
surface wipe_alpha(const surface &surf)
Definition: utils.cpp:690
surface flop_surface(const surface &surf)
Definition: utils.cpp:1771
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:397
surface monochrome_image(const surface &surf, const int threshold)
Definition: utils.cpp:537
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1707
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1665
surface greyscale_image(const surface &surf)
Definition: utils.cpp:492
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:131
surface mask_surface(const surface &surf, const surface &mask, bool *empty_result, const std::string &filename)
Applies a mask on a surface.
Definition: utils.cpp:985
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1554
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:54
channel
Definition: utils.hpp:122
@ BLUE
Definition: utils.hpp:122
@ ALPHA
Definition: utils.hpp:122
@ GREEN
Definition: utils.hpp:122
@ RED
Definition: utils.hpp:122
#define e
#define h
#define b