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::map<light_string, surface>;
126 using lit_texture_variants = std::map<light_string, 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, 0, orig_surf, &area);
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::invoke(*mod,
surf);
399 std::ostringstream ss;
403 ss <<
"\t" << mod_name <<
"\n";
406 ERR_CFG <<
"Failed to apply a modification to an image:\n"
408 <<
"Modifications: " << ss.str() <<
"\n"
409 <<
"Error: " <<
e.message;
431 bool is_empty =
false;
444 is_empty_hex_.add_to_cache(loc, is_empty);
458 std::string_view stripped = fn.substr(0, fn.find(
","));
459 ERR_IMG <<
"Invalid data URI: " << stripped;
460 }
else if(parsed.mime.substr(0, 5) !=
"image") {
461 ERR_IMG <<
"Data URI not of image MIME type: " << parsed.mime;
463 const std::vector<uint8_t> image_data =
base64::decode(parsed.data);
466 if(image_data.empty()) {
467 ERR_IMG <<
"Invalid encoding in data URI";
468 }
else if(parsed.mime ==
"image/png") {
469 surf = IMG_LoadTyped_RW(rwops.release(),
true,
"PNG");
470 }
else if(parsed.mime ==
"image/jpeg") {
471 surf = IMG_LoadTyped_RW(rwops.release(),
true,
"JPG");
473 ERR_IMG <<
"Invalid image MIME type: " << parsed.mime;
483 return static_cast<int8_t
>(std::clamp(
i / 2, -128, 127));
489 static_cast<int8_t
>(op),
502 int m = ls[0] == -1 ? 2 : 1;
509 auto i = surface_lightmaps_.find(ls);
510 if(
i != surface_lightmaps_.end()) {
511 lightmap =
i->second;
514 static const std::string
p =
"terrain/light/light";
515 static const std::string lm_img[19] {
517 p +
"-concave-2-tr.png",
p +
"-concave-2-r.png",
p +
"-concave-2-br.png",
518 p +
"-concave-2-bl.png",
p +
"-concave-2-l.png",
p +
"-concave-2-tl.png",
519 p +
"-convex-br-bl.png",
p +
"-convex-bl-l.png",
p +
"-convex-l-tl.png",
520 p +
"-convex-tl-tr.png",
p +
"-convex-tr-r.png",
p +
"-convex-r-br.png",
521 p +
"-convex-l-bl.png",
p +
"-convex-tl-l.png",
p +
"-convex-tr-tl.png",
522 p +
"-convex-r-tr.png",
p +
"-convex-br-r.png",
p +
"-convex-bl-br.png"
526 for(std::size_t
c = 0;
c + 3 < ls.size();
c += 4) {
535 if(lightmap ==
nullptr) {
537 lightmap = lts.
clone();
539 sdl_blit(lts,
nullptr, lightmap,
nullptr);
544 surface_lightmaps_[ls] = lightmap;
579 if(r != red_adjust ||
g != green_adjust ||
b != blue_adjust) {
584 lit_surfaces_.flush();
585 lit_textures_.flush();
586 texture_tod_colored_.clear();
598 <<
" image to hex mask: " << i_locator;
602 if(
image->w > mask->w ||
image->h >= mask->h) {
604 SDL_FillRect(fit,
nullptr, SDL_MapRGBA(fit->format, 0, 0, 0, 0));
606 int cutx = std::max(0,
image->w - mask->w) / 2;
607 int cuty = std::max(0,
image->h - mask->h) / 2;
608 int cutw = std::min(
image->w, mask->w);
609 int cuth = std::min(
image->h, mask->h);
614 int placex = (mask->w -
image->w) / 2;
615 int placey = (mask->h -
image->h) / 2;
621 bool is_empty =
false;
623 is_empty_hex_.add_to_cache(i_locator, is_empty);
638 if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
668 WRN_IMG <<
"get_surface called with unknown image type";
671 surface_cache& imap = surfaces_[
type];
674 if(
const surface* cached_surf = imap.locate_in_cache(i_locator)) {
677 DBG_IMG <<
"surface cache [" <<
type <<
"] miss: " << i_locator;
693 throw game::error(
"get_surface somehow lost image type?");
696 bool_cache& skip = skipped_cache_[
type];
699 if(
const bool* cached_value = skip.locate_in_cache(i_locator)) {
702 DBG_IMG <<
"duplicate load: " << i_locator
703 <<
" [" <<
type <<
"]"
704 <<
" (" << duplicate_loads_ <<
"/" << total_loads_ <<
" total)";
712 DBG_IMG <<
"surface cache [" <<
type <<
"] skip: " << i_locator;
713 skip.add_to_cache(i_locator,
true);
715 imap.add_to_cache(i_locator, res);
727 lit_surface_variants& lvar = lit_surfaces_.access_in_cache(i_locator);
730 if(
auto lvi = lvar.find(ls); lvi != lvar.end()) {
734 DBG_IMG <<
"lit surface cache miss: " << i_locator;
753 lit_texture_variants& lvar = lit_textures_.access_in_cache(i_locator);
756 if(
auto lvi = lvar.find(ls); lvi != lvar.end()) {
760 DBG_IMG <<
"lit texture cache miss: " << i_locator;
788 if(
const bool* cached_value = in_hex_info_.locate_in_cache(i_locator)) {
789 return *cached_value;
792 in_hex_info_.add_to_cache(i_locator, res);
799 if(
const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
800 return *cached_value;
806 if(
const bool* cached_value = is_empty_hex_.locate_in_cache(i_locator)) {
807 return *cached_value;
812 bool is_empty =
false;
814 is_empty_hex_.add_to_cache(i_locator, is_empty);
828 bool& cache = iter->second;
842 const std::string checked_dir = dir +
"/" + subdir;
843 if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
847 precached_dirs.insert(checked_dir);
853 std::vector<std::string> files_found;
854 std::vector<std::string> dirs_found;
858 for(
const auto&
f : files_found) {
859 image_existence_map[subdir +
f] =
true;
862 for(
const auto&
d : dirs_found) {
876 const auto b = image_existence_map.find(file);
877 if(
b != image_existence_map.end()) {
895 if(boost::algorithm::ends_with(
filename,
".jpeg") || boost::algorithm::ends_with(
filename,
".jpg") || boost::algorithm::ends_with(
filename,
".jpe")) {
902 if(boost::algorithm::ends_with(
filename,
".png")) {
940 texture_cache* cache =
nullptr;
944 cache = &textures_hexed_[quality];
947 cache = &texture_tod_colored_[quality];
950 cache = &textures_[quality];
956 if(
const texture* cached_texture = cache->locate_in_cache(i_locator)) {
957 return *cached_texture;
959 DBG_IMG <<
"texture cache [" <<
type <<
"] miss: " << i_locator;
979 DBG_IMG <<
"texture cache [" <<
type <<
"] skip: " << i_locator;
981 cache->add_to_cache(i_locator, res);
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.
modification * top() const
Returns the top element in the queue .
void pop()
Removes the top element from the queue.
Base abstract class for an image-path modification.
static modification_queue decode(const std::string &)
Decodes modifications from a modification string.
surface clone() const
Makes a copy of this surface.
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.
std::pair< std::string, unsigned > item
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)
static surface apply_light(surface surf, const light_string &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)
static surface load_from_disk(const locator &loc)
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)
texture get_lighted_texture(const image::locator &i_locator, const light_string &ls)
save_result save_image(const locator &i_locator, const std::string &filename)
std::basic_string< signed char > light_string
Type used to store color information of central and adjacent hexes.
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)
surface get_lighted_image(const image::locator &i_locator, const light_string &ls)
Caches and returns an image with a lightmap applied to it.
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
static int8_t col_to_uchar(int i)
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
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::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.
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)