The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
picture.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
3 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16/**
17 * @file
18 * Routines for images: load, scale, re-color, etc.
19 */
20
21#include "picture.hpp"
22
23#include "filesystem.hpp"
24#include "game_config.hpp"
26#include "log.hpp"
29#include "sdl/rect.hpp"
30#include "sdl/texture.hpp"
31
32#include <SDL3_image/SDL_image.h>
33
34#include <boost/algorithm/string.hpp>
35
36#if BOOST_VERSION >= 108100
37
38#include <boost/unordered/unordered_flat_map.hpp>
39template<typename Key, typename Value>
40using cache_map = boost::unordered_flat_map<Key, Value>;
41
42#else
43
44#include <boost/unordered/unordered_map.hpp>
45template<typename Key, typename Value>
46using cache_map = boost::unordered_map<Key, Value>;
47
48#endif
49
50#include <array>
51#include <set>
52
53static lg::log_domain log_image("image");
54#define ERR_IMG LOG_STREAM(err, log_image)
55#define WRN_IMG LOG_STREAM(warn, log_image)
56#define LOG_IMG LOG_STREAM(info, log_image)
57#define DBG_IMG LOG_STREAM(debug, log_image)
58
59static lg::log_domain log_config("config");
60#define ERR_CFG LOG_STREAM(err, log_config)
61
63
64namespace image
65{
66/** Hash function overload for boost::hash. Must be in the image namespace to satisfy ADL. */
67std::size_t hash_value(const image::locator& val)
68{
69 std::size_t hash = std::hash<unsigned>{}(val.type_);
70
72 boost::hash_combine(hash, val.filename_);
73 }
74
76 boost::hash_combine(hash, val.loc_.x);
77 boost::hash_combine(hash, val.loc_.y);
78 boost::hash_combine(hash, val.center_x_);
79 boost::hash_combine(hash, val.center_y_);
80 boost::hash_combine(hash, val.modifications_);
81 }
82
83 return hash;
84}
85
86template<typename T>
88{
89public:
90 bool in_cache(const locator& item) const
91 {
92 return content_.find(item) != content_.end(); // TODO C++20: use content_.contains()
93 }
94
95 /** Returns a pointer to the cached value, or nullptr if not found. */
96 const T* locate_in_cache(const locator& item) const
97 {
98 if(auto iter = content_.find(item); iter != content_.end()) {
99 return &iter->second;
100 } else {
101 return nullptr;
102 }
103 }
104
105 /**
106 * Returns a reference to the cache item associated with the given key.
107 * If no corresponding value is found, a default instance will be created.
108 */
109 T& access_in_cache(const locator& item)
110 {
111 return content_[item];
112 }
113
114 void add_to_cache(const locator& item, T data)
115 {
116 content_.insert_or_assign(item, std::move(data));
117 }
118
119 void flush()
120 {
121 content_.clear();
122 }
123
124private:
126};
127
128namespace
129{
130using surface_cache = cache_type<surface>;
131using texture_cache = cache_type<texture>;
132using bool_cache = cache_type<bool>;
133
134/** Type used to pair light possibilities with the corresponding lit surface. */
135using lit_surface_variants = cache_map<std::vector<light_adjust>, surface>;
136using lit_texture_variants = cache_map<std::vector<light_adjust>, texture>;
137
138/** Lit variants for each locator. */
139using lit_surface_cache = cache_type<lit_surface_variants>;
140using lit_texture_cache = cache_type<lit_texture_variants>;
141
142/** Definition of all image maps */
143std::array<surface_cache, NUM_TYPES> surfaces_;
144
145/**
146 * Texture caches.
147 * Note that the latter two are temporary and should be removed once we have OGL and shader support.
148 */
149using texture_cache_map = std::map<image::scale_quality, image::texture_cache>;
150
151texture_cache_map textures_;
152texture_cache_map textures_hexed_;
153texture_cache_map texture_tod_colored_;
154
155// cache storing if each image fit in a hex
156image::bool_cache in_hex_info_;
157
158// cache storing if this is an empty hex
159image::bool_cache is_empty_hex_;
160
161// caches storing the different lighted cases for each image
162image::lit_surface_cache lit_surfaces_;
163image::lit_texture_cache lit_textures_;
164// caches storing each lightmap generated
165image::lit_surface_variants surface_lightmaps_;
166image::lit_texture_variants texture_lightmaps_;
167
168// diagnostics for tracking skipped cache impact
169std::array<bool_cache, NUM_TYPES> skipped_cache_;
170int duplicate_loads_ = 0;
171int total_loads_ = 0;
172
173// const int cache_version_ = 0;
174
175std::map<std::string, bool> image_existence_map;
176
177// directories where we already cached file existence
178std::set<std::string> precached_dirs;
179
180int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
181
182const std::string data_uri_prefix = "data:";
183struct parsed_data_URI{
184 explicit parsed_data_URI(std::string_view data_URI);
185 std::string_view scheme;
186 std::string_view mime;
187 std::string_view base64;
188 std::string_view data;
189 bool good;
190};
191parsed_data_URI::parsed_data_URI(std::string_view data_URI)
192{
193 const std::size_t colon = data_URI.find(':');
194 const std::string_view after_scheme = data_URI.substr(colon + 1);
195
196 const std::size_t comma = after_scheme.find(',');
197 const std::string_view type_info = after_scheme.substr(0, comma);
198
199 const std::size_t semicolon = type_info.find(';');
200
201 scheme = data_URI.substr(0, colon);
202 base64 = type_info.substr(semicolon + 1);
203 mime = type_info.substr(0, semicolon);
204 data = after_scheme.substr(comma + 1);
205 good = (scheme == "data" && base64 == "base64" && mime.length() > 0 && data.length() > 0);
206}
207
208} // end anon namespace
209
211{
212 for(surface_cache& cache : surfaces_) {
213 cache.flush();
214 }
215 lit_surfaces_.flush();
216 lit_textures_.flush();
217 surface_lightmaps_.clear();
218 texture_lightmaps_.clear();
219 in_hex_info_.flush();
220 is_empty_hex_.flush();
221 textures_.clear();
222 textures_hexed_.clear();
223 texture_tod_colored_.clear();
224 image_existence_map.clear();
225 precached_dirs.clear();
226}
227
228locator locator::clone(const std::string& mods) const
229{
230 locator res = *this;
231 if(!mods.empty()) {
232 res.modifications_ += mods;
233 res.type_ = SUB_FILE;
234 }
235
236 return res;
237}
238
239std::ostream& operator<<(std::ostream& s, const locator& l)
240{
241 s << l.get_filename();
242 if(!l.get_modifications().empty()) {
243 if(l.get_modifications()[0] != '~') {
244 s << '~';
245 }
246 s << l.get_modifications();
247 }
248 return s;
249}
250
251locator::locator(const std::string& fn)
252 : filename_(fn)
253{
254 if(filename_.empty()) {
255 return;
256 }
257
258 if(boost::algorithm::starts_with(filename_, data_uri_prefix)) {
259 if(parsed_data_URI parsed{ filename_ }; !parsed.good) {
260 std::string_view view{ filename_ };
261 std::string_view stripped = view.substr(0, view.find(","));
262 ERR_IMG << "Invalid data URI: " << stripped;
263 }
264
265 is_data_uri_ = true;
266 }
267
268 if(const std::size_t markup_field = filename_.find('~'); markup_field != std::string::npos) {
269 type_ = SUB_FILE;
270 modifications_ = filename_.substr(markup_field, filename_.size() - markup_field);
271 filename_ = filename_.substr(0, markup_field);
272 } else {
273 type_ = FILE;
274 }
275}
276
277locator::locator(const std::string& filename, const std::string& modifications)
278 : type_(SUB_FILE)
280 , modifications_(modifications)
281{
282}
283
285 const std::string& filename,
286 const map_location& loc,
287 int center_x,
288 int center_y,
289 const std::string& modifications)
290 : type_(SUB_FILE)
292 , modifications_(modifications)
293 , loc_(loc)
294 , center_x_(center_x)
295 , center_y_(center_y)
296{
297}
298
299bool locator::operator==(const locator& a) const
300{
301 if(a.type_ != type_) {
302 return false;
303 } else if(type_ == FILE) {
304 return filename_ == a.filename_;
305 } else if(type_ == SUB_FILE) {
306 return std::tie(filename_, loc_, modifications_, center_x_, center_y_) ==
307 std::tie(a.filename_, a.loc_, a.modifications_, a.center_x_, a.center_y_);
308 }
309
310 return false;
311}
312
313bool locator::operator<(const locator& a) const
314{
315 if(type_ != a.type_) {
316 return type_ < a.type_;
317 } else if(type_ == FILE) {
318 return filename_ < a.filename_;
319 } else if(type_ == SUB_FILE) {
320 return std::tie(filename_, loc_, modifications_, center_x_, center_y_) <
321 std::tie(a.filename_, a.loc_, a.modifications_, a.center_x_, a.center_y_);
322 }
323
324 return false;
325}
326
327// Load overlay image and compose it with the original surface.
328static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
329{
330 surface ovr_surf = IMG_Load(ovr_file.c_str());
331 if(!ovr_surf) {
332 return;
333 }
334
335 rect area {0, 0, ovr_surf->w, ovr_surf->h};
336
337 sdl_blit(ovr_surf, nullptr, orig_surf, &area);
338}
339
341{
342 surface res;
343 const std::string& name = loc.get_filename();
344
345 auto location = filesystem::get_binary_file_location("images", name);
346
347 // Many images have been converted from PNG to WEBP format,
348 // but the old filename may still be saved in savegame files etc.
349 // If the file does not exist in ".png" format, also try ".webp".
350 // Similarly for ".jpg", which conveniently has the same number of letters as ".png".
351 if(!location && (boost::algorithm::ends_with(name, ".png") || boost::algorithm::ends_with(name, ".jpg"))) {
352 std::string webp_name = name.substr(0, name.size() - 4) + ".webp";
353 location = filesystem::get_binary_file_location("images", webp_name);
354 if(location) {
355 WRN_IMG << "Replaced missing '" << name << "' with found '"
356 << webp_name << "'.";
357 }
358 }
359
360 {
361 if(location) {
362 // Check if there is a localized image.
363 const auto loc_location = filesystem::get_localized_path(location.value());
364 if(loc_location) {
365 location = loc_location.value();
366 }
367
368 res = IMG_Load(location.value().c_str());
369 if(!res) {
370 ERR_IMG << "Failed to load image with reason: " << SDL_GetError();
371 }
372
373 // If there was no standalone localized image, check if there is an overlay.
374 if(res && !loc_location) {
375 const auto ovr_location = filesystem::get_localized_path(location.value(), "--overlay");
376 if(ovr_location) {
377 add_localized_overlay(ovr_location.value(), res);
378 }
379 }
380 }
381 }
382
383 if(!res && !name.empty()) {
384 ERR_IMG << "could not open image '" << name << "' at location '" << location.value_or("<location missing>") << "'";
389 }
390
391 return res;
392}
393
395{
396 // Create a new surface in-memory on which to apply the modifications
397 surface surf = get_surface(loc.get_filename(), UNSCALED).clone();
398 if(surf == nullptr) {
399 return nullptr;
400 }
401
402 modification_queue mods = modification::decode(loc.get_modifications());
403
404 while(!mods.empty()) {
405 try {
406 std::invoke(mods.top(), surf);
407 } catch(const image::modification::imod_exception& e) {
408 std::ostringstream ss;
409 ss << "\n";
410
411 for(const std::string& mod_name : utils::parenthetical_split(loc.get_modifications(), '~')) {
412 ss << "\t" << mod_name << "\n";
413 }
414
415 ERR_CFG << "Failed to apply a modification to an image:\n"
416 << "Image: " << loc.get_filename() << "\n"
417 << "Modifications: " << ss.str() << "\n"
418 << "Error: " << e.message;
419 }
420
421 mods.pop();
422 }
423
424 if(loc.get_loc().valid()) {
425 rect srcrect(
426 ((tile_size * 3) / 4) * loc.get_loc().x,
427 tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
428 tile_size,
429 tile_size
430 );
431
432 if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
433 srcrect.x += surf->w / 2 - loc.get_center_x();
434 srcrect.y += surf->h / 2 - loc.get_center_y();
435 }
436
437 // cut and hex mask, but also check and cache if empty result
438 surface cut = cut_surface(surf, srcrect);
439 bool is_empty = mask_surface(cut, get_hexmask());
440
441 // discard empty images to free memory
442 if(is_empty) {
443 // Safe because those images are only used by terrain rendering
444 // and it filters them out.
445 // A safer and more general way would be to keep only one copy of it
446 surf = nullptr;
447 } else {
448 surf = cut;
449 }
450
451 is_empty_hex_.add_to_cache(loc, is_empty);
452 }
453
454 return surf;
455}
456
458{
460
461 parsed_data_URI parsed{loc.get_filename()};
462
463 if(!parsed.good) {
464 std::string_view fn = loc.get_filename();
465 std::string_view stripped = fn.substr(0, fn.find(","));
466 ERR_IMG << "Invalid data URI: " << stripped;
467 } else if(parsed.mime.substr(0, 5) != "image") {
468 ERR_IMG << "Data URI not of image MIME type: " << parsed.mime;
469 } else {
470 const std::vector<uint8_t> image_data = base64::decode(parsed.data);
471
472 if(image_data.empty()) {
473 ERR_IMG << "Invalid encoding in data URI";
474 } else if(parsed.mime == "image/png") {
475 surf = IMG_LoadTyped_IO(SDL_IOFromConstMem(image_data.data(), image_data.size()), true, "PNG");
476 } else if(parsed.mime == "image/jpeg") {
477 surf = IMG_LoadTyped_IO(SDL_IOFromConstMem(image_data.data(), image_data.size()), true, "JPG");
478 } else if(parsed.mime == "image/webp") {
479 surf = IMG_LoadTyped_IO(SDL_IOFromConstMem(image_data.data(), image_data.size()), true, "WEBP");
480 } else {
481 ERR_IMG << "Invalid image MIME type: " << parsed.mime;
482 }
483 }
484
485 return surf;
486}
487
488light_adjust::light_adjust(int op, int rr, int gg, int bb)
489 : l(op), r(), g(), b()
490{
491 constexpr int min = std::numeric_limits<int8_t>::min();
492 constexpr int max = std::numeric_limits<int8_t>::max();
493
494 // Clamp in a wider type (int) to avoid truncating the input value prematurely
495 r = std::clamp(rr / 2, min, max);
496 g = std::clamp(gg / 2, min, max);
497 b = std::clamp(bb / 2, min, max);
498}
499
500#ifndef __cpp_impl_three_way_comparison
502{
503 return std::tie(l, r, g, b) == std::tie(o.l, o.r, o.g, o.b);
504}
505#endif
506
507/** Hash function overload for boost::hash. Must be in the image namespace to satisfy ADL. */
508static std::size_t hash_value(const light_adjust& adj)
509{
510 std::size_t hash{0};
511
512 boost::hash_combine(hash, adj.l);
513 boost::hash_combine(hash, adj.r);
514 boost::hash_combine(hash, adj.g);
515 boost::hash_combine(hash, adj.b);
516
517 return hash;
518}
519
520static surface apply_light(surface surf, const std::vector<light_adjust>& ls)
521{
522 // atomic lightmap operation are handled directly (important to end recursion)
523 if(ls.size() == 1) {
524 // if no lightmap (first char = -1) then we need the initial value
525 //(before the halving done for lightmap)
526 int m = ls[0].l == -1 ? 2 : 1;
527 adjust_surface_color(surf, ls[0].r * m, ls[0].g * m, ls[0].b * m);
528 return surf;
529 }
530
531 const auto get_lightmap = [&ls]
532 {
533 const auto iter = surface_lightmaps_.find(ls);
534 if(iter != surface_lightmaps_.end()) {
535 return iter->second;
536 }
537
538 // build all the paths for lightmap sources
539 static const std::string p = "terrain/light/light";
540 static const std::string lm_img[19] {
541 p + ".png",
542 p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
543 p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
544 p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
545 p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
546 p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
547 p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
548 };
549
550 // first image will be the base onto which we blit the others
551 surface base;
552
553 for(const light_adjust& adj : ls) {
554 // get the corresponding image and apply the lightmap operation to it
555 // This allows to also cache lightmap parts.
556 // note that we avoid infinite recursion by using only atomic operation
557 surface lts = image::get_lighted_image(lm_img[adj.l], std::vector{adj});
558
559 if(base == nullptr) {
560 // copy the cached image to avoid modifying the cache
561 base = lts.clone();
562 } else {
563 sdl_blit(lts, nullptr, base, nullptr);
564 }
565 }
566
567 // cache the result
568 surface_lightmaps_[ls] = base;
569 return base;
570 };
571
572 // apply the final lightmap
573 light_surface(surf, get_lightmap());
574 return surf;
575}
576
578{
579 switch(loc.get_type()) {
580 case locator::FILE:
581 if(loc.is_data_uri()){
582 return load_image_data_uri(loc);
583 } else {
584 return load_image_file(loc);
585 }
587 return load_image_sub_file(loc);
588 default:
589 return surface(nullptr);
590 }
591}
592
596
601
602void set_color_adjustment(int r, int g, int b)
603{
604 if(r != red_adjust || g != green_adjust || b != blue_adjust) {
605 red_adjust = r;
606 green_adjust = g;
607 blue_adjust = b;
608 surfaces_[TOD_COLORED].flush();
609 lit_surfaces_.flush();
610 lit_textures_.flush();
611 texture_tod_colored_.clear();
612 }
613}
614
615static surface get_hexed(const locator& i_locator, bool skip_cache = false)
616{
617 surface image = get_surface(i_locator, UNSCALED, skip_cache).clone();
618 surface mask = get_hexmask();
619
620 // Ensure the image is the correct size by cropping and/or centering.
621 // TODO: this should probably be a function of sdl/utils
622 if(image && (image->w != mask->w || image->h != mask->h)) {
623 DBG_IMG << "adjusting [" << image->w << ',' << image->h << ']'
624 << " image to hex mask: " << i_locator;
625
626 auto fit = surface(mask->w, mask->h);
627
628 // Fill the crop surface with transparency
629 SDL_FillSurfaceRect(fit, nullptr, SDL_MapSurfaceRGBA(fit, 0, 0, 0, 0));
630
631 // Returns an area rectangle clamped at the max size of the base surface.
632 // If surf is smaller than base, the result is centered relative to base.
633 const auto centered_intersection = [](const surface& surf, const surface& base) -> rect {
634 return {
635 std::max(0, surf->w - base->w) / 2,
636 std::max(0, surf->h - base->h) / 2,
637 std::min(surf->w, base->w),
638 std::min(surf->h, base->h),
639 };
640 };
641
642 rect src = centered_intersection(image, mask);
643 rect dst = centered_intersection(mask, image);
644
645 SDL_BlendMode src_blend;
646 SDL_GetSurfaceBlendMode(image, &src_blend);
647 SDL_SetSurfaceBlendMode(image, SDL_BLENDMODE_NONE);
648 // Take the center area of the source image, up to the size of the hex mask,
649 // and copy it, likewise centered, to the temporary surface. If the image is
650 // larger than the hex mask, its center portion will be retained. If instead
651 // it's *smaller* than the hex mask, it will be copied wholesale to the temp
652 // surface and render centered in any hex to which it is drawn.
653 sdl_blit(image, &src, fit, &dst);
654 SDL_SetSurfaceBlendMode(image, src_blend);
655
656 image = std::move(fit);
657 }
658
659 // hex cut tiles, also check and cache if empty result
660 bool is_empty = mask_surface(image, mask, i_locator.get_filename());
661 is_empty_hex_.add_to_cache(i_locator, is_empty);
662 return image;
663}
664
665static surface get_tod_colored(const locator& i_locator, bool skip_cache = false)
666{
667 surface img = get_surface(i_locator, HEXED, skip_cache).clone();
668 adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
669 return img;
670}
671
672/** translate type to a simpler one when possible */
673static TYPE simplify_type(const image::locator& i_locator, TYPE type)
674{
675 if(type == TOD_COLORED) {
676 if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
677 type = HEXED;
678 }
679 }
680
681 if(type == HEXED) {
682 // check if the image is already hex-cut by the location system
683 if(i_locator.get_loc().valid()) {
684 type = UNSCALED;
685 }
686 }
687
688 return type;
689}
690
692 const image::locator& i_locator,
693 TYPE type,
694 bool skip_cache)
695{
696 surface res;
697
698 if(i_locator.is_void()) {
699 return res;
700 }
701
702 type = simplify_type(i_locator, type);
703
704 // select associated cache
705 if(type >= NUM_TYPES) {
706 WRN_IMG << "get_surface called with unknown image type";
707 return res;
708 }
709 surface_cache& imap = surfaces_[type];
710
711 // return the image if already cached
712 if(const surface* cached_surf = imap.locate_in_cache(i_locator)) {
713 return *cached_surf;
714 } else {
715 DBG_IMG << "surface cache [" << type << "] miss: " << i_locator;
716 }
717
718 // not cached, generate it
719 switch(type) {
720 case UNSCALED:
721 // If type is unscaled, directly load the image from the disk.
722 res = load_from_disk(i_locator);
723 break;
724 case TOD_COLORED:
725 res = get_tod_colored(i_locator, skip_cache);
726 break;
727 case HEXED:
728 res = get_hexed(i_locator, skip_cache);
729 break;
730 default:
731 throw game::error("get_surface somehow lost image type?");
732 }
733
734 bool_cache& skip = skipped_cache_[type];
735
736 // In cache...
737 if(const bool* cached_value = skip.locate_in_cache(i_locator)) {
738 // ... and cached as true
739 if(*cached_value) {
740 DBG_IMG << "duplicate load: " << i_locator
741 << " [" << type << "]"
742 << " (" << duplicate_loads_ << "/" << total_loads_ << " total)";
743 ++duplicate_loads_;
744 }
745 }
746
747 ++total_loads_;
748
749 if(skip_cache) {
750 DBG_IMG << "surface cache [" << type << "] skip: " << i_locator;
751 skip.add_to_cache(i_locator, true);
752 } else {
753 imap.add_to_cache(i_locator, res);
754 }
755
756 return res;
757}
758
759surface get_lighted_image(const image::locator& i_locator, const std::vector<light_adjust>& ls)
760{
761 if(i_locator.is_void()) {
762 return {};
763 }
764
765 lit_surface_variants& lvar = lit_surfaces_.access_in_cache(i_locator);
766
767 // Check the matching list_string variants for this locator
768 if(const auto iter = lvar.find(ls); iter != lvar.end()) {
769 return iter->second;
770 } else {
771 DBG_IMG << "lit surface cache miss: " << i_locator;
772 }
773
774 // not cached yet, generate it
775 surface res = apply_light(get_surface(i_locator, HEXED).clone(), ls);
776
777 // lvar may no longer be valid as apply_light can reenter this function
778 // and mutate lit_surfaces_; get a new reference before updating
779 lit_surface_variants& reacquired_lvar = lit_surfaces_.access_in_cache(i_locator);
780
781 // record the lighted surface in the corresponding variants cache
782 reacquired_lvar[ls] = res;
783 return res;
784}
785
786texture get_lighted_texture(const image::locator& i_locator, const std::vector<light_adjust>& ls)
787{
788 if(i_locator.is_void()) {
789 return texture();
790 }
791
792 lit_texture_variants& lvar = lit_textures_.access_in_cache(i_locator);
793
794 // Check the matching list_string variants for this locator
795 if(const auto iter = lvar.find(ls); iter != lvar.end()) {
796 return iter->second;
797 } else {
798 DBG_IMG << "lit texture cache miss: " << i_locator;
799 }
800
801 // not cached yet, generate it
802 texture tex(get_lighted_image(i_locator, ls));
803
804 // record the lighted texture in the corresponding variants cache
805 lvar[ls] = tex;
806 return tex;
807}
808
810{
811 static const image::locator terrain_mask(game_config::images::terrain_mask);
812 return get_surface(terrain_mask, UNSCALED);
813}
814
815point get_size(const locator& i_locator, bool skip_cache)
816{
817 if(const surface s = get_surface(i_locator, UNSCALED, skip_cache)) {
818 return {s->w, s->h};
819 } else {
820 return {0, 0};
821 }
822}
823
824bool is_in_hex(const locator& i_locator)
825{
826 if(const bool* cached_value = in_hex_info_.locate_in_cache(i_locator)) {
827 return *cached_value;
828 } else {
829 bool res = in_mask_surface(get_surface(i_locator, UNSCALED), get_hexmask());
830 in_hex_info_.add_to_cache(i_locator, res);
831 return res;
832 }
833}
834
835bool is_empty_hex(const locator& i_locator)
836{
837 if(const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
838 return *cached_value;
839 }
840
841 surface surf = get_surface(i_locator, HEXED);
842
843 // Empty state should be cached during surface fetch. Let's check again
844 if(const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
845 return *cached_value;
846 }
847
848 // Should never reach this point, but let's manually do it anyway.
849 surf = surf.clone();
850 bool is_empty = mask_surface(surf, get_hexmask());
851 is_empty_hex_.add_to_cache(i_locator, is_empty);
852 return is_empty;
853}
854
855bool exists(const image::locator& i_locator)
856{
857 if(i_locator.is_void()) {
858 return false;
859 }
860
861 // The insertion will fail if there is already an element in the cache
862 // and this will point to the existing element.
863 auto [iter, success] = image_existence_map.emplace(i_locator.get_filename(), false);
864
865 bool& cache = iter->second;
866 if(success) {
867 if(i_locator.is_data_uri()) {
868 cache = parsed_data_URI{i_locator.get_filename()}.good;
869 } else {
870 cache = filesystem::get_binary_file_location("images", i_locator.get_filename()).has_value();
871 }
872 }
873
874 return cache;
875}
876
877static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
878{
879 const std::string checked_dir = dir + "/" + subdir;
880 if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
881 return;
882 }
883
884 precached_dirs.insert(checked_dir);
885
886 if(!filesystem::is_directory(checked_dir)) {
887 return;
888 }
889
890 std::vector<std::string> files_found;
891 std::vector<std::string> dirs_found;
892 filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::name_mode::FILE_NAME_ONLY,
894
895 for(const auto& f : files_found) {
896 image_existence_map[subdir + f] = true;
897 }
898
899 for(const auto& d : dirs_found) {
900 precache_file_existence_internal(dir, subdir + d + "/");
901 }
902}
903
904void precache_file_existence(const std::string& subdir)
905{
906 for(const auto& p : filesystem::get_binary_paths("images")) {
908 }
909}
910
911bool precached_file_exists(const std::string& file)
912{
913 const auto b = image_existence_map.find(file);
914 if(b != image_existence_map.end()) {
915 return b->second;
916 }
917
918 return false;
919}
920
921save_result save_image(const locator& i_locator, const std::string& filename)
922{
923 return save_image(get_surface(i_locator), filename);
924}
925
926save_result save_image(const surface& surf, const std::string& filename)
927{
928 if(!surf) {
930 }
931
932 if(boost::algorithm::ends_with(filename, ".jpeg") || boost::algorithm::ends_with(filename, ".jpg") || boost::algorithm::ends_with(filename, ".jpe")) {
933 LOG_IMG << "Writing a JPG image to " << filename;
934 const int err = IMG_SaveJPG(surf, filename.c_str(), 75);
936 }
937
938 if(boost::algorithm::ends_with(filename, ".png")) {
939 LOG_IMG << "Writing a PNG image to " << filename;
940 const int err = IMG_SavePNG(surf, filename.c_str());
942 }
943
945}
946
947/*
948 * TEXTURE INTERFACE ======================================================================
949 *
950 * The only important difference here is that textures must have their
951 * scale quality set before creation. All other handling is done by
952 * get_surface.
953 */
954
955texture get_texture(const image::locator& i_locator, TYPE type, bool skip_cache)
956{
957 return get_texture(i_locator, scale_quality::nearest, type, skip_cache);
958}
959
960/** Returns a texture for the corresponding image. */
961texture get_texture(const image::locator& i_locator, scale_quality quality, TYPE type, bool skip_cache)
962{
963 texture res;
964
965 if(i_locator.is_void()) {
966 return res;
967 }
968
969 type = simplify_type(i_locator, type);
970
971 //
972 // Select the appropriate cache. We don't need caches for every single image types,
973 // since some types can be handled by render-time operations.
974 //
975 texture_cache* cache = nullptr;
976
977 switch(type) {
978 case HEXED:
979 cache = &textures_hexed_[quality];
980 break;
981 case TOD_COLORED:
982 cache = &texture_tod_colored_[quality];
983 break;
984 default:
985 cache = &textures_[quality];
986 }
987
988 //
989 // Now attempt to find a cached texture. If found, return it.
990 //
991 if(const texture* cached_texture = cache->locate_in_cache(i_locator)) {
992 return *cached_texture;
993 } else {
994 DBG_IMG << "texture cache [" << type << "] miss: " << i_locator;
995 }
996
997 //
998 // No texture was cached. In that case, create a new one. The explicit cases require special
999 // handling with surfaces in order to generate the desired effect. This shouldn't be the case
1000 // once we get OGL and shader support.
1001 //
1002
1003 // Get it from the surface cache, also setting the desired scale quality.
1004 const bool linear_scaling = quality == scale_quality::linear ? true : false;
1005 if(i_locator.get_modifications().empty()) {
1006 // skip cache if we're loading plain files with no modifications
1007 res = texture(get_surface(i_locator, type, true), linear_scaling);
1008 } else {
1009 res = texture(get_surface(i_locator, type, skip_cache), linear_scaling);
1010 }
1011
1012 // Cache the texture.
1013 if(skip_cache) {
1014 DBG_IMG << "texture cache [" << type << "] skip: " << i_locator;
1015 } else {
1016 cache->add_to_cache(i_locator, res);
1017 }
1018
1019 return res;
1020}
1021
1022} // end namespace image
std::string filename_
map_location loc
Definition move.cpp:172
#define ERR_CFG
Definition manager.cpp:30
bool in_cache(const locator &item) const
Definition picture.cpp:90
T & access_in_cache(const locator &item)
Returns a reference to the cache item associated with the given key.
Definition picture.cpp:109
const T * locate_in_cache(const locator &item) const
Returns a pointer to the cached value, or nullptr if not found.
Definition picture.cpp:96
cache_map< locator, T > content_
Definition picture.cpp:125
void add_to_cache(const locator &item, T data)
Definition picture.cpp:114
Generic locator abstracting the location of an image.
Definition picture.hpp:59
bool is_void() const
Returns true if the locator does not correspond to an actual image.
Definition picture.hpp:93
map_location loc_
Definition picture.hpp:100
std::string filename_
Definition picture.hpp:98
const std::string & get_filename() const
Definition picture.hpp:82
bool is_data_uri() const
Definition picture.hpp:83
bool is_data_uri_
Definition picture.hpp:97
const map_location & get_loc() const
Definition picture.hpp:84
locator::type type_
Definition picture.hpp:96
locator()=default
bool operator<(const locator &a) const
Definition picture.cpp:313
bool operator==(const locator &a) const
Definition picture.cpp:299
locator clone(const std::string &mods) const
Returns a copy of this locator with the given IPF.
Definition picture.cpp:228
std::string modifications_
Definition picture.hpp:99
const std::string & get_modifications() const
Definition picture.hpp:87
A modified priority queue used to order image modifications.
const modification & top() const
Returns a const reference to the top element in the queue.
void pop()
Removes the top element from the queue.
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
surface clone() const
Creates a new, duplicate surface in memory using the 'neutral' pixel format.
Definition surface.cpp:97
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition texture.hpp:33
Declarations for File-IO.
Standard logging facilities (interface).
std::vector< uint8_t > decode(std::string_view in)
Definition base64.cpp:219
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, name_mode mode=name_mode::FILE_NAME_ONLY, filter_mode filter=filter_mode::NO_FILTER, reorder_mode reorder=reorder_mode::DONT_REORDER, file_tree_checksum *checksum=nullptr)
Get a list of all files and/or directories in a given directory.
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
utils::optional< 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, if it exists.
utils::optional< std::string > get_localized_path(const std::string &file, const std::string &suff="")
Returns the localized version of the given filename, if it exists.
NOT_DANGLING const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
std::string terrain_mask
const bool & debug
unsigned int tile_size
Functions to load and save images from/to disk.
static surface load_image_sub_file(const image::locator &loc)
Definition picture.cpp:394
bool is_empty_hex(const locator &i_locator)
Checks if an image is empty after hex masking.
Definition picture.cpp:835
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
Definition picture.cpp:328
bool precached_file_exists(const std::string &file)
Definition picture.cpp:911
save_result
Definition picture.hpp:271
static TYPE simplify_type(const image::locator &i_locator, TYPE type)
translate type to a simpler one when possible
Definition picture.cpp:673
static surface load_image_data_uri(const image::locator &loc)
Definition picture.cpp:457
static surface get_tod_colored(const locator &i_locator, bool skip_cache=false)
Definition picture.cpp:665
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
void flush_cache()
Purges all image caches.
Definition picture.cpp:210
static void precache_file_existence_internal(const std::string &dir, const std::string &subdir)
Definition picture.cpp:877
static surface load_from_disk(const locator &loc)
Definition picture.cpp:577
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition picture.cpp:691
static surface apply_light(surface surf, const std::vector< light_adjust > &ls)
Definition picture.cpp:520
surface get_hexmask()
Retrieves the standard hexagonal tile mask.
Definition picture.cpp:809
std::ostream & operator<<(std::ostream &s, const locator &l)
Definition picture.cpp:239
save_result save_image(const locator &i_locator, const std::string &filename)
Definition picture.cpp:921
void precache_file_existence(const std::string &subdir)
Precache the existence of files in a binary path subdirectory (e.g.
Definition picture.cpp:904
TYPE
Used to specify the rendering format of images.
Definition picture.hpp:171
@ HEXED
Standard hexagonal tile mask applied, removing portions that don't fit.
Definition picture.hpp:175
@ NUM_TYPES
Definition picture.hpp:178
@ TOD_COLORED
Same as HEXED, but with Time of Day color tint applied.
Definition picture.hpp:177
@ UNSCALED
Unmodified original-size image.
Definition picture.hpp:173
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition picture.cpp:955
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition picture.cpp:815
static surface get_hexed(const locator &i_locator, bool skip_cache=false)
Definition picture.cpp:615
texture get_lighted_texture(const image::locator &i_locator, const std::vector< light_adjust > &ls)
Definition picture.cpp:786
std::size_t hash_value(const image::locator &val)
Hash function overload for boost::hash.
Definition picture.cpp:67
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition picture.cpp:602
surface get_lighted_image(const image::locator &i_locator, const std::vector< light_adjust > &ls)
Caches and returns an image with a lightmap applied to it.
Definition picture.cpp:759
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
Definition picture.cpp:824
scale_quality
Definition picture.hpp:181
static surface load_image_file(const image::locator &loc)
Definition picture.cpp:340
std::vector< std::string > parenthetical_split(std::string_view val, const char separator, std::string_view left, std::string_view right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
#define DBG_IMG
Definition picture.cpp:57
static lg::log_domain log_image("image")
std::string_view scheme
Definition picture.cpp:185
boost::unordered_map< Key, Value > cache_map
Definition picture.cpp:46
std::string_view mime
Definition picture.cpp:186
bool good
Definition picture.cpp:189
#define WRN_IMG
Definition picture.cpp:55
#define LOG_IMG
Definition picture.cpp:56
#define ERR_IMG
Definition picture.cpp:54
std::string_view data
Definition picture.cpp:188
static lg::log_domain log_config("config")
Contains the SDL_Rect helper code.
std::unordered_map< std::string, cache_item > cache
Definition sound.cpp:146
rect dst
Location on the final composed sheet.
surface surf
Image.
rect src
Non-transparent portion of the surface to compose.
std::string filename
Filename.
Base class for all the errors encountered by the engine.
Type used to store color information of central and adjacent hexes.
Definition picture.hpp:124
bool operator==(const light_adjust &o) const
Definition picture.cpp:501
light_adjust(int op, int r, int g, int b)
Definition picture.cpp:488
Exception thrown by the operator() when an error occurs.
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
Holds a 2D point.
Definition point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
mock_party p
static map_location::direction s
surface cut_surface(const surface &surf, const rect &r)
Cuts a rectangle from a surface.
Definition utils.cpp:1092
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
Definition utils.cpp:405
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
Definition utils.cpp:790
bool in_mask_surface(const surface &nsurf, const surface &nmask)
Checks if the source surface is entirely covered by the visible area of the mask.
Definition utils.cpp:756
bool mask_surface(surface &nsurf, const surface &nmask, const std::string &filename)
Applies a mask to the source surface by calculating the minimum alpha channel value for every corresp...
Definition utils.cpp:708
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition utils.hpp:43
#define g
#define d
#define e
#define f
#define b