32 #include <SDL2/SDL_image.h>
35 #include <boost/algorithm/string.hpp>
41 #define ERR_IMG LOG_STREAM(err, log_image)
42 #define WRN_IMG LOG_STREAM(warn, log_image)
43 #define LOG_IMG LOG_STREAM(info, log_image)
44 #define DBG_IMG LOG_STREAM(debug, log_image)
47 #define ERR_CFG LOG_STREAM(err, log_config)
52 struct std::hash<
image::locator>
56 std::size_t hash = std::hash<unsigned>{}(val.
type_);
63 boost::hash_combine(hash, val.
loc_.
x);
64 boost::hash_combine(hash, val.
loc_.
y);
125 using lit_surface_variants = std::unordered_map<std::size_t, surface>;
126 using lit_texture_variants = std::unordered_map<std::size_t, texture>;
133 std::array<surface_cache, NUM_TYPES> surfaces_;
139 using texture_cache_map = std::map<image::scale_quality, image::texture_cache>;
141 texture_cache_map textures_;
142 texture_cache_map textures_hexed_;
143 texture_cache_map texture_tod_colored_;
146 image::bool_cache in_hex_info_;
149 image::bool_cache is_empty_hex_;
152 image::lit_surface_cache lit_surfaces_;
153 image::lit_texture_cache lit_textures_;
155 image::lit_surface_variants surface_lightmaps_;
156 image::lit_texture_variants texture_lightmaps_;
159 std::array<bool_cache, NUM_TYPES> skipped_cache_;
160 int duplicate_loads_ = 0;
161 int total_loads_ = 0;
165 std::map<std::string, bool> image_existence_map;
168 std::set<std::string> precached_dirs;
170 int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
172 const std::string data_uri_prefix =
"data:";
173 struct parsed_data_URI{
174 explicit parsed_data_URI(std::string_view data_URI);
181 parsed_data_URI::parsed_data_URI(std::string_view data_URI)
183 const std::size_t colon = data_URI.find(
':');
184 const std::string_view after_scheme = data_URI.substr(colon + 1);
186 const std::size_t
comma = after_scheme.find(
',');
187 const std::string_view type_info = after_scheme.substr(0,
comma);
189 const std::size_t
semicolon = type_info.find(
';');
191 scheme = data_URI.substr(0, colon);
202 for(surface_cache& cache : surfaces_) {
205 lit_surfaces_.flush();
206 lit_textures_.flush();
207 surface_lightmaps_.clear();
208 texture_lightmaps_.clear();
209 in_hex_info_.flush();
210 is_empty_hex_.flush();
212 textures_hexed_.clear();
213 texture_tod_colored_.clear();
214 image_existence_map.clear();
215 precached_dirs.clear();
248 if(boost::algorithm::starts_with(
filename_, data_uri_prefix)) {
249 if(parsed_data_URI parsed{
filename_ }; !parsed.good) {
251 std::string_view stripped = view.substr(0, view.find(
","));
252 ERR_IMG <<
"Invalid data URI: " << stripped;
258 if(
const std::size_t markup_field =
filename_.find(
'~'); markup_field != std::string::npos) {
270 , modifications_(modifications)
279 const std::string& modifications)
282 , modifications_(modifications)
284 , center_x_(center_x)
285 , center_y_(center_y)
321 surface ovr_surf = IMG_Load_RW(rwops.release(),
true);
326 SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
328 sdl_blit(ovr_surf,
nullptr, orig_surf, &area);
334 const std::string& name =
loc.get_filename();
342 if(!location && (boost::algorithm::ends_with(name,
".png") || boost::algorithm::ends_with(name,
".jpg"))) {
343 std::string webp_name = name.substr(0, name.size() - 4) +
".webp";
346 WRN_IMG <<
"Replaced missing '" << name <<
"' with found '"
347 << webp_name <<
"'.";
356 location = loc_location.value();
360 res = IMG_Load_RW(rwops.release(),
true);
363 if(res && !loc_location) {
372 if(!res && !name.empty()) {
373 ERR_IMG <<
"could not open image '" << name <<
"'";
387 if(
surf ==
nullptr) {
393 while(!mods.
empty()) {
397 std::ostringstream ss;
401 ss <<
"\t" << mod_name <<
"\n";
404 ERR_CFG <<
"Failed to apply a modification to an image:\n"
405 <<
"Image: " <<
loc.get_filename() <<
"\n"
406 <<
"Modifications: " << ss.str() <<
"\n"
407 <<
"Error: " <<
e.message;
421 if(
loc.get_center_x() >= 0 &&
loc.get_center_y() >= 0) {
422 srcrect.x +=
surf->w / 2 -
loc.get_center_x();
423 srcrect.y +=
surf->h / 2 -
loc.get_center_y();
428 bool is_empty =
false;
441 is_empty_hex_.add_to_cache(
loc, is_empty);
451 parsed_data_URI parsed{
loc.get_filename()};
454 std::string_view fn =
loc.get_filename();
455 std::string_view stripped = fn.substr(0, fn.find(
","));
456 ERR_IMG <<
"Invalid data URI: " << stripped;
457 }
else if(parsed.mime.substr(0, 5) !=
"image") {
458 ERR_IMG <<
"Data URI not of image MIME type: " << parsed.mime;
460 const std::vector<uint8_t> image_data =
base64::decode(parsed.data);
463 if(image_data.empty()) {
464 ERR_IMG <<
"Invalid encoding in data URI";
465 }
else if(parsed.mime ==
"image/png") {
466 surf = IMG_LoadPNG_RW(rwops.release());
467 }
else if(parsed.mime ==
"image/jpeg") {
468 surf = IMG_LoadJPG_RW(rwops.release());
469 }
else if(parsed.mime ==
"image/webp") {
470 surf = IMG_LoadWEBP_RW(rwops.release());
472 ERR_IMG <<
"Invalid image MIME type: " << parsed.mime;
480 : l(op), r(),
g(),
b()
482 constexpr
int min = std::numeric_limits<int8_t>::min();
483 constexpr
int max = std::numeric_limits<int8_t>::max();
486 r = std::clamp(rr / 2, min, max);
487 g = std::clamp(gg / 2, min, max);
488 b = std::clamp(bb / 2, min, max);
496 for(
const auto& adjustment : range) {
497 hash += adjustment.l + adjustment.r + adjustment.g + adjustment.b;
511 int m = ls[0].l == -1 ? 2 : 1;
516 const auto get_lightmap = [&ls]
518 const auto hash = hash_light_range(ls);
519 const auto iter = surface_lightmaps_.find(hash);
521 if(iter != surface_lightmaps_.end()) {
526 static const std::string
p =
"terrain/light/light";
527 static const std::string lm_img[19] {
529 p +
"-concave-2-tr.png",
p +
"-concave-2-r.png",
p +
"-concave-2-br.png",
530 p +
"-concave-2-bl.png",
p +
"-concave-2-l.png",
p +
"-concave-2-tl.png",
531 p +
"-convex-br-bl.png",
p +
"-convex-bl-l.png",
p +
"-convex-l-tl.png",
532 p +
"-convex-tl-tr.png",
p +
"-convex-tr-r.png",
p +
"-convex-r-br.png",
533 p +
"-convex-l-bl.png",
p +
"-convex-tl-l.png",
p +
"-convex-tr-tl.png",
534 p +
"-convex-r-tr.png",
p +
"-convex-br-r.png",
p +
"-convex-bl-br.png"
546 if(base ==
nullptr) {
550 sdl_blit(lts,
nullptr, base,
nullptr);
555 surface_lightmaps_[hash] = base;
566 switch(
loc.get_type()) {
568 if(
loc.is_data_uri()){
591 if(r != red_adjust ||
g != green_adjust ||
b != blue_adjust) {
596 lit_surfaces_.flush();
597 lit_textures_.flush();
598 texture_tod_colored_.clear();
610 <<
" image to hex mask: " << i_locator;
614 if(
image->w > mask->w ||
image->h >= mask->h) {
616 SDL_FillRect(fit,
nullptr, SDL_MapRGBA(fit->format, 0, 0, 0, 0));
618 int cutx = std::max(0,
image->w - mask->w) / 2;
619 int cuty = std::max(0,
image->h - mask->h) / 2;
620 int cutw = std::min(
image->w, mask->w);
621 int cuth = std::min(
image->h, mask->h);
626 int placex = (mask->w -
image->w) / 2;
627 int placey = (mask->h -
image->h) / 2;
633 bool is_empty =
false;
635 is_empty_hex_.add_to_cache(i_locator, is_empty);
650 if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
680 WRN_IMG <<
"get_surface called with unknown image type";
683 surface_cache& imap = surfaces_[
type];
686 if(
const surface* cached_surf = imap.locate_in_cache(i_locator)) {
689 DBG_IMG <<
"surface cache [" <<
type <<
"] miss: " << i_locator;
705 throw game::error(
"get_surface somehow lost image type?");
708 bool_cache& skip = skipped_cache_[
type];
711 if(
const bool* cached_value = skip.locate_in_cache(i_locator)) {
714 DBG_IMG <<
"duplicate load: " << i_locator
715 <<
" [" <<
type <<
"]"
716 <<
" (" << duplicate_loads_ <<
"/" << total_loads_ <<
" total)";
724 DBG_IMG <<
"surface cache [" <<
type <<
"] skip: " << i_locator;
725 skip.add_to_cache(i_locator,
true);
727 imap.add_to_cache(i_locator, res);
739 lit_surface_variants& lvar = lit_surfaces_.access_in_cache(i_locator);
742 const auto hash = hash_light_range(ls);
743 const auto iter = lvar.find(hash);
745 if(iter != lvar.end()) {
748 DBG_IMG <<
"lit surface cache miss: " << i_locator;
765 lit_texture_variants& lvar = lit_textures_.access_in_cache(i_locator);
768 const auto hash = hash_light_range(ls);
769 const auto iter = lvar.find(hash);
771 if(iter != lvar.end()) {
774 DBG_IMG <<
"lit texture cache miss: " << i_locator;
802 if(
const bool* cached_value = in_hex_info_.locate_in_cache(i_locator)) {
803 return *cached_value;
806 in_hex_info_.add_to_cache(i_locator, res);
813 if(
const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
814 return *cached_value;
820 if(
const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
821 return *cached_value;
826 bool is_empty =
false;
828 is_empty_hex_.add_to_cache(i_locator, is_empty);
842 bool& cache = iter->second;
856 const std::string checked_dir = dir +
"/" + subdir;
857 if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
861 precached_dirs.insert(checked_dir);
867 std::vector<std::string> files_found;
868 std::vector<std::string> dirs_found;
872 for(
const auto&
f : files_found) {
873 image_existence_map[subdir +
f] =
true;
876 for(
const auto&
d : dirs_found) {
890 const auto b = image_existence_map.find(file);
891 if(
b != image_existence_map.end()) {
909 if(boost::algorithm::ends_with(
filename,
".jpeg") || boost::algorithm::ends_with(
filename,
".jpg") || boost::algorithm::ends_with(
filename,
".jpe")) {
916 if(boost::algorithm::ends_with(
filename,
".png")) {
954 texture_cache* cache =
nullptr;
958 cache = &textures_hexed_[quality];
961 cache = &texture_tod_colored_[quality];
964 cache = &textures_[quality];
970 if(
const texture* cached_texture = cache->locate_in_cache(i_locator)) {
971 return *cached_texture;
973 DBG_IMG <<
"texture cache [" <<
type <<
"] miss: " << i_locator;
993 DBG_IMG <<
"texture cache [" <<
type <<
"] skip: " << i_locator;
995 cache->add_to_cache(i_locator, res);
constexpr size_type size() const noexcept
bool in_cache(const locator &item) const
T & access_in_cache(const locator &item)
Returns a reference to the cache item associated with the given key.
const T * locate_in_cache(const locator &item) const
Returns a pointer to the cached value, or nullptr if not found.
void add_to_cache(const locator &item, T data)
std::unordered_map< locator, T > content_
Generic locator abstracting the location of an image.
bool is_void() const
Returns true if the locator does not correspond to an actual image.
const std::string & get_filename() const
const std::string & get_modifications() const
const map_location & get_loc() const
bool operator<(const locator &a) const
bool operator==(const locator &a) const
locator clone(const std::string &mods) const
Returns a copy of this locator with the given IPF.
std::string modifications_
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.
Wrapper class to encapsulate creation and management of an SDL_Texture.
Declarations for File-IO.
Standard logging facilities (interface).
std::vector< uint8_t > decode(std::string_view in)
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
std::unique_ptr< SDL_RWops, sdl_rwops_deleter > rwops_ptr
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
rwops_ptr make_read_RWops(const std::string &path)
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.
rwops_ptr make_write_RWops(const std::string &path)
const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
Functions to load and save images from/to disk.
static surface load_image_sub_file(const image::locator &loc)
bool is_empty_hex(const locator &i_locator)
Checks if an image is empty after hex masking.
static void add_localized_overlay(const std::string &ovr_file, surface &orig_surf)
texture get_lighted_texture(const image::locator &i_locator, utils::span< const light_adjust > ls)
bool precached_file_exists(const std::string &file)
static TYPE simplify_type(const image::locator &i_locator, TYPE type)
translate type to a simpler one when possible
static surface load_image_data_uri(const image::locator &loc)
static surface get_tod_colored(const locator &i_locator, bool skip_cache=false)
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
void flush_cache()
Purges all image caches.
static void precache_file_existence_internal(const std::string &dir, const std::string &subdir)
surface get_lighted_image(const image::locator &i_locator, utils::span< const light_adjust > ls)
Caches and returns an image with a lightmap applied to it.
static surface load_from_disk(const locator &loc)
static surface apply_light(surface surf, utils::span< const light_adjust > ls)
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
surface get_hexmask()
Retrieves the standard hexagonal tile mask.
std::ostream & operator<<(std::ostream &s, const locator &l)
save_result save_image(const locator &i_locator, const std::string &filename)
void precache_file_existence(const std::string &subdir)
Precache the existence of files in a binary path subdirectory (e.g.
TYPE
Used to specify the rendering format of images.
@ HEXED
Standard hexagonal tile mask applied, removing portions that don't fit.
@ TOD_COLORED
Same as HEXED, but with Time of Day color tint applied.
@ UNSCALED
Unmodified original-size image.
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
static surface get_hexed(const locator &i_locator, bool skip_cache=false)
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
static surface load_image_file(const image::locator &loc)
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
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...
static lg::log_domain log_image("image")
static lg::log_domain log_config("config")
Contains the SDL_Rect helper code.
rect dst
Location on the final composed sheet.
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.
light_adjust(int op, int r, int g, int b)
Exception thrown by the operator() when an error occurs.
Encapsulates the map of the game.
An abstract description of a rectangle with integer coordinates.
std::size_t operator()(const image::locator &val) const
static map_location::direction s
void mask_surface(surface &nsurf, const surface &nmask, bool *empty_result, const std::string &filename)
Applies a mask on a surface.
void adjust_surface_color(surface &nsurf, int red, int green, int blue)
surface cut_surface(const surface &surf, const SDL_Rect &r)
Cuts a rectangle from a surface.
void light_surface(surface &nsurf, const surface &lightmap)
Light surf using lightmap.
bool in_mask_surface(const surface &nsurf, const surface &nmask)
Check if a surface fit into a mask.
void sdl_blit(const surface &src, const SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)