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