The Battle for Wesnoth  1.15.2+dev
picture.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 /**
16  * @file
17  * Routines for images: load, scale, re-color, etc.
18  */
19 
20 #include "picture.hpp"
21 
22 #include "config.hpp"
23 #include "display.hpp"
24 #include "filesystem.hpp"
25 #include "game_config.hpp"
26 #include "image_modifications.hpp"
27 #include "log.hpp"
28 #include "preferences/general.hpp"
29 #include "serialization/base64.hpp"
31 #include "sdl/rect.hpp"
32 #include "utils/general.hpp"
33 
34 #include <SDL2/SDL_image.h>
35 
36 #include "utils/functional.hpp"
37 
38 #include <boost/algorithm/string.hpp>
39 #include <boost/functional/hash_fwd.hpp>
40 
41 #include <set>
42 
43 static lg::log_domain log_display("display");
44 #define ERR_DP LOG_STREAM(err, log_display)
45 #define LOG_DP LOG_STREAM(info, log_display)
46 
47 static lg::log_domain log_config("config");
48 #define ERR_CFG LOG_STREAM(err, log_config)
49 
51 
52 template<typename T>
53 struct cache_item
54 {
56  : item()
57  , loaded(false)
58  {
59  }
60 
61  cache_item(const T& item)
62  : item(item)
63  , loaded(true)
64  {
65  }
66 
67  T item;
68  bool loaded;
69 };
70 
71 namespace std
72 {
73 template<>
74 struct hash<image::locator::value>
75 {
76  std::size_t operator()(const image::locator::value& val) const
77  {
78  std::size_t hash = std::hash<unsigned>{}(val.type_);
79 
81  boost::hash_combine(hash, val.filename_);
82  }
83 
84  if(val.type_ == image::locator::SUB_FILE) {
85  boost::hash_combine(hash, val.loc_.x);
86  boost::hash_combine(hash, val.loc_.y);
87  boost::hash_combine(hash, val.center_x_);
88  boost::hash_combine(hash, val.center_y_);
89  boost::hash_combine(hash, val.modifications_);
90  }
91 
92  return hash;
93  }
94 };
95 }
96 
97 namespace image
98 {
99 template<typename T>
101 {
102 public:
104  : content_()
105  {
106  }
107 
109  {
110  if(static_cast<unsigned>(index) >= content_.size())
111  content_.resize(index + 1);
112  return content_[index];
113  }
114 
115  void flush()
116  {
117  content_.clear();
118  }
119 
120 private:
121  std::vector<cache_item<T>> content_;
122 };
123 
124 template<typename T>
125 bool locator::in_cache(cache_type<T>& cache) const
126 {
127  return index_ < 0 ? false : cache.get_element(index_).loaded;
128 }
129 
130 template<typename T>
131 const T& locator::locate_in_cache(cache_type<T>& cache) const
132 {
133  static T dummy;
134  return index_ < 0 ? dummy : cache.get_element(index_).item;
135 }
136 
137 template<typename T>
138 T& locator::access_in_cache(cache_type<T>& cache) const
139 {
140  static T dummy;
141  return index_ < 0 ? dummy : cache.get_element(index_).item;
142 }
143 
144 template<typename T>
145 void locator::add_to_cache(cache_type<T>& cache, const T& data) const
146 {
147  if(index_ >= 0) {
148  cache.get_element(index_) = cache_item<T>(data);
149  }
150 }
151 }
152 
153 namespace
154 {
155 image::locator::locator_finder_t locator_finder;
156 
157 /** Definition of all image maps */
158 image::image_cache images_, scaled_to_zoom_, hexed_images_, scaled_to_hex_images_, tod_colored_images_,
159  brightened_images_;
160 
161 // cache storing if each image fit in a hex
162 image::bool_cache in_hex_info_;
163 
164 // cache storing if this is an empty hex
165 image::bool_cache is_empty_hex_;
166 
167 // caches storing the different lighted cases for each image
168 image::lit_cache lit_images_, lit_scaled_images_;
169 // caches storing each lightmap generated
170 image::lit_variants lightmaps_;
171 
172 // const int cache_version_ = 0;
173 
174 std::map<std::string, bool> image_existence_map;
175 
176 // directories where we already cached file existence
177 std::set<std::string> precached_dirs;
178 
179 std::map<surface, surface> reversed_images_;
180 
181 int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
182 
183 /** List of colors used by the TC image modification */
184 std::vector<std::string> team_colors;
185 
186 unsigned int zoom = tile_size;
187 unsigned int cached_zoom = 0;
188 
189 /** Algorithm choices */
190 // typedef std::function<surface(const surface &, int, int)> scaling_function;
191 typedef surface (*scaling_function)(const surface&, int, int);
192 scaling_function scale_to_zoom_func;
193 scaling_function scale_to_hex_func;
194 
195 const std::string data_uri_prefix = "data:";
196 struct parsed_data_URI{
197  explicit parsed_data_URI(utils::string_view data_URI);
198  utils::string_view scheme;
199  utils::string_view mime;
201  utils::string_view data;
202  bool good;
203 };
204 parsed_data_URI::parsed_data_URI(utils::string_view data_URI)
205 {
206  const std::size_t colon = data_URI.find(':');
207  const utils::string_view after_scheme = data_URI.substr(colon + 1);
208 
209  const std::size_t comma = after_scheme.find(',');
210  const utils::string_view type_info = after_scheme.substr(0, comma);
211 
212  const std::size_t semicolon = type_info.find(';');
213 
214  scheme = data_URI.substr(0, colon);
215  base64 = type_info.substr(semicolon + 1);
216  mime = type_info.substr(0, semicolon);
217  data = after_scheme.substr(comma + 1);
218  good = (scheme == "data" && base64 == "base64" && mime.length() > 0 && data.length() > 0);
219 }
220 
221 } // end anon namespace
222 
223 namespace image
224 {
228 
229 static int last_index_ = 0;
230 
232 {
233  {
234  images_.flush();
235  hexed_images_.flush();
236  tod_colored_images_.flush();
237  scaled_to_zoom_.flush();
238  scaled_to_hex_images_.flush();
239  brightened_images_.flush();
240  lit_images_.flush();
241  lit_scaled_images_.flush();
242  in_hex_info_.flush();
243  is_empty_hex_.flush();
244  mini_terrain_cache.clear();
245  mini_fogged_terrain_cache.clear();
246  mini_highlighted_terrain_cache.clear();
247  reversed_images_.clear();
248  image_existence_map.clear();
249  precached_dirs.clear();
250  }
251  /* We can't reset last_index_, since some locators are still alive
252  when using :refresh. That would cause them to point to the wrong
253  images. Not resetting the variable causes a memory leak, though. */
254  // last_index_ = 0;
255 }
256 
257 void locator::init_index()
258 {
259  auto i = locator_finder.find(val_);
260 
261  if(i == locator_finder.end()) {
262  index_ = last_index_++;
263  locator_finder.emplace(val_, index_);
264  } else {
265  index_ = i->second;
266  }
267 }
268 
269 void locator::parse_arguments()
270 {
271  std::string& fn = val_.filename_;
272  if(fn.empty()) {
273  return;
274  }
275 
276  if(boost::algorithm::starts_with(fn, data_uri_prefix)) {
277  parsed_data_URI parsed{fn};
278 
279  if(!parsed.good) {
280  utils::string_view view{ fn };
281  utils::string_view stripped = view.substr(0, view.find(","));
282  ERR_DP << "Invalid data URI: " << stripped << std::endl;
283  }
284 
285  val_.is_data_uri_ = true;
286  }
287 
288  std::size_t markup_field = fn.find('~');
289 
290  if(markup_field != std::string::npos) {
291  val_.type_ = SUB_FILE;
292  val_.modifications_ = fn.substr(markup_field, fn.size() - markup_field);
293  fn = fn.substr(0, markup_field);
294  }
295 }
296 
297 locator::locator()
298  : index_(-1)
299  , val_()
300 {
301 }
302 
303 locator::locator(const locator& a, const std::string& mods)
304  : index_(-1)
305  , val_(a.val_)
306 {
307  if(!mods.empty()) {
308  val_.modifications_ += mods;
309  val_.type_ = SUB_FILE;
310  init_index();
311  } else {
312  index_ = a.index_;
313  }
314 }
315 
316 locator::locator(const char* filename)
317  : index_(-1)
318  , val_(filename)
319 {
320  parse_arguments();
321  init_index();
322 }
323 
324 locator::locator(const std::string& filename)
325  : index_(-1)
326  , val_(filename)
327 {
328  parse_arguments();
329  init_index();
330 }
331 
332 locator::locator(const std::string& filename, const std::string& modifications)
333  : index_(-1)
334  , val_(filename, modifications)
335 {
336  init_index();
337 }
338 
339 locator::locator(const std::string& filename,
340  const map_location& loc,
341  int center_x,
342  int center_y,
343  const std::string& modifications)
344  : index_(-1)
345  , val_(filename, loc, center_x, center_y, modifications)
346 {
347  init_index();
348 }
349 
351 {
352  index_ = a.index_;
353  val_ = a.val_;
354 
355  return *this;
356 }
357 
359  : type_(NONE)
360  , is_data_uri_(false)
361  , filename_()
362  , loc_()
363  , modifications_()
364  , center_x_(0)
365  , center_y_(0)
366 {
367 }
368 
369 locator::value::value(const char* filename)
370  : type_(FILE)
371  , is_data_uri_(false)
372  , filename_(filename)
373  , loc_()
374  , modifications_()
375  , center_x_(0)
376  , center_y_(0)
377 {
378 }
379 
380 locator::value::value(const std::string& filename)
381  : type_(FILE)
382  , is_data_uri_(false)
383  , filename_(filename)
384  , loc_()
385  , modifications_()
386  , center_x_(0)
387  , center_y_(0)
388 {
389 }
390 
391 locator::value::value(const std::string& filename, const std::string& modifications)
392  : type_(SUB_FILE)
393  , is_data_uri_(false)
394  , filename_(filename)
395  , loc_()
396  , modifications_(modifications)
397  , center_x_(0)
398  , center_y_(0)
399 {
400 }
401 
402 locator::value::value(const std::string& filename,
403  const map_location& loc,
404  int center_x,
405  int center_y,
406  const std::string& modifications)
407  : type_(SUB_FILE)
408  , is_data_uri_(false)
409  , filename_(filename)
410  , loc_(loc)
411  , modifications_(modifications)
412  , center_x_(center_x)
413  , center_y_(center_y)
414 {
415 }
416 
418 {
419  if(a.type_ != type_) {
420  return false;
421  } else if(type_ == FILE) {
422  return filename_ == a.filename_;
423  } else if(type_ == SUB_FILE) {
424  return filename_ == a.filename_ && loc_ == a.loc_ && modifications_ == a.modifications_
425  && center_x_ == a.center_x_ && center_y_ == a.center_y_;
426  }
427 
428  return false;
429 }
430 
432 {
433  if(type_ != a.type_) {
434  return type_ < a.type_;
435  } else if(type_ == FILE) {
436  return filename_ < a.filename_;
437  } else if(type_ == SUB_FILE) {
438  if(filename_ != a.filename_)
439  return filename_ < a.filename_;
440  if(loc_ != a.loc_)
441  return loc_ < a.loc_;
442  if(center_x_ != a.center_x_)
443  return center_x_ < a.center_x_;
444  if(center_y_ != a.center_y_)
445  return center_y_ < a.center_y_;
446  return (modifications_ < a.modifications_);
447  }
448 
449  return false;
450 }
451 
452 // Load overlay image and compose it with the original surface.
453 static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
454 {
456  surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
457  if(!ovr_surf) {
458  return;
459  }
460 
461  SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
462 
463  sdl_blit(ovr_surf, 0, orig_surf, &area);
464 }
465 
467 {
468  surface res;
469 
470  std::string location = filesystem::get_binary_file_location("images", loc.get_filename());
471 
472  {
473  if(!location.empty()) {
474  // Check if there is a localized image.
475  const std::string loc_location = filesystem::get_localized_path(location);
476  if(!loc_location.empty()) {
477  location = loc_location;
478  }
479 
481  res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
482 
483  // If there was no standalone localized image, check if there is an overlay.
484  if(res && loc_location.empty()) {
485  const std::string ovr_location = filesystem::get_localized_path(location, "--overlay");
486  if(!ovr_location.empty()) {
487  add_localized_overlay(ovr_location, res);
488  }
489  }
490  }
491  }
492 
493  if(!res && !loc.get_filename().empty()) {
494  ERR_DP << "could not open image '" << loc.get_filename() << "'" << std::endl;
497  }
498 
499  return res;
500 }
501 
503 {
504  surface surf = get_image(loc.get_filename(), UNSCALED);
505  if(surf == nullptr) {
506  return nullptr;
507  }
508 
510 
511  while(!mods.empty()) {
512  modification* mod = mods.top();
513 
514  try {
515  surf = (*mod)(surf);
516  } catch(const image::modification::imod_exception& e) {
517  std::ostringstream ss;
518  ss << "\n";
519 
520  for(const std::string& mod_name : utils::parenthetical_split(loc.get_modifications(), '~')) {
521  ss << "\t" << mod_name << "\n";
522  }
523 
524  ERR_CFG << "Failed to apply a modification to an image:\n"
525  << "Image: " << loc.get_filename() << "\n"
526  << "Modifications: " << ss.str() << "\n"
527  << "Error: " << e.message << "\n";
528  }
529 
530  // NOTE: do this *after* applying the mod or you'll get crashes!
531  mods.pop();
532  }
533 
534  if(loc.get_loc().valid()) {
535  SDL_Rect srcrect = sdl::create_rect(
536  ((tile_size * 3) / 4) * loc.get_loc().x,
537  tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
538  tile_size,
539  tile_size
540  );
541 
542  if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
543  srcrect.x += surf->w / 2 - loc.get_center_x();
544  srcrect.y += surf->h / 2 - loc.get_center_y();
545  }
546 
547  // cut and hex mask, but also check and cache if empty result
548  surface cut(cut_surface(surf, srcrect));
549  bool is_empty = false;
550  surf = mask_surface(cut, get_hexmask(), &is_empty);
551 
552  // discard empty images to free memory
553  if(is_empty) {
554  // Safe because those images are only used by terrain rendering
555  // and it filters them out.
556  // A safer and more general way would be to keep only one copy of it
557  surf = nullptr;
558  }
559 
560  loc.add_to_cache(is_empty_hex_, is_empty);
561  }
562 
563  return surf;
564 }
565 
567 {
568  surface surf;
569 
570  parsed_data_URI parsed{loc.get_filename()};
571 
572  if(!parsed.good) {
573  utils::string_view fn = loc.get_filename();
574  utils::string_view stripped = fn.substr(0, fn.find(","));
575  ERR_DP << "Invalid data URI: " << stripped << std::endl;
576  } else if(parsed.mime.substr(0, 5) != "image") {
577  ERR_DP << "Data URI not of image MIME type: " << parsed.mime << std::endl;
578  } else {
579  const std::vector<uint8_t> image_data = base64::decode(parsed.data);
580  filesystem::rwops_ptr rwops{SDL_RWFromConstMem(image_data.data(), image_data.size()), &SDL_FreeRW};
581 
582  if(image_data.empty()) {
583  ERR_DP << "Invalid encoding in data URI" << std::endl;
584  } else if(parsed.mime == "image/png") {
585  surf = IMG_LoadTyped_RW(rwops.release(), true, "PNG");
586  } else if(parsed.mime == "image/jpeg") {
587  surf = IMG_LoadTyped_RW(rwops.release(), true, "JPG");
588  } else {
589  ERR_DP << "Invalid image MIME type: " << parsed.mime << std::endl;
590  }
591  }
592 
593  return surf;
594 }
595 
596 // small utility function to store an int from (-256,254) to an signed char
597 static signed char col_to_uchar(int i)
598 {
599  return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
600 }
601 
602 light_string get_light_string(int op, int r, int g, int b)
603 {
604  light_string ls;
605  ls.reserve(4);
606  ls.push_back(op);
607  ls.push_back(col_to_uchar(r));
608  ls.push_back(col_to_uchar(g));
609  ls.push_back(col_to_uchar(b));
610 
611  return ls;
612 }
613 
614 static surface apply_light(surface surf, const light_string& ls)
615 {
616  // atomic lightmap operation are handled directly (important to end recursion)
617  if(ls.size() == 4) {
618  // if no lightmap (first char = -1) then we need the initial value
619  //(before the halving done for lightmap)
620  int m = ls[0] == -1 ? 2 : 1;
621  return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
622  }
623 
624  // check if the lightmap is already cached or need to be generated
625  surface lightmap = nullptr;
626  auto i = lightmaps_.find(ls);
627  if(i != lightmaps_.end()) {
628  lightmap = i->second;
629  } else {
630  // build all the paths for lightmap sources
631  static const std::string p = "terrain/light/light";
632  static const std::string lm_img[19] {
633  p + ".png",
634  p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
635  p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
636  p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
637  p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
638  p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
639  p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
640  };
641 
642  // decompose into atomic lightmap operations (4 chars)
643  for(std::size_t c = 0; c + 3 < ls.size(); c += 4) {
644  light_string sls = ls.substr(c, 4);
645 
646  // get the corresponding image and apply the lightmap operation to it
647  // This allows to also cache lightmap parts.
648  // note that we avoid infinite recursion by using only atomic operation
649  surface lts = image::get_lighted_image(lm_img[sls[0]], sls, HEXED);
650 
651  // first image will be the base where we blit the others
652  if(lightmap == nullptr) {
653  // copy the cached image to avoid modifying the cache
654  lightmap = lts.clone();
655  } else {
656  sdl_blit(lts, nullptr, lightmap, nullptr);
657  }
658  }
659 
660  // cache the result
661  lightmaps_[ls] = lightmap;
662  }
663 
664  // apply the final lightmap
665  return light_surface(surf, lightmap);
666 }
667 
669 {
670  return val_.is_data_uri_
671  ? parsed_data_URI{val_.filename_}.good
673 }
674 
676 {
677  switch(loc.get_type()) {
678  case locator::FILE:
679  if(loc.is_data_uri()){
680  return load_image_data_uri(loc);
681  } else {
682  return load_image_file(loc);
683  }
684  case locator::SUB_FILE:
685  return load_image_sub_file(loc);
686  default:
687  return surface(nullptr);
688  }
689 }
690 
692 {
693 }
694 
696 {
697  flush_cache();
698 }
699 
700 void set_color_adjustment(int r, int g, int b)
701 {
702  if(r != red_adjust || g != green_adjust || b != blue_adjust) {
703  red_adjust = r;
704  green_adjust = g;
705  blue_adjust = b;
706  tod_colored_images_.flush();
707  brightened_images_.flush();
708  lit_images_.flush();
709  lit_scaled_images_.flush();
710  reversed_images_.clear();
711  }
712 }
713 
714 void set_team_colors(const std::vector<std::string>* colors)
715 {
716  if(colors == nullptr) {
717  team_colors.clear();
718  } else {
719  team_colors = *colors;
720  }
721 }
722 
723 const std::vector<std::string>& get_team_colors()
724 {
725  return team_colors;
726 }
727 
728 void set_zoom(unsigned int amount)
729 {
730  if(amount != zoom) {
731  zoom = amount;
732  tod_colored_images_.flush();
733  brightened_images_.flush();
734  reversed_images_.clear();
735 
736  // We keep these caches if:
737  // we use default zoom (it doesn't need those)
738  // or if they are already at the wanted zoom.
739  if(zoom != tile_size && zoom != cached_zoom) {
740  scaled_to_zoom_.flush();
741  scaled_to_hex_images_.flush();
742  lit_scaled_images_.flush();
743  cached_zoom = zoom;
744  }
745  }
746 }
747 
748 // F should be a scaling algorithm without "integral" zoom limitations
749 template<scaling_function F>
750 static surface scale_xbrz_helper(const surface& res, int w, int h)
751 {
752  int best_integer_zoom = std::min(w / res->w, h / res->h);
753  int legal_zoom = utils::clamp(best_integer_zoom, 1, 5);
754  return F(scale_surface_xbrz(res, legal_zoom), w, h);
755 }
756 
758 
759 static scaling_function select_algorithm(SCALING_ALGORITHM algo)
760 {
761  switch(algo.v) {
762  case SCALING_ALGORITHM::LINEAR: {
763  scaling_function result = &scale_surface;
764  return result;
765  }
766  case SCALING_ALGORITHM::NEAREST_NEIGHBOR: {
767  scaling_function result = &scale_surface_nn;
768  return result;
769  }
770  case SCALING_ALGORITHM::XBRZ_LIN: {
771  scaling_function result = &scale_xbrz_helper<scale_surface>;
772  return result;
773  }
774  case SCALING_ALGORITHM::XBRZ_NN: {
775  scaling_function result = &scale_xbrz_helper<scale_surface_nn>;
776  return result;
777  }
778  default:
779  assert(false && "I don't know how to implement this scaling algorithm");
780  throw 42;
781  }
782 }
783 
784 static surface get_hexed(const locator& i_locator)
785 {
786  surface image(get_image(i_locator, UNSCALED));
787  // hex cut tiles, also check and cache if empty result
788  bool is_empty = false;
789  surface res = mask_surface(image, get_hexmask(), &is_empty, i_locator.get_filename());
790  i_locator.add_to_cache(is_empty_hex_, is_empty);
791  return res;
792 }
793 
794 static surface get_scaled_to_hex(const locator& i_locator)
795 {
796  surface img = get_image(i_locator, HEXED);
797  // return scale_surface(img, zoom, zoom);
798 
799  if(img) {
800  return scale_to_hex_func(img, zoom, zoom);
801  }
802 
803  return surface(nullptr);
804 
805 }
806 
807 static surface get_tod_colored(const locator& i_locator)
808 {
809  surface img = get_image(i_locator, SCALED_TO_HEX);
810  return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
811 }
812 
813 static surface get_scaled_to_zoom(const locator& i_locator)
814 {
815  assert(zoom != tile_size);
816  assert(tile_size != 0);
817 
818  surface res(get_image(i_locator, UNSCALED));
819  // For some reason haloes seems to have invalid images, protect against crashing
820  if(res) {
821  return scale_to_zoom_func(res, ((res->w * zoom) / tile_size), ((res->h * zoom) / tile_size));
822  }
823 
824  return surface(nullptr);
825 }
826 
827 static surface get_brightened(const locator& i_locator)
828 {
829  surface image(get_image(i_locator, TOD_COLORED));
831 }
832 
833 /// translate type to a simpler one when possible
834 static TYPE simplify_type(const image::locator& i_locator, TYPE type)
835 {
836  switch(type) {
837  case SCALED_TO_ZOOM:
838  if(zoom == tile_size) {
839  type = UNSCALED;
840  }
841 
842  break;
843  case BRIGHTENED:
845  type = TOD_COLORED;
846  }
847 
848  break;
849  default:
850  break;
851  }
852 
853  if(type == TOD_COLORED) {
854  if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
855  type = SCALED_TO_HEX;
856  }
857  }
858 
859  if(type == SCALED_TO_HEX) {
860  if(zoom == tile_size) {
861  type = HEXED;
862  }
863  }
864 
865  if(type == HEXED) {
866  // check if the image is already hex-cut by the location system
867  if(i_locator.get_loc().valid()) {
868  type = UNSCALED;
869  }
870  }
871 
872  return type;
873 }
874 
876 {
877  surface res;
878 
879  if(i_locator.is_void()) {
880  return res;
881  }
882 
883  type = simplify_type(i_locator, type);
884 
885  image_cache* imap;
886  // select associated cache
887  switch(type) {
888  case UNSCALED:
889  imap = &images_;
890  break;
891  case TOD_COLORED:
892  imap = &tod_colored_images_;
893  break;
894  case SCALED_TO_ZOOM:
895  imap = &scaled_to_zoom_;
896  break;
897  case HEXED:
898  imap = &hexed_images_;
899  break;
900  case SCALED_TO_HEX:
901  imap = &scaled_to_hex_images_;
902  break;
903  case BRIGHTENED:
904  imap = &brightened_images_;
905  break;
906  default:
907  return res;
908  }
909 
910  // return the image if already cached
911  bool tmp = i_locator.in_cache(*imap);
912 
913  if(tmp) {
914  surface result = i_locator.locate_in_cache(*imap);
915  return result;
916  }
917 
918  // not cached, generate it
919  switch(type) {
920  case UNSCALED:
921  // If type is unscaled, directly load the image from the disk.
922  res = load_from_disk(i_locator);
923  break;
924  case TOD_COLORED:
925  res = get_tod_colored(i_locator);
926  break;
927  case SCALED_TO_ZOOM:
928  res = get_scaled_to_zoom(i_locator);
929  break;
930  case HEXED:
931  res = get_hexed(i_locator);
932  break;
933  case SCALED_TO_HEX:
934  res = get_scaled_to_hex(i_locator);
935  break;
936  case BRIGHTENED:
937  res = get_brightened(i_locator);
938  break;
939  default:
940  return res;
941  }
942 
943  i_locator.add_to_cache(*imap, res);
944 
945  return res;
946 }
947 
949 {
950  surface res;
951  if(i_locator.is_void()) {
952  return res;
953  }
954 
955  if(type == SCALED_TO_HEX && zoom == tile_size) {
956  type = HEXED;
957  }
958 
959  // select associated cache
960  lit_cache* imap = &lit_images_;
961  if(type == SCALED_TO_HEX) {
962  imap = &lit_scaled_images_;
963  }
964 
965  // if no light variants yet, need to add an empty map
966  if(!i_locator.in_cache(*imap)) {
967  i_locator.add_to_cache(*imap, lit_variants());
968  }
969 
970  // need access to add it if not found
971  { // enclose reference pointing to data stored in a changing vector
972  const lit_variants& lvar = i_locator.locate_in_cache(*imap);
973  auto lvi = lvar.find(ls);
974  if(lvi != lvar.end()) {
975  return lvi->second;
976  }
977  }
978 
979  // not cached yet, generate it
980  switch(type) {
981  case HEXED:
982  res = get_image(i_locator, HEXED);
983  res = apply_light(res, ls);
984  break;
985  case SCALED_TO_HEX:
986  // we light before scaling to reuse the unscaled cache
987  res = get_lighted_image(i_locator, ls, HEXED);
988  res = scale_surface(res, zoom, zoom);
989  break;
990  default:
991  break;
992  }
993 
994  // record the lighted surface in the corresponding variants cache
995  i_locator.access_in_cache(*imap)[ls] = res;
996 
997  return res;
998 }
999 
1001 {
1003  return get_image(terrain_mask, UNSCALED);
1004 }
1005 
1006 bool is_in_hex(const locator& i_locator)
1007 {
1008  bool result;
1009  {
1010  if(i_locator.in_cache(in_hex_info_)) {
1011  result = i_locator.locate_in_cache(in_hex_info_);
1012  } else {
1013  const surface image(get_image(i_locator, UNSCALED));
1014 
1015  bool res = in_mask_surface(image, get_hexmask());
1016 
1017  i_locator.add_to_cache(in_hex_info_, res);
1018 
1019  // std::cout << "in_hex : " << i_locator.get_filename()
1020  // << " " << (res ? "yes" : "no") << "\n";
1021 
1022  result = res;
1023  }
1024  }
1025 
1026  return result;
1027 }
1028 
1029 bool is_empty_hex(const locator& i_locator)
1030 {
1031  if(!i_locator.in_cache(is_empty_hex_)) {
1032  const surface surf = get_image(i_locator, HEXED);
1033  // emptiness of terrain image is checked during hex cut
1034  // so, maybe in cache now, let's recheck
1035  if(!i_locator.in_cache(is_empty_hex_)) {
1036  // should never reach here
1037  // but do it manually if it happens
1038  // assert(false);
1039  bool is_empty = false;
1040  mask_surface(surf, get_hexmask(), &is_empty);
1041  i_locator.add_to_cache(is_empty_hex_, is_empty);
1042  }
1043  }
1044 
1045  return i_locator.locate_in_cache(is_empty_hex_);
1046 }
1047 
1049 {
1050  if(surf == nullptr) {
1051  return surface(nullptr);
1052  }
1053 
1054  const auto itor = reversed_images_.find(surf);
1055  if(itor != reversed_images_.end()) {
1056  // sdl_add_ref(itor->second);
1057  return itor->second;
1058  }
1059 
1060  const surface rev(flip_surface(surf));
1061  if(rev == nullptr) {
1062  return surface(nullptr);
1063  }
1064 
1065  reversed_images_.emplace(surf, rev);
1066  // sdl_add_ref(rev);
1067  return rev;
1068 }
1069 
1070 bool exists(const image::locator& i_locator)
1071 {
1072  typedef image::locator loc;
1073  loc::type type = i_locator.get_type();
1074  if(type != loc::FILE && type != loc::SUB_FILE) {
1075  return false;
1076  }
1077 
1078  // The insertion will fail if there is already an element in the cache
1079  // and this will point to the existing element.
1080  auto iter = image_existence_map.begin();
1081  bool success;
1082 
1083  std::tie(iter, success) = image_existence_map.emplace(i_locator.get_filename(), false);
1084 
1085  bool& cache = iter->second;
1086  if(success) {
1087  if(i_locator.is_data_uri()) {
1088  cache = parsed_data_URI{i_locator.get_filename()}.good;
1089  } else {
1090  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
1091  }
1092  }
1093 
1094  return cache;
1095 }
1096 
1097 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
1098 {
1099  const std::string checked_dir = dir + "/" + subdir;
1100  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
1101  return;
1102  }
1103 
1104  precached_dirs.insert(checked_dir);
1105 
1106  if(!filesystem::is_directory(checked_dir)) {
1107  return;
1108  }
1109 
1110  std::vector<std::string> files_found;
1111  std::vector<std::string> dirs_found;
1112  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::FILE_NAME_ONLY,
1114 
1115  for(const auto& f : files_found) {
1116  image_existence_map[subdir + f] = true;
1117  }
1118 
1119  for(const auto& d : dirs_found) {
1120  precache_file_existence_internal(dir, subdir + d + "/");
1121  }
1122 }
1123 
1124 void precache_file_existence(const std::string& subdir)
1125 {
1126  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
1127 
1128  for(const auto& p : paths) {
1130  }
1131 }
1132 
1133 bool precached_file_exists(const std::string& file)
1134 {
1135  const auto b = image_existence_map.find(file);
1136  if(b != image_existence_map.end()) {
1137  return b->second;
1138  }
1139 
1140  return false;
1141 }
1142 
1143 save_result save_image(const locator& i_locator, const std::string& filename)
1144 {
1145  return save_image(get_image(i_locator), filename);
1146 }
1147 
1148 save_result save_image(const surface& surf, const std::string& filename)
1149 {
1150  if(!surf) {
1151  return save_result::no_image;
1152  }
1153 
1154 #ifdef SDL_IMAGE_VERSION_ATLEAST
1155 #if SDL_IMAGE_VERSION_ATLEAST(2, 0, 2)
1156  if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
1157  LOG_DP << "Writing a JPG image to " << filename << std::endl;
1158 
1159  const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
1160  return err == 0 ? save_result::success : save_result::save_failed;
1161  }
1162 #endif
1163 #endif
1164 
1165  if(filesystem::ends_with(filename, ".png")) {
1166  LOG_DP << "Writing a PNG image to " << filename << std::endl;
1167 
1168  const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
1169  return err == 0 ? save_result::success : save_result::save_failed;
1170  }
1171 
1172  if(filesystem::ends_with(filename, ".bmp")) {
1173  LOG_DP << "Writing a BMP image to " << filename << std::endl;
1174  const int err = SDL_SaveBMP(surf, filename.c_str()) == 0;
1175  return err == 0 ? save_result::success : save_result::save_failed;
1176  }
1177 
1179 }
1180 
1182 {
1184  try {
1185  algo = SCALING_ALGORITHM::string_to_enum(preferences::get("scale_hex"));
1186  } catch(const bad_enum_cast&) {
1187  }
1188 
1189  scale_to_hex_func = select_algorithm(algo);
1190 
1192  try {
1193  algo = SCALING_ALGORITHM::string_to_enum(preferences::get("scale_zoom"));
1194  } catch(const bad_enum_cast&) {
1195  }
1196 
1197  scale_to_zoom_func = select_algorithm(algo);
1198 
1199  return true;
1200 }
1201 
1202 } // end namespace image
constexpr const T & clamp(const T &value, const T &min, const T &max)
Definition: general.hpp:31
TYPE
UNSCALED : image will be drawn "as is" without changing size, even in case of redraw SCALED_TO_ZOOM :...
Definition: picture.hpp:185
static surface load_image_file(const image::locator &loc)
Definition: picture.cpp:466
surface get_image(const image::locator &i_locator, TYPE type)
function to get the surface corresponding to an image.
Definition: picture.cpp:875
const std::string & get_modifications() const
Definition: picture.hpp:89
preferences::SCALING_ALGORITHM SCALING_ALGORITHM
Definition: picture.cpp:757
int get_center_y() const
Definition: picture.hpp:88
BOOST_CXX14_CONSTEXPR size_type find(basic_string_view s, size_type pos=0) const BOOST_NOEXCEPT
void precache_file_existence(const std::string &subdir)
precache the existence of files in the subdir (ex: "terrain/")
Definition: picture.cpp:1124
static surface scale_xbrz_helper(const surface &res, int w, int h)
Definition: picture.cpp:750
map_location loc_
Definition: picture.hpp:55
void add_to_cache(cache_type< T > &cache, const T &data) const
Definition: picture.cpp:145
std::string filename_
Definition: picture.hpp:54
void init_index()
Definition: picture.cpp:257
int dummy
Definition: lstrlib.cpp:1125
const SCALING_ALGORITHM default_scaling_algorithm
Definition: general.cpp:74
static lg::log_domain log_display("display")
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:595
void set_team_colors(const std::vector< std::string > *colors)
set the team colors used by the TC image modification use a vector with one string for each team usin...
Definition: picture.cpp:714
static surface get_scaled_to_hex(const locator &i_locator)
Definition: picture.cpp:794
static surface get_hexed(const locator &i_locator)
Definition: picture.cpp:784
bool precached_file_exists(const std::string &file)
Definition: picture.cpp:1133
save_result
Definition: picture.hpp:220
surface reverse_image(const surface &surf)
function to reverse an image.
Definition: picture.cpp:1048
#define a
A modified priority queue used to order image modifications.
bool ends_with(const std::string &str, const std::string &suffix)
mini_terrain_cache_map mini_fogged_terrain_cache
Definition: picture.cpp:226
static surface load_image_data_uri(const image::locator &loc)
Definition: picture.cpp:566
cache_item< T > & get_element(int index)
Definition: picture.cpp:108
rwops_ptr make_read_RWops(const std::string &path)
#define ERR_CFG
Definition: picture.cpp:48
#define val_(o)
Definition: lobject.h:123
static signed char col_to_uchar(int i)
Definition: picture.cpp:597
BOOST_CXX14_CONSTEXPR basic_string_view substr(size_type pos, size_type n=npos) const
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:1143
bool in_mask_surface(const surface &surf, const surface &mask)
Check if a surface fit into a mask.
Definition: utils.cpp:1202
T & access_in_cache(cache_type< T > &cache) const
Definition: picture.cpp:138
STL namespace.
#define h
void parse_arguments()
Definition: picture.cpp:269
static surface get_scaled_to_zoom(const locator &i_locator)
Definition: picture.cpp:813
std::map< t_translation::terrain_code, surface > mini_terrain_cache_map
Definition: picture.hpp:130
std::string get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type or an empty string if the file isn&#39;t prese...
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
type get_type() const
Definition: picture.hpp:90
#define d
surface flip_surface(const surface &surf)
Definition: utils.cpp:1951
Definitions for the interface to Wesnoth Markup Language (WML).
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:75
std::string terrain_mask
void flush_cache()
Definition: picture.cpp:231
std::string filename_
Definition: action_wml.cpp:555
map_location loc_
#define b
static TYPE simplify_type(const image::locator &i_locator, TYPE type)
translate type to a simpler one when possible
Definition: picture.cpp:834
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1070
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
const T & locate_in_cache(cache_type< T > &cache) const
Definition: picture.cpp:131
std::string get(const std::string &key)
Definition: general.cpp:228
static surface get_brightened(const locator &i_locator)
Definition: picture.cpp:827
#define LOG_DP
Definition: picture.cpp:45
static surface load_image_sub_file(const image::locator &loc)
Definition: picture.cpp:502
rwops_ptr make_write_RWops(const std::string &path)
static surface get_tod_colored(const locator &i_locator)
Definition: picture.cpp:807
light_string get_light_string(int op, int r, int g, int b)
return light_string of one light operation(see above)
Definition: picture.cpp:602
std::string modifications_
Definition: picture.hpp:56
static int last_index_
Definition: picture.cpp:229
bool valid() const
Definition: location.hpp:93
std::unordered_map< value, int > locator_finder_t
Definition: picture.hpp:65
#define ERR_DP
Definition: picture.cpp:44
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
void set_zoom(unsigned int amount)
sets the amount scaled images should be scaled.
Definition: picture.cpp:728
std::map< light_string, surface > lit_variants
Definition: picture.hpp:148
std::vector< cache_item< T > > content_
Definition: picture.cpp:121
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1658
map_display and display: classes which take care of displaying the map and game-data on the screen...
Base abstract class for an image-path modification.
static void precache_file_existence_internal(const std::string &dir, const std::string &subdir)
Definition: picture.cpp:1097
surface scale_surface_nn(const surface &surf, int w, int h)
Scale a surface using the nearest neighbor algorithm (provided by xBRZ lib)
Definition: utils.cpp:159
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
void set_color_adjustment(int r, int g, int b)
will make all scaled images have these rgb values added to all their pixels.
Definition: picture.cpp:700
std::size_t operator()(const image::locator::value &val) const
Definition: picture.cpp:76
#define ftofxp(x)
IN: float or int - OUT: fixed_t.
Definition: math.hpp:297
Encapsulates the map of the game.
Definition: location.hpp:42
std::vector< uint8_t > decode(utils::string_view in)
Definition: base64.cpp:215
surface load_from_disk(const locator &loc)
Definition: picture.cpp:675
const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, file_name_option mode, file_filter_option filter, file_reorder_option reorder, file_tree_checksum *checksum)
Populates &#39;files&#39; with all the files and &#39;dirs&#39; with all the directories in dir.
Definition: filesystem.cpp:352
std::size_t i
Definition: function.cpp:933
logger & err()
Definition: log.cpp:78
cache_item()
Definition: picture.cpp:55
mock_party p
static lg::log_domain log_config("config")
locator & operator=(const locator &a)
Definition: picture.cpp:350
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
static tcache cache
Definition: minimap.cpp:134
surface get_hexmask()
function to get the standard hex mask
Definition: picture.cpp:1000
std::basic_string< signed char > light_string
light_string store colors info of central and adjacent hexes.
Definition: picture.hpp:143
mini_terrain_cache_map mini_highlighted_terrain_cache
Definition: picture.cpp:227
int get_center_x() const
Definition: picture.hpp:87
Declarations for File-IO.
int w
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
Definition: filesystem.hpp:42
const bool & debug
std::string get_localized_path(const std::string &file, const std::string &suff)
Returns the localized version of the given filename, if it exists.
const std::string & get_filename() const
Definition: picture.hpp:84
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
bool is_in_hex(const locator &i_locator)
function to check if an image fit into an hex return false if the image has not the standard size...
Definition: picture.cpp:1006
bool is_data_uri() const
Definition: picture.hpp:85
bool operator<(const value &a) const
Definition: picture.cpp:431
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1310
mini_terrain_cache_map mini_terrain_cache
Definition: picture.cpp:225
cache_item(const T &item)
Definition: picture.cpp:61
surface get_lighted_image(const image::locator &i_locator, const light_string &ls, TYPE type)
function to get the surface corresponding to an image.
Definition: picture.cpp:948
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an SDL_Rect with the given dimensions.
Definition: rect.hpp:39
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:739
Contains the SDL_Rect helper code.
#define f
const map_location & get_loc() const
Definition: picture.hpp:86
unsigned int tile_size
Definition: game_config.cpp:68
bool is_empty_hex(const locator &i_locator)
function to check if an image is empty after hex cut should be only used on terrain image (cache the ...
Definition: picture.cpp:1029
surface brighten_image(const surface &surf, fixed_t amount)
Definition: utils.cpp:1044
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
Definition: picture.cpp:453
static scaling_function select_algorithm(SCALING_ALGORITHM algo)
Definition: picture.cpp:759
this module manages the cache of images.
Standard logging facilities (interface).
double hex_brightening
Definition: game_config.cpp:80
modification * top() const
Returns the top element in the queue .
bool operator==(const value &a) const
Definition: picture.cpp:417
const std::vector< std::string > & get_team_colors()
Definition: picture.cpp:723
#define e
void pop()
Removes the top element from the queue.
bool update_from_preferences()
initialize any private data, e.g. algorithm choices from preferences
Definition: picture.cpp:1181
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
bool is_void() const
Definition: picture.hpp:95
mock_char c
bool in_cache(cache_type< T > &cache) const
Definition: picture.cpp:125
Exception thrown by the operator() when an error occurs.
bool file_exists() const
Tests whether the file the locater points at exists.
Definition: picture.cpp:668
static surface apply_light(surface surf, const light_string &ls)
Definition: picture.cpp:614
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...
const std::string message
The error message regarding the failed operation.
bool loaded
Definition: picture.cpp:68