The Battle for Wesnoth  1.17.10+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 #include "sdl/texture.hpp"
34 
35 #include <SDL2/SDL_image.h>
36 
37 #include <functional>
38 
39 #include <boost/algorithm/string.hpp>
40 #include <boost/functional/hash_fwd.hpp>
41 
42 #include <array>
43 #include <set>
44 
45 static lg::log_domain log_image("image");
46 #define ERR_IMG LOG_STREAM(err, log_image)
47 #define WRN_IMG LOG_STREAM(warn, log_image)
48 #define LOG_IMG LOG_STREAM(info, log_image)
49 #define DBG_IMG LOG_STREAM(debug, log_image)
50 
51 static lg::log_domain log_config("config");
52 #define ERR_CFG LOG_STREAM(err, log_config)
53 
55 
56 template<typename T>
57 struct cache_item
58 {
60  : item()
61  , loaded(false)
62  {
63  }
64 
65  cache_item(const T& item)
66  : item(item)
67  , loaded(true)
68  {
69  }
70 
71  T item;
72  bool loaded;
73 };
74 
75 namespace std
76 {
77 template<>
78 struct hash<image::locator::value>
79 {
80  std::size_t operator()(const image::locator::value& val) const
81  {
82  std::size_t hash = std::hash<unsigned>{}(val.type_);
83 
85  boost::hash_combine(hash, val.filename_);
86  }
87 
88  if(val.type_ == image::locator::SUB_FILE) {
89  boost::hash_combine(hash, val.loc_.x);
90  boost::hash_combine(hash, val.loc_.y);
91  boost::hash_combine(hash, val.center_x_);
92  boost::hash_combine(hash, val.center_y_);
93  boost::hash_combine(hash, val.modifications_);
94  }
95 
96  return hash;
97  }
98 };
99 }
100 
101 namespace image
102 {
103 template<typename T>
105 {
106 public:
108  : content_()
109  {
110  }
111 
113  {
114  if(static_cast<unsigned>(index) >= content_.size())
115  content_.resize(index + 1);
116  return content_[index];
117  }
118 
119  void flush()
120  {
121  content_.clear();
122  }
123 
124 private:
125  std::vector<cache_item<T>> content_;
126 };
127 
128 template<typename T>
129 bool locator::in_cache(cache_type<T>& cache) const
130 {
131  return index_ < 0 ? false : cache.get_element(index_).loaded;
132 }
133 
134 template<typename T>
135 const T& locator::locate_in_cache(cache_type<T>& cache) const
136 {
137  static T dummy;
138  return index_ < 0 ? dummy : cache.get_element(index_).item;
139 }
140 
141 template<typename T>
142 T& locator::access_in_cache(cache_type<T>& cache) const
143 {
144  static T dummy;
145  return index_ < 0 ? dummy : cache.get_element(index_).item;
146 }
147 
148 template<typename T>
149 void locator::add_to_cache(cache_type<T>& cache, const T& data) const
150 {
151  if(index_ >= 0) {
152  cache.get_element(index_) = cache_item<T>(data);
153  }
154 }
155 
156 namespace
157 {
158 image::locator::locator_finder_t locator_finder;
159 
160 /** Definition of all image maps */
161 std::array<surface_cache, NUM_TYPES> surfaces_;
162 
163 /**
164  * Texture caches.
165  * Note that the latter two are temporary and should be removed once we have OGL and shader support.
166  */
167 using texture_cache_map = std::map<image::scale_quality, image::texture_cache>;
168 
169 texture_cache_map textures_;
170 texture_cache_map textures_hexed_;
171 texture_cache_map texture_tod_colored_;
172 
173 // cache storing if each image fit in a hex
174 image::bool_cache in_hex_info_;
175 
176 // cache storing if this is an empty hex
177 image::bool_cache is_empty_hex_;
178 
179 // caches storing the different lighted cases for each image
180 image::lit_surface_cache lit_surfaces_;
181 image::lit_texture_cache lit_textures_;
182 // caches storing each lightmap generated
183 image::lit_surface_variants surface_lightmaps_;
184 image::lit_texture_variants texture_lightmaps_;
185 
186 // diagnostics for tracking skipped cache impact
187 std::array<bool_cache, NUM_TYPES> skipped_cache_;
188 int duplicate_loads_ = 0;
189 int total_loads_ = 0;
190 
191 // const int cache_version_ = 0;
192 
193 std::map<std::string, bool> image_existence_map;
194 
195 // directories where we already cached file existence
196 std::set<std::string> precached_dirs;
197 
198 int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
199 
200 const std::string data_uri_prefix = "data:";
201 struct parsed_data_URI{
202  explicit parsed_data_URI(std::string_view data_URI);
203  std::string_view scheme;
204  std::string_view mime;
205  std::string_view base64;
206  std::string_view data;
207  bool good;
208 };
209 parsed_data_URI::parsed_data_URI(std::string_view data_URI)
210 {
211  const std::size_t colon = data_URI.find(':');
212  const std::string_view after_scheme = data_URI.substr(colon + 1);
213 
214  const std::size_t comma = after_scheme.find(',');
215  const std::string_view type_info = after_scheme.substr(0, comma);
216 
217  const std::size_t semicolon = type_info.find(';');
218 
219  scheme = data_URI.substr(0, colon);
220  base64 = type_info.substr(semicolon + 1);
221  mime = type_info.substr(0, semicolon);
222  data = after_scheme.substr(comma + 1);
223  good = (scheme == "data" && base64 == "base64" && mime.length() > 0 && data.length() > 0);
224 }
225 
226 } // end anon namespace
227 
231 
232 static int last_index_ = 0;
233 
235 {
236  for(surface_cache& cache : surfaces_) {
237  cache.flush();
238  }
239  lit_surfaces_.flush();
240  lit_textures_.flush();
241  surface_lightmaps_.clear();
242  texture_lightmaps_.clear();
243  in_hex_info_.flush();
244  is_empty_hex_.flush();
245  textures_.clear();
246  textures_hexed_.clear();
247  texture_tod_colored_.clear();
248  mini_terrain_cache.clear();
249  mini_fogged_terrain_cache.clear();
250  mini_highlighted_terrain_cache.clear();
251  image_existence_map.clear();
252  precached_dirs.clear();
253  /* We can't reset last_index_, since some locators are still alive
254  when using :refresh. That would cause them to point to the wrong
255  images. Not resetting the variable causes a memory leak, though. */
256  // last_index_ = 0;
257 }
258 
259 void locator::init_index()
260 {
261  auto i = locator_finder.find(val_);
262 
263  if(i == locator_finder.end()) {
264  index_ = last_index_++;
265  locator_finder.emplace(val_, index_);
266  } else {
267  index_ = i->second;
268  }
269 }
270 
271 void locator::parse_arguments()
272 {
273  std::string& fn = val_.filename_;
274  if(fn.empty()) {
275  return;
276  }
277 
278  if(boost::algorithm::starts_with(fn, data_uri_prefix)) {
279  parsed_data_URI parsed{fn};
280 
281  if(!parsed.good) {
282  std::string_view view{ fn };
283  std::string_view stripped = view.substr(0, view.find(","));
284  ERR_IMG << "Invalid data URI: " << stripped;
285  }
286 
287  val_.is_data_uri_ = true;
288  }
289 
290  std::size_t markup_field = fn.find('~');
291 
292  if(markup_field != std::string::npos) {
293  val_.type_ = SUB_FILE;
294  val_.modifications_ = fn.substr(markup_field, fn.size() - markup_field);
295  fn = fn.substr(0, markup_field);
296  }
297 }
298 
299 locator::locator()
300  : index_(-1)
301  , val_()
302 {
303 }
304 
305 locator::locator(const locator& a, const std::string& mods)
306  : index_(-1)
307  , val_(a.val_)
308 {
309  if(!mods.empty()) {
310  val_.modifications_ += mods;
311  val_.type_ = SUB_FILE;
312  init_index();
313  } else {
314  index_ = a.index_;
315  }
316 }
317 
318 locator::locator(const char* filename)
319  : index_(-1)
320  , val_(filename)
321 {
322  parse_arguments();
323  init_index();
324 }
325 
326 locator::locator(const std::string& filename)
327  : index_(-1)
328  , val_(filename)
329 {
330  parse_arguments();
331  init_index();
332 }
333 
334 locator::locator(const std::string& filename, const std::string& modifications)
335  : index_(-1)
336  , val_(filename, modifications)
337 {
338  init_index();
339 }
340 
341 locator::locator(const char* filename, const char* modifications)
342  : index_(-1)
343  , val_(filename, modifications)
344 {
345  init_index();
346 }
347 
348 locator::locator(const std::string& filename,
349  const map_location& loc,
350  int center_x,
351  int center_y,
352  const std::string& modifications)
353  : index_(-1)
354  , val_(filename, loc, center_x, center_y, modifications)
355 {
356  init_index();
357 }
358 
360 {
361  index_ = a.index_;
362  val_ = a.val_;
363 
364  return *this;
365 }
366 
367 std::ostream& operator<<(std::ostream& s, const locator& l)
368 {
369  s << l.get_filename();
370  if(!l.get_modifications().empty()) {
371  if(l.get_modifications()[0] != '~') {
372  s << '~';
373  }
374  s << l.get_modifications();
375  }
376  return s;
377 }
378 
380  : type_(NONE)
381  , is_data_uri_(false)
382  , filename_()
383  , loc_()
384  , modifications_()
385  , center_x_(0)
386  , center_y_(0)
387 {
388 }
389 
390 locator::value::value(const char* filename)
391  : type_(FILE)
392  , is_data_uri_(false)
393  , filename_(filename)
394  , loc_()
395  , modifications_()
396  , center_x_(0)
397  , center_y_(0)
398 {
399 }
400 
401 locator::value::value(const std::string& filename)
402  : type_(FILE)
403  , is_data_uri_(false)
404  , filename_(filename)
405  , loc_()
406  , modifications_()
407  , center_x_(0)
408  , center_y_(0)
409 {
410 }
411 
412 locator::value::value(const std::string& filename, const std::string& modifications)
413  : type_(SUB_FILE)
414  , is_data_uri_(false)
415  , filename_(filename)
416  , loc_()
417  , modifications_(modifications)
418  , center_x_(0)
419  , center_y_(0)
420 {
421 }
422 
423 locator::value::value(const char* filename, const char* modifications)
424  : type_(FILE)
425  , is_data_uri_(false)
426  , filename_(filename)
427  , loc_()
428  , modifications_(modifications)
429  , center_x_(0)
430  , center_y_(0)
431 {
432 }
433 
434 locator::value::value(const std::string& filename,
435  const map_location& loc,
436  int center_x,
437  int center_y,
438  const std::string& modifications)
439  : type_(SUB_FILE)
440  , is_data_uri_(false)
441  , filename_(filename)
442  , loc_(loc)
443  , modifications_(modifications)
444  , center_x_(center_x)
445  , center_y_(center_y)
446 {
447 }
448 
450 {
451  if(a.type_ != type_) {
452  return false;
453  } else if(type_ == FILE) {
454  return filename_ == a.filename_;
455  } else if(type_ == SUB_FILE) {
456  return filename_ == a.filename_ && loc_ == a.loc_ && modifications_ == a.modifications_
457  && center_x_ == a.center_x_ && center_y_ == a.center_y_;
458  }
459 
460  return false;
461 }
462 
464 {
465  if(type_ != a.type_) {
466  return type_ < a.type_;
467  } else if(type_ == FILE) {
468  return filename_ < a.filename_;
469  } else if(type_ == SUB_FILE) {
470  if(filename_ != a.filename_)
471  return filename_ < a.filename_;
472  if(loc_ != a.loc_)
473  return loc_ < a.loc_;
474  if(center_x_ != a.center_x_)
475  return center_x_ < a.center_x_;
476  if(center_y_ != a.center_y_)
477  return center_y_ < a.center_y_;
478  return (modifications_ < a.modifications_);
479  }
480 
481  return false;
482 }
483 
484 // Load overlay image and compose it with the original surface.
485 static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
486 {
488  surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
489  if(!ovr_surf) {
490  return;
491  }
492 
493  SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
494 
495  sdl_blit(ovr_surf, 0, orig_surf, &area);
496 }
497 
499 {
500  surface res;
501  const std::string& name = loc.get_filename();
502 
503  std::string location = filesystem::get_binary_file_location("images", name);
504 
505  // Many images have been converted from PNG to WEBP format,
506  // but the old filename may still be saved in savegame files etc.
507  // If the file does not exist in ".png" format, also try ".webp".
508  if(location.empty() && filesystem::ends_with(name, ".png")) {
509  std::string webp_name = name.substr(0, name.size() - 4) + ".webp";
510  location = filesystem::get_binary_file_location("images", webp_name);
511  if(!location.empty()) {
512  WRN_IMG << "Replaced missing '" << name << "' with found '"
513  << webp_name << "'.";
514  }
515  }
516 
517  {
518  if(!location.empty()) {
519  // Check if there is a localized image.
520  const std::string loc_location = filesystem::get_localized_path(location);
521  if(!loc_location.empty()) {
522  location = loc_location;
523  }
524 
526  res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
527 
528  // If there was no standalone localized image, check if there is an overlay.
529  if(res && loc_location.empty()) {
530  const std::string ovr_location = filesystem::get_localized_path(location, "--overlay");
531  if(!ovr_location.empty()) {
532  add_localized_overlay(ovr_location, res);
533  }
534  }
535  }
536  }
537 
538  if(!res && !name.empty()) {
539  ERR_IMG << "could not open image '" << name << "'";
542  if(name != game_config::images::blank)
544  }
545 
546  return res;
547 }
548 
550 {
551  surface surf = get_surface(loc.get_filename(), UNSCALED);
552  if(surf == nullptr) {
553  return nullptr;
554  }
555 
557 
558  while(!mods.empty()) {
559  modification* mod = mods.top();
560 
561  try {
562  surf = (*mod)(surf);
563  } catch(const image::modification::imod_exception& e) {
564  std::ostringstream ss;
565  ss << "\n";
566 
567  for(const std::string& mod_name : utils::parenthetical_split(loc.get_modifications(), '~')) {
568  ss << "\t" << mod_name << "\n";
569  }
570 
571  ERR_CFG << "Failed to apply a modification to an image:\n"
572  << "Image: " << loc.get_filename() << "\n"
573  << "Modifications: " << ss.str() << "\n"
574  << "Error: " << e.message;
575  }
576 
577  // NOTE: do this *after* applying the mod or you'll get crashes!
578  mods.pop();
579  }
580 
581  if(loc.get_loc().valid()) {
582  rect srcrect(
583  ((tile_size * 3) / 4) * loc.get_loc().x,
584  tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
585  tile_size,
586  tile_size
587  );
588 
589  if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
590  srcrect.x += surf->w / 2 - loc.get_center_x();
591  srcrect.y += surf->h / 2 - loc.get_center_y();
592  }
593 
594  // cut and hex mask, but also check and cache if empty result
595  surface cut(cut_surface(surf, srcrect));
596  bool is_empty = false;
597  surf = mask_surface(cut, get_hexmask(), &is_empty);
598 
599  // discard empty images to free memory
600  if(is_empty) {
601  // Safe because those images are only used by terrain rendering
602  // and it filters them out.
603  // A safer and more general way would be to keep only one copy of it
604  surf = nullptr;
605  }
606 
607  loc.add_to_cache(is_empty_hex_, is_empty);
608  }
609 
610  return surf;
611 }
612 
614 {
615  surface surf;
616 
617  parsed_data_URI parsed{loc.get_filename()};
618 
619  if(!parsed.good) {
620  std::string_view fn = loc.get_filename();
621  std::string_view stripped = fn.substr(0, fn.find(","));
622  ERR_IMG << "Invalid data URI: " << stripped;
623  } else if(parsed.mime.substr(0, 5) != "image") {
624  ERR_IMG << "Data URI not of image MIME type: " << parsed.mime;
625  } else {
626  const std::vector<uint8_t> image_data = base64::decode(parsed.data);
627  filesystem::rwops_ptr rwops{SDL_RWFromConstMem(image_data.data(), image_data.size()), &SDL_FreeRW};
628 
629  if(image_data.empty()) {
630  ERR_IMG << "Invalid encoding in data URI";
631  } else if(parsed.mime == "image/png") {
632  surf = IMG_LoadTyped_RW(rwops.release(), true, "PNG");
633  } else if(parsed.mime == "image/jpeg") {
634  surf = IMG_LoadTyped_RW(rwops.release(), true, "JPG");
635  } else {
636  ERR_IMG << "Invalid image MIME type: " << parsed.mime;
637  }
638  }
639 
640  return surf;
641 }
642 
643 // small utility function to store an int from (-256,254) to an signed char
644 static signed char col_to_uchar(int i)
645 {
646  return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
647 }
648 
649 light_string get_light_string(int op, int r, int g, int b)
650 {
651  light_string ls;
652  ls.reserve(4);
653  ls.push_back(op);
654  ls.push_back(col_to_uchar(r));
655  ls.push_back(col_to_uchar(g));
656  ls.push_back(col_to_uchar(b));
657 
658  return ls;
659 }
660 
661 static surface apply_light(surface surf, const light_string& ls)
662 {
663  // atomic lightmap operation are handled directly (important to end recursion)
664  if(ls.size() == 4) {
665  // if no lightmap (first char = -1) then we need the initial value
666  //(before the halving done for lightmap)
667  int m = ls[0] == -1 ? 2 : 1;
668  return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
669  }
670 
671  // check if the lightmap is already cached or need to be generated
672  surface lightmap = nullptr;
673  auto i = surface_lightmaps_.find(ls);
674  if(i != surface_lightmaps_.end()) {
675  lightmap = i->second;
676  } else {
677  // build all the paths for lightmap sources
678  static const std::string p = "terrain/light/light";
679  static const std::string lm_img[19] {
680  p + ".png",
681  p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
682  p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
683  p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
684  p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
685  p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
686  p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
687  };
688 
689  // decompose into atomic lightmap operations (4 chars)
690  for(std::size_t c = 0; c + 3 < ls.size(); c += 4) {
691  light_string sls = ls.substr(c, 4);
692 
693  // get the corresponding image and apply the lightmap operation to it
694  // This allows to also cache lightmap parts.
695  // note that we avoid infinite recursion by using only atomic operation
696  surface lts = image::get_lighted_image(lm_img[sls[0]], sls);
697 
698  // first image will be the base where we blit the others
699  if(lightmap == nullptr) {
700  // copy the cached image to avoid modifying the cache
701  lightmap = lts.clone();
702  } else {
703  sdl_blit(lts, nullptr, lightmap, nullptr);
704  }
705  }
706 
707  // cache the result
708  surface_lightmaps_[ls] = lightmap;
709  }
710 
711  // apply the final lightmap
712  return light_surface(surf, lightmap);
713 }
714 
716 {
717  return val_.is_data_uri_
718  ? parsed_data_URI{val_.filename_}.good
720 }
721 
722 static surface load_from_disk(const locator& loc)
723 {
724  switch(loc.get_type()) {
725  case locator::FILE:
726  if(loc.is_data_uri()){
727  return load_image_data_uri(loc);
728  } else {
729  return load_image_file(loc);
730  }
731  case locator::SUB_FILE:
732  return load_image_sub_file(loc);
733  default:
734  return surface(nullptr);
735  }
736 }
737 
739 {
740 }
741 
743 {
744  flush_cache();
745 }
746 
747 void set_color_adjustment(int r, int g, int b)
748 {
749  if(r != red_adjust || g != green_adjust || b != blue_adjust) {
750  red_adjust = r;
751  green_adjust = g;
752  blue_adjust = b;
753  surfaces_[TOD_COLORED].flush();
754  lit_surfaces_.flush();
755  lit_textures_.flush();
756  texture_tod_colored_.clear();
757  }
758 }
759 
760 static surface get_hexed(const locator& i_locator, bool skip_cache = false)
761 {
762  surface image(get_surface(i_locator, UNSCALED, skip_cache));
763  surface mask(get_hexmask());
764  // Ensure the image is the correct size by cropping and/or centering.
765  // TODO: this should probably be a function of sdl/utils
766  if(image && (image->w != mask->w || image->h != mask->h)) {
767  DBG_IMG << "adjusting [" << image->w << ',' << image->h << ']'
768  << " image to hex mask: " << i_locator;
769  // the fitted surface
770  surface fit(mask->w, mask->h);
771  // if the image is too large in either dimension, crop it.
772  if(image->w > mask->w || image->h >= mask->h) {
773  // fill the crop surface with transparency
774  sdl::fill_surface_rect(fit, nullptr,
775  SDL_MapRGBA(fit->format, 0, 0, 0, 0)
776  );
777  // crop the input image to hexmask dimensions
778  int cutx = std::max(0, image->w - mask->w) / 2;
779  int cuty = std::max(0, image->h - mask->h) / 2;
780  int cutw = std::min(image->w, mask->w);
781  int cuth = std::min(image->h, mask->h);
782  image = cut_surface(image, {cutx, cuty, cutw, cuth});
783  // image will now have dimensions <= mask
784  }
785  // center image
786  int placex = (mask->w - image->w) / 2;
787  int placey = (mask->h - image->h) / 2;
788  rect dst = {placex, placey, image->w, image->h};
789  sdl_blit(image, nullptr, fit, &dst);
790  image = fit;
791  }
792  // hex cut tiles, also check and cache if empty result
793  bool is_empty = false;
794  surface res = mask_surface(image, mask, &is_empty, i_locator.get_filename());
795  i_locator.add_to_cache(is_empty_hex_, is_empty);
796  return res;
797 }
798 
799 static surface get_tod_colored(const locator& i_locator, bool skip_cache = false)
800 {
801  surface img = get_surface(i_locator, HEXED, skip_cache);
802  return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
803 }
804 
805 /** translate type to a simpler one when possible */
806 static TYPE simplify_type(const image::locator& i_locator, TYPE type)
807 {
808  if(type == TOD_COLORED) {
809  if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
810  type = HEXED;
811  }
812  }
813 
814  if(type == HEXED) {
815  // check if the image is already hex-cut by the location system
816  if(i_locator.get_loc().valid()) {
817  type = UNSCALED;
818  }
819  }
820 
821  return type;
822 }
823 
825  const image::locator& i_locator,
826  TYPE type,
827  bool skip_cache)
828 {
829  surface res;
830 
831  if(i_locator.is_void()) {
832  return res;
833  }
834 
835  type = simplify_type(i_locator, type);
836 
837  // select associated cache
838  if(type >= NUM_TYPES) {
839  WRN_IMG << "get_surface called with unknown image type";
840  return res;
841  }
842  surface_cache& imap = surfaces_[type];
843 
844  // return the image if already cached
845  if (i_locator.in_cache(imap)) {
846  return i_locator.locate_in_cache(imap);
847  }
848 
849  DBG_IMG << "surface cache [" << type << "] miss: " << i_locator;
850 
851  // not cached, generate it
852  switch(type) {
853  case UNSCALED:
854  // If type is unscaled, directly load the image from the disk.
855  res = load_from_disk(i_locator);
856  break;
857  case TOD_COLORED:
858  res = get_tod_colored(i_locator, skip_cache);
859  break;
860  case HEXED:
861  res = get_hexed(i_locator, skip_cache);
862  break;
863  default:
864  throw game::error("get_surface somehow lost image type?");
865  }
866 
867  bool_cache& skip = skipped_cache_[type];
868  if(i_locator.in_cache(skip) && i_locator.locate_in_cache(skip))
869  {
870  DBG_IMG << "duplicate load: " << i_locator
871  << " [" << type << "]"
872  << " (" << duplicate_loads_ << "/" << total_loads_ << " total)";
873  ++duplicate_loads_;
874  }
875  ++total_loads_;
876 
877  if(skip_cache) {
878  DBG_IMG << "surface cache [" << type << "] skip: " << i_locator;
879  i_locator.add_to_cache(skip, true);
880  } else {
881  i_locator.add_to_cache(imap, res);
882  }
883 
884  return res;
885 }
886 
888 {
889  return get_surface(i_locator, type);
890 }
891 
893 {
894  surface res;
895  if(i_locator.is_void()) {
896  return res;
897  }
898 
899  // select associated cache
900  lit_surface_cache* imap = &lit_surfaces_;
901 
902  // if no light variants yet, need to add an empty map
903  if(!i_locator.in_cache(*imap)) {
904  i_locator.add_to_cache(*imap, lit_surface_variants());
905  }
906 
907  // need access to add it if not found
908  { // enclose reference pointing to data stored in a changing vector
909  const lit_surface_variants& lvar = i_locator.locate_in_cache(*imap);
910  auto lvi = lvar.find(ls);
911  if(lvi != lvar.end()) {
912  return lvi->second;
913  }
914  }
915 
916  DBG_IMG << "lit surface cache miss: " << i_locator;
917 
918  // not cached yet, generate it
919  res = get_surface(i_locator, HEXED);
920  res = apply_light(res, ls);
921 
922  // record the lighted surface in the corresponding variants cache
923  i_locator.access_in_cache(*imap)[ls] = res;
924 
925  return res;
926 }
927 
929  const image::locator& i_locator,
930  const light_string& ls)
931 {
932  if(i_locator.is_void()) {
933  return texture();
934  }
935 
936  // select associated cache
937  lit_texture_cache* imap = &lit_textures_;
938 
939  // if no light variants yet, need to add an empty map
940  if(!i_locator.in_cache(*imap)) {
941  i_locator.add_to_cache(*imap, lit_texture_variants());
942  }
943 
944  // need access to add it if not found
945  { // enclose reference pointing to data stored in a changing vector
946  const lit_texture_variants& lvar = i_locator.locate_in_cache(*imap);
947  auto lvi = lvar.find(ls);
948  if(lvi != lvar.end()) {
949  return lvi->second;
950  }
951  }
952 
953  DBG_IMG << "lit texture cache miss: " << i_locator;
954 
955  // not cached yet, generate it
956  texture tex(get_lighted_image(i_locator, ls));
957 
958  // record the lighted texture in the corresponding variants cache
959  i_locator.access_in_cache(*imap)[ls] = tex;
960 
961  return tex;
962 }
963 
965 {
967  return get_surface(terrain_mask, UNSCALED);
968 }
969 
970 point get_size(const locator& i_locator, bool skip_cache)
971 {
972  const surface s(get_surface(i_locator, UNSCALED, skip_cache));
973  if (s != nullptr) {
974  return {s->w, s->h};
975  } else {
976  return {0, 0};
977  }
978 }
979 
980 bool is_in_hex(const locator& i_locator)
981 {
982  bool result;
983  {
984  if(i_locator.in_cache(in_hex_info_)) {
985  result = i_locator.locate_in_cache(in_hex_info_);
986  } else {
987  const surface image(get_surface(i_locator, UNSCALED));
988 
989  bool res = in_mask_surface(image, get_hexmask());
990 
991  i_locator.add_to_cache(in_hex_info_, res);
992 
993  // std::cout << "in_hex : " << i_locator.get_filename()
994  // << " " << (res ? "yes" : "no") << "\n";
995 
996  result = res;
997  }
998  }
999 
1000  return result;
1001 }
1002 
1003 bool is_empty_hex(const locator& i_locator)
1004 {
1005  if(!i_locator.in_cache(is_empty_hex_)) {
1006  const surface surf = get_surface(i_locator, HEXED);
1007  // emptiness of terrain image is checked during hex cut
1008  // so, maybe in cache now, let's recheck
1009  if(!i_locator.in_cache(is_empty_hex_)) {
1010  // should never reach here
1011  // but do it manually if it happens
1012  // assert(false);
1013  bool is_empty = false;
1014  mask_surface(surf, get_hexmask(), &is_empty);
1015  i_locator.add_to_cache(is_empty_hex_, is_empty);
1016  }
1017  }
1018 
1019  return i_locator.locate_in_cache(is_empty_hex_);
1020 }
1021 
1022 bool exists(const image::locator& i_locator)
1023 {
1024  typedef image::locator loc;
1025  loc::type type = i_locator.get_type();
1026  if(type != loc::FILE && type != loc::SUB_FILE) {
1027  return false;
1028  }
1029 
1030  // The insertion will fail if there is already an element in the cache
1031  // and this will point to the existing element.
1032  auto [iter, success] = image_existence_map.emplace(i_locator.get_filename(), false);
1033 
1034  bool& cache = iter->second;
1035  if(success) {
1036  if(i_locator.is_data_uri()) {
1037  cache = parsed_data_URI{i_locator.get_filename()}.good;
1038  } else {
1039  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
1040  }
1041  }
1042 
1043  return cache;
1044 }
1045 
1046 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
1047 {
1048  const std::string checked_dir = dir + "/" + subdir;
1049  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
1050  return;
1051  }
1052 
1053  precached_dirs.insert(checked_dir);
1054 
1055  if(!filesystem::is_directory(checked_dir)) {
1056  return;
1057  }
1058 
1059  std::vector<std::string> files_found;
1060  std::vector<std::string> dirs_found;
1061  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::name_mode::FILE_NAME_ONLY,
1063 
1064  for(const auto& f : files_found) {
1065  image_existence_map[subdir + f] = true;
1066  }
1067 
1068  for(const auto& d : dirs_found) {
1069  precache_file_existence_internal(dir, subdir + d + "/");
1070  }
1071 }
1072 
1073 void precache_file_existence(const std::string& subdir)
1074 {
1075  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
1076 
1077  for(const auto& p : paths) {
1079  }
1080 }
1081 
1082 bool precached_file_exists(const std::string& file)
1083 {
1084  const auto b = image_existence_map.find(file);
1085  if(b != image_existence_map.end()) {
1086  return b->second;
1087  }
1088 
1089  return false;
1090 }
1091 
1092 save_result save_image(const locator& i_locator, const std::string& filename)
1093 {
1094  return save_image(get_surface(i_locator), filename);
1095 }
1096 
1097 save_result save_image(const surface& surf, const std::string& filename)
1098 {
1099  if(!surf) {
1100  return save_result::no_image;
1101  }
1102 
1103  if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
1104  LOG_IMG << "Writing a JPG image to " << filename;
1105 
1106  const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
1107  return err == 0 ? save_result::success : save_result::save_failed;
1108  }
1109 
1110  if(filesystem::ends_with(filename, ".png")) {
1111  LOG_IMG << "Writing a PNG image to " << filename;
1112 
1113  const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
1114  return err == 0 ? save_result::success : save_result::save_failed;
1115  }
1116 
1118 }
1119 
1120 /*
1121  * TEXTURE INTERFACE ======================================================================
1122  *
1123  * The only important difference here is that textures must have their
1124  * scale quality set before creation. All other handling is done by
1125  * get_surface.
1126  */
1127 
1128 texture get_texture(const image::locator& i_locator, TYPE type, bool skip_cache)
1129 {
1130  return get_texture(i_locator, scale_quality::nearest, type, skip_cache);
1131 }
1132 
1133 /** Returns a texture for the corresponding image. */
1134 texture get_texture(const image::locator& i_locator, scale_quality quality, TYPE type, bool skip_cache)
1135 {
1136  texture res;
1137 
1138  if(i_locator.is_void()) {
1139  return res;
1140  }
1141 
1142  type = simplify_type(i_locator, type);
1143 
1144  //
1145  // Select the appropriate cache. We don't need caches for every single image types,
1146  // since some types can be handled by render-time operations.
1147  //
1148  texture_cache* cache = nullptr;
1149 
1150  switch(type) {
1151  case HEXED:
1152  cache = &textures_hexed_[quality];
1153  break;
1154  case TOD_COLORED:
1155  cache = &texture_tod_colored_[quality];
1156  break;
1157  default:
1158  cache = &textures_[quality];
1159  }
1160 
1161  //
1162  // Now attempt to find a cached texture. If found, return it.
1163  //
1164  bool in_cache = i_locator.in_cache(*cache);
1165 
1166  if(in_cache) {
1167  res = i_locator.locate_in_cache(*cache);
1168  return res;
1169  }
1170 
1171  DBG_IMG << "texture cache [" << type << "] miss: " << i_locator;
1172 
1173  //
1174  // No texture was cached. In that case, create a new one. The explicit cases require special
1175  // handling with surfaces in order to generate the desired effect. This shouldn't be the case
1176  // once we get OGL and shader support.
1177  //
1178 
1179  // Get it from the surface cache, also setting the desired scale quality.
1180  const bool linear_scaling = quality == scale_quality::linear ? true : false;
1181  if(i_locator.get_modifications().empty()) {
1182  // skip cache if we're loading plain files with no modifications
1183  res = texture(get_surface(i_locator, type, true), linear_scaling);
1184  } else {
1185  res = texture(get_surface(i_locator, type, skip_cache), linear_scaling);
1186  }
1187 
1188  // Cache the texture.
1189  if(skip_cache) {
1190  DBG_IMG << "texture cache [" << type << "] skip: " << i_locator;
1191  } else {
1192  i_locator.add_to_cache(*cache, res);
1193  }
1194 
1195  return res;
1196 }
1197 
1198 } // end namespace image
TYPE
Used to specify the rendering format of images.
Definition: picture.hpp:228
static surface load_image_file(const image::locator &loc)
Definition: picture.cpp:498
surface get_image(const image::locator &i_locator, TYPE type)
[DEPRECATED] Caches and returns an image.
Definition: picture.cpp:887
const std::string & get_modifications() const
Definition: picture.hpp:86
int get_center_y() const
Definition: picture.hpp:85
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:57
#define WRN_IMG
Definition: picture.cpp:47
static surface get_hexed(const locator &i_locator, bool skip_cache=false)
Definition: picture.cpp:760
void precache_file_existence(const std::string &subdir)
Precache the existence of files in a binary path subdirectory (e.g.
Definition: picture.cpp:1073
map_location loc_
Definition: picture.hpp:141
std::string_view scheme
Definition: picture.cpp:203
void add_to_cache(cache_type< T > &cache, const T &data) const
Definition: picture.cpp:149
std::string filename_
Definition: picture.hpp:140
void init_index()
Definition: picture.cpp:259
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:484
#define DBG_IMG
Definition: picture.cpp:49
bool precached_file_exists(const std::string &file)
Definition: picture.cpp:1082
save_result
Definition: picture.hpp:338
static surface load_from_disk(const locator &loc)
Definition: picture.cpp:722
#define a
A modified priority queue used to order image modifications.
bool ends_with(const std::string &str, const std::string &suffix)
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:824
mini_terrain_cache_map mini_fogged_terrain_cache
Definition: picture.cpp:229
scale_quality
Definition: picture.hpp:239
static surface load_image_data_uri(const image::locator &loc)
Definition: picture.cpp:613
cache_item< T > & get_element(int index)
Definition: picture.cpp:112
rwops_ptr make_read_RWops(const std::string &path)
#define ERR_CFG
Definition: picture.cpp:52
std::string_view data
Definition: picture.cpp:206
static signed char col_to_uchar(int i)
Definition: picture.cpp:644
void fill_surface_rect(surface &dst, SDL_Rect *dst_rect, const uint32_t color)
Fill a rectangle on a given surface.
Definition: utils.hpp:49
std::map< light_string, surface > lit_surface_variants
Type used to pair light possibilities with the corresponding lit surface.
Definition: picture.hpp:185
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:1092
bool in_mask_surface(const surface &surf, const surface &mask)
Check if a surface fit into a mask.
Definition: utils.cpp:1092
STL namespace.
void parse_arguments()
Definition: picture.cpp:271
std::map< t_translation::terrain_code, surface > mini_terrain_cache_map
Definition: picture.hpp:164
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:87
#define d
Standard hexagonal tile mask applied, removing portions that don&#39;t fit.
Definition: picture.hpp:233
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:970
Definitions for the interface to Wesnoth Markup Language (WML).
Same as HEXED, but with Time of Day color tint applied.
Definition: picture.hpp:235
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
std::string terrain_mask
void flush_cache()
Purges all image caches.
Definition: picture.cpp:234
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:806
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1022
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:1022
const T & locate_in_cache(cache_type< T > &cache) const
Definition: picture.cpp:135
bool good
Definition: picture.cpp:207
static surface load_image_sub_file(const image::locator &loc)
Definition: picture.cpp:549
rwops_ptr make_write_RWops(const std::string &path)
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
Definition: picture.cpp:649
std::string_view mime
Definition: picture.cpp:204
std::string modifications_
Definition: picture.hpp:142
static int last_index_
Definition: picture.cpp:232
bool valid() const
Definition: location.hpp:89
std::ostream & operator<<(std::ostream &s, const locator &l)
Definition: picture.cpp:367
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:350
std::unordered_map< value, int > locator_finder_t
Definition: picture.hpp:148
std::map< light_string, texture > lit_texture_variants
Definition: picture.hpp:186
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::vector< cache_item< T > > content_
Definition: picture.cpp:125
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1486
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:1046
Generic locator abstracting the location of an image.
Definition: picture.hpp:62
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:747
std::size_t operator()(const image::locator::value &val) const
Definition: picture.cpp:80
Encapsulates the map of the game.
Definition: location.hpp:38
surface get_lighted_image(const image::locator &i_locator, const light_string &ls)
Caches and returns an image with a lightmap applied to it.
Definition: picture.cpp:892
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
#define ERR_IMG
Definition: picture.cpp:46
std::size_t i
Definition: function.cpp:967
logger & err()
Definition: log.cpp:170
cache_item()
Definition: picture.cpp:59
mock_party p
static map_location::DIRECTION s
static lg::log_domain log_config("config")
locator & operator=(const locator &a)
Definition: picture.cpp:359
double g
Definition: astarsearch.cpp:65
surface get_hexmask()
Retrieves the standard hexagonal tile mask.
Definition: picture.cpp:964
std::basic_string< signed char > light_string
Type used to store color information of central and adjacent hexes.
Definition: picture.hpp:182
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
Holds a 2D point.
Definition: point.hpp:24
mini_terrain_cache_map mini_highlighted_terrain_cache
Definition: picture.cpp:230
int get_center_x() const
Definition: picture.hpp:84
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:81
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
static lg::log_domain log_image("image")
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
Definition: picture.cpp:980
bool is_data_uri() const
Definition: picture.hpp:82
bool operator<(const value &a) const
Definition: picture.cpp:463
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1138
mini_terrain_cache_map mini_terrain_cache
Definition: picture.cpp:228
#define LOG_IMG
Definition: picture.cpp:48
cache_item(const T &item)
Definition: picture.cpp:65
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:712
Contains the SDL_Rect helper code.
texture get_lighted_texture(const image::locator &i_locator, const light_string &ls)
Definition: picture.cpp:928
#define f
const map_location & get_loc() const
Definition: picture.hpp:83
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:28
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:1003
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
Definition: picture.cpp:485
Functions to load and save images from/to disk.
Standard logging facilities (interface).
modification * top() const
Returns the top element in the queue .
static surface get_tod_colored(const locator &i_locator, bool skip_cache=false)
Definition: picture.cpp:799
bool operator==(const value &a) const
Definition: picture.cpp:449
#define e
void pop()
Removes the top element from the queue.
bool is_void() const
Returns true if the locator does not correspond to an actual image.
Definition: picture.hpp:93
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:1128
mock_char c
bool in_cache(cache_type< T > &cache) const
Definition: picture.cpp:129
Unmodified original-size image.
Definition: picture.hpp:231
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:715
static surface apply_light(surface surf, const light_string &ls)
Definition: picture.cpp:661
std::string_view base64
Definition: picture.cpp:205
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:72