41 priorities_[mod->priority()].push_back(std::move(mod));
47 auto& mod_list = top_pair->second;
50 mod_list.erase(mod_list.begin());
53 if(mod_list.empty()) {
60 std::size_t count = 0;
77using mod_parser = std::function<std::unique_ptr<modification>(std::string_view)>;
84std::map<std::string, mod_parser, std::less<>> mod_parsers;
93std::unique_ptr<modification> decode_modification(
const std::string& encoded_mod)
97 if(split.size() != 2) {
98 ERR_DP <<
"error parsing image modifications: " << encoded_mod;
102 const std::string& mod_type = split[0];
103 const std::string& args = split[1];
105 if(
const auto parser = mod_parsers.find(mod_type); parser != mod_parsers.end()) {
106 return std::invoke(parser->second, args);
108 ERR_DP <<
"unknown image function in path: " << mod_type;
117 : message(message_stream.str())
142 if(
auto mod = decode_modification(encoded_mod)) {
143 mods.
push(std::move(mod));
157 if(horiz_ && vert_ ) {
170 const int normalized = degrees_ >= 0 ?
171 degrees_ - 360 * (degrees_ / 360) :
172 degrees_ + 360 * (1 + (-degrees_) / 360);
174 switch ( normalized )
202 if(src_rect.w ==
src->w && src_rect.h ==
src->h) {
209 ERR_DP <<
"Failed to either crop or scale the surface";
243 : color_callable(clr), coord(),
w(
w),
h(
h)
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");
264 return variant(coord.x);
265 }
else if(key ==
"y") {
266 return variant(coord.y);
267 }
else if(key ==
"width") {
269 }
else if(key ==
"height") {
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));
277 return color_callable::get_value(key);
291 std::size_t index{0};
297 color.a = std::min<unsigned>(new_alpha.
evaluate(px).
as_int(), 255);
299 pixel = color.to_argb_bytes();
313 std::size_t index{0};
324 pixel = color.to_argb_bytes();
346 std::stringstream sstr;
347 sstr <<
"~BLIT(): x-coordinate '"
348 << x_ <<
"' larger than destination image's width '"
349 <<
src->w <<
"' no blitting performed.\n";
355 std::stringstream sstr;
356 sstr <<
"~BLIT(): y-coordinate '"
357 << y_ <<
"' larger than destination image's height '"
358 <<
src->h <<
"' no blitting performed.\n";
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";
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";
379 rect r {x_, y_, 0, 0};
385 if(
src->w == mask_->w &&
src->h == mask_->h && x_ == 0 && y_ == 0) {
390 rect r {x_, y_, 0, 0};
392 sdl_blit(mask_,
nullptr, new_mask, &r);
398 if(
src ==
nullptr) {
return; }
401 if(surf_->w !=
src->w || surf_->h !=
src->h) {
410 point size = target_size_;
414 }
else if(flags_ & X_BY_FACTOR) {
415 size.x =
src->w * (
static_cast<double>(size.x) / 100);
420 }
else if(flags_ & Y_BY_FACTOR) {
421 size.y =
src->h * (
static_cast<double>(size.y) / 100);
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
431 static_cast<int>(
src->w * ratio),
432 static_cast<int>(
src->h * ratio)
436 if(flags_ & SCALE_SHARP) {
453 const int new_w =
src->w + left_ + right_;
454 const int new_h =
src->h + top_ + bottom_;
467 SDL_BlendMode original_blend_mode;
468 SDL_GetSurfaceBlendMode(
src, &original_blend_mode);
470 SDL_SetSurfaceBlendMode(
src, SDL_BLENDMODE_NONE);
473 SDL_BlitSurface(
src,
nullptr, padded, &dstrect);
475 SDL_SetSurfaceBlendMode(padded, original_blend_mode);
490 if((r_ != 0 || g_ != 0 ||
b_ != 0)) {
508 SDL_FillSurfaceRect(ret,
nullptr, SDL_MapSurfaceRGBA(ret, color_.r, color_.g, color_.b, color_.a));
520struct parse_mod_registration
522 parse_mod_registration(
const char* name, mod_parser parser)
524 mod_parsers[name] = std::move(parser);
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) \
547 if(params.size() < 2) {
548 ERR_DP <<
"too few arguments passed to the ~TC() function";
553 const int side_n = utils::from_chars<int>(params[0]).value_or(-1);
555 ERR_DP <<
"Invalid side (" << params[0] <<
") passed to the ~TC() function";
563 ERR_DP <<
"could not load TC info for '" << params[1] <<
"' palette";
564 ERR_DP <<
"bailing out from TC";
575 ERR_DP <<
"caught config::error while processing TC: " <<
e.message;
576 ERR_DP <<
"bailing out from TC";
587 if(recolor_params.size() <= 1) {
601 <<
"caught config::error while processing color-range RC: "
603 ERR_DP <<
"bailing out from RC";
613 if(remap_params.size() < 2) {
614 ERR_DP <<
"not enough arguments passed to the ~PAL() function: " << args;
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];
628 return std::make_unique<rc_modification>(std::move(rc_map));
631 <<
"caught config::error while processing PAL function: "
634 <<
"bailing out from PAL";
643 bool horiz = (args.empty() || args.find(
"horiz") != std::string::npos);
644 bool vert = (args.find(
"vert") != std::string::npos);
646 return std::make_unique<fl_modification>(horiz, vert);
654 switch(slice_params.size()) {
656 return std::make_unique<rotate_modification>();
658 return std::make_unique<rotate_modification>(
659 utils::from_chars<int>(slice_params[0]).value_or(0));
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));
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));
676 return std::make_unique<gs_modification>();
682 return std::make_unique<crop_transparency_modification>();
686bool in_range(
int val,
int min,
int max)
688 return min <= val && val <= max;
696 if(params.size() != 1) {
697 ERR_DP <<
"~BW() requires exactly one argument";
702 const auto threshold = utils::from_chars<int>(params[0]);
704 ERR_DP <<
"unsupported argument in ~BW() function";
708 if(!in_range(*threshold, 0, 255)) {
709 ERR_DP <<
"~BW() argument out of range 0 - 255";
713 return std::make_unique<bw_modification>(*threshold);
719 return std::make_unique<sepia_modification>();
727 switch(params.size()) {
731 return std::make_unique<negative_modification>(-1, -1, -1);
734 const auto threshold = utils::from_chars<int>(params[0]);
736 if(threshold && in_range(*threshold, -1, 255)) {
737 return std::make_unique<negative_modification>(*threshold, *threshold, *threshold);
739 ERR_DP <<
"unsupported argument value in ~NEG() function";
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]);
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);
752 ERR_DP <<
"unsupported argument value in ~NEG() function";
758 ERR_DP <<
"~NEG() requires 0, 1 or 3 arguments";
766 return std::make_unique<plot_alpha_modification>();
772 return std::make_unique<wipe_alpha_modification>();
782 if(params.size() != 1) {
783 ERR_DP <<
"~ADJUST_ALPHA() requires exactly 1 arguments";
787 return std::make_unique<adjust_alpha_modification>(params.at(0));
797 if(params.size() < 1 || params.size() > 4) {
798 ERR_DP <<
"~CHAN() requires 1 to 4 arguments";
802 return std::make_unique<adjust_channels_modification>(params);
809 const std::size_t
s = factors.size();
812 ERR_DP <<
"no arguments passed to the ~CS() function";
816 int r = 0,
g = 0,
b = 0;
818 r = utils::from_chars<int>(factors[0]).value_or(0);
821 g = utils::from_chars<int>(factors[1]).value_or(0);
824 b = utils::from_chars<int>(factors[2]).value_or(0);
827 return std::make_unique<cs_modification>(r,
g ,
b);
835 if(params.size() != 4) {
836 ERR_DP <<
"~BLEND() requires exactly 4 arguments";
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(
'%');
844 if(p100_pos == std::string::npos)
845 opacity = utils::from_chars<float>(opacity_str).value_or(0.0f);
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);
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),
864 const std::size_t
s = slice_params.size();
866 if(
s == 0 || (
s == 1 && slice_params[0].empty())) {
867 ERR_DP <<
"no arguments passed to the ~CROP() function";
871 rect slice_rect { 0, 0, 0, 0 };
873 slice_rect.x = utils::from_chars<int16_t>(slice_params[0]).value_or(0);
876 slice_rect.y = utils::from_chars<int16_t>(slice_params[1]).value_or(0);
879 slice_rect.w = utils::from_chars<uint16_t>(slice_params[2]).value_or(0);
882 slice_rect.h = utils::from_chars<uint16_t>(slice_params[3]).value_or(0);
885 return std::make_unique<crop_modification>(slice_rect);
888static bool check_image(
const image::locator& img, std::stringstream & message)
891 message <<
" image not found: '" <<
img.get_filename() <<
"'\n";
900 const std::size_t
s = param.size();
902 if(
s == 0 || (
s == 1 && param[0].empty())){
903 ERR_DP <<
"no arguments passed to the ~BLIT() function";
908 ERR_DP <<
"too many arguments passed to the ~BLIT() function";
915 x = utils::from_chars<int>(param[1]).value_or(0);
916 y = utils::from_chars<int>(param[2]).value_or(0);
922 if(!check_image(img, message))
926 return std::make_unique<blit_modification>(
surf, x, y);
933 const std::size_t
s = param.size();
935 if(
s == 0 || (
s == 1 && param[0].empty())){
936 ERR_DP <<
"no arguments passed to the ~MASK() function";
943 x = utils::from_chars<int>(param[1]).value_or(0);
944 y = utils::from_chars<int>(param[2]).value_or(0);
948 ERR_DP <<
"negative position arguments in ~MASK() function";
955 if(!check_image(img, message))
959 return std::make_unique<mask_modification>(
surf, x, y);
966 ERR_DP <<
"no arguments passed to the ~L() function";
971 return std::make_unique<light_modification>(
surf);
976std::pair<int, bool> parse_scale_value(std::string_view arg)
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 };
981 return { utils::from_chars<int>(arg).value_or(0),
false };
986utils::optional<std::pair<point, uint8_t>> parse_scale_args(std::string_view args)
989 const std::size_t num_args = scale_params.size();
991 if(num_args == 0 || (num_args == 1 && scale_params[0].empty())) {
992 return utils::nullopt;
996 std::array<int, 2> parsed_sizes{0,0};
998 for(
unsigned i = 0; i < std::min<unsigned>(2, num_args); ++
i) {
999 const auto& [
size, relative] = parse_scale_value(scale_params[
i]);
1002 ERR_DP <<
"Negative size passed to scaling IPF. Original image dimension will be used instead";
1006 parsed_sizes[
i] =
size;
1013 return std::pair{
point{parsed_sizes[0], parsed_sizes[1]}, flags};
1021 if(
auto params = parse_scale_args(args)) {
1023 return std::make_unique<scale_modification>(params->first, mode | params->second);
1025 ERR_DP <<
"no arguments passed to the ~SCALE() function";
1032 if(
auto params = parse_scale_args(args)) {
1034 return std::make_unique<scale_modification>(params->first, mode | params->second);
1036 ERR_DP <<
"no arguments passed to the ~SCALE_SHARP() function";
1043 if(
auto params = parse_scale_args(args)) {
1045 return std::make_unique<scale_modification>(params->first, mode | params->second);
1047 ERR_DP <<
"no arguments passed to the ~SCALE_INTO() function";
1054 if(
auto params = parse_scale_args(args)) {
1056 return std::make_unique<scale_modification>(params->first, mode | params->second);
1058 ERR_DP <<
"no arguments passed to the ~SCALE_INTO_SHARP() function";
1066 const int factor = std::clamp(utils::from_chars<int>(args).value_or(1), 1, 6);
1067 return std::make_unique<xbrz_modification>(factor);
1079 if(args.find(
'=') != std::string_view::npos) {
1084 const std::map<std::string, int*> alias_map = {
1089 {
"bottom", &bottom},
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();
1102 ERR_DP <<
"~PAD() keyword argument '" << key <<
"' requires a valid integer value. Received: '" <<
value <<
"'.";
1106 ERR_DP <<
"~PAD() found an unknown keyword: '" << key <<
"'. Valid keywords: top, t, right, r, bottom, b, left, l.";
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.";
1119 if(utils::optional padding = utils::from_chars<int>(params[0])) {
1120 top = right = bottom = left = padding.value();
1122 ERR_DP <<
"~PAD() numeric argument (pad all sides) requires a single valid integer. Received: '" << params[0] <<
"'.";
1127 return std::make_unique<pad_modification>(top, right, bottom, left);
1133 const int depth = std::max<int>(0, utils::from_chars<int>(args).value_or(0));
1134 return std::make_unique<bl_modification>(depth);
1140 const std::string::size_type p100_pos = args.find(
'%');
1142 if(p100_pos == std::string::npos) {
1143 num = utils::from_chars<float>(args).value_or(0.0f);
1146 const std::string_view parsed_field = args.substr(0, p100_pos);
1147 num = utils::from_chars<float>(parsed_field).value_or(0.0f);
1151 return std::make_unique<o_modification>(num);
1161 const int r = utils::from_chars<int>(args).value_or(0);
1162 return std::make_unique<cs_modification>(r, 0, 0);
1168 const int g = utils::from_chars<int>(args).value_or(0);
1169 return std::make_unique<cs_modification>(0,
g, 0);
1175 const int b = utils::from_chars<int>(args).value_or(0);
1176 return std::make_unique<cs_modification>(0, 0,
b);
1200 int c[4] { 0, 0, 0, SDL_ALPHA_OPAQUE };
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);
1207 return std::make_unique<background_modification>(
color_t(
c[0],
c[1],
c[2],
c[3]));
1216 if(params.size() != 3 && params.size() != 4) {
1217 ERR_DP <<
"incorrect number of arguments in ~SWAP() function, they must be 3 or 4";
1221 channel redValue, greenValue, blueValue, alphaValue;
1223 if(params[0] ==
"red") {
1225 }
else if(params[0] ==
"green") {
1227 }
else if(params[0] ==
"blue") {
1229 }
else if(params[0] ==
"alpha") {
1232 ERR_DP <<
"unsupported argument value in ~SWAP() function: " << params[0];
1237 if(params[1] ==
"red") {
1239 }
else if(params[1] ==
"green") {
1241 }
else if(params[1] ==
"blue") {
1243 }
else if(params[1] ==
"alpha") {
1246 ERR_DP <<
"unsupported argument value in ~SWAP() function: " << params[0];
1250 if(params[2] ==
"red") {
1252 }
else if(params[2] ==
"green") {
1254 }
else if(params[2] ==
"blue") {
1256 }
else if(params[2] ==
"alpha") {
1259 ERR_DP <<
"unsupported argument value in ~SWAP() function: " << params[0];
1265 if(params.size() == 3) {
1268 if(params[3] ==
"red") {
1270 }
else if(params[3] ==
"green") {
1272 }
else if(params[3] ==
"blue") {
1274 }
else if(params[3] ==
"alpha") {
1277 ERR_DP <<
"unsupported argument value in ~SWAP() function: " << params[3];
1282 return std::make_unique<swap_modification>(redValue, greenValue, blueValue, alphaValue);