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