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