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