The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
image_modifications.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2009 - 2025
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
17
18#include <utility>
19
20#include "color.hpp"
21#include "config.hpp"
22#include "game_config.hpp"
23#include "picture.hpp"
24#include "log.hpp"
26#include "team.hpp"
27#include "utils/charconv.hpp"
28
29#include "formula/formula.hpp"
31
32#define GETTEXT_DOMAIN "wesnoth-lib"
33
34static lg::log_domain log_display("display");
35#define ERR_DP LOG_STREAM(err, log_display)
36
37namespace image {
38
39void modification_queue::push(std::unique_ptr<modification>&& mod)
40{
41 priorities_[mod->priority()].push_back(std::move(mod));
42}
43
45{
46 auto top_pair = priorities_.begin();
47 auto& mod_list = top_pair->second;
48
49 // Erase the top element.
50 mod_list.erase(mod_list.begin());
51
52 // We need to keep the map clean.
53 if(mod_list.empty()) {
54 priorities_.erase(top_pair);
55 }
56}
57
58std::size_t modification_queue::size() const
59{
60 std::size_t count = 0;
61 for(const auto& [priority, mods] : priorities_) {
62 count += mods.size();
63 }
64
65 return count;
66}
67
69{
70 return *priorities_.begin()->second.front();
71}
72
73
74namespace {
75
76/** A function used to parse modification arguments */
77using mod_parser = std::function<std::unique_ptr<modification>(std::string_view)>;
78
79/** A map of all registered mod parsers
80 *
81 * The mapping is between the modification name and the parser function pointer
82 * An example of an entry would be "TC" -> &parse_TC_mod
83 */
84std::map<std::string, mod_parser, std::less<>> mod_parsers;
85
86/** Decodes a single modification using an appropriate mod_parser
87 *
88 * @param encoded_mod A string representing a single modification
89 *
90 * @return A pointer to the decoded modification object
91 * @retval nullptr if the string is invalid or a parser isn't found
92 */
93std::unique_ptr<modification> decode_modification(const std::string& encoded_mod)
94{
95 const std::vector<std::string> split = utils::parenthetical_split(encoded_mod);
96
97 if(split.size() != 2) {
98 ERR_DP << "error parsing image modifications: " << encoded_mod;
99 return nullptr;
100 }
101
102 const std::string& mod_type = split[0];
103 const std::string& args = split[1];
104
105 if(const auto parser = mod_parsers.find(mod_type); parser != mod_parsers.end()) {
106 return std::invoke(parser->second, args);
107 } else {
108 ERR_DP << "unknown image function in path: " << mod_type;
109 return nullptr;
110 }
111}
112
113} // end anon namespace
114
115
116modification::imod_exception::imod_exception(const std::stringstream& message_stream)
117 : message(message_stream.str())
118{
119 this->store();
120}
121
123 : message(message)
124{
125 this->store();
126}
127
128/** Decodes the modification string
129 *
130 * Important:
131 * It creates new objects which need to be deleted after use
132 *
133 * @param encoded_mods A string representing any number of modifications
134 *
135 * @return A modification_queue filled with decoded modification pointers
136 */
137modification_queue modification::decode(const std::string& encoded_mods)
138{
140
141 for(const std::string& encoded_mod : utils::parenthetical_split(encoded_mods, '~')) {
142 if(auto mod = decode_modification(encoded_mod)) {
143 mods.push(std::move(mod));
144 }
145 }
146
147 return mods;
148}
149
151{
152 recolor_image(src, rc_map_);
153}
154
156{
157 if(horiz_ && vert_ ) {
158 // Slightly faster than doing both a flip and a flop.
160 } else if(horiz_) {
162 } else if(vert_) {
164 }
165}
166
168{
169 // Convert the number of degrees to the interval [0,360].
170 const int normalized = degrees_ >= 0 ?
171 degrees_ - 360 * (degrees_ / 360) :
172 degrees_ + 360 * (1 + (-degrees_) / 360); // In case compilers disagree as to what -90/360 is.
173
174 switch ( normalized )
175 {
176 case 0:
177 return;
178 case 90:
179 src = rotate_90_surface(src, true);
180 return;
181 case 180:
183 return;
184 case 270:
185 src = rotate_90_surface(src, false);
186 return;
187 case 360:
188 return;
189 }
190
191 src = rotate_any_surface(src, normalized, zoom_, offset_);
192}
193
198
200{
202 if(src_rect.w == src->w && src_rect.h == src->h) {
203 return;
204 }
205
206 if(surface cropped = get_surface_portion(src, src_rect)) {
207 src = cropped;
208 } else {
209 ERR_DP << "Failed to either crop or scale the surface";
210 }
211}
212
214{
215 monochrome_image(src, threshold_);
216}
217
222
224{
225 negative_image(src, red_, green_, blue_);
226}
227
232
237
238// TODO: Is this useful enough to move into formula/callable_objects?
240{
241public:
242 pixel_callable(std::size_t index, color_t clr, int w, int h)
243 : color_callable(clr), coord(), w(w), h(h)
244 {
245 coord.x = index % w;
246 coord.y = index / w;
247 }
248
249 void get_inputs(wfl::formula_input_vector& inputs) const override
250 {
251 color_callable::get_inputs(inputs);
252 add_input(inputs, "x");
253 add_input(inputs, "y");
254 add_input(inputs, "u");
255 add_input(inputs, "v");
256 add_input(inputs, "height");
257 add_input(inputs, "width");
258 }
259
260 wfl::variant get_value(const std::string& key) const override
261 {
262 using wfl::variant;
263 if(key == "x") {
264 return variant(coord.x);
265 } else if(key == "y") {
266 return variant(coord.y);
267 } else if(key == "width") {
268 return variant(w);
269 } else if(key == "height") {
270 return variant(h);
271 } else if(key == "u") {
272 return variant(coord.x / static_cast<float>(w));
273 } else if(key == "v") {
274 return variant(coord.y / static_cast<float>(h));
275 }
276
277 return color_callable::get_value(key);
278 }
279
280private:
282 int w, h;
283};
284
286{
287 if(src) {
288 wfl::formula new_alpha(formula_);
289
290 surface_lock lock(src);
291 std::size_t index{0};
292
293 for(auto& pixel : lock.pixel_span()) {
294 auto color = color_t::from_argb_bytes(pixel);
295
296 auto px = pixel_callable{index++, color, src->w, src->h};
297 color.a = std::min<unsigned>(new_alpha.evaluate(px).as_int(), 255);
298
299 pixel = color.to_argb_bytes();
300 }
301 }
302}
303
305{
306 if(src) {
307 wfl::formula new_r(formulas_[0]);
308 wfl::formula new_g(formulas_[1]);
309 wfl::formula new_b(formulas_[2]);
310 wfl::formula new_a(formulas_[3]);
311
312 surface_lock lock(src);
313 std::size_t index{0};
314
315 for(auto& pixel : lock.pixel_span()) {
316 auto color = color_t::from_argb_bytes(pixel);
317
318 auto px = pixel_callable{index++, color, src->w, src->h};
319 color.r = std::min<unsigned>(new_r.evaluate(px).as_int(), 255);
320 color.g = std::min<unsigned>(new_g.evaluate(px).as_int(), 255);
321 color.b = std::min<unsigned>(new_b.evaluate(px).as_int(), 255);
322 color.a = std::min<unsigned>(new_a.evaluate(px).as_int(), 255);
323
324 pixel = color.to_argb_bytes();
325 }
326 }
327}
328
330{
331 rect area = slice_;
332 if(area.w == 0) {
333 area.w = src->w;
334 }
335
336 if(area.h == 0) {
337 area.h = src->h;
338 }
339
340 src = cut_surface(src, area);
341}
342
344{
345 if(x_ >= src->w) {
346 std::stringstream sstr;
347 sstr << "~BLIT(): x-coordinate '"
348 << x_ << "' larger than destination image's width '"
349 << src->w << "' no blitting performed.\n";
350
351 throw imod_exception(sstr);
352 }
353
354 if(y_ >= src->h) {
355 std::stringstream sstr;
356 sstr << "~BLIT(): y-coordinate '"
357 << y_ << "' larger than destination image's height '"
358 << src->h << "' no blitting performed.\n";
359
360 throw imod_exception(sstr);
361 }
362
363 if(surf_->w + x_ < 0) {
364 std::stringstream sstr;
365 sstr << "~BLIT(): offset and width '"
366 << x_ + surf_->w << "' less than zero no blitting performed.\n";
367
368 throw imod_exception(sstr);
369 }
370
371 if(surf_->h + y_ < 0) {
372 std::stringstream sstr;
373 sstr << "~BLIT(): offset and height '"
374 << y_ + surf_->h << "' less than zero no blitting performed.\n";
375
376 throw imod_exception(sstr);
377 }
378
379 rect r {x_, y_, 0, 0};
380 sdl_blit(surf_, nullptr, src, &r);
381}
382
384{
385 if(src->w == mask_->w && src->h == mask_->h && x_ == 0 && y_ == 0) {
386 mask_surface(src, mask_);
387 return;
388 }
389
390 rect r {x_, y_, 0, 0};
391 surface new_mask(src->w, src->h);
392 sdl_blit(mask_, nullptr, new_mask, &r);
393 mask_surface(src, new_mask);
394}
395
397{
398 if(src == nullptr) { return; }
399
400 // light_surface wants a neutral surface having same dimensions
401 if(surf_->w != src->w || surf_->h != src->h) {
402 light_surface(src, scale_surface(surf_, src->w, src->h));
403 } else {
404 light_surface(src, surf_);
405 }
406}
407
409{
410 point size = target_size_;
411
412 if(size.x <= 0) {
413 size.x = src->w;
414 } else if(flags_ & X_BY_FACTOR) {
415 size.x = src->w * (static_cast<double>(size.x) / 100);
416 }
417
418 if(size.y <= 0) {
419 size.y = src->h;
420 } else if(flags_ & Y_BY_FACTOR) {
421 size.y = src->h * (static_cast<double>(size.y) / 100);
422 }
423
424 if(flags_ & PRESERVE_ASPECT_RATIO) {
425 const auto ratio = std::min(
426 static_cast<long double>(size.x) / src->w,
427 static_cast<long double>(size.y) / src->h
428 );
429
430 size = {
431 static_cast<int>(src->w * ratio),
432 static_cast<int>(src->h * ratio)
433 };
434 }
435
436 if(flags_ & SCALE_SHARP) {
437 src = scale_surface_sharp(src, size.x, size.y);
438 } else {
439 src = scale_surface_legacy(src, size.x, size.y);
440 }
441}
442
444{
445 if(z_ != 1) {
447 }
448}
449
451{
452 // Calculate the new dimensions of the padded surface
453 const int new_w = src->w + left_ + right_;
454 const int new_h = src->h + top_ + bottom_;
455
456 // Create a new transparent surface with the calculated dimensions
457 surface padded(new_w, new_h);
458
459 // Define the destination rectangle for the original image
460 rect dstrect{
461 left_,
462 top_,
463 0, // These two values are ignored by SDL_BlitSurface, so we set them to 0.
464 0 // The function always blits the entire source surface.
465 };
466
467 SDL_BlendMode original_blend_mode;
468 SDL_GetSurfaceBlendMode(src, &original_blend_mode);
469 // Set blend mode to none to prevent blending
470 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
471
472 // Blit the original surface onto the new, padded surface
473 SDL_BlitSurface(src, nullptr, padded, &dstrect);
474
475 SDL_SetSurfaceBlendMode(padded, original_blend_mode);
476
477 src = padded;
478}
479
480/* The Opacity IPF doesn't seem to work with surface-wide alpha and instead needs per-pixel alpha. */
482{
483 if(src) {
484 apply_surface_opacity(src, opacity_);
485 }
486}
487
489{
490 if((r_ != 0 || g_ != 0 || b_ != 0)) {
491 adjust_surface_color(src, r_, g_, b_);
492 }
493}
494
496{
497 blend_surface(src, static_cast<double>(a_), color_t(r_, g_, b_));
498}
499
501{
502 blur_alpha_surface(src, depth_);
503}
504
506{
507 surface ret = src.clone();
508 SDL_FillSurfaceRect(ret, nullptr, SDL_MapSurfaceRGBA(ret, color_.r, color_.g, color_.b, color_.a));
509 sdl_blit(src, nullptr, ret, nullptr);
510 src = ret;
511}
512
514{
515 swap_channels_image(src, red_, green_, blue_, alpha_);
516}
517
518namespace {
519
520struct parse_mod_registration
521{
522 parse_mod_registration(const char* name, mod_parser parser)
523 {
524 mod_parsers[name] = std::move(parser);
525 }
526};
527
528/** A macro for automatic modification parser registration
529 *
530 * It automatically registers the created parser in the mod_parsers map
531 * It should be used just like a function header (look at the uses below)
532 * It should only be used within an anonymous namespace
533 *
534 * @param type The modification type to be registered (unquoted)
535 * @param args_var The name for the string argument provided
536 */
537#define REGISTER_MOD_PARSER(type, args_var) \
538 static std::unique_ptr<modification> parse_##type##_mod(std::string_view); \
539 static parse_mod_registration parse_##type##_mod_registration_aux(#type, &parse_##type##_mod); \
540 static std::unique_ptr<modification> parse_##type##_mod(std::string_view args_var) \
541
542// Color-range-based recoloring
543REGISTER_MOD_PARSER(TC, args)
544{
545 const auto params = utils::split_view(args,',');
546
547 if(params.size() < 2) {
548 ERR_DP << "too few arguments passed to the ~TC() function";
549
550 return nullptr;
551 }
552
553 const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
554 if(side_n < 1) {
555 ERR_DP << "Invalid side (" << params[0] << ") passed to the ~TC() function";
556 return nullptr;
557 }
558
559 //
560 // Pass argseters for RC functor
561 //
562 if(!game_config::tc_info(params[1]).size()){
563 ERR_DP << "could not load TC info for '" << params[1] << "' palette";
564 ERR_DP << "bailing out from TC";
565
566 return nullptr;
567 }
568
569 try {
570 const color_range& new_color = team::get_side_color_range(side_n);
571 const std::vector<color_t>& old_color = game_config::tc_info(params[1]);
572
573 return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
574 } catch(const config::error& e) {
575 ERR_DP << "caught config::error while processing TC: " << e.message;
576 ERR_DP << "bailing out from TC";
577
578 return nullptr;
579 }
580}
581
582// Team-color-based color range selection and recoloring
583REGISTER_MOD_PARSER(RC, args)
584{
585 const auto recolor_params = utils::split_view(args,'>');
586
587 if(recolor_params.size() <= 1) {
588 return nullptr;
589 }
590
591 //
592 // recolor source palette to color range
593 //
594 try {
595 const color_range& new_color = game_config::color_info(recolor_params[1]);
596 const std::vector<color_t>& old_color = game_config::tc_info(recolor_params[0]);
597
598 return std::make_unique<rc_modification>(generate_color_mapping(new_color, old_color));
599 } catch (const config::error& e) {
600 ERR_DP
601 << "caught config::error while processing color-range RC: "
602 << e.message;
603 ERR_DP << "bailing out from RC";
604 return nullptr;
605 }
606}
607
608// Palette switch
609REGISTER_MOD_PARSER(PAL, args)
610{
611 const auto remap_params = utils::split_view(args,'>');
612
613 if(remap_params.size() < 2) {
614 ERR_DP << "not enough arguments passed to the ~PAL() function: " << args;
615
616 return nullptr;
617 }
618
619 try {
620 color_mapping rc_map;
621 const std::vector<color_t>& old_palette = game_config::tc_info(remap_params[0]);
622 const std::vector<color_t>& new_palette = game_config::tc_info(remap_params[1]);
623
624 for(std::size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
625 rc_map[old_palette[i]] = new_palette[i];
626 }
627
628 return std::make_unique<rc_modification>(std::move(rc_map));
629 } catch(const config::error& e) {
630 ERR_DP
631 << "caught config::error while processing PAL function: "
632 << e.message;
633 ERR_DP
634 << "bailing out from PAL";
635
636 return nullptr;
637 }
638}
639
640// Flip/flop
641REGISTER_MOD_PARSER(FL, args)
642{
643 bool horiz = (args.empty() || args.find("horiz") != std::string::npos);
644 bool vert = (args.find("vert") != std::string::npos);
645
646 return std::make_unique<fl_modification>(horiz, vert);
647}
648
649// Rotations
650REGISTER_MOD_PARSER(ROTATE, args)
651{
652 const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
653
654 switch(slice_params.size()) {
655 case 0:
656 return std::make_unique<rotate_modification>();
657 case 1:
658 return std::make_unique<rotate_modification>(
659 utils::from_chars<int>(slice_params[0]).value_or(0));
660 case 2:
661 return std::make_unique<rotate_modification>(
662 utils::from_chars<int>(slice_params[0]).value_or(0),
663 utils::from_chars<int>(slice_params[1]).value_or(0));
664 case 3:
665 return std::make_unique<rotate_modification>(
666 utils::from_chars<int>(slice_params[0]).value_or(0),
667 utils::from_chars<int>(slice_params[1]).value_or(0),
668 utils::from_chars<int>(slice_params[2]).value_or(0));
669 }
670 return nullptr;
671}
672
673// Grayscale
675{
676 return std::make_unique<gs_modification>();
677}
678
679// crop transparent padding
680REGISTER_MOD_PARSER(CROP_TRANSPARENCY, )
681{
682 return std::make_unique<crop_transparency_modification>();
683}
684
685// TODO: should this be made a more general util function?
686bool in_range(int val, int min, int max)
687{
688 return min <= val && val <= max;
689}
690
691// Black and white
692REGISTER_MOD_PARSER(BW, args)
693{
694 const auto params = utils::split_view(args, ',');
695
696 if(params.size() != 1) {
697 ERR_DP << "~BW() requires exactly one argument";
698 return nullptr;
699 }
700
701 // TODO: maybe get this directly as uint8_t?
702 const auto threshold = utils::from_chars<int>(params[0]);
703 if(!threshold) {
704 ERR_DP << "unsupported argument in ~BW() function";
705 return nullptr;
706 }
707
708 if(!in_range(*threshold, 0, 255)) {
709 ERR_DP << "~BW() argument out of range 0 - 255";
710 return nullptr;
711 }
712
713 return std::make_unique<bw_modification>(*threshold);
714}
715
716// Sepia
717REGISTER_MOD_PARSER(SEPIA, )
718{
719 return std::make_unique<sepia_modification>();
720}
721
722// Negative
723REGISTER_MOD_PARSER(NEG, args)
724{
725 const auto params = utils::split_view(args, ',');
726
727 switch(params.size()) {
728 case 0:
729 // apparently -1 may be a magic number but this is the threshold
730 // value required to fully invert a channel
731 return std::make_unique<negative_modification>(-1, -1, -1);
732
733 case 1: {
734 const auto threshold = utils::from_chars<int>(params[0]);
735
736 if(threshold && in_range(*threshold, -1, 255)) {
737 return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
738 } else {
739 ERR_DP << "unsupported argument value in ~NEG() function";
740 return nullptr;
741 }
742 }
743
744 case 3: {
745 const auto thR = utils::from_chars<int>(params[0]);
746 const auto thG = utils::from_chars<int>(params[1]);
747 const auto thB = utils::from_chars<int>(params[2]);
748
749 if(thR && thG && thB && in_range(*thR, -1, 255) && in_range(*thG, -1, 255) && in_range(*thB, -1, 255)) {
750 return std::make_unique<negative_modification>(*thR, *thG, *thB);
751 } else {
752 ERR_DP << "unsupported argument value in ~NEG() function";
753 return nullptr;
754 }
755 }
756
757 default:
758 ERR_DP << "~NEG() requires 0, 1 or 3 arguments";
759 return nullptr;
760 }
761}
762
763// Plot Alpha
764REGISTER_MOD_PARSER(PLOT_ALPHA, )
765{
766 return std::make_unique<plot_alpha_modification>();
767}
768
769// Wipe Alpha
770REGISTER_MOD_PARSER(WIPE_ALPHA, )
771{
772 return std::make_unique<wipe_alpha_modification>();
773}
774
775// Adjust Alpha
776REGISTER_MOD_PARSER(ADJUST_ALPHA, args)
777{
778 // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
779 // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
780 const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
781
782 if(params.size() != 1) {
783 ERR_DP << "~ADJUST_ALPHA() requires exactly 1 arguments";
784 return nullptr;
785 }
786
787 return std::make_unique<adjust_alpha_modification>(params.at(0));
788}
789
790// Adjust Channels
791REGISTER_MOD_PARSER(CHAN, args)
792{
793 // Formulas may contain commas, so use parenthetical split to ensure that they're properly considered a single argument.
794 // (A comma in a formula is only valid in function parameters or list/map literals, so this should always work.)
795 const std::vector<std::string>& params = utils::parenthetical_split(args, ',', "([", ")]");
796
797 if(params.size() < 1 || params.size() > 4) {
798 ERR_DP << "~CHAN() requires 1 to 4 arguments";
799 return nullptr;
800 }
801
802 return std::make_unique<adjust_channels_modification>(params);
803}
804
805// Color-shift
806REGISTER_MOD_PARSER(CS, args)
807{
808 const auto factors = utils::split_view(args, ',');
809 const std::size_t s = factors.size();
810
811 if(s == 0) {
812 ERR_DP << "no arguments passed to the ~CS() function";
813 return nullptr;
814 }
815
816 int r = 0, g = 0, b = 0;
817
818 r = utils::from_chars<int>(factors[0]).value_or(0);
819
820 if(s > 1 ) {
821 g = utils::from_chars<int>(factors[1]).value_or(0);
822 }
823 if(s > 2 ) {
824 b = utils::from_chars<int>(factors[2]).value_or(0);
825 }
826
827 return std::make_unique<cs_modification>(r, g , b);
828}
829
830// Color blending
831REGISTER_MOD_PARSER(BLEND, args)
832{
833 const auto params = utils::split_view(args, ',');
834
835 if(params.size() != 4) {
836 ERR_DP << "~BLEND() requires exactly 4 arguments";
837 return nullptr;
838 }
839
840 float opacity = 0.0f;
841 const std::string_view& opacity_str = params[3];
842 const std::string_view::size_type p100_pos = opacity_str.find('%');
843
844 if(p100_pos == std::string::npos)
845 opacity = utils::from_chars<float>(opacity_str).value_or(0.0f);
846 else {
847 // make multiplier
848 const std::string_view parsed_field = opacity_str.substr(0, p100_pos);
849 opacity = utils::from_chars<float>(parsed_field).value_or(0.0f);
850 opacity /= 100.0f;
851 }
852
853 return std::make_unique<blend_modification>(
854 utils::from_chars<int>(params[0]).value_or(0),
855 utils::from_chars<int>(params[1]).value_or(0),
856 utils::from_chars<int>(params[2]).value_or(0),
857 opacity);
858}
859
860// Crop/slice
861REGISTER_MOD_PARSER(CROP, args)
862{
863 const auto slice_params = utils::split_view(args, ',', utils::STRIP_SPACES);
864 const std::size_t s = slice_params.size();
865
866 if(s == 0 || (s == 1 && slice_params[0].empty())) {
867 ERR_DP << "no arguments passed to the ~CROP() function";
868 return nullptr;
869 }
870
871 rect slice_rect { 0, 0, 0, 0 };
872
873 slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
874
875 if(s > 1) {
876 slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
877 }
878 if(s > 2) {
879 slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
880 }
881 if(s > 3) {
882 slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
883 }
884
885 return std::make_unique<crop_modification>(slice_rect);
886}
887
888static bool check_image(const image::locator& img, std::stringstream & message)
889{
890 if(image::exists(img)) return true;
891 message << " image not found: '" << img.get_filename() << "'\n";
892 ERR_DP << message.str();
893 return false;
894}
895
896// Blit
897REGISTER_MOD_PARSER(BLIT, args)
898{
899 std::vector<std::string> param = utils::parenthetical_split(args, ',');
900 const std::size_t s = param.size();
901
902 if(s == 0 || (s == 1 && param[0].empty())){
903 ERR_DP << "no arguments passed to the ~BLIT() function";
904 return nullptr;
905 }
906
907 if(s > 3){
908 ERR_DP << "too many arguments passed to the ~BLIT() function";
909 return nullptr;
910 }
911
912 int x = 0, y = 0;
913
914 if(s == 3) {
915 x = utils::from_chars<int>(param[1]).value_or(0);
916 y = utils::from_chars<int>(param[2]).value_or(0);
917 }
918
919 const image::locator img(param[0]);
920 std::stringstream message;
921 message << "~BLIT():";
922 if(!check_image(img, message))
923 return nullptr;
924 surface surf = get_surface(img);
925
926 return std::make_unique<blit_modification>(surf, x, y);
927}
928
929// Mask
930REGISTER_MOD_PARSER(MASK, args)
931{
932 std::vector<std::string> param = utils::parenthetical_split(args, ',');
933 const std::size_t s = param.size();
934
935 if(s == 0 || (s == 1 && param[0].empty())){
936 ERR_DP << "no arguments passed to the ~MASK() function";
937 return nullptr;
938 }
939
940 int x = 0, y = 0;
941
942 if(s == 3) {
943 x = utils::from_chars<int>(param[1]).value_or(0);
944 y = utils::from_chars<int>(param[2]).value_or(0);
945 }
946
947 if(x < 0 || y < 0) {
948 ERR_DP << "negative position arguments in ~MASK() function";
949 return nullptr;
950 }
951
952 const image::locator img(param[0]);
953 std::stringstream message;
954 message << "~MASK():";
955 if(!check_image(img, message))
956 return nullptr;
957 surface surf = get_surface(img);
958
959 return std::make_unique<mask_modification>(surf, x, y);
960}
961
962// Light
963REGISTER_MOD_PARSER(L, args)
964{
965 if(args.empty()){
966 ERR_DP << "no arguments passed to the ~L() function";
967 return nullptr;
968 }
969
970 surface surf = get_surface(std::string{args}); // FIXME: string_view for image::locator::value
971 return std::make_unique<light_modification>(surf);
972}
973
974namespace
975{
976std::pair<int, bool> parse_scale_value(std::string_view arg)
977{
978 if(const std::size_t pos = arg.rfind('%'); pos != std::string_view::npos) {
979 return { utils::from_chars<int>(arg.substr(0, pos)).value_or(0), true };
980 } else {
981 return { utils::from_chars<int>(arg).value_or(0), false };
982 }
983}
984
985/** Common helper function to parse scaling IPF inputs. */
986utils::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
987{
988 const auto scale_params = utils::split_view(args, ',', utils::STRIP_SPACES);
989 const std::size_t num_args = scale_params.size();
990
991 if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
992 return utils::nullopt;
993 }
994
995 uint8_t flags = 0;
996 std::array<int, 2> parsed_sizes{0,0};
997
998 for(unsigned i = 0; i < std::min<unsigned>(2, num_args); ++i) {
999 const auto& [size, relative] = parse_scale_value(scale_params[i]);
1000
1001 if(size < 0) {
1002 ERR_DP << "Negative size passed to scaling IPF. Original image dimension will be used instead";
1003 continue;
1004 }
1005
1006 parsed_sizes[i] = size;
1007
1008 if(relative) {
1010 }
1011 }
1012
1013 return std::pair{point{parsed_sizes[0], parsed_sizes[1]}, flags};
1014}
1015
1016} // namespace
1017
1018// Scale
1019REGISTER_MOD_PARSER(SCALE, args)
1020{
1021 if(auto params = parse_scale_args(args)) {
1023 return std::make_unique<scale_modification>(params->first, mode | params->second);
1024 } else {
1025 ERR_DP << "no arguments passed to the ~SCALE() function";
1026 return nullptr;
1027 }
1028}
1029
1030REGISTER_MOD_PARSER(SCALE_SHARP, args)
1031{
1032 if(auto params = parse_scale_args(args)) {
1034 return std::make_unique<scale_modification>(params->first, mode | params->second);
1035 } else {
1036 ERR_DP << "no arguments passed to the ~SCALE_SHARP() function";
1037 return nullptr;
1038 }
1039}
1040
1041REGISTER_MOD_PARSER(SCALE_INTO, args)
1042{
1043 if(auto params = parse_scale_args(args)) {
1045 return std::make_unique<scale_modification>(params->first, mode | params->second);
1046 } else {
1047 ERR_DP << "no arguments passed to the ~SCALE_INTO() function";
1048 return nullptr;
1049 }
1050}
1051
1052REGISTER_MOD_PARSER(SCALE_INTO_SHARP, args)
1053{
1054 if(auto params = parse_scale_args(args)) {
1056 return std::make_unique<scale_modification>(params->first, mode | params->second);
1057 } else {
1058 ERR_DP << "no arguments passed to the ~SCALE_INTO_SHARP() function";
1059 return nullptr;
1060 }
1061}
1062
1063// xBRZ
1064REGISTER_MOD_PARSER(XBRZ, args)
1065{
1066 const int factor = std::clamp(utils::from_chars<int>(args).value_or(1), 1, 6);
1067 return std::make_unique<xbrz_modification>(factor);
1068}
1069
1070// Pad
1071REGISTER_MOD_PARSER(PAD, args)
1072{
1073 int top = 0;
1074 int right = 0;
1075 int bottom = 0;
1076 int left = 0;
1077
1078 // Check for the presence of an '=' sign to determine the parsing mode.
1079 if(args.find('=') != std::string_view::npos) {
1080 // --- Keyword-Argument Mode ---
1081 const auto params = utils::map_split(std::string{args}, ',', '='); // map_split needs a std::string
1082
1083 // Map valid input strings to the corresponding integer reference
1084 const std::map<std::string, int*> alias_map = {
1085 {"top", &top},
1086 {"t", &top},
1087 {"right", &right},
1088 {"r", &right},
1089 {"bottom", &bottom},
1090 {"b", &bottom},
1091 {"left", &left},
1092 {"l", &left},
1093 };
1094
1095 // Parse and assign values if keywords are valid
1096 for(const auto& [key, value] : params) {
1097 auto it = alias_map.find(key);
1098 if(it != alias_map.end()) {
1099 if(utils::optional padding = utils::from_chars<int>(value)) {
1100 *it->second = padding.value();
1101 } else {
1102 ERR_DP << "~PAD() keyword argument '" << key << "' requires a valid integer value. Received: '" << value << "'.";
1103 return nullptr;
1104 }
1105 } else {
1106 ERR_DP << "~PAD() found an unknown keyword: '" << key << "'. Valid keywords: top, t, right, r, bottom, b, left, l.";
1107 return nullptr;
1108 }
1109 }
1110 } else {
1111 // --- Numeric-Argument Mode ---
1112 const auto params = utils::split_view(args, ',');
1113 if(params.size() != 1) {
1114 ERR_DP << "~PAD() takes either 1 numeric argument for the padding on all sides or a comma separated list of '<keyword>=<number>' pairs with available keywords: top, t, right, r, bottom, b, left, l.";
1115 return nullptr;
1116 }
1117
1118 // Single integer argument: apply to all sides
1119 if(utils::optional padding = utils::from_chars<int>(params[0])) {
1120 top = right = bottom = left = padding.value();
1121 } else {
1122 ERR_DP << "~PAD() numeric argument (pad all sides) requires a single valid integer. Received: '" << params[0] << "'.";
1123 return nullptr;
1124 }
1125 }
1126
1127 return std::make_unique<pad_modification>(top, right, bottom, left);
1128}
1129
1130// Gaussian-like blur
1131REGISTER_MOD_PARSER(BL, args)
1132{
1133 const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1134 return std::make_unique<bl_modification>(depth);
1135}
1136
1137// Opacity-shift
1138REGISTER_MOD_PARSER(O, args)
1139{
1140 const std::string::size_type p100_pos = args.find('%');
1141 float num = 0.0f;
1142 if(p100_pos == std::string::npos) {
1143 num = utils::from_chars<float>(args).value_or(0.0f);
1144 } else {
1145 // make multiplier
1146 const std::string_view parsed_field = args.substr(0, p100_pos);
1147 num = utils::from_chars<float>(parsed_field).value_or(0.0f);
1148 num /= 100.0f;
1149 }
1150
1151 return std::make_unique<o_modification>(num);
1152}
1153
1154//
1155// ~R(), ~G() and ~B() are the children of ~CS(). Merely syntactic sugar.
1156// Hence they are at the end of the evaluation.
1157//
1158// Red component color-shift
1159REGISTER_MOD_PARSER(R, args)
1160{
1161 const int r = utils::from_chars<int>(args).value_or(0);
1162 return std::make_unique<cs_modification>(r, 0, 0);
1163}
1164
1165// Green component color-shift
1166REGISTER_MOD_PARSER(G, args)
1167{
1168 const int g = utils::from_chars<int>(args).value_or(0);
1169 return std::make_unique<cs_modification>(0, g, 0);
1170}
1171
1172// Blue component color-shift
1173REGISTER_MOD_PARSER(B, args)
1174{
1175 const int b = utils::from_chars<int>(args).value_or(0);
1176 return std::make_unique<cs_modification>(0, 0, b);
1177}
1178
1180{
1181 return nullptr;
1182}
1183
1184// Only used to tag terrain images which should not be color-shifted by ToD
1185REGISTER_MOD_PARSER(NO_TOD_SHIFT, )
1186{
1187 return nullptr;
1188}
1189
1190// Fake image function used by GUI2 portraits until
1191// Mordante gets rid of it. *tsk* *tsk*
1192REGISTER_MOD_PARSER(RIGHT, )
1193{
1194 return nullptr;
1195}
1196
1197// Add a background color.
1198REGISTER_MOD_PARSER(BG, args)
1199{
1200 int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
1201 const auto factors = utils::split_view(args, ',');
1202
1203 for(int i = 0; i < std::min<int>(factors.size(), 4); ++i) {
1204 c[i] = utils::from_chars<int>(factors[i]).value_or(0);
1205 }
1206
1207 return std::make_unique<background_modification>(color_t(c[0], c[1], c[2], c[3]));
1208}
1209
1210// Channel swap
1211REGISTER_MOD_PARSER(SWAP, args)
1212{
1213 const auto params = utils::split_view(args, ',', utils::STRIP_SPACES);
1214
1215 // accept 3 arguments (rgb) or 4 (rgba)
1216 if(params.size() != 3 && params.size() != 4) {
1217 ERR_DP << "incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1218 return nullptr;
1219 }
1220
1221 channel redValue, greenValue, blueValue, alphaValue;
1222 // compare the parameter's value with the constants defined in the channels enum
1223 if(params[0] == "red") {
1224 redValue = RED;
1225 } else if(params[0] == "green") {
1226 redValue = GREEN;
1227 } else if(params[0] == "blue") {
1228 redValue = BLUE;
1229 } else if(params[0] == "alpha") {
1230 redValue = ALPHA;
1231 } else {
1232 ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1233 return nullptr;
1234 }
1235
1236 // wash, rinse and repeat for the other three channels
1237 if(params[1] == "red") {
1238 greenValue = RED;
1239 } else if(params[1] == "green") {
1240 greenValue = GREEN;
1241 } else if(params[1] == "blue") {
1242 greenValue = BLUE;
1243 } else if(params[1] == "alpha") {
1244 greenValue = ALPHA;
1245 } else {
1246 ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1247 return nullptr;
1248 }
1249
1250 if(params[2] == "red") {
1251 blueValue = RED;
1252 } else if(params[2] == "green") {
1253 blueValue = GREEN;
1254 } else if(params[2] == "blue") {
1255 blueValue = BLUE;
1256 } else if(params[2] == "alpha") {
1257 blueValue = ALPHA;
1258 } else {
1259 ERR_DP << "unsupported argument value in ~SWAP() function: " << params[0];
1260 return nullptr;
1261 }
1262
1263 // additional check: the params vector may not have a fourth elementh
1264 // if so, default to the same channel
1265 if(params.size() == 3) {
1266 alphaValue = ALPHA;
1267 } else {
1268 if(params[3] == "red") {
1269 alphaValue = RED;
1270 } else if(params[3] == "green") {
1271 alphaValue = GREEN;
1272 } else if(params[3] == "blue") {
1273 alphaValue = BLUE;
1274 } else if(params[3] == "alpha") {
1275 alphaValue = ALPHA;
1276 } else {
1277 ERR_DP << "unsupported argument value in ~SWAP() function: " << params[3];
1278 return nullptr;
1279 }
1280 }
1281
1282 return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);
1283}
1284
1285} // end anon namespace
1286
1287} /* end namespace image */
Representing the "team color" information, this C++ class contains a subset of WML's [color_range] ta...
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Generic locator abstracting the location of an image.
Definition picture.hpp:59
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
A modified priority queue used to order image modifications.
void push(std::unique_ptr< modification > &&mod)
Adds mod to the queue.
const modification & top() const
Returns a const reference to the top element in the queue.
std::map< int, std::vector< std::unique_ptr< modification > >, std::greater< int > > priorities_
Map from a mod's priority() to the mods having that priority.
void pop()
Removes the top element from the queue.
std::size_t size() const
Returns the number of elements in the queue.
Base abstract class for an image-path modification.
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
pixel_callable(std::size_t index, color_t clr, int w, int h)
wfl::variant get_value(const std::string &key) const override
void get_inputs(wfl::formula_input_vector &inputs) const override
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
void store() const noexcept
Stores a copy the current exception to be rethrown.
Helper class for pinning SDL surfaces into memory.
Definition surface.hpp:98
utils::span< pixel_t > pixel_span() const
Definition surface.hpp:122
static color_range get_side_color_range(int side)
Definition team.cpp:931
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition formula.hpp:48
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition variant.cpp:336
color_mapping generate_color_mapping(const color_range &new_range, const std::vector< color_t > &old_rgb)
Converts a source palette using the specified color_range object.
std::unordered_map< color_t, color_t > color_mapping
Definitions for the interface to Wesnoth Markup Language (WML).
#define ERR_DP
variant a_
Definition function.cpp:821
variant b_
Definition function.cpp:821
#define REGISTER_MOD_PARSER(type, args_var)
A macro for automatic modification parser registration.
static lg::log_domain log_display("display")
Standard logging facilities (interface).
const std::vector< color_t > & tc_info(std::string_view name)
const color_range & color_info(std::string_view name)
Functions to load and save images from/to disk.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition picture.cpp:691
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition markup.cpp:37
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition unicode.cpp:81
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
std::vector< std::string_view > split_view(std::string_view s, const char sep, const int flags)
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
std::vector< std::string > parenthetical_split(std::string_view val, const char separator, std::string_view left, std::string_view right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
std::vector< formula_input > formula_input_vector
int w
Definition pathfind.cpp:188
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
surface surf
Image.
rect src
Non-transparent portion of the surface to compose.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
static constexpr color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition color.hpp:102
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Exception thrown by the operator() when an error occurs.
imod_exception(const std::stringstream &message_stream)
Constructor.
virtual void operator()(surface &src) const override
Applies the image-path modification on the specified surface.
Holds a 2D point.
Definition point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
static map_location::direction s
surface get_surface_portion(const surface &src, rect &area)
Get a portion of the screen.
Definition utils.cpp:1356
void alpha_to_greyscale(surface &nsurf)
Definition utils.cpp:504
void recolor_image(surface &nsurf, const color_mapping &map_rgb)
Recolors a surface using a map with source and converted palette values.
Definition utils.cpp:636
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:225
void wipe_alpha(surface &nsurf)
Definition utils.cpp:517
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition utils.cpp:1092
void sepia_image(surface &nsurf)
Definition utils.cpp:464
void blend_surface(surface &nsurf, const double amount, const color_t color)
Blends a surface with a color.
Definition utils.cpp:1153
void swap_channels_image(surface &nsurf, channel r, channel g, channel b, channel a)
Definition utils.cpp:554
rect get_non_transparent_portion(const surface &surf)
Definition utils.cpp:1440
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition utils.cpp:405
void negative_image(surface &nsurf, const int thresholdR, const int thresholdG, const int thresholdB)
Definition utils.cpp:483
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition utils.cpp:790
surface scale_surface_xbrz(const surface &surf, std::size_t z)
Scale a surface using xBRZ algorithm.
Definition utils.cpp:61
surface scale_surface_sharp(const surface &surf, int w, int h)
Scale a surface using modified nearest neighbour algorithm.
Definition utils.cpp:358
void blur_alpha_surface(surface &res, int depth)
Cross-fades a surface with alpha channel.
Definition utils.cpp:954
void greyscale_image(surface &nsurf)
Definition utils.cpp:424
void apply_surface_opacity(surface &surf, float opacity)
Applies an opacity modification to a surface.
Definition utils.cpp:1392
void flop_surface(surface &nsurf)
Definition utils.cpp:1340
surface rotate_90_surface(const surface &surf, bool clockwise)
Rotates a surface 90 degrees.
Definition utils.cpp:1283
surface rotate_180_surface(const surface &surf)
Rotates a surface 180 degrees.
Definition utils.cpp:1243
void flip_surface(surface &nsurf)
Definition utils.cpp:1317
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:99
surface rotate_any_surface(const surface &surf, float angle, int zoom, int offset)
Rotates a surface by any degrees.
Definition utils.cpp:1181
void monochrome_image(surface &nsurf, const int threshold)
Definition utils.cpp:446
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask to the source surface by calculating the minimum alpha channel value for every corresp...
Definition utils.cpp:708
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition utils.hpp:43
channel
Definition utils.hpp:103
@ BLUE
Definition utils.hpp:103
@ ALPHA
Definition utils.hpp:103
@ GREEN
Definition utils.hpp:103
@ RED
Definition utils.hpp:103
#define i
#define g
#define e
#define h
#define c
#define b