The Battle for Wesnoth  1.17.0-dev
picture.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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 
34 #include <SDL2/SDL_image.h>
35 
36 #include <functional>
37 
38 #include <boost/algorithm/string.hpp>
39 #include <boost/functional/hash_fwd.hpp>
40 
41 #include <set>
42 
43 static lg::log_domain log_display("display");
44 #define ERR_DP LOG_STREAM(err, log_display)
45 #define LOG_DP LOG_STREAM(info, log_display)
46 
47 static lg::log_domain log_config("config");
48 #define ERR_CFG LOG_STREAM(err, log_config)
49 
51 
52 template<typename T>
53 struct cache_item
54 {
56  : item()
57  , loaded(false)
58  {
59  }
60 
61  cache_item(const T& item)
62  : item(item)
63  , loaded(true)
64  {
65  }
66 
67  T item;
68  bool loaded;
69 };
70 
71 namespace std
72 {
73 template<>
74 struct hash<image::locator::value>
75 {
76  std::size_t operator()(const image::locator::value& val) const
77  {
78  std::size_t hash = std::hash<unsigned>{}(val.type_);
79 
81  boost::hash_combine(hash, val.filename_);
82  }
83 
84  if(val.type_ == image::locator::SUB_FILE) {
85  boost::hash_combine(hash, val.loc_.x);
86  boost::hash_combine(hash, val.loc_.y);
87  boost::hash_combine(hash, val.center_x_);
88  boost::hash_combine(hash, val.center_y_);
89  boost::hash_combine(hash, val.modifications_);
90  }
91 
92  return hash;
93  }
94 };
95 }
96 
97 namespace image
98 {
99 template<typename T>
101 {
102 public:
104  : content_()
105  {
106  }
107 
109  {
110  if(static_cast<unsigned>(index) >= content_.size())
111  content_.resize(index + 1);
112  return content_[index];
113  }
114 
115  void flush()
116  {
117  content_.clear();
118  }
119 
120 private:
121  std::vector<cache_item<T>> content_;
122 };
123 
124 template<typename T>
125 bool locator::in_cache(cache_type<T>& cache) const
126 {
127  return index_ < 0 ? false : cache.get_element(index_).loaded;
128 }
129 
130 template<typename T>
131 const T& locator::locate_in_cache(cache_type<T>& cache) const
132 {
133  static T dummy;
134  return index_ < 0 ? dummy : cache.get_element(index_).item;
135 }
136 
137 template<typename T>
138 T& locator::access_in_cache(cache_type<T>& cache) const
139 {
140  static T dummy;
141  return index_ < 0 ? dummy : cache.get_element(index_).item;
142 }
143 
144 template<typename T>
145 void locator::add_to_cache(cache_type<T>& cache, const T& data) const
146 {
147  if(index_ >= 0) {
148  cache.get_element(index_) = cache_item<T>(data);
149  }
150 }
151 }
152 
153 namespace
154 {
155 image::locator::locator_finder_t locator_finder;
156 
157 /** Definition of all image maps */
158 image::image_cache images_, scaled_to_zoom_, hexed_images_, scaled_to_hex_images_, tod_colored_images_,
159  brightened_images_;
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_cache lit_images_, lit_scaled_images_;
169 // caches storing each lightmap generated
170 image::lit_variants lightmaps_;
171 
172 // const int cache_version_ = 0;
173 
174 std::map<std::string, bool> image_existence_map;
175 
176 // directories where we already cached file existence
177 std::set<std::string> precached_dirs;
178 
179 std::map<surface, surface> reversed_images_;
180 
181 int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
182 
183 unsigned int zoom = tile_size;
184 unsigned int cached_zoom = 0;
185 
186 const std::string data_uri_prefix = "data:";
187 struct parsed_data_URI{
188  explicit parsed_data_URI(std::string_view data_URI);
189  std::string_view scheme;
190  std::string_view mime;
191  std::string_view base64;
192  std::string_view data;
193  bool good;
194 };
195 parsed_data_URI::parsed_data_URI(std::string_view data_URI)
196 {
197  const std::size_t colon = data_URI.find(':');
198  const std::string_view after_scheme = data_URI.substr(colon + 1);
199 
200  const std::size_t comma = after_scheme.find(',');
201  const std::string_view type_info = after_scheme.substr(0, comma);
202 
203  const std::size_t semicolon = type_info.find(';');
204 
205  scheme = data_URI.substr(0, colon);
206  base64 = type_info.substr(semicolon + 1);
207  mime = type_info.substr(0, semicolon);
208  data = after_scheme.substr(comma + 1);
209  good = (scheme == "data" && base64 == "base64" && mime.length() > 0 && data.length() > 0);
210 }
211 
212 } // end anon namespace
213 
214 namespace image
215 {
219 
220 static int last_index_ = 0;
221 
223 {
224  {
225  images_.flush();
226  hexed_images_.flush();
227  tod_colored_images_.flush();
228  scaled_to_zoom_.flush();
229  scaled_to_hex_images_.flush();
230  brightened_images_.flush();
231  lit_images_.flush();
232  lit_scaled_images_.flush();
233  in_hex_info_.flush();
234  is_empty_hex_.flush();
235  mini_terrain_cache.clear();
236  mini_fogged_terrain_cache.clear();
237  mini_highlighted_terrain_cache.clear();
238  reversed_images_.clear();
239  image_existence_map.clear();
240  precached_dirs.clear();
241  }
242  /* We can't reset last_index_, since some locators are still alive
243  when using :refresh. That would cause them to point to the wrong
244  images. Not resetting the variable causes a memory leak, though. */
245  // last_index_ = 0;
246 }
247 
248 void locator::init_index()
249 {
250  auto i = locator_finder.find(val_);
251 
252  if(i == locator_finder.end()) {
253  index_ = last_index_++;
254  locator_finder.emplace(val_, index_);
255  } else {
256  index_ = i->second;
257  }
258 }
259 
260 void locator::parse_arguments()
261 {
262  std::string& fn = val_.filename_;
263  if(fn.empty()) {
264  return;
265  }
266 
267  if(boost::algorithm::starts_with(fn, data_uri_prefix)) {
268  parsed_data_URI parsed{fn};
269 
270  if(!parsed.good) {
271  std::string_view view{ fn };
272  std::string_view stripped = view.substr(0, view.find(","));
273  ERR_DP << "Invalid data URI: " << stripped << std::endl;
274  }
275 
276  val_.is_data_uri_ = true;
277  }
278 
279  std::size_t markup_field = fn.find('~');
280 
281  if(markup_field != std::string::npos) {
282  val_.type_ = SUB_FILE;
283  val_.modifications_ = fn.substr(markup_field, fn.size() - markup_field);
284  fn = fn.substr(0, markup_field);
285  }
286 }
287 
288 locator::locator()
289  : index_(-1)
290  , val_()
291 {
292 }
293 
294 locator::locator(const locator& a, const std::string& mods)
295  : index_(-1)
296  , val_(a.val_)
297 {
298  if(!mods.empty()) {
299  val_.modifications_ += mods;
300  val_.type_ = SUB_FILE;
301  init_index();
302  } else {
303  index_ = a.index_;
304  }
305 }
306 
307 locator::locator(const char* filename)
308  : index_(-1)
309  , val_(filename)
310 {
311  parse_arguments();
312  init_index();
313 }
314 
315 locator::locator(const std::string& filename)
316  : index_(-1)
317  , val_(filename)
318 {
319  parse_arguments();
320  init_index();
321 }
322 
323 locator::locator(const std::string& filename, const std::string& modifications)
324  : index_(-1)
325  , val_(filename, modifications)
326 {
327  init_index();
328 }
329 
330 locator::locator(const std::string& filename,
331  const map_location& loc,
332  int center_x,
333  int center_y,
334  const std::string& modifications)
335  : index_(-1)
336  , val_(filename, loc, center_x, center_y, modifications)
337 {
338  init_index();
339 }
340 
342 {
343  index_ = a.index_;
344  val_ = a.val_;
345 
346  return *this;
347 }
348 
350  : type_(NONE)
351  , is_data_uri_(false)
352  , filename_()
353  , loc_()
354  , modifications_()
355  , center_x_(0)
356  , center_y_(0)
357 {
358 }
359 
360 locator::value::value(const char* filename)
361  : type_(FILE)
362  , is_data_uri_(false)
363  , filename_(filename)
364  , loc_()
365  , modifications_()
366  , center_x_(0)
367  , center_y_(0)
368 {
369 }
370 
371 locator::value::value(const std::string& filename)
372  : type_(FILE)
373  , is_data_uri_(false)
374  , filename_(filename)
375  , loc_()
376  , modifications_()
377  , center_x_(0)
378  , center_y_(0)
379 {
380 }
381 
382 locator::value::value(const std::string& filename, const std::string& modifications)
383  : type_(SUB_FILE)
384  , is_data_uri_(false)
385  , filename_(filename)
386  , loc_()
387  , modifications_(modifications)
388  , center_x_(0)
389  , center_y_(0)
390 {
391 }
392 
393 locator::value::value(const std::string& filename,
394  const map_location& loc,
395  int center_x,
396  int center_y,
397  const std::string& modifications)
398  : type_(SUB_FILE)
399  , is_data_uri_(false)
400  , filename_(filename)
401  , loc_(loc)
402  , modifications_(modifications)
403  , center_x_(center_x)
404  , center_y_(center_y)
405 {
406 }
407 
409 {
410  if(a.type_ != type_) {
411  return false;
412  } else if(type_ == FILE) {
413  return filename_ == a.filename_;
414  } else if(type_ == SUB_FILE) {
415  return filename_ == a.filename_ && loc_ == a.loc_ && modifications_ == a.modifications_
416  && center_x_ == a.center_x_ && center_y_ == a.center_y_;
417  }
418 
419  return false;
420 }
421 
423 {
424  if(type_ != a.type_) {
425  return type_ < a.type_;
426  } else if(type_ == FILE) {
427  return filename_ < a.filename_;
428  } else if(type_ == SUB_FILE) {
429  if(filename_ != a.filename_)
430  return filename_ < a.filename_;
431  if(loc_ != a.loc_)
432  return loc_ < a.loc_;
433  if(center_x_ != a.center_x_)
434  return center_x_ < a.center_x_;
435  if(center_y_ != a.center_y_)
436  return center_y_ < a.center_y_;
437  return (modifications_ < a.modifications_);
438  }
439 
440  return false;
441 }
442 
443 // Load overlay image and compose it with the original surface.
444 static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
445 {
447  surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
448  if(!ovr_surf) {
449  return;
450  }
451 
452  SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
453 
454  sdl_blit(ovr_surf, 0, orig_surf, &area);
455 }
456 
458 {
459  surface res;
460 
461  std::string location = filesystem::get_binary_file_location("images", loc.get_filename());
462 
463  {
464  if(!location.empty()) {
465  // Check if there is a localized image.
466  const std::string loc_location = filesystem::get_localized_path(location);
467  if(!loc_location.empty()) {
468  location = loc_location;
469  }
470 
472  res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
473 
474  // If there was no standalone localized image, check if there is an overlay.
475  if(res && loc_location.empty()) {
476  const std::string ovr_location = filesystem::get_localized_path(location, "--overlay");
477  if(!ovr_location.empty()) {
478  add_localized_overlay(ovr_location, res);
479  }
480  }
481  }
482  }
483 
484  if(!res && !loc.get_filename().empty()) {
485  ERR_DP << "could not open image '" << loc.get_filename() << "'" << std::endl;
488  }
489 
490  return res;
491 }
492 
494 {
495  surface surf = get_image(loc.get_filename(), UNSCALED);
496  if(surf == nullptr) {
497  return nullptr;
498  }
499 
501 
502  while(!mods.empty()) {
503  modification* mod = mods.top();
504 
505  try {
506  surf = (*mod)(surf);
507  } catch(const image::modification::imod_exception& e) {
508  std::ostringstream ss;
509  ss << "\n";
510 
511  for(const std::string& mod_name : utils::parenthetical_split(loc.get_modifications(), '~')) {
512  ss << "\t" << mod_name << "\n";
513  }
514 
515  ERR_CFG << "Failed to apply a modification to an image:\n"
516  << "Image: " << loc.get_filename() << "\n"
517  << "Modifications: " << ss.str() << "\n"
518  << "Error: " << e.message << "\n";
519  }
520 
521  // NOTE: do this *after* applying the mod or you'll get crashes!
522  mods.pop();
523  }
524 
525  if(loc.get_loc().valid()) {
526  SDL_Rect srcrect = sdl::create_rect(
527  ((tile_size * 3) / 4) * loc.get_loc().x,
528  tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
529  tile_size,
530  tile_size
531  );
532 
533  if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
534  srcrect.x += surf->w / 2 - loc.get_center_x();
535  srcrect.y += surf->h / 2 - loc.get_center_y();
536  }
537 
538  // cut and hex mask, but also check and cache if empty result
539  surface cut(cut_surface(surf, srcrect));
540  bool is_empty = false;
541  surf = mask_surface(cut, get_hexmask(), &is_empty);
542 
543  // discard empty images to free memory
544  if(is_empty) {
545  // Safe because those images are only used by terrain rendering
546  // and it filters them out.
547  // A safer and more general way would be to keep only one copy of it
548  surf = nullptr;
549  }
550 
551  loc.add_to_cache(is_empty_hex_, is_empty);
552  }
553 
554  return surf;
555 }
556 
558 {
559  surface surf;
560 
561  parsed_data_URI parsed{loc.get_filename()};
562 
563  if(!parsed.good) {
564  std::string_view fn = loc.get_filename();
565  std::string_view stripped = fn.substr(0, fn.find(","));
566  ERR_DP << "Invalid data URI: " << stripped << std::endl;
567  } else if(parsed.mime.substr(0, 5) != "image") {
568  ERR_DP << "Data URI not of image MIME type: " << parsed.mime << std::endl;
569  } else {
570  const std::vector<uint8_t> image_data = base64::decode(parsed.data);
571  filesystem::rwops_ptr rwops{SDL_RWFromConstMem(image_data.data(), image_data.size()), &SDL_FreeRW};
572 
573  if(image_data.empty()) {
574  ERR_DP << "Invalid encoding in data URI" << std::endl;
575  } else if(parsed.mime == "image/png") {
576  surf = IMG_LoadTyped_RW(rwops.release(), true, "PNG");
577  } else if(parsed.mime == "image/jpeg") {
578  surf = IMG_LoadTyped_RW(rwops.release(), true, "JPG");
579  } else {
580  ERR_DP << "Invalid image MIME type: " << parsed.mime << std::endl;
581  }
582  }
583 
584  return surf;
585 }
586 
587 // small utility function to store an int from (-256,254) to an signed char
588 static signed char col_to_uchar(int i)
589 {
590  return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
591 }
592 
593 light_string get_light_string(int op, int r, int g, int b)
594 {
595  light_string ls;
596  ls.reserve(4);
597  ls.push_back(op);
598  ls.push_back(col_to_uchar(r));
599  ls.push_back(col_to_uchar(g));
600  ls.push_back(col_to_uchar(b));
601 
602  return ls;
603 }
604 
605 static surface apply_light(surface surf, const light_string& ls)
606 {
607  // atomic lightmap operation are handled directly (important to end recursion)
608  if(ls.size() == 4) {
609  // if no lightmap (first char = -1) then we need the initial value
610  //(before the halving done for lightmap)
611  int m = ls[0] == -1 ? 2 : 1;
612  return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
613  }
614 
615  // check if the lightmap is already cached or need to be generated
616  surface lightmap = nullptr;
617  auto i = lightmaps_.find(ls);
618  if(i != lightmaps_.end()) {
619  lightmap = i->second;
620  } else {
621  // build all the paths for lightmap sources
622  static const std::string p = "terrain/light/light";
623  static const std::string lm_img[19] {
624  p + ".png",
625  p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
626  p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
627  p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
628  p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
629  p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
630  p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
631  };
632 
633  // decompose into atomic lightmap operations (4 chars)
634  for(std::size_t c = 0; c + 3 < ls.size(); c += 4) {
635  light_string sls = ls.substr(c, 4);
636 
637  // get the corresponding image and apply the lightmap operation to it
638  // This allows to also cache lightmap parts.
639  // note that we avoid infinite recursion by using only atomic operation
640  surface lts = image::get_lighted_image(lm_img[sls[0]], sls, HEXED);
641 
642  // first image will be the base where we blit the others
643  if(lightmap == nullptr) {
644  // copy the cached image to avoid modifying the cache
645  lightmap = lts.clone();
646  } else {
647  sdl_blit(lts, nullptr, lightmap, nullptr);
648  }
649  }
650 
651  // cache the result
652  lightmaps_[ls] = lightmap;
653  }
654 
655  // apply the final lightmap
656  return light_surface(surf, lightmap);
657 }
658 
660 {
661  return val_.is_data_uri_
662  ? parsed_data_URI{val_.filename_}.good
664 }
665 
667 {
668  switch(loc.get_type()) {
669  case locator::FILE:
670  if(loc.is_data_uri()){
671  return load_image_data_uri(loc);
672  } else {
673  return load_image_file(loc);
674  }
675  case locator::SUB_FILE:
676  return load_image_sub_file(loc);
677  default:
678  return surface(nullptr);
679  }
680 }
681 
683 {
684 }
685 
687 {
688  flush_cache();
689 }
690 
691 void set_color_adjustment(int r, int g, int b)
692 {
693  if(r != red_adjust || g != green_adjust || b != blue_adjust) {
694  red_adjust = r;
695  green_adjust = g;
696  blue_adjust = b;
697  tod_colored_images_.flush();
698  brightened_images_.flush();
699  lit_images_.flush();
700  lit_scaled_images_.flush();
701  reversed_images_.clear();
702  }
703 }
704 
705 void set_zoom(unsigned int amount)
706 {
707  if(amount != zoom) {
708  zoom = amount;
709  tod_colored_images_.flush();
710  brightened_images_.flush();
711  reversed_images_.clear();
712 
713  // We keep these caches if:
714  // we use default zoom (it doesn't need those)
715  // or if they are already at the wanted zoom.
716  if(zoom != tile_size && zoom != cached_zoom) {
717  scaled_to_zoom_.flush();
718  scaled_to_hex_images_.flush();
719  lit_scaled_images_.flush();
720  cached_zoom = zoom;
721  }
722  }
723 }
724 
725 static surface get_hexed(const locator& i_locator)
726 {
727  surface image(get_image(i_locator, UNSCALED));
728  // hex cut tiles, also check and cache if empty result
729  bool is_empty = false;
730  surface res = mask_surface(image, get_hexmask(), &is_empty, i_locator.get_filename());
731  i_locator.add_to_cache(is_empty_hex_, is_empty);
732  return res;
733 }
734 
735 static surface get_scaled_to_hex(const locator& i_locator)
736 {
737  surface img = get_image(i_locator, HEXED);
738  // return scale_surface(img, zoom, zoom);
739 
740  if(img) {
741  return scale_surface_nn(img, zoom, zoom);
742  }
743 
744  return surface(nullptr);
745 
746 }
747 
748 static surface get_tod_colored(const locator& i_locator)
749 {
750  surface img = get_image(i_locator, SCALED_TO_HEX);
751  return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
752 }
753 
754 static surface get_scaled_to_zoom(const locator& i_locator)
755 {
756  assert(zoom != tile_size);
757  assert(tile_size != 0);
758 
759  surface res(get_image(i_locator, UNSCALED));
760  // For some reason haloes seems to have invalid images, protect against crashing
761  if(res) {
762  return scale_surface_nn(res, ((res->w * zoom) / tile_size), ((res->h * zoom) / tile_size));
763  }
764 
765  return surface(nullptr);
766 }
767 
768 static surface get_brightened(const locator& i_locator)
769 {
770  surface image(get_image(i_locator, TOD_COLORED));
772 }
773 
774 /** translate type to a simpler one when possible */
775 static TYPE simplify_type(const image::locator& i_locator, TYPE type)
776 {
777  switch(type) {
778  case SCALED_TO_ZOOM:
779  if(zoom == tile_size) {
780  type = UNSCALED;
781  }
782 
783  break;
784  case BRIGHTENED:
786  type = TOD_COLORED;
787  }
788 
789  break;
790  default:
791  break;
792  }
793 
794  if(type == TOD_COLORED) {
795  if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
796  type = SCALED_TO_HEX;
797  }
798  }
799 
800  if(type == SCALED_TO_HEX) {
801  if(zoom == tile_size) {
802  type = HEXED;
803  }
804  }
805 
806  if(type == HEXED) {
807  // check if the image is already hex-cut by the location system
808  if(i_locator.get_loc().valid()) {
809  type = UNSCALED;
810  }
811  }
812 
813  return type;
814 }
815 
817 {
818  surface res;
819 
820  if(i_locator.is_void()) {
821  return res;
822  }
823 
824  type = simplify_type(i_locator, type);
825 
826  image_cache* imap;
827  // select associated cache
828  switch(type) {
829  case UNSCALED:
830  imap = &images_;
831  break;
832  case TOD_COLORED:
833  imap = &tod_colored_images_;
834  break;
835  case SCALED_TO_ZOOM:
836  imap = &scaled_to_zoom_;
837  break;
838  case HEXED:
839  imap = &hexed_images_;
840  break;
841  case SCALED_TO_HEX:
842  imap = &scaled_to_hex_images_;
843  break;
844  case BRIGHTENED:
845  imap = &brightened_images_;
846  break;
847  default:
848  return res;
849  }
850 
851  // return the image if already cached
852  bool tmp = i_locator.in_cache(*imap);
853 
854  if(tmp) {
855  surface result = i_locator.locate_in_cache(*imap);
856  return result;
857  }
858 
859  // not cached, generate it
860  switch(type) {
861  case UNSCALED:
862  // If type is unscaled, directly load the image from the disk.
863  res = load_from_disk(i_locator);
864  break;
865  case TOD_COLORED:
866  res = get_tod_colored(i_locator);
867  break;
868  case SCALED_TO_ZOOM:
869  res = get_scaled_to_zoom(i_locator);
870  break;
871  case HEXED:
872  res = get_hexed(i_locator);
873  break;
874  case SCALED_TO_HEX:
875  res = get_scaled_to_hex(i_locator);
876  break;
877  case BRIGHTENED:
878  res = get_brightened(i_locator);
879  break;
880  default:
881  return res;
882  }
883 
884  i_locator.add_to_cache(*imap, res);
885 
886  return res;
887 }
888 
890 {
891  surface res;
892  if(i_locator.is_void()) {
893  return res;
894  }
895 
896  if(type == SCALED_TO_HEX && zoom == tile_size) {
897  type = HEXED;
898  }
899 
900  // select associated cache
901  lit_cache* imap = &lit_images_;
902  if(type == SCALED_TO_HEX) {
903  imap = &lit_scaled_images_;
904  }
905 
906  // if no light variants yet, need to add an empty map
907  if(!i_locator.in_cache(*imap)) {
908  i_locator.add_to_cache(*imap, lit_variants());
909  }
910 
911  // need access to add it if not found
912  { // enclose reference pointing to data stored in a changing vector
913  const lit_variants& lvar = i_locator.locate_in_cache(*imap);
914  auto lvi = lvar.find(ls);
915  if(lvi != lvar.end()) {
916  return lvi->second;
917  }
918  }
919 
920  // not cached yet, generate it
921  switch(type) {
922  case HEXED:
923  res = get_image(i_locator, HEXED);
924  res = apply_light(res, ls);
925  break;
926  case SCALED_TO_HEX:
927  // we light before scaling to reuse the unscaled cache
928  res = get_lighted_image(i_locator, ls, HEXED);
929  res = scale_surface_nn(res, zoom, zoom);
930  break;
931  default:
932  break;
933  }
934 
935  // record the lighted surface in the corresponding variants cache
936  i_locator.access_in_cache(*imap)[ls] = res;
937 
938  return res;
939 }
940 
942 {
944  return get_image(terrain_mask, UNSCALED);
945 }
946 
947 bool is_in_hex(const locator& i_locator)
948 {
949  bool result;
950  {
951  if(i_locator.in_cache(in_hex_info_)) {
952  result = i_locator.locate_in_cache(in_hex_info_);
953  } else {
954  const surface image(get_image(i_locator, UNSCALED));
955 
956  bool res = in_mask_surface(image, get_hexmask());
957 
958  i_locator.add_to_cache(in_hex_info_, res);
959 
960  // std::cout << "in_hex : " << i_locator.get_filename()
961  // << " " << (res ? "yes" : "no") << "\n";
962 
963  result = res;
964  }
965  }
966 
967  return result;
968 }
969 
970 bool is_empty_hex(const locator& i_locator)
971 {
972  if(!i_locator.in_cache(is_empty_hex_)) {
973  const surface surf = get_image(i_locator, HEXED);
974  // emptiness of terrain image is checked during hex cut
975  // so, maybe in cache now, let's recheck
976  if(!i_locator.in_cache(is_empty_hex_)) {
977  // should never reach here
978  // but do it manually if it happens
979  // assert(false);
980  bool is_empty = false;
981  mask_surface(surf, get_hexmask(), &is_empty);
982  i_locator.add_to_cache(is_empty_hex_, is_empty);
983  }
984  }
985 
986  return i_locator.locate_in_cache(is_empty_hex_);
987 }
988 
990 {
991  if(surf == nullptr) {
992  return surface(nullptr);
993  }
994 
995  const auto itor = reversed_images_.find(surf);
996  if(itor != reversed_images_.end()) {
997  // sdl_add_ref(itor->second);
998  return itor->second;
999  }
1000 
1001  const surface rev(flip_surface(surf));
1002  if(rev == nullptr) {
1003  return surface(nullptr);
1004  }
1005 
1006  reversed_images_.emplace(surf, rev);
1007  // sdl_add_ref(rev);
1008  return rev;
1009 }
1010 
1011 bool exists(const image::locator& i_locator)
1012 {
1013  typedef image::locator loc;
1014  loc::type type = i_locator.get_type();
1015  if(type != loc::FILE && type != loc::SUB_FILE) {
1016  return false;
1017  }
1018 
1019  // The insertion will fail if there is already an element in the cache
1020  // and this will point to the existing element.
1021  auto [iter, success] = image_existence_map.emplace(i_locator.get_filename(), false);
1022 
1023  bool& cache = iter->second;
1024  if(success) {
1025  if(i_locator.is_data_uri()) {
1026  cache = parsed_data_URI{i_locator.get_filename()}.good;
1027  } else {
1028  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
1029  }
1030  }
1031 
1032  return cache;
1033 }
1034 
1035 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
1036 {
1037  const std::string checked_dir = dir + "/" + subdir;
1038  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
1039  return;
1040  }
1041 
1042  precached_dirs.insert(checked_dir);
1043 
1044  if(!filesystem::is_directory(checked_dir)) {
1045  return;
1046  }
1047 
1048  std::vector<std::string> files_found;
1049  std::vector<std::string> dirs_found;
1050  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::name_mode::FILE_NAME_ONLY,
1052 
1053  for(const auto& f : files_found) {
1054  image_existence_map[subdir + f] = true;
1055  }
1056 
1057  for(const auto& d : dirs_found) {
1058  precache_file_existence_internal(dir, subdir + d + "/");
1059  }
1060 }
1061 
1062 void precache_file_existence(const std::string& subdir)
1063 {
1064  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
1065 
1066  for(const auto& p : paths) {
1068  }
1069 }
1070 
1071 bool precached_file_exists(const std::string& file)
1072 {
1073  const auto b = image_existence_map.find(file);
1074  if(b != image_existence_map.end()) {
1075  return b->second;
1076  }
1077 
1078  return false;
1079 }
1080 
1081 save_result save_image(const locator& i_locator, const std::string& filename)
1082 {
1083  return save_image(get_image(i_locator), filename);
1084 }
1085 
1086 save_result save_image(const surface& surf, const std::string& filename)
1087 {
1088  if(!surf) {
1089  return save_result::no_image;
1090  }
1091 
1092  if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
1093  LOG_DP << "Writing a JPG image to " << filename << std::endl;
1094 
1095  const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
1096  return err == 0 ? save_result::success : save_result::save_failed;
1097  }
1098 
1099  if(filesystem::ends_with(filename, ".png")) {
1100  LOG_DP << "Writing a PNG image to " << filename << std::endl;
1101 
1102  const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
1103  return err == 0 ? save_result::success : save_result::save_failed;
1104  }
1105 
1107 }
1108 
1109 } // end namespace image
TYPE
Used to specify the rendering format of images.
Definition: picture.hpp:229
static surface load_image_file(const image::locator &loc)
Definition: picture.cpp:457
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
const std::string & get_modifications() const
Definition: picture.hpp:83
int get_center_y() const
Definition: picture.hpp:82
void precache_file_existence(const std::string &subdir)
Precache the existence of files in a binary path subdirectory (e.g.
Definition: picture.cpp:1062
map_location loc_
Definition: picture.hpp:137
void add_to_cache(cache_type< T > &cache, const T &data) const
Definition: picture.cpp:145
std::string filename_
Definition: picture.hpp:136
void init_index()
Definition: picture.cpp:248
int dummy
Definition: lstrlib.cpp:1347
static lg::log_domain log_display("display")
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:596
static surface get_scaled_to_hex(const locator &i_locator)
Definition: picture.cpp:735
static surface get_hexed(const locator &i_locator)
Definition: picture.cpp:725
bool precached_file_exists(const std::string &file)
Definition: picture.cpp:1071
save_result
Definition: picture.hpp:300
surface reverse_image(const surface &surf)
Horizontally flips an image.
Definition: picture.cpp:989
#define a
A modified priority queue used to order image modifications.
bool ends_with(const std::string &str, const std::string &suffix)
mini_terrain_cache_map mini_fogged_terrain_cache
Definition: picture.cpp:217
static surface load_image_data_uri(const image::locator &loc)
Definition: picture.cpp:557
cache_item< T > & get_element(int index)
Definition: picture.cpp:108
rwops_ptr make_read_RWops(const std::string &path)
#define ERR_CFG
Definition: picture.cpp:48
#define val_(o)
Definition: lobject.h:70
static signed char col_to_uchar(int i)
Definition: picture.cpp:588
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:1081
bool in_mask_surface(const surface &surf, const surface &mask)
Check if a surface fit into a mask.
Definition: utils.cpp:1203
T & access_in_cache(cache_type< T > &cache) const
Definition: picture.cpp:138
STL namespace.
void parse_arguments()
Definition: picture.cpp:260
static surface get_scaled_to_zoom(const locator &i_locator)
Definition: picture.cpp:754
std::map< t_translation::terrain_code, surface > mini_terrain_cache_map
Definition: picture.hpp:158
std::string get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type or an empty string if the file isn&#39;t prese...
type get_type() const
Definition: picture.hpp:84
#define d
Standard hexagonal tile mask applied, removing portions that don&#39;t fit.
Definition: picture.hpp:236
surface flip_surface(const surface &surf)
Definition: utils.cpp:1952
Definitions for the interface to Wesnoth Markup Language (WML).
Same as SCALED_TO_HEX, but with Time of Day color tint applied.
Definition: picture.hpp:240
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
std::string terrain_mask
void flush_cache()
Purges all image caches.
Definition: picture.cpp:222
std::string filename_
Definition: action_wml.cpp:564
map_location loc_
#define b
static TYPE simplify_type(const image::locator &i_locator, TYPE type)
translate type to a simpler one when possible
Definition: picture.cpp:775
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1011
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:1134
const T & locate_in_cache(cache_type< T > &cache) const
Definition: picture.cpp:131
static surface get_brightened(const locator &i_locator)
Definition: picture.cpp:768
#define LOG_DP
Definition: picture.cpp:45
static surface load_image_sub_file(const image::locator &loc)
Definition: picture.cpp:493
rwops_ptr make_write_RWops(const std::string &path)
static surface get_tod_colored(const locator &i_locator)
Definition: picture.cpp:748
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
Definition: picture.cpp:593
std::string modifications_
Definition: picture.hpp:138
static int last_index_
Definition: picture.cpp:220
bool valid() const
Definition: location.hpp:89
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:349
std::unordered_map< value, int > locator_finder_t
Definition: picture.hpp:144
#define ERR_DP
Definition: picture.cpp:44
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
void set_zoom(unsigned int amount)
Sets the scaling factor for images.
Definition: picture.cpp:705
std::map< light_string, surface > lit_variants
Type used to pair light possibilities with the corresponding lit surface.
Definition: picture.hpp:179
std::vector< cache_item< T > > content_
Definition: picture.cpp:121
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1659
map_display and display: classes which take care of displaying the map and game-data on the screen...
Base abstract class for an image-path modification.
static void precache_file_existence_internal(const std::string &dir, const std::string &subdir)
Definition: picture.cpp:1035
surface scale_surface_nn(const surface &surf, int w, int h)
Scale a surface using the nearest neighbor algorithm (provided by xBRZ lib)
Definition: utils.cpp:160
Generic locator abstracting the location of an image.
Definition: picture.hpp:60
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:691
std::size_t operator()(const image::locator::value &val) const
Definition: picture.cpp:76
#define ftofxp(x)
IN: float or int - OUT: fixed_t.
Definition: math.hpp:318
Image rescaled according to the zoom settings.
Definition: picture.hpp:234
Encapsulates the map of the game.
Definition: location.hpp:38
surface load_from_disk(const locator &loc)
Definition: picture.cpp:666
const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
std::vector< uint8_t > decode(std::string_view in)
Definition: base64.cpp:216
std::size_t i
Definition: function.cpp:967
logger & err()
Definition: log.cpp:77
cache_item()
Definition: picture.cpp:55
mock_party p
static lg::log_domain log_config("config")
locator & operator=(const locator &a)
Definition: picture.cpp:341
double g
Definition: astarsearch.cpp:65
static tcache cache
Definition: minimap.cpp:124
surface get_hexmask()
Retrieves the standard hexagonal tile mask.
Definition: picture.cpp:941
std::basic_string< signed char > light_string
Type used to store color information of central and adjacent hexes.
Definition: picture.hpp:176
mini_terrain_cache_map mini_highlighted_terrain_cache
Definition: picture.cpp:218
int get_center_x() const
Definition: picture.hpp:81
Declarations for File-IO.
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
Definition: filesystem.hpp:42
const bool & debug
std::string get_localized_path(const std::string &file, const std::string &suff)
Returns the localized version of the given filename, if it exists.
const std::string & get_filename() const
Definition: picture.hpp:78
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
Definition: picture.cpp:947
bool is_data_uri() const
Definition: picture.hpp:79
bool operator<(const value &a) const
Definition: picture.cpp:422
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1311
mini_terrain_cache_map mini_terrain_cache
Definition: picture.cpp:216
cache_item(const T &item)
Definition: picture.cpp:61
surface get_lighted_image(const image::locator &i_locator, const light_string &ls, TYPE type)
Caches and returns an image with a lightmap applied to it.
Definition: picture.cpp:889
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an SDL_Rect with the given dimensions.
Definition: rect.hpp:40
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:725
Image rescaled to fit into a hexagonal tile according to the zoom settings.
Definition: picture.hpp:238
Contains the SDL_Rect helper code.
#define f
const map_location & get_loc() const
Definition: picture.hpp:80
unsigned int tile_size
Definition: game_config.cpp:69
bool is_empty_hex(const locator &i_locator)
Checks if an image is empty after hex masking.
Definition: picture.cpp:970
surface brighten_image(const surface &surf, fixed_t amount)
Definition: utils.cpp:1045
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
Definition: picture.cpp:444
Functions to load and save images from/to disk.
Standard logging facilities (interface).
double hex_brightening
Definition: game_config.cpp:81
modification * top() const
Returns the top element in the queue .
bool operator==(const value &a) const
Definition: picture.cpp:408
#define e
void pop()
Removes the top element from the queue.
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:32
bool is_void() const
Returns true if the locator does not correspond to an actual image.
Definition: picture.hpp:90
mock_char c
bool in_cache(cache_type< T > &cache) const
Definition: picture.cpp:125
Unmodified original-size image.
Definition: picture.hpp:232
Exception thrown by the operator() when an error occurs.
bool file_exists() const
Tests whether the file the locator points at exists.
Definition: picture.cpp:659
static surface apply_light(surface surf, const light_string &ls)
Definition: picture.cpp:605
Same as TOD_COLORED, but also brightened.
Definition: picture.hpp:242
std::vector< std::string > parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
const std::string message
The error message regarding the failed operation.
bool loaded
Definition: picture.cpp:68