The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
image.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 http://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 #define GETTEXT_DOMAIN "wesnoth-lib"
21 
22 #include "image.hpp"
23 
24 #include "config.hpp"
25 #include "display.hpp"
26 #include "filesystem.hpp"
27 #include "game_config.hpp"
28 #include "gettext.hpp"
29 #include "image_modifications.hpp"
30 #include "log.hpp"
31 #include "preferences/general.hpp"
33 #include "sdl/rect.hpp"
34 #include "utils/general.hpp"
35 
36 #ifdef HAVE_LIBPNG
37 #include "SDL_SavePNG/savepng.h"
38 #endif
39 
40 #include <SDL_image.h>
41 
42 #include "utils/functional.hpp"
43 
44 #include <boost/algorithm/string.hpp>
45 #include <boost/functional/hash_fwd.hpp>
46 
47 #include <set>
48 
49 static lg::log_domain log_display("display");
50 #define ERR_DP LOG_STREAM(err, log_display)
51 #define LOG_DP LOG_STREAM(info, log_display)
52 
53 static lg::log_domain log_config("config");
54 #define ERR_CFG LOG_STREAM(err, log_config)
55 
57 
58 template<typename T>
59 struct cache_item
60 {
62  : item()
63  , loaded(false)
64  {
65  }
66 
67  cache_item(const T& item)
68  : item(item)
69  , loaded(true)
70  {
71  }
72 
73  T item;
74  bool loaded;
75 };
76 
77 namespace std
78 {
79 template<>
80 struct hash<image::locator::value>
81 {
82  size_t operator()(const image::locator::value& val) const
83  {
84  size_t hash = std::hash<unsigned>{}(val.type_);
85 
87  boost::hash_combine(hash, val.filename_);
88  }
89 
90  if(val.type_ == image::locator::SUB_FILE) {
91  boost::hash_combine(hash, val.loc_.x);
92  boost::hash_combine(hash, val.loc_.y);
93  boost::hash_combine(hash, val.center_x_);
94  boost::hash_combine(hash, val.center_y_);
95  boost::hash_combine(hash, val.modifications_);
96  }
97 
98  return hash;
99  }
100 };
101 }
102 
103 namespace image
104 {
105 template<typename T>
107 {
108 public:
110  : content_()
111  {
112  }
113 
115  {
116  if(static_cast<unsigned>(index) >= content_.size())
117  content_.resize(index + 1);
118  return content_[index];
119  }
120 
121  void flush()
122  {
123  content_.clear();
124  }
125 
126 private:
127  std::vector<cache_item<T>> content_;
128 };
129 
130 template<typename T>
132 {
133  return index_ < 0 ? false : cache.get_element(index_).loaded;
134 }
135 
136 template<typename T>
138 {
139  static T dummy;
140  return index_ < 0 ? dummy : cache.get_element(index_).item;
141 }
142 
143 template<typename T>
145 {
146  static T dummy;
147  return index_ < 0 ? dummy : cache.get_element(index_).item;
148 }
149 
150 template<typename T>
151 void locator::add_to_cache(cache_type<T>& cache, const T& data) const
152 {
153  if(index_ >= 0) {
154  cache.get_element(index_) = cache_item<T>(data);
155  }
156 }
157 }
158 
159 namespace
160 {
161 image::locator::locator_finder_t locator_finder;
162 
163 /** Definition of all image maps */
164 image::image_cache images_, scaled_to_zoom_, hexed_images_, scaled_to_hex_images_, tod_colored_images_,
165  brightened_images_;
166 
167 // cache storing if each image fit in a hex
168 image::bool_cache in_hex_info_;
169 
170 // cache storing if this is an empty hex
171 image::bool_cache is_empty_hex_;
172 
173 // caches storing the different lighted cases for each image
174 image::lit_cache lit_images_, lit_scaled_images_;
175 // caches storing each lightmap generated
176 image::lit_variants lightmaps_;
177 
178 // const int cache_version_ = 0;
179 
180 std::map<std::string, bool> image_existence_map;
181 
182 // directories where we already cached file existence
183 std::set<std::string> precached_dirs;
184 
185 std::map<surface, surface> reversed_images_;
186 
187 int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
188 
189 /** List of colors used by the TC image modification */
190 std::vector<std::string> team_colors;
191 
192 unsigned int zoom = tile_size;
193 unsigned int cached_zoom = 0;
194 
195 /** Algorithm choices */
196 // typedef std::function<surface(const surface &, int, int)> scaling_function;
197 typedef surface (*scaling_function)(const surface&, int, int);
198 scaling_function scale_to_zoom_func;
199 scaling_function scale_to_hex_func;
200 
201 } // end anon namespace
202 
203 namespace image
204 {
208 
209 static int last_index_ = 0;
210 
212 {
213 #ifdef _OPENMP
214 #pragma omp critical(image_cache)
215 #endif //_OPENMP
216  {
217  images_.flush();
218  hexed_images_.flush();
219  tod_colored_images_.flush();
220  scaled_to_zoom_.flush();
221  scaled_to_hex_images_.flush();
222  brightened_images_.flush();
223  lit_images_.flush();
224  lit_scaled_images_.flush();
225  in_hex_info_.flush();
226  is_empty_hex_.flush();
227  mini_terrain_cache.clear();
228  mini_fogged_terrain_cache.clear();
229  mini_highlighted_terrain_cache.clear();
230  reversed_images_.clear();
231  image_existence_map.clear();
232  precached_dirs.clear();
233  }
234  /* We can't reset last_index_, since some locators are still alive
235  when using :refresh. That would cause them to point to the wrong
236  images. Not resetting the variable causes a memory leak, though. */
237  // last_index_ = 0;
238 }
239 
241 {
242  auto i = locator_finder.find(val_);
243 
244  if(i == locator_finder.end()) {
245  index_ = last_index_++;
246  locator_finder.emplace(val_, index_);
247  } else {
248  index_ = i->second;
249  }
250 }
251 
253 {
254  std::string& fn = val_.filename_;
255  if(fn.empty()) {
256  return;
257  }
258 
259  size_t markup_field = fn.find('~');
260 
261  if(markup_field != std::string::npos) {
262  val_.type_ = SUB_FILE;
263  val_.modifications_ = fn.substr(markup_field, fn.size() - markup_field);
264  fn = fn.substr(0, markup_field);
265  }
266 }
267 
269  : index_(-1)
270  , val_()
271 {
272 }
273 
274 locator::locator(const locator& a, const std::string& mods)
275  : index_(-1)
276  , val_(a.val_)
277 {
278  if(!mods.empty()) {
279  val_.modifications_ += mods;
280  val_.type_ = SUB_FILE;
281  init_index();
282  } else {
283  index_ = a.index_;
284  }
285 }
286 
287 locator::locator(const char* filename)
288  : index_(-1)
289  , val_(filename)
290 {
291  parse_arguments();
292  init_index();
293 }
294 
296  : index_(-1)
297  , val_(filename)
298 {
299  parse_arguments();
300  init_index();
301 }
302 
304  : index_(-1)
305  , val_(filename, modifications)
306 {
307  init_index();
308 }
309 
311  const map_location& loc,
312  int center_x,
313  int center_y,
314  const std::string& modifications)
315  : index_(-1)
316  , val_(filename, loc, center_x, center_y, modifications)
317 {
318  init_index();
319 }
320 
322 {
323  index_ = a.index_;
324  val_ = a.val_;
325 
326  return *this;
327 }
328 
330  : type_(a.type_)
331  , filename_(a.filename_)
332  , loc_(a.loc_)
333  , modifications_(a.modifications_)
334  , center_x_(a.center_x_)
335  , center_y_(a.center_y_)
336 {
337 }
338 
340  : type_(NONE)
341  , filename_()
342  , loc_()
343  , modifications_()
344  , center_x_(0)
345  , center_y_(0)
346 {
347 }
348 
349 locator::value::value(const char* filename)
350  : type_(FILE)
351  , filename_(filename)
352  , loc_()
353  , modifications_()
354  , center_x_(0)
355  , center_y_(0)
356 {
357 }
358 
360  : type_(FILE)
361  , filename_(filename)
362  , loc_()
363  , modifications_()
364  , center_x_(0)
365  , center_y_(0)
366 {
367 }
368 
370  : type_(SUB_FILE)
371  , filename_(filename)
372  , loc_()
373  , modifications_(modifications)
374  , center_x_(0)
375  , center_y_(0)
376 {
377 }
378 
380  const map_location& loc,
381  int center_x,
382  int center_y,
383  const std::string& modifications)
384  : type_(SUB_FILE)
385  , filename_(filename)
386  , loc_(loc)
387  , modifications_(modifications)
388  , center_x_(center_x)
389  , center_y_(center_y)
390 {
391 }
392 
394 {
395  if(a.type_ != type_) {
396  return false;
397  } else if(type_ == FILE) {
398  return filename_ == a.filename_;
399  } else if(type_ == SUB_FILE) {
400  return filename_ == a.filename_ && loc_ == a.loc_ && modifications_ == a.modifications_
401  && center_x_ == a.center_x_ && center_y_ == a.center_y_;
402  }
403 
404  return false;
405 }
406 
408 {
409  if(type_ != a.type_) {
410  return type_ < a.type_;
411  } else if(type_ == FILE) {
412  return filename_ < a.filename_;
413  } else if(type_ == SUB_FILE) {
414  if(filename_ != a.filename_)
415  return filename_ < a.filename_;
416  if(loc_ != a.loc_)
417  return loc_ < a.loc_;
418  if(center_x_ != a.center_x_)
419  return center_x_ < a.center_x_;
420  if(center_y_ != a.center_y_)
421  return center_y_ < a.center_y_;
422  return (modifications_ < a.modifications_);
423  }
424 
425  return false;
426 }
427 
428 // Check if localized file is up-to-date according to l10n track index.
429 // Make sure only that the image is not explicitly recorded as fuzzy,
430 // in order to be able to use non-tracked images (e.g. from UMC).
431 static std::set<std::string> fuzzy_localized_files;
432 static bool localized_file_uptodate(const std::string& loc_file)
433 {
434  if(fuzzy_localized_files.empty()) {
435  // First call, parse track index to collect fuzzy files by path.
436  std::string fsep = "\xC2\xA6"; // UTF-8 for "broken bar"
437  std::string trackpath = filesystem::get_binary_file_location("", "l10n-track");
438  std::string contents = filesystem::read_file(trackpath);
439 
440  for(const std::string& line : utils::split(contents, '\n')) {
441  size_t p1 = line.find(fsep);
442  if(p1 == std::string::npos) {
443  continue;
444  }
445 
446  std::string state = line.substr(0, p1);
447  boost::trim(state);
448  if(state == "fuzzy") {
449  size_t p2 = line.find(fsep, p1 + fsep.length());
450  if(p2 == std::string::npos) {
451  continue;
452  }
453 
454  std::string relpath = line.substr(p1 + fsep.length(), p2 - p1 - fsep.length());
455  fuzzy_localized_files.insert(game_config::path + '/' + relpath);
456  }
457  }
458 
459  fuzzy_localized_files.insert(""); // make sure not empty any more
460  }
461 
462  return fuzzy_localized_files.count(loc_file) == 0;
463 }
464 
465 // Return path to localized counterpart of the given file, if any, or empty string.
466 // Localized counterpart may also be requested to have a suffix to base name.
467 static std::string get_localized_path(const std::string& file, const std::string& suff = "")
468 {
470  std::string base = filesystem::base_name(file);
471 
472  const size_t pos_ext = base.rfind(".");
473 
474  std::string loc_base;
475  if(pos_ext != std::string::npos) {
476  loc_base = base.substr(0, pos_ext) + suff + base.substr(pos_ext);
477  } else {
478  loc_base = base + suff;
479  }
480 
481  // TRANSLATORS: This is the language code which will be used
482  // to store and fetch localized non-textual resources, such as images,
483  // when they exist. Normally it is just the code of the PO file itself,
484  // e.g. "de" of de.po for German. But it can also be a comma-separated
485  // list of language codes by priority, when the localized resource
486  // found for first of those languages will be used. This is useful when
487  // two languages share sufficient commonality, that they can use each
488  // other's resources rather than duplicating them. For example,
489  // Swedish (sv) and Danish (da) are such, so Swedish translator could
490  // translate this message as "sv,da", while Danish as "da,sv".
491  std::vector<std::string> langs = utils::split(_("language code for localized resources^en_US"));
492 
493  // In case even the original image is split into base and overlay,
494  // add en_US with lowest priority, since the message above will
495  // not have it when translated.
496  langs.push_back("en_US");
497  for(const std::string& lang : langs) {
498  std::string loc_file = dir + "/" + "l10n" + "/" + lang + "/" + loc_base;
499  if(filesystem::file_exists(loc_file) && localized_file_uptodate(loc_file)) {
500  return loc_file;
501  }
502  }
503 
504  return "";
505 }
506 
507 // Load overlay image and compose it with the original surface.
508 static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
509 {
511  surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
512  if(ovr_surf.null()) {
513  return;
514  }
515 
516  SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
517 
518  sdl_blit(ovr_surf, 0, orig_surf, &area);
519 }
520 
522 {
523  surface res;
524 
526 
527  {
528  if(!location.empty()) {
529  // Check if there is a localized image.
530  const std::string loc_location = get_localized_path(location);
531  if(!loc_location.empty()) {
532  location = loc_location;
533  }
535  res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
536  // If there was no standalone localized image, check if there is an overlay.
537  if(!res.null() && loc_location.empty()) {
538  const std::string ovr_location = get_localized_path(location, "--overlay");
539  if(!ovr_location.empty()) {
540  add_localized_overlay(ovr_location, res);
541  }
542  }
543  }
544  }
545 
546  if(res.null() && !loc.get_filename().empty()) {
547  ERR_DP << "could not open image '" << loc.get_filename() << "'" << std::endl;
550  }
551 
552  return res;
553 }
554 
556 {
557  surface surf = get_image(loc.get_filename(), UNSCALED);
558  if(surf == nullptr) {
559  return nullptr;
560  }
561 
563 
564  while(!mods.empty()) {
565  modification* mod = mods.top();
566 
567  try {
568  surf = (*mod)(surf);
569  } catch(const image::modification::imod_exception& e) {
570  ERR_CFG << "Failed to apply a modification to an image:\n"
571  << "Image: " << loc.get_filename() << ".\n"
572  << "Modifications: " << loc.get_modifications() << ".\n"
573  << "Error: " << e.message;
574  }
575 
576  // NOTE: do this *after* applying the mod or you'll get crashes!
577  mods.pop();
578  }
579 
580  if(loc.get_loc().valid()) {
581  SDL_Rect srcrect = sdl::create_rect(
582  ((tile_size * 3) / 4) * loc.get_loc().x,
583  tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
584  tile_size,
585  tile_size
586  );
587 
588  if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
589  srcrect.x += surf->w / 2 - loc.get_center_x();
590  srcrect.y += surf->h / 2 - loc.get_center_y();
591  }
592 
593  // cut and hex mask, but also check and cache if empty result
594  surface cut(cut_surface(surf, srcrect));
595  bool is_empty = false;
596  surf = mask_surface(cut, get_hexmask(), &is_empty);
597 
598  // discard empty images to free memory
599  if(is_empty) {
600  // Safe because those images are only used by terrain rendering
601  // and it filters them out.
602  // A safer and more general way would be to keep only one copy of it
603  surf = nullptr;
604  }
605 
606  loc.add_to_cache(is_empty_hex_, is_empty);
607  }
608 
609  return surf;
610 }
611 
612 // small utility function to store an int from (-256,254) to an signed char
613 static signed char col_to_uchar(int i)
614 {
615  return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
616 }
617 
618 light_string get_light_string(int op, int r, int g, int b)
619 {
620  light_string ls;
621  ls.reserve(4);
622  ls.push_back(op);
623  ls.push_back(col_to_uchar(r));
624  ls.push_back(col_to_uchar(g));
625  ls.push_back(col_to_uchar(b));
626 
627  return ls;
628 }
629 
630 static surface apply_light(surface surf, const light_string& ls)
631 {
632  // atomic lightmap operation are handled directly (important to end recursion)
633  if(ls.size() == 4) {
634  // if no lightmap (first char = -1) then we need the initial value
635  //(before the halving done for lightmap)
636  int m = ls[0] == -1 ? 2 : 1;
637  return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
638  }
639 
640  // check if the lightmap is already cached or need to be generated
641  surface lightmap = nullptr;
642  auto i = lightmaps_.find(ls);
643  if(i != lightmaps_.end()) {
644  lightmap = i->second;
645  } else {
646  // build all the paths for lightmap sources
647  static const std::string p = "terrain/light/light";
648  static const std::string lm_img[19] {
649  p + ".png",
650  p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
651  p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
652  p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
653  p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
654  p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
655  p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
656  };
657 
658  // decompose into atomic lightmap operations (4 chars)
659  for(size_t c = 0; c + 3 < ls.size(); c += 4) {
660  light_string sls = ls.substr(c, 4);
661 
662  // get the corresponding image and apply the lightmap operation to it
663  // This allows to also cache lightmap parts.
664  // note that we avoid infinite recursion by using only atomic operation
665  surface lts = image::get_lighted_image(lm_img[sls[0]], sls, HEXED);
666 
667  // first image will be the base where we blit the others
668  if(lightmap == nullptr) {
669  // copy the cached image to avoid modifying the cache
670  lightmap = make_neutral_surface(lts);
671  } else {
672  sdl_blit(lts, nullptr, lightmap, nullptr);
673  }
674  }
675 
676  // cache the result
677  lightmaps_[ls] = lightmap;
678  }
679 
680  // apply the final lightmap
681  return light_surface(surf, lightmap);
682 }
683 
685 {
686  return !filesystem::get_binary_file_location("images", val_.filename_).empty();
687 }
688 
690 {
691  switch(loc.get_type()) {
692  case locator::FILE:
693  return load_image_file(loc);
694  case locator::SUB_FILE:
695  return load_image_sub_file(loc);
696  default:
697  return surface(nullptr);
698  }
699 }
700 
702 {
703 }
704 
706 {
707  flush_cache();
708 }
709 
710 static SDL_PixelFormat last_pixel_format;
711 
712 void set_pixel_format(SDL_PixelFormat* format)
713 {
714  assert(format != nullptr);
715 
716  SDL_PixelFormat& f = *format;
717  SDL_PixelFormat& l = last_pixel_format;
718  // if the pixel format change, we clear the cache,
719  // because some images are now optimized for the wrong display format
720  // FIXME: 8 bpp use palette, need to compare them. For now assume a change
721  if(format->BitsPerPixel == 8
722  || f.BitsPerPixel != l.BitsPerPixel
723  || f.BytesPerPixel != l.BytesPerPixel
724  || f.Rmask != l.Rmask
725  || f.Gmask != l.Gmask
726  || f.Bmask != l.Bmask
727 // || f.Amask != l.Amask This field in not checked, not sure why.
728  || f.Rloss != l.Rloss
729  || f.Gloss != l.Gloss
730  || f.Bloss != l.Bloss
731 // || f.Aloss != l.Aloss This field in not checked, not sure why.
732  || f.Rshift != l.Rshift
733  || f.Gshift != l.Gshift
734  || f.Bshift != l.Bshift
735 // || f.Ashift != l.Ashift This field in not checked, not sure why.
736  )
737  {
738  LOG_DP << "detected a new display format\n";
739  flush_cache();
740  }
741 
742  last_pixel_format = *format;
743 }
744 
745 void set_color_adjustment(int r, int g, int b)
746 {
747  if(r != red_adjust || g != green_adjust || b != blue_adjust) {
748  red_adjust = r;
749  green_adjust = g;
750  blue_adjust = b;
751  tod_colored_images_.flush();
752  brightened_images_.flush();
753  lit_images_.flush();
754  lit_scaled_images_.flush();
755  reversed_images_.clear();
756  }
757 }
758 
759 void set_team_colors(const std::vector<std::string>* colors)
760 {
761  if(colors == nullptr) {
762  team_colors.clear();
763  } else {
764  team_colors = *colors;
765  }
766 }
767 
768 const std::vector<std::string>& get_team_colors()
769 {
770  return team_colors;
771 }
772 
773 void set_zoom(unsigned int amount)
774 {
775  if(amount != zoom) {
776  zoom = amount;
777  tod_colored_images_.flush();
778  brightened_images_.flush();
779  reversed_images_.clear();
780 
781  // We keep these caches if:
782  // we use default zoom (it doesn't need those)
783  // or if they are already at the wanted zoom.
784  if(zoom != tile_size && zoom != cached_zoom) {
785  scaled_to_zoom_.flush();
786  scaled_to_hex_images_.flush();
787  lit_scaled_images_.flush();
788  cached_zoom = zoom;
789  }
790  }
791 }
792 
793 // F should be a scaling algorithm without "integral" zoom limitations
794 template<scaling_function F>
795 static surface scale_xbrz_helper(const surface& res, int w, int h)
796 {
797  int best_integer_zoom = std::min(w / res->w, h / res->h);
798  int legal_zoom = utils::clamp(best_integer_zoom, 1, 5);
799  return F(scale_surface_xbrz(res, legal_zoom), w, h);
800 }
801 
803 
804 static scaling_function select_algorithm(SCALING_ALGORITHM algo)
805 {
806  switch(algo.v) {
807  case SCALING_ALGORITHM::LINEAR: {
808  scaling_function result = &scale_surface;
809  return result;
810  }
811  case SCALING_ALGORITHM::NEAREST_NEIGHBOR: {
812  scaling_function result = &scale_surface_nn;
813  return result;
814  }
815  case SCALING_ALGORITHM::XBRZ_LIN: {
816  scaling_function result = &scale_xbrz_helper<scale_surface>;
817  return result;
818  }
819  case SCALING_ALGORITHM::XBRZ_NN: {
820  scaling_function result = &scale_xbrz_helper<scale_surface_nn>;
821  return result;
822  }
823  default:
824  assert(false && "I don't know how to implement this scaling algorithm");
825  throw 42;
826  }
827 }
828 
829 static surface get_hexed(const locator& i_locator)
830 {
831  surface image(get_image(i_locator, UNSCALED));
832  // hex cut tiles, also check and cache if empty result
833  bool is_empty = false;
834  surface res = mask_surface(image, get_hexmask(), &is_empty, i_locator.get_filename());
835  i_locator.add_to_cache(is_empty_hex_, is_empty);
836  return res;
837 }
838 
839 static surface get_scaled_to_hex(const locator& i_locator)
840 {
841  surface img = get_image(i_locator, HEXED);
842  // return scale_surface(img, zoom, zoom);
843 
844  if(!img.null()) {
845  return scale_to_hex_func(img, zoom, zoom);
846  }
847 
848  return surface(nullptr);
849 
850 }
851 
852 static surface get_tod_colored(const locator& i_locator)
853 {
854  surface img = get_image(i_locator, SCALED_TO_HEX);
855  return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
856 }
857 
858 static surface get_scaled_to_zoom(const locator& i_locator)
859 {
860  assert(zoom != tile_size);
861  assert(tile_size != 0);
862 
863  surface res(get_image(i_locator, UNSCALED));
864  // For some reason haloes seems to have invalid images, protect against crashing
865  if(!res.null()) {
866  return scale_to_zoom_func(res, ((res->w * zoom) / tile_size), ((res->h * zoom) / tile_size));
867  }
868 
869  return surface(nullptr);
870 }
871 
872 static surface get_brightened(const locator& i_locator)
873 {
874  surface image(get_image(i_locator, TOD_COLORED));
876 }
877 
878 /// translate type to a simpler one when possible
879 static TYPE simplify_type(const image::locator& i_locator, TYPE type)
880 {
881  switch(type) {
882  case SCALED_TO_ZOOM:
883  if(zoom == tile_size) {
884  type = UNSCALED;
885  }
886 
887  break;
888  case BRIGHTENED:
890  type = TOD_COLORED;
891  }
892 
893  break;
894  default:
895  break;
896  }
897 
898  if(type == TOD_COLORED) {
899  if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
900  type = SCALED_TO_HEX;
901  }
902  }
903 
904  if(type == SCALED_TO_HEX) {
905  if(zoom == tile_size) {
906  type = HEXED;
907  }
908  }
909 
910  if(type == HEXED) {
911  // check if the image is already hex-cut by the location system
912  if(i_locator.get_loc().valid()) {
913  type = UNSCALED;
914  }
915  }
916 
917  return type;
918 }
919 
921 {
922  surface res;
923 
924  if(i_locator.is_void()) {
925  return res;
926  }
927 
928  type = simplify_type(i_locator, type);
929 
930  image_cache* imap;
931  // select associated cache
932  switch(type) {
933  case UNSCALED:
934  imap = &images_;
935  break;
936  case TOD_COLORED:
937  imap = &tod_colored_images_;
938  break;
939  case SCALED_TO_ZOOM:
940  imap = &scaled_to_zoom_;
941  break;
942  case HEXED:
943  imap = &hexed_images_;
944  break;
945  case SCALED_TO_HEX:
946  imap = &scaled_to_hex_images_;
947  break;
948  case BRIGHTENED:
949  imap = &brightened_images_;
950  break;
951  default:
952  return res;
953  }
954 
955  // return the image if already cached
956  bool tmp;
957 #ifdef _OPENMP
958 #pragma omp critical(image_cache)
959 #endif //_OPENMP
960  tmp = i_locator.in_cache(*imap);
961 
962  if(tmp) {
963  surface result;
964 #ifdef _OPENMP
965 #pragma omp critical(image_cache)
966 #endif //_OPENMP
967  result = i_locator.locate_in_cache(*imap);
968  return result;
969  }
970 
971  // not cached, generate it
972  switch(type) {
973  case UNSCALED:
974  // If type is unscaled, directly load the image from the disk.
975  res = load_from_disk(i_locator);
976  break;
977  case TOD_COLORED:
978  res = get_tod_colored(i_locator);
979  break;
980  case SCALED_TO_ZOOM:
981  res = get_scaled_to_zoom(i_locator);
982  break;
983  case HEXED:
984  res = get_hexed(i_locator);
985  break;
986  case SCALED_TO_HEX:
987  res = get_scaled_to_hex(i_locator);
988  break;
989  case BRIGHTENED:
990  res = get_brightened(i_locator);
991  break;
992  default:
993  return res;
994  }
995 
996 #ifdef _OPENMP
997 #pragma omp critical(image_cache)
998 #endif //_OPENMP
999  i_locator.add_to_cache(*imap, res);
1000 
1001  return res;
1002 }
1003 
1005 {
1006  surface res;
1007  if(i_locator.is_void()) {
1008  return res;
1009  }
1010 
1011  if(type == SCALED_TO_HEX && zoom == tile_size) {
1012  type = HEXED;
1013  }
1014 
1015  // select associated cache
1016  lit_cache* imap = &lit_images_;
1017  if(type == SCALED_TO_HEX) {
1018  imap = &lit_scaled_images_;
1019  }
1020 
1021  // if no light variants yet, need to add an empty map
1022  if(!i_locator.in_cache(*imap)) {
1023  i_locator.add_to_cache(*imap, lit_variants());
1024  }
1025 
1026  // need access to add it if not found
1027  { // enclose reference pointing to data stored in a changing vector
1028  const lit_variants& lvar = i_locator.locate_in_cache(*imap);
1029  auto lvi = lvar.find(ls);
1030  if(lvi != lvar.end()) {
1031  return lvi->second;
1032  }
1033  }
1034 
1035  // not cached yet, generate it
1036  switch(type) {
1037  case HEXED:
1038  res = get_image(i_locator, HEXED);
1039  res = apply_light(res, ls);
1040  break;
1041  case SCALED_TO_HEX:
1042  // we light before scaling to reuse the unscaled cache
1043  res = get_lighted_image(i_locator, ls, HEXED);
1044  res = scale_surface(res, zoom, zoom);
1045  break;
1046  default:
1047  break;
1048  }
1049 
1050  // record the lighted surface in the corresponding variants cache
1051  i_locator.access_in_cache(*imap)[ls] = res;
1052 
1053  return res;
1054 }
1055 
1057 {
1059  return get_image(terrain_mask, UNSCALED);
1060 }
1061 
1062 bool is_in_hex(const locator& i_locator)
1063 {
1064  bool result;
1065 #ifdef _OPENMP
1066 #pragma omp critical(in_hex_info_)
1067 #endif //_OPENMP
1068  {
1069  if(i_locator.in_cache(in_hex_info_)) {
1070  result = i_locator.locate_in_cache(in_hex_info_);
1071  } else {
1072  const surface image(get_image(i_locator, UNSCALED));
1073 
1074  bool res = in_mask_surface(image, get_hexmask());
1075 
1076  i_locator.add_to_cache(in_hex_info_, res);
1077 
1078  // std::cout << "in_hex : " << i_locator.get_filename()
1079  // << " " << (res ? "yes" : "no") << "\n";
1080 
1081  result = res;
1082  }
1083  }
1084 
1085  return result;
1086 }
1087 
1088 bool is_empty_hex(const locator& i_locator)
1089 {
1090  if(!i_locator.in_cache(is_empty_hex_)) {
1091  const surface surf = get_image(i_locator, HEXED);
1092  // emptiness of terrain image is checked during hex cut
1093  // so, maybe in cache now, let's recheck
1094  if(!i_locator.in_cache(is_empty_hex_)) {
1095  // should never reach here
1096  // but do it manually if it happens
1097  // assert(false);
1098  bool is_empty = false;
1099  mask_surface(surf, get_hexmask(), &is_empty);
1100  i_locator.add_to_cache(is_empty_hex_, is_empty);
1101  }
1102  }
1103 
1104  return i_locator.locate_in_cache(is_empty_hex_);
1105 }
1106 
1108 {
1109  if(surf == nullptr) {
1110  return surface(nullptr);
1111  }
1112 
1113  const auto itor = reversed_images_.find(surf);
1114  if(itor != reversed_images_.end()) {
1115  // sdl_add_ref(itor->second);
1116  return itor->second;
1117  }
1118 
1119  const surface rev(flip_surface(surf));
1120  if(rev == nullptr) {
1121  return surface(nullptr);
1122  }
1123 
1124  reversed_images_.emplace(surf, rev);
1125  // sdl_add_ref(rev);
1126  return rev;
1127 }
1128 
1129 bool exists(const image::locator& i_locator)
1130 {
1131  typedef image::locator loc;
1132  loc::type type = i_locator.get_type();
1133  if(type != loc::FILE && type != loc::SUB_FILE) {
1134  return false;
1135  }
1136 
1137  // The insertion will fail if there is already an element in the cache
1138  // and this will point to the existing element.
1139  auto iter = image_existence_map.begin();
1140  bool success;
1141 
1142  std::tie(iter, success) = image_existence_map.emplace(i_locator.get_filename(), false);
1143 
1144  bool& cache = iter->second;
1145  if(success) {
1146  cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
1147  }
1148 
1149  return cache;
1150 }
1151 
1152 static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
1153 {
1154  const std::string checked_dir = dir + "/" + subdir;
1155  if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
1156  return;
1157  }
1158 
1159  precached_dirs.insert(checked_dir);
1160 
1161  if(!filesystem::is_directory(checked_dir)) {
1162  return;
1163  }
1164 
1165  std::vector<std::string> files_found;
1166  std::vector<std::string> dirs_found;
1167  filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::FILE_NAME_ONLY,
1169 
1170  for(const auto& f : files_found) {
1171  image_existence_map[subdir + f] = true;
1172  }
1173 
1174  for(const auto& d : dirs_found) {
1175  precache_file_existence_internal(dir, subdir + d + "/");
1176  }
1177 }
1178 
1180 {
1181  const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
1182 
1183  for(const auto& p : paths) {
1185  }
1186 }
1187 
1189 {
1190  const auto b = image_existence_map.find(file);
1191  if(b != image_existence_map.end()) {
1192  return b->second;
1193  }
1194 
1195  return false;
1196 }
1197 
1198 bool save_image(const locator& i_locator, const std::string& filename)
1199 {
1200  return save_image(get_image(i_locator), filename);
1201 }
1202 
1203 bool save_image(const surface& surf, const std::string& filename)
1204 {
1205  if(surf.null()) {
1206  return false;
1207  }
1208 
1209 #ifdef HAVE_LIBPNG
1210  if(!filesystem::ends_with(filename, ".bmp")) {
1211  LOG_DP << "Writing a png image to " << filename << std::endl;
1212 
1213  surface tmp = SDL_PNGFormatAlpha(surf.get());
1214  const int err = SDL_SavePNG_RW(tmp, filesystem::make_write_RWops(filename).release(), 1); //1 means SDL takes ownership of the RWops
1215  return err == 0;
1216  }
1217 #endif
1218 
1219  LOG_DP << "Writing a bmp image to " << filename << std::endl;
1220  return SDL_SaveBMP(surf, filename.c_str()) == 0;
1221 }
1222 
1224 {
1226  try {
1227  algo = SCALING_ALGORITHM::string_to_enum(preferences::get("scale_hex"));
1228  } catch(bad_enum_cast&) {
1229  }
1230 
1231  scale_to_hex_func = select_algorithm(algo);
1232 
1234  try {
1235  algo = SCALING_ALGORITHM::string_to_enum(preferences::get("scale_zoom"));
1236  } catch(bad_enum_cast&) {
1237  }
1238 
1239  scale_to_zoom_func = select_algorithm(algo);
1240 
1241  return true;
1242 }
1243 
1244 } // end namespace image
TYPE
UNSCALED : image will be drawn "as is" without changing size, even in case of redraw SCALED_TO_ZOOM :...
Definition: image.hpp:189
static surface load_image_file(const image::locator &loc)
Definition: image.cpp:521
surface get_image(const image::locator &i_locator, TYPE type)
function to get the surface corresponding to an image.
Definition: image.cpp:920
preferences::SCALING_ALGORITHM SCALING_ALGORITHM
Definition: image.cpp:802
void precache_file_existence(const std::string &subdir)
precache the existence of files in the subdir (ex: "terrain/")
Definition: image.cpp:1179
static surface scale_xbrz_helper(const surface &res, int w, int h)
Definition: image.cpp:795
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
void set_pixel_format(SDL_PixelFormat *format)
sets the pixel format used by the images.
Definition: image.cpp:712
map_location loc_
Definition: image.hpp:56
bool is_void() const
Definition: image.hpp:95
std::string filename_
Definition: image.hpp:55
value val_
Definition: image.hpp:121
void init_index()
Definition: image.cpp:240
bool operator<(const value &a) const
Definition: image.cpp:407
int dummy
Definition: lstrlib.cpp:1125
const SCALING_ALGORITHM default_scaling_algorithm
Definition: general.cpp:76
surface adjust_surface_color(const surface &surf, int red, int green, int blue)
Definition: utils.cpp:681
bool null() const
Definition: surface.hpp:79
void set_team_colors(const std::vector< std::string > *colors)
set the team colors used by the TC image modification use a vector with one string for each team usin...
Definition: image.cpp:759
static surface get_scaled_to_hex(const locator &i_locator)
Definition: image.cpp:839
static surface get_hexed(const locator &i_locator)
Definition: image.cpp:829
bool precached_file_exists(const std::string &file)
Definition: image.cpp:1188
int get_center_y() const
Definition: image.hpp:88
surface reverse_image(const surface &surf)
function to reverse an image.
Definition: image.cpp:1107
static std::string get_localized_path(const std::string &file, const std::string &suff="")
Definition: image.cpp:467
#define a
A modified priority queue used to order image modifications.
bool ends_with(const std::string &str, const std::string &suffix)
bool file_exists() const
Tests whether the file the locater points at exists.
Definition: image.cpp:684
mini_terrain_cache_map mini_fogged_terrain_cache
Definition: image.cpp:206
type get_type() const
Definition: image.hpp:90
cache_item< T > & get_element(int index)
Definition: image.cpp:114
rwops_ptr make_read_RWops(const std::string &path)
#define val_(o)
Definition: lobject.h:123
static signed char col_to_uchar(int i)
Definition: image.cpp:613
#define LOG_DP
Definition: image.cpp:51
#define ERR_CFG
Definition: image.cpp:54
bool in_mask_surface(const surface &surf, const surface &mask)
Check if a surface fit into a mask.
Definition: utils.cpp:1288
void add_to_cache(cache_type< T > &cache, const T &data) const
Definition: image.cpp:151
STL namespace.
#define h
void parse_arguments()
Definition: image.cpp:252
static surface get_scaled_to_zoom(const locator &i_locator)
Definition: image.cpp:858
std::map< t_translation::terrain_code, surface > mini_terrain_cache_map
Definition: image.hpp:130
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...
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition: utils.cpp:272
#define d
surface flip_surface(const surface &surf)
Definition: utils.cpp:2039
Definitions for the interface to Wesnoth Markup Language (WML).
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
const map_location & get_loc() const
Definition: image.hpp:86
std::string terrain_mask
bool save_image(const locator &i_locator, const std::string &filename)
Definition: image.cpp:1198
void flush_cache()
Definition: image.cpp:211
static std::set< std::string > fuzzy_localized_files
Definition: image.cpp:431
std::string filename_
Definition: action_wml.cpp:546
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: image.cpp:879
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1129
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
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:1219
std::string get(const std::string &key)
Definition: general.cpp:230
bool in_cache(cache_type< T > &cache) const
Definition: image.cpp:131
static surface get_brightened(const locator &i_locator)
Definition: image.cpp:872
static surface load_image_sub_file(const image::locator &loc)
Definition: image.cpp:555
rwops_ptr make_write_RWops(const std::string &path)
static surface get_tod_colored(const locator &i_locator)
Definition: image.cpp:852
light_string get_light_string(int op, int r, int g, int b)
return light_string of one light operation(see above)
Definition: image.cpp:618
bool operator==(const value &a) const
Definition: image.cpp:393
bool valid() const
Definition: location.hpp:74
std::string modifications_
Definition: image.hpp:57
static int last_index_
Definition: image.cpp:209
std::unordered_map< value, int > locator_finder_t
Definition: image.hpp:66
SDL_Surface * get() const
Definition: surface.hpp:75
static SDL_PixelFormat last_pixel_format
Definition: image.cpp:710
size_t operator()(const image::locator::value &val) const
Definition: image.cpp:82
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
void set_zoom(unsigned int amount)
sets the amount scaled images should be scaled.
Definition: image.cpp:773
std::map< light_string, surface > lit_variants
Definition: image.hpp:148
std::string path
Definition: game_config.cpp:56
std::vector< cache_item< T > > content_
Definition: image.cpp:127
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
Definition: utils.cpp:1744
map_display and display: classes which take care of displaying the map and game-data on the screen...
T & access_in_cache(cache_type< T > &cache) const
Definition: image.cpp:144
Base abstract class for an image-path modification.
SDL_Surface * SDL_PNGFormatAlpha(SDL_Surface *src)
Definition: savepng.c:43
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
static void precache_file_existence_internal(const std::string &dir, const std::string &subdir)
Definition: image.cpp:1152
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:232
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
void set_color_adjustment(int r, int g, int b)
will make all scaled images have these rgb values added to all their pixels.
Definition: image.cpp:745
#define ftofxp(x)
IN: float or int - OUT: fixed_t.
Definition: math.hpp:312
Encapsulates the map of the game.
Definition: location.hpp:42
const std::string & get_filename() const
Definition: image.hpp:85
const std::string & get_modifications() const
Definition: image.hpp:89
surface load_from_disk(const locator &loc)
Definition: image.cpp:689
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.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, file_name_option mode=FILE_NAME_ONLY, file_filter_option filter=NO_FILTER, file_reorder_option reorder=DONT_REORDER, file_tree_checksum *checksum=nullptr)
Populates 'files' with all the files and 'dirs' with all the directories in dir.
logger & err()
Definition: log.cpp:79
modification * top() const
Returns the top element in the queue .
cache_item()
Definition: image.cpp:61
#define ERR_DP
Definition: image.cpp:50
mock_party p
surface scale_surface_xbrz(const surface &surf, size_t z)
Scale a surface using xBRZ algorithm.
Definition: utils.cpp:193
locator & operator=(const locator &a)
Definition: image.cpp:321
double g
Definition: astarsearch.cpp:64
static tcache cache
Definition: minimap.cpp:134
surface get_hexmask()
function to get the standard hex mask
Definition: image.cpp:1056
std::basic_string< signed char > light_string
light_string store colors info of central and adjacent hexes.
Definition: image.hpp:143
size_t i
Definition: function.cpp:933
mini_terrain_cache_map mini_highlighted_terrain_cache
Definition: image.cpp:207
Declarations for File-IO.
int w
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
Definition: filesystem.hpp:40
bool is_in_hex(const locator &i_locator)
function to check if an image fit into an hex return false if the image has not the standard size...
Definition: image.cpp:1062
static bool localized_file_uptodate(const std::string &loc_file)
Definition: image.cpp:432
surface light_surface(const surface &surf, const surface &lightmap)
Light surf using lightmap.
Definition: utils.cpp:1396
mini_terrain_cache_map mini_terrain_cache
Definition: image.cpp:205
cache_item(const T &item)
Definition: image.cpp:67
std::string base_name(const std::string &file, const bool remove_extension=false)
Returns the base filename of a file, with directory name stripped.
surface get_lighted_image(const image::locator &i_locator, const light_string &ls, TYPE type)
function to get the surface corresponding to an image.
Definition: image.cpp:1004
const T & locate_in_cache(cache_type< T > &cache) const
Definition: image.cpp:137
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:730
Contains the SDL_Rect helper code.
CONSTEXPR const T & clamp(const T &value, const T &min, const T &max)
Definition: general.hpp:31
#define f
unsigned int tile_size
Definition: game_config.cpp:84
bool is_empty_hex(const locator &i_locator)
function to check if an image is empty after hex cut should be only used on terrain image (cache the ...
Definition: image.cpp:1088
int get_center_x() const
Definition: image.hpp:87
surface brighten_image(const surface &surf, fixed_t amount)
Definition: utils.cpp:1130
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
Definition: image.cpp:508
static scaling_function select_algorithm(SCALING_ALGORITHM algo)
Definition: image.cpp:804
surface make_neutral_surface(const surface &surf)
Definition: utils.cpp:70
this module manages the cache of images.
Definition: image.cpp:103
Standard logging facilities (interface).
double hex_brightening
Definition: game_config.cpp:96
static lg::log_domain log_config("config")
const std::vector< std::string > & get_team_colors()
Definition: image.cpp:768
#define e
void pop()
Removes the top element from the queue.
bool update_from_preferences()
initialize any private data, e.g. algorithm choices from preferences
Definition: image.cpp:1223
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
mock_char c
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
Exception thrown by the operator() when an error occurs.
int SDL_SavePNG_RW(SDL_Surface *surface, SDL_RWops *dst, int freedst)
Definition: savepng.c:64
static lg::log_domain log_display("display")
static surface apply_light(surface surf, const light_string &ls)
Definition: image.cpp:630
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
const std::string message
The error message regarding the failed operation.
bool loaded
Definition: image.cpp:74