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)
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)
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  if(location.empty() && filesystem::ends_with(name, ".png")) {
357  std::string webp_name = name.substr(0, name.size() - 4) + ".webp";
358  location = filesystem::get_binary_file_location("images", webp_name);
359  if(!location.empty()) {
360  WRN_IMG << "Replaced missing '" << name << "' with found '"
361  << webp_name << "'.";
362  }
363  }
364 
365  {
366  if(!location.empty()) {
367  // Check if there is a localized image.
368  const std::string loc_location = filesystem::get_localized_path(location);
369  if(!loc_location.empty()) {
370  location = loc_location;
371  }
372 
374  res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
375 
376  // If there was no standalone localized image, check if there is an overlay.
377  if(res && loc_location.empty()) {
378  const std::string ovr_location = filesystem::get_localized_path(location, "--overlay");
379  if(!ovr_location.empty()) {
380  add_localized_overlay(ovr_location, res);
381  }
382  }
383  }
384  }
385 
386  if(!res && !name.empty()) {
387  ERR_IMG << "could not open image '" << name << "'";
390  if(name != game_config::images::blank)
392  }
393 
394  return res;
395 }
396 
398 {
399  surface surf = get_surface(loc.get_filename(), UNSCALED);
400  if(surf == nullptr) {
401  return nullptr;
402  }
403 
405 
406  while(!mods.empty()) {
407  modification* mod = mods.top();
408 
409  try {
410  surf = (*mod)(surf);
411  } catch(const image::modification::imod_exception& e) {
412  std::ostringstream ss;
413  ss << "\n";
414 
415  for(const std::string& mod_name : utils::parenthetical_split(loc.get_modifications(), '~')) {
416  ss << "\t" << mod_name << "\n";
417  }
418 
419  ERR_CFG << "Failed to apply a modification to an image:\n"
420  << "Image: " << loc.get_filename() << "\n"
421  << "Modifications: " << ss.str() << "\n"
422  << "Error: " << e.message;
423  }
424 
425  // NOTE: do this *after* applying the mod or you'll get crashes!
426  mods.pop();
427  }
428 
429  if(loc.get_loc().valid()) {
430  rect srcrect(
431  ((tile_size * 3) / 4) * loc.get_loc().x,
432  tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
433  tile_size,
434  tile_size
435  );
436 
437  if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
438  srcrect.x += surf->w / 2 - loc.get_center_x();
439  srcrect.y += surf->h / 2 - loc.get_center_y();
440  }
441 
442  // cut and hex mask, but also check and cache if empty result
443  surface cut(cut_surface(surf, srcrect));
444  bool is_empty = false;
445  surf = mask_surface(cut, get_hexmask(), &is_empty);
446 
447  // discard empty images to free memory
448  if(is_empty) {
449  // Safe because those images are only used by terrain rendering
450  // and it filters them out.
451  // A safer and more general way would be to keep only one copy of it
452  surf = nullptr;
453  }
454 
455  loc.add_to_cache(is_empty_hex_, is_empty);
456  }
457 
458  return surf;
459 }
460 
462 {
463  surface surf;
464 
465  parsed_data_URI parsed{loc.get_filename()};
466 
467  if(!parsed.good) {
468  std::string_view fn = loc.get_filename();
469  std::string_view stripped = fn.substr(0, fn.find(","));
470  ERR_IMG << "Invalid data URI: " << stripped;
471  } else if(parsed.mime.substr(0, 5) != "image") {
472  ERR_IMG << "Data URI not of image MIME type: " << parsed.mime;
473  } else {
474  const std::vector<uint8_t> image_data = base64::decode(parsed.data);
475  filesystem::rwops_ptr rwops{SDL_RWFromConstMem(image_data.data(), image_data.size())};
476 
477  if(image_data.empty()) {
478  ERR_IMG << "Invalid encoding in data URI";
479  } else if(parsed.mime == "image/png") {
480  surf = IMG_LoadTyped_RW(rwops.release(), true, "PNG");
481  } else if(parsed.mime == "image/jpeg") {
482  surf = IMG_LoadTyped_RW(rwops.release(), true, "JPG");
483  } else {
484  ERR_IMG << "Invalid image MIME type: " << parsed.mime;
485  }
486  }
487 
488  return surf;
489 }
490 
491 // small utility function to store an int from (-256,254) to an signed char
492 static signed char col_to_uchar(int i)
493 {
494  return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
495 }
496 
497 light_string get_light_string(int op, int r, int g, int b)
498 {
499  light_string ls;
500  ls.reserve(4);
501  ls.push_back(op);
502  ls.push_back(col_to_uchar(r));
503  ls.push_back(col_to_uchar(g));
504  ls.push_back(col_to_uchar(b));
505 
506  return ls;
507 }
508 
509 static surface apply_light(surface surf, const light_string& ls)
510 {
511  // atomic lightmap operation are handled directly (important to end recursion)
512  if(ls.size() == 4) {
513  // if no lightmap (first char = -1) then we need the initial value
514  //(before the halving done for lightmap)
515  int m = ls[0] == -1 ? 2 : 1;
516  return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
517  }
518 
519  // check if the lightmap is already cached or need to be generated
520  surface lightmap = nullptr;
521  auto i = surface_lightmaps_.find(ls);
522  if(i != surface_lightmaps_.end()) {
523  lightmap = i->second;
524  } else {
525  // build all the paths for lightmap sources
526  static const std::string p = "terrain/light/light";
527  static const std::string lm_img[19] {
528  p + ".png",
529  p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
530  p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
531  p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
532  p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
533  p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
534  p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
535  };
536 
537  // decompose into atomic lightmap operations (4 chars)
538  for(std::size_t c = 0; c + 3 < ls.size(); c += 4) {
539  light_string sls = ls.substr(c, 4);
540 
541  // get the corresponding image and apply the lightmap operation to it
542  // This allows to also cache lightmap parts.
543  // note that we avoid infinite recursion by using only atomic operation
544  surface lts = image::get_lighted_image(lm_img[sls[0]], sls);
545 
546  // first image will be the base where we blit the others
547  if(lightmap == nullptr) {
548  // copy the cached image to avoid modifying the cache
549  lightmap = lts.clone();
550  } else {
551  sdl_blit(lts, nullptr, lightmap, nullptr);
552  }
553  }
554 
555  // cache the result
556  surface_lightmaps_[ls] = lightmap;
557  }
558 
559  // apply the final lightmap
560  return light_surface(surf, lightmap);
561 }
562 
564 {
565  return val_.is_data_uri
566  ? parsed_data_URI{val_.filename}.good
567  : !filesystem::get_binary_file_location("images", val_.filename).empty();
568 }
569 
570 static surface load_from_disk(const locator& loc)
571 {
572  switch(loc.get_type()) {
573  case locator::FILE:
574  if(loc.is_data_uri()){
575  return load_image_data_uri(loc);
576  } else {
577  return load_image_file(loc);
578  }
579  case locator::SUB_FILE:
580  return load_image_sub_file(loc);
581  default:
582  return surface(nullptr);
583  }
584 }
585 
587 {
588 }
589 
591 {
592  flush_cache();
593 }
594 
595 void set_color_adjustment(int r, int g, int b)
596 {
597  if(r != red_adjust || g != green_adjust || b != blue_adjust) {
598  red_adjust = r;
599  green_adjust = g;
600  blue_adjust = b;
601  surfaces_[TOD_COLORED].flush();
602  lit_surfaces_.flush();
603  lit_textures_.flush();
604  texture_tod_colored_.clear();
605  }
606 }
607 
608 static surface get_hexed(const locator& i_locator, bool skip_cache = false)
609 {
610  surface image(get_surface(i_locator, UNSCALED, skip_cache));
611  surface mask(get_hexmask());
612  // Ensure the image is the correct size by cropping and/or centering.
613  // TODO: this should probably be a function of sdl/utils
614  if(image && (image->w != mask->w || image->h != mask->h)) {
615  DBG_IMG << "adjusting [" << image->w << ',' << image->h << ']'
616  << " image to hex mask: " << i_locator;
617  // the fitted surface
618  surface fit(mask->w, mask->h);
619  // if the image is too large in either dimension, crop it.
620  if(image->w > mask->w || image->h >= mask->h) {
621  // fill the crop surface with transparency
622  sdl::fill_surface_rect(fit, nullptr,
623  SDL_MapRGBA(fit->format, 0, 0, 0, 0)
624  );
625  // crop the input image to hexmask dimensions
626  int cutx = std::max(0, image->w - mask->w) / 2;
627  int cuty = std::max(0, image->h - mask->h) / 2;
628  int cutw = std::min(image->w, mask->w);
629  int cuth = std::min(image->h, mask->h);
630  image = cut_surface(image, {cutx, cuty, cutw, cuth});
631  // image will now have dimensions <= mask
632  }
633  // center image
634  int placex = (mask->w - image->w) / 2;
635  int placey = (mask->h - image->h) / 2;
636  rect dst = {placex, placey, image->w, image->h};
637  sdl_blit(image, nullptr, fit, &dst);
638  image = fit;
639  }
640  // hex cut tiles, also check and cache if empty result
641  bool is_empty = false;
642  surface res = mask_surface(image, mask, &is_empty, i_locator.get_filename());
643  i_locator.add_to_cache(is_empty_hex_, is_empty);
644  return res;
645 }
646 
647 static surface get_tod_colored(const locator& i_locator, bool skip_cache = false)
648 {
649  surface img = get_surface(i_locator, HEXED, skip_cache);
650  return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
651 }
652 
653 /** translate type to a simpler one when possible */
654 static TYPE simplify_type(const image::locator& i_locator, TYPE type)
655 {
656  if(type == TOD_COLORED) {
657  if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
658  type = HEXED;
659  }
660  }
661 
662  if(type == HEXED) {
663  // check if the image is already hex-cut by the location system
664  if(i_locator.get_loc().valid()) {
665  type = UNSCALED;
666  }
667  }
668 
669  return type;
670 }
671 
673  const image::locator& i_locator,
674  TYPE type,
675  bool skip_cache)
676 {
677  surface res;
678 
679  if(i_locator.is_void()) {
680  return res;
681  }
682 
683  type = simplify_type(i_locator, type);
684 
685  // select associated cache
686  if(type >= NUM_TYPES) {
687  WRN_IMG << "get_surface called with unknown image type";
688  return res;
689  }
690  surface_cache& imap = surfaces_[type];
691 
692  // return the image if already cached
693  if(auto cached_item = i_locator.copy_from_cache(imap)) {
694  return *cached_item;
695  }
696 
697  DBG_IMG << "surface cache [" << type << "] miss: " << i_locator;
698 
699  // not cached, generate it
700  switch(type) {
701  case UNSCALED:
702  // If type is unscaled, directly load the image from the disk.
703  res = load_from_disk(i_locator);
704  break;
705  case TOD_COLORED:
706  res = get_tod_colored(i_locator, skip_cache);
707  break;
708  case HEXED:
709  res = get_hexed(i_locator, skip_cache);
710  break;
711  default:
712  throw game::error("get_surface somehow lost image type?");
713  }
714 
715  bool_cache& skip = skipped_cache_[type];
716  if(i_locator.in_cache(skip) && i_locator.locate_in_cache(skip))
717  {
718  DBG_IMG << "duplicate load: " << i_locator
719  << " [" << type << "]"
720  << " (" << duplicate_loads_ << "/" << total_loads_ << " total)";
721  ++duplicate_loads_;
722  }
723  ++total_loads_;
724 
725  if(skip_cache) {
726  DBG_IMG << "surface cache [" << type << "] skip: " << i_locator;
727  i_locator.add_to_cache(skip, true);
728  } else {
729  i_locator.add_to_cache(imap, res);
730  }
731 
732  return res;
733 }
734 
736 {
737  surface res;
738  if(i_locator.is_void()) {
739  return res;
740  }
741 
742  // select associated cache
743  lit_surface_cache* imap = &lit_surfaces_;
744 
745  // if no light variants yet, need to add an empty map
746  if(!i_locator.in_cache(*imap)) {
747  i_locator.add_to_cache(*imap, lit_surface_variants());
748  }
749 
750  // need access to add it if not found
751  { // enclose reference pointing to data stored in a changing vector
752  const lit_surface_variants& lvar = i_locator.locate_in_cache(*imap);
753  auto lvi = lvar.find(ls);
754  if(lvi != lvar.end()) {
755  return lvi->second;
756  }
757  }
758 
759  DBG_IMG << "lit surface cache miss: " << i_locator;
760 
761  // not cached yet, generate it
762  res = get_surface(i_locator, HEXED);
763  res = apply_light(res, ls);
764 
765  // record the lighted surface in the corresponding variants cache
766  i_locator.access_in_cache(*imap)[ls] = res;
767 
768  return res;
769 }
770 
772  const image::locator& i_locator,
773  const light_string& ls)
774 {
775  if(i_locator.is_void()) {
776  return texture();
777  }
778 
779  // select associated cache
780  lit_texture_cache* imap = &lit_textures_;
781 
782  // if no light variants yet, need to add an empty map
783  if(!i_locator.in_cache(*imap)) {
784  i_locator.add_to_cache(*imap, lit_texture_variants());
785  }
786 
787  // need access to add it if not found
788  { // enclose reference pointing to data stored in a changing vector
789  const lit_texture_variants& lvar = i_locator.locate_in_cache(*imap);
790  auto lvi = lvar.find(ls);
791  if(lvi != lvar.end()) {
792  return lvi->second;
793  }
794  }
795 
796  DBG_IMG << "lit texture cache miss: " << i_locator;
797 
798  // not cached yet, generate it
799  texture tex(get_lighted_image(i_locator, ls));
800 
801  // record the lighted texture in the corresponding variants cache
802  i_locator.access_in_cache(*imap)[ls] = tex;
803 
804  return tex;
805 }
806 
808 {
811 }
812 
813 point get_size(const locator& i_locator, bool skip_cache)
814 {
815  const surface s(get_surface(i_locator, UNSCALED, skip_cache));
816  if (s != nullptr) {
817  return {s->w, s->h};
818  } else {
819  return {0, 0};
820  }
821 }
822 
823 bool is_in_hex(const locator& i_locator)
824 {
825  if(auto cached_val = i_locator.copy_from_cache(in_hex_info_)) {
826  return *cached_val;
827  }
828 
829  bool res = in_mask_surface(get_surface(i_locator, UNSCALED), get_hexmask());
830  i_locator.add_to_cache(in_hex_info_, res);
831  return res;
832 }
833 
834 bool is_empty_hex(const locator& i_locator)
835 {
836  if(!i_locator.in_cache(is_empty_hex_)) {
837  const surface surf = get_surface(i_locator, HEXED);
838  // emptiness of terrain image is checked during hex cut
839  // so, maybe in cache now, let's recheck
840  if(!i_locator.in_cache(is_empty_hex_)) {
841  // should never reach here
842  // but do it manually if it happens
843  // assert(false);
844  bool is_empty = false;
845  mask_surface(surf, get_hexmask(), &is_empty);
846  i_locator.add_to_cache(is_empty_hex_, is_empty);
847  }
848  }
849 
850  return i_locator.locate_in_cache(is_empty_hex_);
851 }
852 
853 bool exists(const image::locator& i_locator)
854 {
855  typedef image::locator loc;
856  loc::type type = i_locator.get_type();
857  if(type != loc::FILE && type != loc::SUB_FILE) {
858  return false;
859  }
860 
861  // The insertion will fail if there is already an element in the cache
862  // and this will point to the existing element.
863  auto [iter, success] = image_existence_map.emplace(i_locator.get_filename(), false);
864 
865  bool& cache = iter->second;
866  if(success) {
867  if(i_locator.is_data_uri()) {
868  cache = parsed_data_URI{i_locator.get_filename()}.good;
869  } else {
870  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
871  }
872  }
873 
874  return cache;
875 }
876 
877 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
878 {
879  const std::string checked_dir = dir + "/" + subdir;
880  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
881  return;
882  }
883 
884  precached_dirs.insert(checked_dir);
885 
886  if(!filesystem::is_directory(checked_dir)) {
887  return;
888  }
889 
890  std::vector<std::string> files_found;
891  std::vector<std::string> dirs_found;
892  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::name_mode::FILE_NAME_ONLY,
894 
895  for(const auto& f : files_found) {
896  image_existence_map[subdir + f] = true;
897  }
898 
899  for(const auto& d : dirs_found) {
900  precache_file_existence_internal(dir, subdir + d + "/");
901  }
902 }
903 
904 void precache_file_existence(const std::string& subdir)
905 {
906  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
907 
908  for(const auto& p : paths) {
910  }
911 }
912 
913 bool precached_file_exists(const std::string& file)
914 {
915  const auto b = image_existence_map.find(file);
916  if(b != image_existence_map.end()) {
917  return b->second;
918  }
919 
920  return false;
921 }
922 
923 save_result save_image(const locator& i_locator, const std::string& filename)
924 {
925  return save_image(get_surface(i_locator), filename);
926 }
927 
928 save_result save_image(const surface& surf, const std::string& filename)
929 {
930  if(!surf) {
931  return save_result::no_image;
932  }
933 
934  if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
935  LOG_IMG << "Writing a JPG image to " << filename;
936 
937  const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
939  }
940 
941  if(filesystem::ends_with(filename, ".png")) {
942  LOG_IMG << "Writing a PNG image to " << filename;
943 
944  const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
946  }
947 
949 }
950 
951 /*
952  * TEXTURE INTERFACE ======================================================================
953  *
954  * The only important difference here is that textures must have their
955  * scale quality set before creation. All other handling is done by
956  * get_surface.
957  */
958 
959 texture get_texture(const image::locator& i_locator, TYPE type, bool skip_cache)
960 {
961  return get_texture(i_locator, scale_quality::nearest, type, skip_cache);
962 }
963 
964 /** Returns a texture for the corresponding image. */
965 texture get_texture(const image::locator& i_locator, scale_quality quality, TYPE type, bool skip_cache)
966 {
967  texture res;
968 
969  if(i_locator.is_void()) {
970  return res;
971  }
972 
973  type = simplify_type(i_locator, type);
974 
975  //
976  // Select the appropriate cache. We don't need caches for every single image types,
977  // since some types can be handled by render-time operations.
978  //
979  texture_cache* cache = nullptr;
980 
981  switch(type) {
982  case HEXED:
983  cache = &textures_hexed_[quality];
984  break;
985  case TOD_COLORED:
986  cache = &texture_tod_colored_[quality];
987  break;
988  default:
989  cache = &textures_[quality];
990  }
991 
992  //
993  // Now attempt to find a cached texture. If found, return it.
994  //
995  if(auto cached_item = i_locator.copy_from_cache(*cache)) {
996  return *cached_item;
997  }
998 
999  DBG_IMG << "texture cache [" << type << "] miss: " << i_locator;
1000 
1001  //
1002  // No texture was cached. In that case, create a new one. The explicit cases require special
1003  // handling with surfaces in order to generate the desired effect. This shouldn't be the case
1004  // once we get OGL and shader support.
1005  //
1006 
1007  // Get it from the surface cache, also setting the desired scale quality.
1008  const bool linear_scaling = quality == scale_quality::linear ? true : false;
1009  if(i_locator.get_modifications().empty()) {
1010  // skip cache if we're loading plain files with no modifications
1011  res = texture(get_surface(i_locator, type, true), linear_scaling);
1012  } else {
1013  res = texture(get_surface(i_locator, type, skip_cache), linear_scaling);
1014  }
1015 
1016  // Cache the texture.
1017  if(skip_cache) {
1018  DBG_IMG << "texture cache [" << type << "] skip: " << i_locator;
1019  } else {
1020  i_locator.add_to_cache(*cache, res);
1021  }
1022 
1023  return res;
1024 }
1025 
1026 } // 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:563
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:404
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:412
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:397
bool is_empty_hex(const locator &i_locator)
Checks if an image is empty after hex masking.
Definition: picture.cpp:834
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:509
bool precached_file_exists(const std::string &file)
Definition: picture.cpp:913
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:654
static surface load_image_data_uri(const image::locator &loc)
Definition: picture.cpp:461
static surface get_tod_colored(const locator &i_locator, bool skip_cache=false)
Definition: picture.cpp:647
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:853
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:877
static surface load_from_disk(const locator &loc)
Definition: picture.cpp:570
std::map< light_string, texture > lit_texture_variants
Definition: picture.hpp:181
static signed char col_to_uchar(int i)
Definition: picture.cpp:492
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:672
surface get_hexmask()
Retrieves the standard hexagonal tile mask.
Definition: picture.cpp:807
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:771
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:923
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:904
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:959
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:813
static surface get_hexed(const locator &i_locator, bool skip_cache=false)
Definition: picture.cpp:608
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:735
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:595
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
Definition: picture.cpp:497
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
Definition: picture.cpp:823
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
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:708
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