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