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