The Battle for Wesnoth  1.17.0-dev
image_modifications.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2021
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 << "\n";
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 << '\n';
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  std::cerr << "could not make neutral surface...\n";
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  std::cerr << "could not make neutral surface...\n";
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" << std::endl;
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" << std::endl;
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" << std::endl;
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" << std::endl;
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  std::cerr << "could not make neutral surface...\n";
545  return nullptr;
546  }
547 
548  uint16_t amount = ftofxp(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 = std::min<unsigned>(static_cast<unsigned>(fxpmult(alpha,amount)), 255);
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" << std::endl;
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\n";
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]
653  << "' palette\n"
654  << "bailing out from TC\n";
655 
656  return nullptr;
657  }
658 
659  color_range_map rc_map;
660  try {
661  const color_range& new_color = team::get_side_color_range(side_n);
662  const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
663 
664  rc_map = recolor_range(new_color,old_color);
665  } catch(const config::error& e) {
666  ERR_DP << "caught config::error while processing TC: "
667  << e.message
668  << '\n'
669  << "bailing out from TC\n";
670 
671  return nullptr;
672  }
673 
674  return new rc_modification(rc_map);
675 }
676 
677 // Team-color-based color range selection and recoloring
678 REGISTER_MOD_PARSER(RC, args)
679 {
680  const std::vector<std::string> recolor_params = utils::split(args,'>');
681 
682  if(recolor_params.size() <= 1) {
683  return nullptr;
684  }
685 
686  //
687  // recolor source palette to color range
688  //
689  color_range_map rc_map;
690  try {
691  const color_range& new_color = game_config::color_info(recolor_params[1]);
692  const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
693 
694  rc_map = recolor_range(new_color,old_color);
695  } catch (const config::error& e) {
696  ERR_DP
697  << "caught config::error while processing color-range RC: "
698  << e.message
699  << '\n';
700  ERR_DP
701  << "bailing out from RC\n";
702  rc_map.clear();
703  }
704 
705  return new rc_modification(rc_map);
706 }
707 
708 // Palette switch
709 REGISTER_MOD_PARSER(PAL, args)
710 {
711  const std::vector<std::string> remap_params = utils::split(args,'>');
712 
713  if(remap_params.size() < 2) {
714  ERR_DP << "not enough arguments passed to the ~PAL() function: " << args << "\n";
715 
716  return nullptr;
717  }
718 
719  try {
720  color_range_map rc_map;
721  const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
722  const std::vector<color_t>& new_palette =game_config::tc_info(remap_params[1]);
723 
724  for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
725  rc_map[old_palette[i]] = new_palette[i];
726  }
727 
728  return new rc_modification(rc_map);
729  } catch(const config::error& e) {
730  ERR_DP
731  << "caught config::error while processing PAL function: "
732  << e.message
733  << '\n';
734  ERR_DP
735  << "bailing out from PAL\n";
736 
737  return nullptr;
738  }
739 }
740 
741 // Flip/flop
742 REGISTER_MOD_PARSER(FL, args)
743 {
744  bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
745  bool vert = (args.find("vert") != std::string::npos);
746 
747  return new fl_modification(horiz, vert);
748 }
749 
750 // Rotations
751 REGISTER_MOD_PARSER(ROTATE, args)
752 {
753  const std::vector<std::string>& slice_params = utils::split(args, ',', utils::STRIP_SPACES);
754  const std::size_t s = slice_params.size();
755 
756  switch(s) {
757  case 0:
758  return new rotate_modification();
759  break;
760  case 1:
761  return new rotate_modification(
762  lexical_cast_default<int>(slice_params[0]));
763  break;
764  case 2:
765  return new rotate_modification(
766  lexical_cast_default<int>(slice_params[0]),
767  lexical_cast_default<int>(slice_params[1]));
768  break;
769  case 3:
770  return new rotate_modification(
771  lexical_cast_default<int>(slice_params[0]),
772  lexical_cast_default<int>(slice_params[1]),
773  lexical_cast_default<int>(slice_params[2]));
774  break;
775  }
776  return nullptr;
777 }
778 
779 // Grayscale
781 {
782  return new gs_modification;
783 }
784 
785 // Black and white
786 REGISTER_MOD_PARSER(BW, args)
787 {
788  const std::vector<std::string>& params = utils::split(args, ',');
789 
790  if(params.size() != 1) {
791  ERR_DP << "~BW() requires exactly one argument" << std::endl;
792  return nullptr;
793  }
794 
795  try {
796  int threshold = std::stoi(params[0]);
797  if(threshold < 0 || threshold > 255) {
798  ERR_DP << "~BW() argument out of range 0 - 255" << std::endl;
799  return nullptr;
800  } else {
801  return new bw_modification(threshold);
802  }
803  } catch (const std::invalid_argument&) {
804  ERR_DP << "unsupported argument in ~BW() function" << std::endl;
805  return nullptr;
806  }
807 }
808 
809 // Sepia
810 REGISTER_MOD_PARSER(SEPIA, )
811 {
812  return new sepia_modification;
813 }
814 
815 // Negative
816 REGISTER_MOD_PARSER(NEG, args)
817 {
818  const std::vector<std::string>& params = utils::split(args, ',');
819 
820  switch (params.size()) {
821  case 0:
822  // apparently -1 may be a magic number
823  // but this is the threshold value required
824  // to fully invert a channel
825  return new negative_modification(-1,-1,-1);
826  break;
827  case 1:
828  try {
829  int threshold = std::stoi(params[0]);
830  if(threshold < -1 || threshold > 255) {
831  ERR_DP << "unsupported argument value in ~NEG() function" << std::endl;
832  return nullptr;
833  } else {
834  return new negative_modification(threshold, threshold, threshold);
835  }
836  } catch (const std::invalid_argument&) {
837  ERR_DP << "unsupported argument value in ~NEG() function" << std::endl;
838  return nullptr;
839  }
840  break;
841  case 3:
842  try {
843  int thresholdRed = std::stoi(params[0]);
844  int thresholdGreen = std::stoi(params[1]);
845  int thresholdBlue = std::stoi(params[2]);
846  if(thresholdRed < -1 || thresholdRed > 255 || thresholdGreen < -1 || thresholdGreen > 255 || thresholdBlue < -1 || thresholdBlue > 255) {
847  ERR_DP << "unsupported argument value in ~NEG() function" << std::endl;
848  return nullptr;
849  } else {
850  return new negative_modification(thresholdRed, thresholdGreen, thresholdBlue);
851  }
852  } catch (const std::invalid_argument&) {
853  ERR_DP << "unsupported argument value in ~NEG() function" << std::endl;
854  return nullptr;
855  }
856  break;
857  default:
858  ERR_DP << "~NEG() requires 0, 1 or 3 arguments" << std::endl;
859  return nullptr;
860  }
861 
862  return nullptr;
863 }
864 
865 // Plot Alpha
866 REGISTER_MOD_PARSER(PLOT_ALPHA, )
867 {
868  return new plot_alpha_modification;
869 }
870 
871 // Wipe Alpha
872 REGISTER_MOD_PARSER(WIPE_ALPHA, )
873 {
874  return new wipe_alpha_modification;
875 }
876 
877 // Adjust Alpha
878 REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
879 {
880  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
881  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
882  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
883 
884  if(params.size() != 1) {
885  ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments" << std::endl;
886  return nullptr;
887  }
888 
889  return new adjust_alpha_modification(params.at(0));
890 }
891 
892 // Adjust Channels
893 REGISTER_MOD_PARSER(CHAN, args)
894 {
895  // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
896  // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
897  const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
898 
899  if(params.size() < 1 || params.size() > 4) {
900  ERR_DP << "~CHAN() requires 1 to 4 arguments" << std::endl;
901  return nullptr;
902  }
903 
904  return new adjust_channels_modification(params);
905 }
906 
907 // Color-shift
908 REGISTER_MOD_PARSER(CS, args)
909 {
910  std::vector<std::string> const factors = utils::split(args, ',');
911  const std::size_t s = factors.size();
912 
913  if(s == 0) {
914  ERR_DP << "no arguments passed to the ~CS() function" << std::endl;
915  return nullptr;
916  }
917 
918  int r = 0, g = 0, b = 0;
919 
920  r = lexical_cast_default<int>(factors[0]);
921 
922  if(s > 1 ) {
923  g = lexical_cast_default<int>(factors[1]);
924  }
925  if(s > 2 ) {
926  b = lexical_cast_default<int>(factors[2]);
927  }
928 
929  return new cs_modification(r, g, b);
930 }
931 
932 // Color blending
933 REGISTER_MOD_PARSER(BLEND, args)
934 {
935  const std::vector<std::string>& params = utils::split(args, ',');
936 
937  if(params.size() != 4) {
938  ERR_DP << "~BLEND() requires exactly 4 arguments" << std::endl;
939  return nullptr;
940  }
941 
942  float opacity = 0.0f;
943  const std::string& opacity_str = params[3];
944  const std::string::size_type p100_pos = opacity_str.find('%');
945 
946  if(p100_pos == std::string::npos)
947  opacity = lexical_cast_default<float>(opacity_str);
948  else {
949  // make multiplier
950  const std::string& parsed_field = opacity_str.substr(0, p100_pos);
951  opacity = lexical_cast_default<float>(parsed_field);
952  opacity /= 100.0f;
953  }
954 
955  return new blend_modification(
956  lexical_cast_default<int>(params[0]),
957  lexical_cast_default<int>(params[1]),
958  lexical_cast_default<int>(params[2]),
959  opacity);
960 }
961 
962 // Crop/slice
963 REGISTER_MOD_PARSER(CROP, args)
964 {
965  const std::vector<std::string>& slice_params = utils::split(args, ',', utils::STRIP_SPACES);
966  const std::size_t s = slice_params.size();
967 
968  if(s == 0 || (s == 1 && slice_params[0].empty())) {
969  ERR_DP << "no arguments passed to the ~CROP() function" << std::endl;
970  return nullptr;
971  }
972 
973  SDL_Rect slice_rect { 0, 0, 0, 0 };
974 
975  slice_rect.x = lexical_cast_default<int16_t, const std::string&>(slice_params[0]);
976 
977  if(s > 1) {
978  slice_rect.y = lexical_cast_default<int16_t, const std::string&>(slice_params[1]);
979  }
980  if(s > 2) {
981  slice_rect.w = lexical_cast_default<uint16_t, const std::string&>(slice_params[2]);
982  }
983  if(s > 3) {
984  slice_rect.h = lexical_cast_default<uint16_t, const std::string&>(slice_params[3]);
985  }
986 
987  return new crop_modification(slice_rect);
988 }
989 
990 static bool check_image(const image::locator& img, std::stringstream & message)
991 {
992  if(img.file_exists()) return true;
993  message << " image not found: '" << img.get_filename() << "'\n";
994  ERR_DP << message.str();
995  return false;
996 }
997 
998 // Blit
999 REGISTER_MOD_PARSER(BLIT, args)
1000 {
1001  std::vector<std::string> param = utils::parenthetical_split(args, ',');
1002  const std::size_t s = param.size();
1003 
1004  if(s == 0 || (s == 1 && param[0].empty())){
1005  ERR_DP << "no arguments passed to the ~BLIT() function" << std::endl;
1006  return nullptr;
1007  }
1008 
1009  if(s > 3){
1010  ERR_DP << "too many arguments passed to the ~BLIT() function" << std::endl;
1011  return nullptr;
1012  }
1013 
1014  int x = 0, y = 0;
1015 
1016  if(s == 3) {
1017  x = lexical_cast_default<int>(param[1]);
1018  y = lexical_cast_default<int>(param[2]);
1019  }
1020 
1021  const image::locator img(param[0]);
1022  std::stringstream message;
1023  message << "~BLIT():";
1024  if(!check_image(img, message))
1025  return nullptr;
1026  surface surf = get_image(img);
1027 
1028  return new blit_modification(surf, x, y);
1029 }
1030 
1031 // Mask
1032 REGISTER_MOD_PARSER(MASK, args)
1033 {
1034  std::vector<std::string> param = utils::parenthetical_split(args, ',');
1035  const std::size_t s = param.size();
1036 
1037  if(s == 0 || (s == 1 && param[0].empty())){
1038  ERR_DP << "no arguments passed to the ~MASK() function" << std::endl;
1039  return nullptr;
1040  }
1041 
1042  int x = 0, y = 0;
1043 
1044  if(s == 3) {
1045  x = lexical_cast_default<int>(param[1]);
1046  y = lexical_cast_default<int>(param[2]);
1047  }
1048 
1049  if(x < 0 || y < 0) {
1050  ERR_DP << "negative position arguments in ~MASK() function" << std::endl;
1051  return nullptr;
1052  }
1053 
1054  const image::locator img(param[0]);
1055  std::stringstream message;
1056  message << "~MASK():";
1057  if(!check_image(img, message))
1058  return nullptr;
1059  surface surf = get_image(img);
1060 
1061  return new mask_modification(surf, x, y);
1062 }
1063 
1064 // Light
1065 REGISTER_MOD_PARSER(L, args)
1066 {
1067  if(args.empty()){
1068  ERR_DP << "no arguments passed to the ~L() function" << std::endl;
1069  return nullptr;
1070  }
1071 
1072  surface surf = get_image(args);
1073 
1074  return new light_modification(surf);
1075 }
1076 
1077 // Scale
1078 REGISTER_MOD_PARSER(SCALE, args)
1079 {
1080  const std::vector<std::string>& scale_params = utils::split(args, ',', utils::STRIP_SPACES);
1081  const std::size_t s = scale_params.size();
1082 
1083  if(s == 0 || (s == 1 && scale_params[0].empty())) {
1084  ERR_DP << "no arguments passed to the ~SCALE() function" << std::endl;
1085  return nullptr;
1086  }
1087 
1088  int w = 0, h = 0;
1089 
1090  w = lexical_cast_default<int, const std::string&>(scale_params[0]);
1091 
1092  if(s > 1) {
1093  h = lexical_cast_default<int, const std::string&>(scale_params[1]);
1094  }
1095 
1096  return new scale_exact_modification(w, h, "SCALE", false);
1097 }
1098 
1099 REGISTER_MOD_PARSER(SCALE_SHARP, args)
1100 {
1101  const std::vector<std::string>& scale_params = utils::split(args, ',', utils::STRIP_SPACES);
1102  const std::size_t s = scale_params.size();
1103 
1104  if(s == 0 || (s == 1 && scale_params[0].empty())) {
1105  ERR_DP << "no arguments passed to the ~SCALE_SHARP() function" << std::endl;
1106  return nullptr;
1107  }
1108 
1109  int w = 0, h = 0;
1110 
1111  w = lexical_cast_default<int, const std::string&>(scale_params[0]);
1112 
1113  if(s > 1) {
1114  h = lexical_cast_default<int, const std::string&>(scale_params[1]);
1115  }
1116 
1117  return new scale_exact_modification(w, h, "SCALE_SHARP", true);
1118 }
1119 
1120 REGISTER_MOD_PARSER(SCALE_INTO, args)
1121 {
1122  const std::vector<std::string>& scale_params = utils::split(args, ',', utils::STRIP_SPACES);
1123  const std::size_t s = scale_params.size();
1124 
1125  if(s == 0 || (s == 1 && scale_params[0].empty())) {
1126  ERR_DP << "no arguments passed to the ~SCALE_INTO() function" << std::endl;
1127  return nullptr;
1128  }
1129 
1130  int w = 0, h = 0;
1131 
1132  w = lexical_cast_default<int, const std::string&>(scale_params[0]);
1133 
1134  if(s > 1) {
1135  h = lexical_cast_default<int, const std::string&>(scale_params[1]);
1136  }
1137 
1138  return new scale_into_modification(w, h, "SCALE_INTO", false);
1139 }
1140 
1141 REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1142 {
1143  const std::vector<std::string>& scale_params = utils::split(args, ',', utils::STRIP_SPACES);
1144  const std::size_t s = scale_params.size();
1145 
1146  if(s == 0 || (s == 1 && scale_params[0].empty())) {
1147  ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function" << std::endl;
1148  return nullptr;
1149  }
1150 
1151  int w = 0, h = 0;
1152 
1153  w = lexical_cast_default<int, const std::string&>(scale_params[0]);
1154 
1155  if(s > 1) {
1156  h = lexical_cast_default<int, const std::string&>(scale_params[1]);
1157  }
1158 
1159  return new scale_into_modification(w, h, "SCALE_INTO_SHARP", true);
1160 }
1161 
1162 // xBRZ
1163 REGISTER_MOD_PARSER(XBRZ, args)
1164 {
1165  int z = lexical_cast_default<int, const std::string &>(args);
1166  if(z < 1 || z > 5) {
1167  z = 5; //only values 2 - 5 are permitted for xbrz scaling factors.
1168  }
1169 
1170  return new xbrz_modification(z);
1171 }
1172 
1173 // scale
1174 
1175 // Gaussian-like blur
1176 REGISTER_MOD_PARSER(BL, args)
1177 {
1178  const int depth = std::max<int>(0, lexical_cast_default<int>(args));
1179 
1180  return new bl_modification(depth);
1181 }
1182 
1183 // Opacity-shift
1184 REGISTER_MOD_PARSER(O, args)
1185 {
1186  const std::string::size_type p100_pos = args.find('%');
1187  float num = 0.0f;
1188  if(p100_pos == std::string::npos) {
1189  num = lexical_cast_default<float,const std::string&>(args);
1190  } else {
1191  // make multiplier
1192  const std::string parsed_field = args.substr(0, p100_pos);
1193  num = lexical_cast_default<float,const std::string&>(parsed_field);
1194  num /= 100.0f;
1195  }
1196 
1197  return new o_modification(num);
1198 }
1199 
1200 //
1201 // ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1202 // Hence they are at the end of the evaluation.
1203 //
1204 // Red component color-shift
1205 REGISTER_MOD_PARSER(R, args)
1206 {
1207  const int r = lexical_cast_default<int>(args);
1208 
1209  return new cs_modification(r,0,0);
1210 }
1211 
1212 // Green component color-shift
1213 REGISTER_MOD_PARSER(G, args)
1214 {
1215  const int g = lexical_cast_default<int>(args);
1216 
1217  return new cs_modification(0,g,0);
1218 }
1219 
1220 // Blue component color-shift
1221 REGISTER_MOD_PARSER(B, args)
1222 {
1223  const int b = lexical_cast_default<int>(args);
1224 
1225  return new cs_modification(0,0,b);
1226 }
1227 
1228 REGISTER_MOD_PARSER(NOP, )
1229 {
1230  return nullptr;
1231 }
1232 
1233 // Only used to tag terrain images which should not be color-shifted by ToD
1234 REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1235 {
1236  return nullptr;
1237 }
1238 
1239 // Fake image function used by GUI2 portraits until
1240 // Mordante gets rid of it. *tsk* *tsk*
1241 REGISTER_MOD_PARSER(RIGHT, )
1242 {
1243  return nullptr;
1244 }
1245 
1246 // Add a background color.
1247 REGISTER_MOD_PARSER(BG, args)
1248 {
1249  int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1250  std::vector<std::string> factors = utils::split(args, ',');
1251 
1252  for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1253  c[i] = lexical_cast_default<int>(factors[i]);
1254  }
1255 
1256  return new background_modification(color_t(c[0], c[1], c[2], c[3]));
1257 }
1258 
1259 // Channel swap
1260 REGISTER_MOD_PARSER(SWAP, args)
1261 {
1262  std::vector<std::string> params = utils::split(args, ',', utils::STRIP_SPACES);
1263 
1264  // accept 3 arguments (rgb) or 4 (rgba)
1265  if(params.size() != 3 && params.size() != 4) {
1266  ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4" << std::endl;
1267  return nullptr;
1268  }
1269 
1270  channel redValue, greenValue, blueValue, alphaValue;
1271  // compare the parameter's value with the constants defined in the channels enum
1272  if(params[0] == "red") {
1273  redValue = RED;
1274  } else if(params[0] == "green") {
1275  redValue = GREEN;
1276  } else if(params[0] == "blue") {
1277  redValue = BLUE;
1278  } else if(params[0] == "alpha") {
1279  redValue = ALPHA;
1280  } else {
1281  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0] << std::endl;
1282  return nullptr;
1283  }
1284 
1285  // wash, rinse and repeat for the other three channels
1286  if(params[1] == "red") {
1287  greenValue = RED;
1288  } else if(params[1] == "green") {
1289  greenValue = GREEN;
1290  } else if(params[1] == "blue") {
1291  greenValue = BLUE;
1292  } else if(params[1] == "alpha") {
1293  greenValue = ALPHA;
1294  } else {
1295  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0] << std::endl;
1296  return nullptr;
1297  }
1298 
1299  if(params[2] == "red") {
1300  blueValue = RED;
1301  } else if(params[2] == "green") {
1302  blueValue = GREEN;
1303  } else if(params[2] == "blue") {
1304  blueValue = BLUE;
1305  } else if(params[2] == "alpha") {
1306  blueValue = ALPHA;
1307  } else {
1308  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0] << std::endl;
1309  return nullptr;
1310  }
1311 
1312  // additional check: the params vector may not have a fourth elementh
1313  // if so, default to the same channel
1314  if(params.size() == 3) {
1315  alphaValue = ALPHA;
1316  } else {
1317  if(params[3] == "red") {
1318  alphaValue = RED;
1319  } else if(params[3] == "green") {
1320  alphaValue = GREEN;
1321  } else if(params[3] == "blue") {
1322  alphaValue = BLUE;
1323  } else if(params[3] == "alpha") {
1324  alphaValue = ALPHA;
1325  } else {
1326  ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3] << std::endl;
1327  return nullptr;
1328  }
1329  }
1330 
1331  return new swap_modification(redValue, greenValue, blueValue, alphaValue);
1332 }
1333 
1334 } // end anon namespace
1335 
1336 } /* end namespace image */
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
Definition: utils.hpp:146
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
pixel_t * pixels() const
Definition: surface.hpp:162
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:1006
surface negative_image(const surface &surf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition: utils.cpp:768
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:326
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.
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
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:596
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
variant b_
Definition: function.cpp:730
New lexcical_cast header.
int as_int() const
Definition: variant.cpp:295
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
static const color_range get_side_color_range(int side)
Definition: team.cpp:949
#define RC(i)
Definition: lvm.cpp:1039
A modified priority queue used to order image modifications.
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
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition: utils.cpp:1875
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.
std::vector< formula_input > formula_input_vector
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
#define REGISTER_MOD_PARSER(type, args_var)
A macro for automatic modification parser registration.
#define G(L)
Definition: lstate.h:298
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
#define h
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:197
surface blend_surface(const surface &surf, const double amount, const color_t color)
Blends a surface with a color.
Definition: utils.cpp:1717
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
surface flip_surface(const surface &surf)
Definition: utils.cpp:1952
Definitions for the interface to Wesnoth Markup Language (WML).
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
virtual int priority() const
Specifies the priority of the modification.
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
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 push(modification *mod)
Adds mod to the queue (unless mod is nullptr).
#define b
std::size_t size() const
Returns the number of elements in the queue.
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:1134
surface flop_surface(const surface &surf)
Definition: utils.cpp:1981
imod_exception(const std::stringstream &message_stream)
Constructor.
static lg::log_domain log_display("display")
#define fxpmult(x, y)
IN: unsigned and fixed_t - OUT: unsigned.
Definition: math.hpp:321
surface blur_alpha_surface(const surface &surf, int depth)
Cross-fades a surface with alpha channel.
Definition: utils.cpp:1512
Definition: utils.hpp:146
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
uint8_t r
Red value.
Definition: color.hpp:178
uint8_t a
Alpha value.
Definition: color.hpp:187
#define ERR_DP
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1659
Base abstract class for an image-path modification.
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
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition: utils.cpp:1917
Generic locator abstracting the location of an image.
Definition: picture.hpp:60
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
#define ftofxp(x)
IN: float or int - OUT: fixed_t.
Definition: math.hpp:318
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
std::unordered_map< color_t, color_t > color_range_map
Definition: color_range.hpp:31
Definition: utils.hpp:146
surface alpha_to_greyscale(const surface &surf)
Definition: utils.cpp:811
std::size_t i
Definition: function.cpp:941
surface monochrome_image(const surface &surf, const int threshold)
Definition: utils.cpp:686
wfl::variant get_value(const std::string &key) const override
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
mock_party p
static map_location::DIRECTION s
double g
Definition: astarsearch.cpp:65
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:123
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:142
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.
int w
Definition: utils.hpp:146
const std::string & get_filename() const
Definition: picture.hpp:78
surface greyscale_image(const surface &surf)
Definition: utils.cpp:641
variant a_
Definition: function.cpp:730
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1311
REMOVE_EMPTY: remove empty elements.
surface wipe_alpha(const surface &surf)
Definition: utils.cpp:839
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.
std::vector< std::string > split(const config_attribute_value &val)
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
pixel_callable(SDL_Point p, color_t clr, uint32_t w, uint32_t h)
const color_range & color_info(const std::string &name)
Functions to load and save images from/to disk.
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition: utils.cpp:463
Standard logging facilities (interface).
modification * top() const
Returns the top element in the queue .
std::string message
Definition: exceptions.hpp:30
map_type priorities_
Map from a mod&#39;s priority() to the mods having that priority.
const std::vector< color_t > & tc_info(const std::string &name)
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
#define e
void pop()
Removes the top element from the queue.
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:32
surface sepia_image(const surface &surf)
Definition: utils.cpp:726
uint8_t g
Green value.
Definition: color.hpp:181
uint8_t b
Blue value.
Definition: color.hpp:184
mock_char c
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition: utils.cpp:1764
bool file_exists() const
Tests whether the file the locator points at exists.
Definition: picture.cpp:659
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
surface swap_channels_image(const surface &surf, channel r, channel g, channel b, channel a)
Definition: utils.cpp:904
virtual surface operator()(const surface &src) const
Applies the image-path modification on the specified surface.
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...
channel
Definition: utils.hpp:146
const std::string message
The error message regarding the failed operation.