The Battle for Wesnoth  1.17.14+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  surface res;
890  if(i_locator.is_void()) {
891  return res;
892  }
893 
894  // select associated cache
895  lit_surface_cache* imap = &lit_surfaces_;
896 
897  // if no light variants yet, need to add an empty map
898  if(!i_locator.in_cache(*imap)) {
899  i_locator.add_to_cache(*imap, lit_surface_variants());
900  }
901 
902  // need access to add it if not found
903  { // enclose reference pointing to data stored in a changing vector
904  const lit_surface_variants& lvar = i_locator.locate_in_cache(*imap);
905  auto lvi = lvar.find(ls);
906  if(lvi != lvar.end()) {
907  return lvi->second;
908  }
909  }
910 
911  DBG_IMG << "lit surface cache miss: " << i_locator;
912 
913  // not cached yet, generate it
914  res = get_surface(i_locator, HEXED);
915  res = apply_light(res, ls);
916 
917  // record the lighted surface in the corresponding variants cache
918  i_locator.access_in_cache(*imap)[ls] = res;
919 
920  return res;
921 }
922 
924  const image::locator& i_locator,
925  const light_string& ls)
926 {
927  if(i_locator.is_void()) {
928  return texture();
929  }
930 
931  // select associated cache
932  lit_texture_cache* imap = &lit_textures_;
933 
934  // if no light variants yet, need to add an empty map
935  if(!i_locator.in_cache(*imap)) {
936  i_locator.add_to_cache(*imap, lit_texture_variants());
937  }
938 
939  // need access to add it if not found
940  { // enclose reference pointing to data stored in a changing vector
941  const lit_texture_variants& lvar = i_locator.locate_in_cache(*imap);
942  auto lvi = lvar.find(ls);
943  if(lvi != lvar.end()) {
944  return lvi->second;
945  }
946  }
947 
948  DBG_IMG << "lit texture cache miss: " << i_locator;
949 
950  // not cached yet, generate it
951  texture tex(get_lighted_image(i_locator, ls));
952 
953  // record the lighted texture in the corresponding variants cache
954  i_locator.access_in_cache(*imap)[ls] = tex;
955 
956  return tex;
957 }
958 
960 {
962  return get_surface(terrain_mask, UNSCALED);
963 }
964 
965 point get_size(const locator& i_locator, bool skip_cache)
966 {
967  const surface s(get_surface(i_locator, UNSCALED, skip_cache));
968  if (s != nullptr) {
969  return {s->w, s->h};
970  } else {
971  return {0, 0};
972  }
973 }
974 
975 bool is_in_hex(const locator& i_locator)
976 {
977  bool result;
978  {
979  if(i_locator.in_cache(in_hex_info_)) {
980  result = i_locator.locate_in_cache(in_hex_info_);
981  } else {
982  const surface image(get_surface(i_locator, UNSCALED));
983 
984  bool res = in_mask_surface(image, get_hexmask());
985 
986  i_locator.add_to_cache(in_hex_info_, res);
987 
988  // std::cout << "in_hex : " << i_locator.get_filename()
989  // << " " << (res ? "yes" : "no") << "\n";
990 
991  result = res;
992  }
993  }
994 
995  return result;
996 }
997 
998 bool is_empty_hex(const locator& i_locator)
999 {
1000  if(!i_locator.in_cache(is_empty_hex_)) {
1001  const surface surf = get_surface(i_locator, HEXED);
1002  // emptiness of terrain image is checked during hex cut
1003  // so, maybe in cache now, let's recheck
1004  if(!i_locator.in_cache(is_empty_hex_)) {
1005  // should never reach here
1006  // but do it manually if it happens
1007  // assert(false);
1008  bool is_empty = false;
1009  mask_surface(surf, get_hexmask(), &is_empty);
1010  i_locator.add_to_cache(is_empty_hex_, is_empty);
1011  }
1012  }
1013 
1014  return i_locator.locate_in_cache(is_empty_hex_);
1015 }
1016 
1017 bool exists(const image::locator& i_locator)
1018 {
1019  typedef image::locator loc;
1020  loc::type type = i_locator.get_type();
1021  if(type != loc::FILE && type != loc::SUB_FILE) {
1022  return false;
1023  }
1024 
1025  // The insertion will fail if there is already an element in the cache
1026  // and this will point to the existing element.
1027  auto [iter, success] = image_existence_map.emplace(i_locator.get_filename(), false);
1028 
1029  bool& cache = iter->second;
1030  if(success) {
1031  if(i_locator.is_data_uri()) {
1032  cache = parsed_data_URI{i_locator.get_filename()}.good;
1033  } else {
1034  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
1035  }
1036  }
1037 
1038  return cache;
1039 }
1040 
1041 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
1042 {
1043  const std::string checked_dir = dir + "/" + subdir;
1044  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
1045  return;
1046  }
1047 
1048  precached_dirs.insert(checked_dir);
1049 
1050  if(!filesystem::is_directory(checked_dir)) {
1051  return;
1052  }
1053 
1054  std::vector<std::string> files_found;
1055  std::vector<std::string> dirs_found;
1056  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::name_mode::FILE_NAME_ONLY,
1058 
1059  for(const auto& f : files_found) {
1060  image_existence_map[subdir + f] = true;
1061  }
1062 
1063  for(const auto& d : dirs_found) {
1064  precache_file_existence_internal(dir, subdir + d + "/");
1065  }
1066 }
1067 
1068 void precache_file_existence(const std::string& subdir)
1069 {
1070  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
1071 
1072  for(const auto& p : paths) {
1074  }
1075 }
1076 
1077 bool precached_file_exists(const std::string& file)
1078 {
1079  const auto b = image_existence_map.find(file);
1080  if(b != image_existence_map.end()) {
1081  return b->second;
1082  }
1083 
1084  return false;
1085 }
1086 
1087 save_result save_image(const locator& i_locator, const std::string& filename)
1088 {
1089  return save_image(get_surface(i_locator), filename);
1090 }
1091 
1092 save_result save_image(const surface& surf, const std::string& filename)
1093 {
1094  if(!surf) {
1095  return save_result::no_image;
1096  }
1097 
1098  if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
1099  LOG_IMG << "Writing a JPG image to " << filename;
1100 
1101  const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
1102  return err == 0 ? save_result::success : save_result::save_failed;
1103  }
1104 
1105  if(filesystem::ends_with(filename, ".png")) {
1106  LOG_IMG << "Writing a PNG image to " << filename;
1107 
1108  const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
1109  return err == 0 ? save_result::success : save_result::save_failed;
1110  }
1111 
1113 }
1114 
1115 /*
1116  * TEXTURE INTERFACE ======================================================================
1117  *
1118  * The only important difference here is that textures must have their
1119  * scale quality set before creation. All other handling is done by
1120  * get_surface.
1121  */
1122 
1123 texture get_texture(const image::locator& i_locator, TYPE type, bool skip_cache)
1124 {
1125  return get_texture(i_locator, scale_quality::nearest, type, skip_cache);
1126 }
1127 
1128 /** Returns a texture for the corresponding image. */
1129 texture get_texture(const image::locator& i_locator, scale_quality quality, TYPE type, bool skip_cache)
1130 {
1131  texture res;
1132 
1133  if(i_locator.is_void()) {
1134  return res;
1135  }
1136 
1137  type = simplify_type(i_locator, type);
1138 
1139  //
1140  // Select the appropriate cache. We don't need caches for every single image types,
1141  // since some types can be handled by render-time operations.
1142  //
1143  texture_cache* cache = nullptr;
1144 
1145  switch(type) {
1146  case HEXED:
1147  cache = &textures_hexed_[quality];
1148  break;
1149  case TOD_COLORED:
1150  cache = &texture_tod_colored_[quality];
1151  break;
1152  default:
1153  cache = &textures_[quality];
1154  }
1155 
1156  //
1157  // Now attempt to find a cached texture. If found, return it.
1158  //
1159  bool in_cache = i_locator.in_cache(*cache);
1160 
1161  if(in_cache) {
1162  res = i_locator.locate_in_cache(*cache);
1163  return res;
1164  }
1165 
1166  DBG_IMG << "texture cache [" << type << "] miss: " << i_locator;
1167 
1168  //
1169  // No texture was cached. In that case, create a new one. The explicit cases require special
1170  // handling with surfaces in order to generate the desired effect. This shouldn't be the case
1171  // once we get OGL and shader support.
1172  //
1173 
1174  // Get it from the surface cache, also setting the desired scale quality.
1175  const bool linear_scaling = quality == scale_quality::linear ? true : false;
1176  if(i_locator.get_modifications().empty()) {
1177  // skip cache if we're loading plain files with no modifications
1178  res = texture(get_surface(i_locator, type, true), linear_scaling);
1179  } else {
1180  res = texture(get_surface(i_locator, type, skip_cache), linear_scaling);
1181  }
1182 
1183  // Cache the texture.
1184  if(skip_cache) {
1185  DBG_IMG << "texture cache [" << type << "] skip: " << i_locator;
1186  } else {
1187  i_locator.add_to_cache(*cache, res);
1188  }
1189 
1190  return res;
1191 }
1192 
1193 } // 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
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:1068
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:1077
save_result
Definition: picture.hpp:328
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:1087
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:965
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: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:806
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1017
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:348
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:1041
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:887
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:968
logger & err()
Definition: log.cpp:216
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:959
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:975
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:711
Contains the SDL_Rect helper code.
texture get_lighted_texture(const image::locator &i_locator, const light_string &ls)
Definition: picture.cpp:923
#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:998
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:1123
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