1517 lines
37 KiB
C++
1517 lines
37 KiB
C++
/*
|
|
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Routines for images: load, scale, re-color, etc.
|
|
*/
|
|
|
|
#define GETTEXT_DOMAIN "wesnoth-lib"
|
|
|
|
#include "picture.hpp"
|
|
|
|
#include "config.hpp"
|
|
#include "display.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "game_config.hpp"
|
|
#include "gettext.hpp"
|
|
#include "image_modifications.hpp"
|
|
#include "log.hpp"
|
|
#include "preferences/general.hpp"
|
|
#include "serialization/base64.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
#include "sdl/rect.hpp"
|
|
#include "sdl/render_utils.hpp"
|
|
#include "sdl/texture.hpp"
|
|
#include "utils/general.hpp"
|
|
|
|
#include <SDL_image.h>
|
|
|
|
#include "utils/functional.hpp"
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/functional/hash_fwd.hpp>
|
|
|
|
#include <set>
|
|
|
|
static lg::log_domain log_display("display");
|
|
#define ERR_DP LOG_STREAM(err, log_display)
|
|
#define LOG_DP LOG_STREAM(info, log_display)
|
|
|
|
static lg::log_domain log_config("config");
|
|
#define ERR_CFG LOG_STREAM(err, log_config)
|
|
|
|
using game_config::tile_size;
|
|
|
|
template<typename T>
|
|
struct cache_item
|
|
{
|
|
cache_item()
|
|
: item()
|
|
, loaded(false)
|
|
{
|
|
}
|
|
|
|
cache_item(const T& item)
|
|
: item(item)
|
|
, loaded(true)
|
|
{
|
|
}
|
|
|
|
T item;
|
|
bool loaded;
|
|
};
|
|
|
|
namespace std
|
|
{
|
|
template<>
|
|
struct hash<image::locator::value>
|
|
{
|
|
std::size_t operator()(const image::locator::value& val) const
|
|
{
|
|
std::size_t hash = std::hash<unsigned>{}(val.type_);
|
|
|
|
if(val.type_ == image::locator::FILE || val.type_ == image::locator::SUB_FILE) {
|
|
boost::hash_combine(hash, val.filename_);
|
|
}
|
|
|
|
if(val.type_ == image::locator::SUB_FILE) {
|
|
boost::hash_combine(hash, val.loc_.x);
|
|
boost::hash_combine(hash, val.loc_.y);
|
|
boost::hash_combine(hash, val.center_x_);
|
|
boost::hash_combine(hash, val.center_y_);
|
|
boost::hash_combine(hash, val.modifications_);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace image
|
|
{
|
|
template<typename T>
|
|
class cache_type
|
|
{
|
|
public:
|
|
cache_type()
|
|
: content_()
|
|
{
|
|
}
|
|
|
|
cache_item<T>& get_element(int index)
|
|
{
|
|
if(static_cast<unsigned>(index) >= content_.size())
|
|
content_.resize(index + 1);
|
|
return content_[index];
|
|
}
|
|
|
|
void flush()
|
|
{
|
|
content_.clear();
|
|
}
|
|
|
|
private:
|
|
std::vector<cache_item<T>> content_;
|
|
};
|
|
|
|
template<typename T>
|
|
bool locator::in_cache(cache_type<T>& cache) const
|
|
{
|
|
return index_ < 0 ? false : cache.get_element(index_).loaded;
|
|
}
|
|
|
|
template<typename T>
|
|
const T& locator::locate_in_cache(cache_type<T>& cache) const
|
|
{
|
|
static T dummy;
|
|
return index_ < 0 ? dummy : cache.get_element(index_).item;
|
|
}
|
|
|
|
template<typename T>
|
|
T& locator::access_in_cache(cache_type<T>& cache) const
|
|
{
|
|
static T dummy;
|
|
return index_ < 0 ? dummy : cache.get_element(index_).item;
|
|
}
|
|
|
|
template<typename T>
|
|
void locator::add_to_cache(cache_type<T>& cache, const T& data) const
|
|
{
|
|
if(index_ >= 0) {
|
|
cache.get_element(index_) = cache_item<T>(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
image::locator::locator_finder_t locator_finder;
|
|
|
|
/** Definition of all image maps */
|
|
image::image_cache
|
|
images_,
|
|
scaled_to_zoom_,
|
|
hexed_images_,
|
|
scaled_to_hex_images_,
|
|
tod_colored_images_,
|
|
brightened_images_;
|
|
|
|
/**
|
|
* Texture caches.
|
|
* Note that the latter two are temporary and should be removed once we have OGL and shader support.
|
|
*/
|
|
using texture_cache_map = std::map<image::SCALE_QUALITY, image::texture_cache>;
|
|
|
|
texture_cache_map
|
|
textures_,
|
|
textures_hexed_,
|
|
texture_tod_colored_;
|
|
|
|
// cache storing if each image fit in a hex
|
|
image::bool_cache in_hex_info_;
|
|
|
|
// cache storing if this is an empty hex
|
|
image::bool_cache is_empty_hex_;
|
|
|
|
// caches storing the different lighted cases for each image
|
|
image::lit_cache lit_images_, lit_scaled_images_;
|
|
|
|
// caches storing each lightmap generated
|
|
image::lit_variants lightmaps_;
|
|
|
|
// const int cache_version_ = 0;
|
|
|
|
std::map<std::string, bool> image_existence_map;
|
|
|
|
// directories where we already cached file existence
|
|
std::set<std::string> precached_dirs;
|
|
|
|
std::map<surface, surface> reversed_images_;
|
|
|
|
int red_adjust = 0, green_adjust = 0, blue_adjust = 0;
|
|
|
|
unsigned int zoom = tile_size;
|
|
unsigned int cached_zoom = 0;
|
|
|
|
const std::string data_uri_prefix = "data:";
|
|
struct parsed_data_URI{
|
|
explicit parsed_data_URI(utils::string_view data_URI);
|
|
utils::string_view scheme;
|
|
utils::string_view mime;
|
|
utils::string_view base64;
|
|
utils::string_view data;
|
|
bool good;
|
|
};
|
|
parsed_data_URI::parsed_data_URI(utils::string_view data_URI)
|
|
{
|
|
const std::size_t colon = data_URI.find(':');
|
|
const utils::string_view after_scheme = data_URI.substr(colon + 1);
|
|
|
|
const std::size_t comma = after_scheme.find(',');
|
|
const utils::string_view type_info = after_scheme.substr(0, comma);
|
|
|
|
const std::size_t semicolon = type_info.find(';');
|
|
|
|
scheme = data_URI.substr(0, colon);
|
|
base64 = type_info.substr(semicolon + 1);
|
|
mime = type_info.substr(0, semicolon);
|
|
data = after_scheme.substr(comma + 1);
|
|
good = (scheme == "data" && base64 == "base64" && mime.length() > 0 && data.length() > 0);
|
|
}
|
|
|
|
} // end anon namespace
|
|
|
|
namespace image
|
|
{
|
|
mini_terrain_cache_map mini_terrain_cache;
|
|
mini_terrain_cache_map mini_fogged_terrain_cache;
|
|
mini_terrain_cache_map mini_highlighted_terrain_cache;
|
|
|
|
static int last_index_ = 0;
|
|
|
|
void flush_cache()
|
|
{
|
|
{
|
|
images_.flush();
|
|
hexed_images_.flush();
|
|
tod_colored_images_.flush();
|
|
scaled_to_zoom_.flush();
|
|
scaled_to_hex_images_.flush();
|
|
brightened_images_.flush();
|
|
lit_images_.flush();
|
|
lit_scaled_images_.flush();
|
|
in_hex_info_.flush();
|
|
is_empty_hex_.flush();
|
|
mini_terrain_cache.clear();
|
|
mini_fogged_terrain_cache.clear();
|
|
mini_highlighted_terrain_cache.clear();
|
|
reversed_images_.clear();
|
|
image_existence_map.clear();
|
|
precached_dirs.clear();
|
|
}
|
|
/* We can't reset last_index_, since some locators are still alive
|
|
when using :refresh. That would cause them to point to the wrong
|
|
images. Not resetting the variable causes a memory leak, though. */
|
|
// last_index_ = 0;
|
|
}
|
|
|
|
void locator::init_index()
|
|
{
|
|
auto i = locator_finder.find(val_);
|
|
|
|
if(i == locator_finder.end()) {
|
|
index_ = last_index_++;
|
|
locator_finder.emplace(val_, index_);
|
|
} else {
|
|
index_ = i->second;
|
|
}
|
|
}
|
|
|
|
void locator::parse_arguments()
|
|
{
|
|
std::string& fn = val_.filename_;
|
|
if(fn.empty()) {
|
|
return;
|
|
}
|
|
|
|
if(boost::algorithm::starts_with(fn, data_uri_prefix)) {
|
|
parsed_data_URI parsed{fn};
|
|
|
|
if(!parsed.good) {
|
|
utils::string_view view{ fn };
|
|
utils::string_view stripped = view.substr(0, view.find(","));
|
|
ERR_DP << "Invalid data URI: " << stripped << std::endl;
|
|
}
|
|
|
|
val_.is_data_uri_ = true;
|
|
}
|
|
|
|
std::size_t markup_field = fn.find('~');
|
|
|
|
if(markup_field != std::string::npos) {
|
|
val_.type_ = SUB_FILE;
|
|
val_.modifications_ = fn.substr(markup_field, fn.size() - markup_field);
|
|
fn = fn.substr(0, markup_field);
|
|
}
|
|
}
|
|
|
|
locator::locator()
|
|
: index_(-1)
|
|
, val_()
|
|
{
|
|
}
|
|
|
|
locator::locator(const locator& a, const std::string& mods)
|
|
: index_(-1)
|
|
, val_(a.val_)
|
|
{
|
|
if(!mods.empty()) {
|
|
val_.modifications_ += mods;
|
|
val_.type_ = SUB_FILE;
|
|
init_index();
|
|
} else {
|
|
index_ = a.index_;
|
|
}
|
|
}
|
|
|
|
locator::locator(const char* filename)
|
|
: index_(-1)
|
|
, val_(filename)
|
|
{
|
|
parse_arguments();
|
|
init_index();
|
|
}
|
|
|
|
locator::locator(const std::string& filename)
|
|
: index_(-1)
|
|
, val_(filename)
|
|
{
|
|
parse_arguments();
|
|
init_index();
|
|
}
|
|
|
|
locator::locator(const std::string& filename, const std::string& modifications)
|
|
: index_(-1)
|
|
, val_(filename, modifications)
|
|
{
|
|
init_index();
|
|
}
|
|
|
|
locator::locator(const std::string& filename,
|
|
const map_location& loc,
|
|
int center_x,
|
|
int center_y,
|
|
const std::string& modifications)
|
|
: index_(-1)
|
|
, val_(filename, loc, center_x, center_y, modifications)
|
|
{
|
|
init_index();
|
|
}
|
|
|
|
locator& locator::operator=(const locator& a)
|
|
{
|
|
index_ = a.index_;
|
|
val_ = a.val_;
|
|
|
|
return *this;
|
|
}
|
|
|
|
locator::value::value(const locator::value& a)
|
|
: type_(a.type_)
|
|
, is_data_uri_(a.is_data_uri_)
|
|
, filename_(a.filename_)
|
|
, loc_(a.loc_)
|
|
, modifications_(a.modifications_)
|
|
, center_x_(a.center_x_)
|
|
, center_y_(a.center_y_)
|
|
{
|
|
}
|
|
|
|
locator::value::value()
|
|
: type_(NONE)
|
|
, is_data_uri_(false)
|
|
, filename_()
|
|
, loc_()
|
|
, modifications_()
|
|
, center_x_(0)
|
|
, center_y_(0)
|
|
{
|
|
}
|
|
|
|
locator::value::value(const char* filename)
|
|
: type_(FILE)
|
|
, is_data_uri_(false)
|
|
, filename_(filename)
|
|
, loc_()
|
|
, modifications_()
|
|
, center_x_(0)
|
|
, center_y_(0)
|
|
{
|
|
}
|
|
|
|
locator::value::value(const std::string& filename)
|
|
: type_(FILE)
|
|
, is_data_uri_(false)
|
|
, filename_(filename)
|
|
, loc_()
|
|
, modifications_()
|
|
, center_x_(0)
|
|
, center_y_(0)
|
|
{
|
|
}
|
|
|
|
locator::value::value(const std::string& filename, const std::string& modifications)
|
|
: type_(SUB_FILE)
|
|
, is_data_uri_(false)
|
|
, filename_(filename)
|
|
, loc_()
|
|
, modifications_(modifications)
|
|
, center_x_(0)
|
|
, center_y_(0)
|
|
{
|
|
}
|
|
|
|
locator::value::value(const std::string& filename,
|
|
const map_location& loc,
|
|
int center_x,
|
|
int center_y,
|
|
const std::string& modifications)
|
|
: type_(SUB_FILE)
|
|
, is_data_uri_(false)
|
|
, filename_(filename)
|
|
, loc_(loc)
|
|
, modifications_(modifications)
|
|
, center_x_(center_x)
|
|
, center_y_(center_y)
|
|
{
|
|
}
|
|
|
|
bool locator::value::operator==(const value& a) const
|
|
{
|
|
if(a.type_ != type_) {
|
|
return false;
|
|
} else if(type_ == FILE) {
|
|
return filename_ == a.filename_;
|
|
} else if(type_ == SUB_FILE) {
|
|
return filename_ == a.filename_ && loc_ == a.loc_ && modifications_ == a.modifications_
|
|
&& center_x_ == a.center_x_ && center_y_ == a.center_y_;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool locator::value::operator<(const value& a) const
|
|
{
|
|
if(type_ != a.type_) {
|
|
return type_ < a.type_;
|
|
} else if(type_ == FILE) {
|
|
return filename_ < a.filename_;
|
|
} else if(type_ == SUB_FILE) {
|
|
if(filename_ != a.filename_)
|
|
return filename_ < a.filename_;
|
|
if(loc_ != a.loc_)
|
|
return loc_ < a.loc_;
|
|
if(center_x_ != a.center_x_)
|
|
return center_x_ < a.center_x_;
|
|
if(center_y_ != a.center_y_)
|
|
return center_y_ < a.center_y_;
|
|
return (modifications_ < a.modifications_);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Check if localized file is up-to-date according to l10n track index.
|
|
// Make sure only that the image is not explicitly recorded as fuzzy,
|
|
// in order to be able to use non-tracked images (e.g. from UMC).
|
|
static std::set<std::string> fuzzy_localized_files;
|
|
static bool localized_file_uptodate(const std::string& loc_file)
|
|
{
|
|
if(fuzzy_localized_files.empty()) {
|
|
// First call, parse track index to collect fuzzy files by path.
|
|
std::string fsep = "\xC2\xA6"; // UTF-8 for "broken bar"
|
|
std::string trackpath = filesystem::get_binary_file_location("", "l10n-track");
|
|
|
|
// l10n-track file not present. Assume image is up-to-date.
|
|
if(trackpath.empty()) {
|
|
return true;
|
|
}
|
|
|
|
std::string contents = filesystem::read_file(trackpath);
|
|
|
|
for(const std::string& line : utils::split(contents, '\n')) {
|
|
std::size_t p1 = line.find(fsep);
|
|
if(p1 == std::string::npos) {
|
|
continue;
|
|
}
|
|
|
|
std::string state = line.substr(0, p1);
|
|
boost::trim(state);
|
|
if(state == "fuzzy") {
|
|
std::size_t p2 = line.find(fsep, p1 + fsep.length());
|
|
if(p2 == std::string::npos) {
|
|
continue;
|
|
}
|
|
|
|
std::string relpath = line.substr(p1 + fsep.length(), p2 - p1 - fsep.length());
|
|
fuzzy_localized_files.insert(game_config::path + '/' + relpath);
|
|
}
|
|
}
|
|
|
|
fuzzy_localized_files.insert(""); // make sure not empty any more
|
|
}
|
|
|
|
return fuzzy_localized_files.count(loc_file) == 0;
|
|
}
|
|
|
|
// Return path to localized counterpart of the given file, if any, or empty string.
|
|
// Localized counterpart may also be requested to have a suffix to base name.
|
|
static std::string get_localized_path(const std::string& file, const std::string& suff = "")
|
|
{
|
|
std::string dir = filesystem::directory_name(file);
|
|
std::string base = filesystem::base_name(file);
|
|
|
|
const std::size_t pos_ext = base.rfind(".");
|
|
|
|
std::string loc_base;
|
|
if(pos_ext != std::string::npos) {
|
|
loc_base = base.substr(0, pos_ext) + suff + base.substr(pos_ext);
|
|
} else {
|
|
loc_base = base + suff;
|
|
}
|
|
|
|
// TRANSLATORS: This is the language code which will be used
|
|
// to store and fetch localized non-textual resources, such as images,
|
|
// when they exist. Normally it is just the code of the PO file itself,
|
|
// e.g. "de" of de.po for German. But it can also be a comma-separated
|
|
// list of language codes by priority, when the localized resource
|
|
// found for first of those languages will be used. This is useful when
|
|
// two languages share sufficient commonality, that they can use each
|
|
// other's resources rather than duplicating them. For example,
|
|
// Swedish (sv) and Danish (da) are such, so Swedish translator could
|
|
// translate this message as "sv,da", while Danish as "da,sv".
|
|
std::vector<std::string> langs = utils::split(_("language code for localized resources^en_US"));
|
|
|
|
// In case even the original image is split into base and overlay,
|
|
// add en_US with lowest priority, since the message above will
|
|
// not have it when translated.
|
|
langs.push_back("en_US");
|
|
for(const std::string& lang : langs) {
|
|
std::string loc_file = dir + "/" + "l10n" + "/" + lang + "/" + loc_base;
|
|
if(filesystem::file_exists(loc_file) && localized_file_uptodate(loc_file)) {
|
|
return loc_file;
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
// Ensure PNG images with an indexed palette are converted to 32-bit RGBA.
|
|
static void standardize_surface_format(surface& surf)
|
|
{
|
|
if(!surf.null() && !is_neutral(surf)) {
|
|
surf = make_neutral_surface(surf);
|
|
assert(is_neutral(surf));
|
|
}
|
|
}
|
|
|
|
// Load overlay image and compose it with the original surface.
|
|
static void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
|
|
{
|
|
filesystem::rwops_ptr rwops = filesystem::make_read_RWops(ovr_file);
|
|
surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
|
|
if(ovr_surf.null()) {
|
|
return;
|
|
}
|
|
|
|
standardize_surface_format(ovr_surf);
|
|
|
|
SDL_Rect area {0, 0, ovr_surf->w, ovr_surf->h};
|
|
|
|
sdl_blit(ovr_surf, 0, orig_surf, &area);
|
|
}
|
|
|
|
static surface load_image_file(const image::locator& loc)
|
|
{
|
|
surface res;
|
|
|
|
std::string location = filesystem::get_binary_file_location("images", loc.get_filename());
|
|
|
|
{
|
|
if(!location.empty()) {
|
|
// Check if there is a localized image.
|
|
const std::string loc_location = get_localized_path(location);
|
|
if(!loc_location.empty()) {
|
|
location = loc_location;
|
|
}
|
|
|
|
filesystem::rwops_ptr rwops = filesystem::make_read_RWops(location);
|
|
res = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
|
|
|
|
standardize_surface_format(res);
|
|
|
|
// If there was no standalone localized image, check if there is an overlay.
|
|
if(!res.null() && loc_location.empty()) {
|
|
const std::string ovr_location = get_localized_path(location, "--overlay");
|
|
if(!ovr_location.empty()) {
|
|
add_localized_overlay(ovr_location, res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(res.null() && !loc.get_filename().empty()) {
|
|
ERR_DP << "could not open image '" << loc.get_filename() << "'" << std::endl;
|
|
if(game_config::debug && loc.get_filename() != game_config::images::missing)
|
|
return get_image(game_config::images::missing, UNSCALED);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static surface load_image_sub_file(const image::locator& loc)
|
|
{
|
|
surface surf = get_image(loc.get_filename(), UNSCALED);
|
|
if(surf == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
modification_queue mods = modification::decode(loc.get_modifications());
|
|
|
|
while(!mods.empty()) {
|
|
modification* mod = mods.top();
|
|
|
|
try {
|
|
surf = (*mod)(surf);
|
|
} catch(const image::modification::imod_exception& e) {
|
|
std::ostringstream ss;
|
|
ss << "\n";
|
|
|
|
for(const std::string& mod2 : utils::parenthetical_split(loc.get_modifications(), '~')) {
|
|
ss << "\t" << mod2 << "\n";
|
|
}
|
|
|
|
ERR_CFG << "Failed to apply a modification to an image:\n"
|
|
<< "Image: " << loc.get_filename() << "\n"
|
|
<< "Modifications: " << ss.str() << "\n"
|
|
<< "Error: " << e.message << "\n";
|
|
}
|
|
|
|
// NOTE: do this *after* applying the mod or you'll get crashes!
|
|
mods.pop();
|
|
}
|
|
|
|
if(loc.get_loc().valid()) {
|
|
SDL_Rect srcrect = sdl::create_rect(
|
|
((tile_size * 3) / 4) * loc.get_loc().x,
|
|
tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
|
|
tile_size,
|
|
tile_size
|
|
);
|
|
|
|
if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
|
|
srcrect.x += surf->w / 2 - loc.get_center_x();
|
|
srcrect.y += surf->h / 2 - loc.get_center_y();
|
|
}
|
|
|
|
// cut and hex mask, but also check and cache if empty result
|
|
surface cut(cut_surface(surf, srcrect));
|
|
bool is_empty = false;
|
|
surf = mask_surface(cut, get_hexmask(), &is_empty);
|
|
|
|
// discard empty images to free memory
|
|
if(is_empty) {
|
|
// Safe because those images are only used by terrain rendering
|
|
// and it filters them out.
|
|
// A safer and more general way would be to keep only one copy of it
|
|
surf = nullptr;
|
|
}
|
|
|
|
loc.add_to_cache(is_empty_hex_, is_empty);
|
|
}
|
|
|
|
return surf;
|
|
}
|
|
|
|
static surface load_image_data_uri(const image::locator& loc)
|
|
{
|
|
surface surf;
|
|
|
|
parsed_data_URI parsed{loc.get_filename()};
|
|
|
|
if(!parsed.good) {
|
|
utils::string_view fn = loc.get_filename();
|
|
utils::string_view stripped = fn.substr(0, fn.find(","));
|
|
ERR_DP << "Invalid data URI: " << stripped << std::endl;
|
|
} else if(parsed.mime.substr(0, 5) != "image") {
|
|
ERR_DP << "Data URI not of image MIME type: " << parsed.mime << std::endl;
|
|
} else {
|
|
const std::vector<uint8_t> image_data = base64::decode(parsed.data);
|
|
filesystem::rwops_ptr rwops{SDL_RWFromConstMem(image_data.data(), image_data.size()), &SDL_FreeRW};
|
|
|
|
if(image_data.empty()) {
|
|
ERR_DP << "Invalid encoding in data URI" << std::endl;
|
|
} else if(parsed.mime == "image/png") {
|
|
surf = IMG_LoadTyped_RW(rwops.release(), true, "PNG");
|
|
} else if(parsed.mime == "image/jpeg") {
|
|
surf = IMG_LoadTyped_RW(rwops.release(), true, "JPG");
|
|
} else {
|
|
ERR_DP << "Invalid image MIME type: " << parsed.mime << std::endl;
|
|
}
|
|
}
|
|
|
|
return surf;
|
|
}
|
|
|
|
// small utility function to store an int from (-256,254) to an signed char
|
|
static signed char col_to_uchar(int i)
|
|
{
|
|
return static_cast<signed char>(std::min<int>(127, std::max<int>(-128, i / 2)));
|
|
}
|
|
|
|
light_string get_light_string(int op, int r, int g, int b)
|
|
{
|
|
light_string ls;
|
|
ls.reserve(4);
|
|
ls.push_back(op);
|
|
ls.push_back(col_to_uchar(r));
|
|
ls.push_back(col_to_uchar(g));
|
|
ls.push_back(col_to_uchar(b));
|
|
|
|
return ls;
|
|
}
|
|
|
|
static surface apply_light(surface surf, const light_string& ls)
|
|
{
|
|
// atomic lightmap operation are handled directly (important to end recursion)
|
|
if(ls.size() == 4) {
|
|
// if no lightmap (first char = -1) then we need the initial value
|
|
//(before the halving done for lightmap)
|
|
int m = ls[0] == -1 ? 2 : 1;
|
|
return adjust_surface_color(surf, ls[1] * m, ls[2] * m, ls[3] * m);
|
|
}
|
|
|
|
// check if the lightmap is already cached or need to be generated
|
|
surface lightmap = nullptr;
|
|
auto i = lightmaps_.find(ls);
|
|
if(i != lightmaps_.end()) {
|
|
lightmap = i->second;
|
|
} else {
|
|
// build all the paths for lightmap sources
|
|
static const std::string p = "terrain/light/light";
|
|
static const std::string lm_img[19] {
|
|
p + ".png",
|
|
p + "-concave-2-tr.png", p + "-concave-2-r.png", p + "-concave-2-br.png",
|
|
p + "-concave-2-bl.png", p + "-concave-2-l.png", p + "-concave-2-tl.png",
|
|
p + "-convex-br-bl.png", p + "-convex-bl-l.png", p + "-convex-l-tl.png",
|
|
p + "-convex-tl-tr.png", p + "-convex-tr-r.png", p + "-convex-r-br.png",
|
|
p + "-convex-l-bl.png", p + "-convex-tl-l.png", p + "-convex-tr-tl.png",
|
|
p + "-convex-r-tr.png", p + "-convex-br-r.png", p + "-convex-bl-br.png"
|
|
};
|
|
|
|
// decompose into atomic lightmap operations (4 chars)
|
|
for(std::size_t c = 0; c + 3 < ls.size(); c += 4) {
|
|
light_string sls = ls.substr(c, 4);
|
|
|
|
// get the corresponding image and apply the lightmap operation to it
|
|
// This allows to also cache lightmap parts.
|
|
// note that we avoid infinite recursion by using only atomic operation
|
|
surface lts = image::get_lighted_image(lm_img[sls[0]], sls, HEXED);
|
|
|
|
// first image will be the base where we blit the others
|
|
if(lightmap == nullptr) {
|
|
// copy the cached image to avoid modifying the cache
|
|
lightmap = make_neutral_surface(lts);
|
|
} else {
|
|
sdl_blit(lts, nullptr, lightmap, nullptr);
|
|
}
|
|
}
|
|
|
|
// cache the result
|
|
lightmaps_[ls] = lightmap;
|
|
}
|
|
|
|
// apply the final lightmap
|
|
return light_surface(surf, lightmap);
|
|
}
|
|
|
|
bool locator::file_exists() const
|
|
{
|
|
return val_.is_data_uri_
|
|
? parsed_data_URI{val_.filename_}.good
|
|
: !filesystem::get_binary_file_location("images", val_.filename_).empty();
|
|
}
|
|
|
|
surface load_from_disk(const locator& loc)
|
|
{
|
|
switch(loc.get_type()) {
|
|
case locator::FILE:
|
|
if(loc.is_data_uri()){
|
|
return load_image_data_uri(loc);
|
|
} else {
|
|
return load_image_file(loc);
|
|
}
|
|
case locator::SUB_FILE:
|
|
return load_image_sub_file(loc);
|
|
default:
|
|
return surface(nullptr);
|
|
}
|
|
}
|
|
|
|
manager::manager()
|
|
{
|
|
}
|
|
|
|
manager::~manager()
|
|
{
|
|
flush_cache();
|
|
}
|
|
|
|
void set_color_adjustment(int r, int g, int b)
|
|
{
|
|
if(r != red_adjust || g != green_adjust || b != blue_adjust) {
|
|
red_adjust = r;
|
|
green_adjust = g;
|
|
blue_adjust = b;
|
|
tod_colored_images_.flush();
|
|
brightened_images_.flush();
|
|
lit_images_.flush();
|
|
lit_scaled_images_.flush();
|
|
reversed_images_.clear();
|
|
}
|
|
}
|
|
|
|
void set_zoom(unsigned int amount)
|
|
{
|
|
if(amount != zoom) {
|
|
zoom = amount;
|
|
tod_colored_images_.flush();
|
|
brightened_images_.flush();
|
|
reversed_images_.clear();
|
|
|
|
// We keep these caches if:
|
|
// we use default zoom (it doesn't need those)
|
|
// or if they are already at the wanted zoom.
|
|
if(zoom != tile_size && zoom != cached_zoom) {
|
|
scaled_to_zoom_.flush();
|
|
scaled_to_hex_images_.flush();
|
|
lit_scaled_images_.flush();
|
|
cached_zoom = zoom;
|
|
}
|
|
}
|
|
}
|
|
|
|
static surface get_hexed(const locator& i_locator)
|
|
{
|
|
surface image(get_image(i_locator, UNSCALED));
|
|
// hex cut tiles, also check and cache if empty result
|
|
bool is_empty = false;
|
|
surface res = mask_surface(image, get_hexmask(), &is_empty, i_locator.get_filename());
|
|
i_locator.add_to_cache(is_empty_hex_, is_empty);
|
|
return res;
|
|
}
|
|
|
|
static surface get_scaled_to_hex(const locator& i_locator)
|
|
{
|
|
surface img = get_image(i_locator, HEXED);
|
|
// return scale_surface(img, zoom, zoom);
|
|
|
|
if(!img.null()) {
|
|
return scale_surface_nn(img, zoom, zoom);
|
|
}
|
|
|
|
return surface(nullptr);
|
|
|
|
}
|
|
|
|
static surface get_tod_colored(const locator& i_locator)
|
|
{
|
|
surface img = get_image(i_locator, SCALED_TO_HEX);
|
|
return adjust_surface_color(img, red_adjust, green_adjust, blue_adjust);
|
|
}
|
|
|
|
static surface get_scaled_to_zoom(const locator& i_locator)
|
|
{
|
|
assert(zoom != tile_size);
|
|
assert(tile_size != 0);
|
|
|
|
surface res(get_image(i_locator, UNSCALED));
|
|
// For some reason haloes seems to have invalid images, protect against crashing
|
|
if(!res.null()) {
|
|
return scale_surface_nn(res, ((res->w * zoom) / tile_size), ((res->h * zoom) / tile_size));
|
|
}
|
|
|
|
return surface(nullptr);
|
|
}
|
|
|
|
static surface get_brightened(const locator& i_locator)
|
|
{
|
|
surface image(get_image(i_locator, TOD_COLORED));
|
|
return brighten_image(image, ftofxp(game_config::hex_brightening));
|
|
}
|
|
|
|
/// translate type to a simpler one when possible
|
|
static TYPE simplify_type(const image::locator& i_locator, TYPE type)
|
|
{
|
|
switch(type) {
|
|
case SCALED_TO_ZOOM:
|
|
if(zoom == tile_size) {
|
|
type = UNSCALED;
|
|
}
|
|
|
|
break;
|
|
case BRIGHTENED:
|
|
if(ftofxp(game_config::hex_brightening) == ftofxp(1.0)) {
|
|
type = TOD_COLORED;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(type == TOD_COLORED) {
|
|
if(red_adjust == 0 && green_adjust == 0 && blue_adjust == 0) {
|
|
type = SCALED_TO_HEX;
|
|
}
|
|
}
|
|
|
|
if(type == SCALED_TO_HEX) {
|
|
if(zoom == tile_size) {
|
|
type = HEXED;
|
|
}
|
|
}
|
|
|
|
if(type == HEXED) {
|
|
// check if the image is already hex-cut by the location system
|
|
if(i_locator.get_loc().valid()) {
|
|
type = UNSCALED;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
surface get_image(const image::locator& i_locator, TYPE type)
|
|
{
|
|
surface res;
|
|
|
|
if(i_locator.is_void()) {
|
|
return res;
|
|
}
|
|
|
|
type = simplify_type(i_locator, type);
|
|
|
|
image_cache* imap;
|
|
// select associated cache
|
|
switch(type) {
|
|
case UNSCALED:
|
|
imap = &images_;
|
|
break;
|
|
case TOD_COLORED:
|
|
imap = &tod_colored_images_;
|
|
break;
|
|
case SCALED_TO_ZOOM:
|
|
imap = &scaled_to_zoom_;
|
|
break;
|
|
case HEXED:
|
|
imap = &hexed_images_;
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
imap = &scaled_to_hex_images_;
|
|
break;
|
|
case BRIGHTENED:
|
|
imap = &brightened_images_;
|
|
break;
|
|
default:
|
|
return res;
|
|
}
|
|
|
|
// return the image if already cached
|
|
bool tmp = i_locator.in_cache(*imap);
|
|
|
|
if(tmp) {
|
|
surface result = i_locator.locate_in_cache(*imap);
|
|
return result;
|
|
}
|
|
|
|
// not cached, generate it
|
|
switch(type) {
|
|
case UNSCALED:
|
|
// If type is unscaled, directly load the image from the disk.
|
|
res = load_from_disk(i_locator);
|
|
break;
|
|
case TOD_COLORED:
|
|
res = get_tod_colored(i_locator);
|
|
break;
|
|
case SCALED_TO_ZOOM:
|
|
res = get_scaled_to_zoom(i_locator);
|
|
break;
|
|
case HEXED:
|
|
res = get_hexed(i_locator);
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
res = get_scaled_to_hex(i_locator);
|
|
break;
|
|
case BRIGHTENED:
|
|
res = get_brightened(i_locator);
|
|
break;
|
|
default:
|
|
return res;
|
|
}
|
|
|
|
i_locator.add_to_cache(*imap, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
surface get_lighted_image(const image::locator& i_locator, const light_string& ls, TYPE type)
|
|
{
|
|
surface res;
|
|
if(i_locator.is_void()) {
|
|
return res;
|
|
}
|
|
|
|
if(type == SCALED_TO_HEX && zoom == tile_size) {
|
|
type = HEXED;
|
|
}
|
|
|
|
// select associated cache
|
|
lit_cache* imap = &lit_images_;
|
|
if(type == SCALED_TO_HEX) {
|
|
imap = &lit_scaled_images_;
|
|
}
|
|
|
|
// if no light variants yet, need to add an empty map
|
|
if(!i_locator.in_cache(*imap)) {
|
|
i_locator.add_to_cache(*imap, lit_variants());
|
|
}
|
|
|
|
// need access to add it if not found
|
|
{ // enclose reference pointing to data stored in a changing vector
|
|
const lit_variants& lvar = i_locator.locate_in_cache(*imap);
|
|
auto lvi = lvar.find(ls);
|
|
if(lvi != lvar.end()) {
|
|
return lvi->second;
|
|
}
|
|
}
|
|
|
|
// not cached yet, generate it
|
|
switch(type) {
|
|
case HEXED:
|
|
res = get_image(i_locator, HEXED);
|
|
res = apply_light(res, ls);
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
// we light before scaling to reuse the unscaled cache
|
|
res = get_lighted_image(i_locator, ls, HEXED);
|
|
res = scale_surface(res, zoom, zoom);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// record the lighted surface in the corresponding variants cache
|
|
i_locator.access_in_cache(*imap)[ls] = res;
|
|
|
|
return res;
|
|
}
|
|
|
|
surface get_hexmask()
|
|
{
|
|
static const image::locator terrain_mask(game_config::images::terrain_mask);
|
|
return get_image(terrain_mask, UNSCALED);
|
|
}
|
|
|
|
bool is_in_hex(const locator& i_locator)
|
|
{
|
|
bool result;
|
|
{
|
|
if(i_locator.in_cache(in_hex_info_)) {
|
|
result = i_locator.locate_in_cache(in_hex_info_);
|
|
} else {
|
|
const surface image(get_image(i_locator, UNSCALED));
|
|
|
|
bool res = in_mask_surface(image, get_hexmask());
|
|
|
|
i_locator.add_to_cache(in_hex_info_, res);
|
|
|
|
// std::cout << "in_hex : " << i_locator.get_filename()
|
|
// << " " << (res ? "yes" : "no") << "\n";
|
|
|
|
result = res;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool is_empty_hex(const locator& i_locator)
|
|
{
|
|
if(!i_locator.in_cache(is_empty_hex_)) {
|
|
const surface surf = get_image(i_locator, HEXED);
|
|
// emptiness of terrain image is checked during hex cut
|
|
// so, maybe in cache now, let's recheck
|
|
if(!i_locator.in_cache(is_empty_hex_)) {
|
|
// should never reach here
|
|
// but do it manually if it happens
|
|
// assert(false);
|
|
bool is_empty = false;
|
|
mask_surface(surf, get_hexmask(), &is_empty);
|
|
i_locator.add_to_cache(is_empty_hex_, is_empty);
|
|
}
|
|
}
|
|
|
|
return i_locator.locate_in_cache(is_empty_hex_);
|
|
}
|
|
|
|
surface reverse_image(const surface& surf)
|
|
{
|
|
if(surf == nullptr) {
|
|
return surface(nullptr);
|
|
}
|
|
|
|
const auto itor = reversed_images_.find(surf);
|
|
if(itor != reversed_images_.end()) {
|
|
// sdl_add_ref(itor->second);
|
|
return itor->second;
|
|
}
|
|
|
|
const surface rev(flip_surface(surf));
|
|
if(rev == nullptr) {
|
|
return surface(nullptr);
|
|
}
|
|
|
|
reversed_images_.emplace(surf, rev);
|
|
// sdl_add_ref(rev);
|
|
return rev;
|
|
}
|
|
|
|
bool exists(const image::locator& i_locator)
|
|
{
|
|
typedef image::locator loc;
|
|
loc::type type = i_locator.get_type();
|
|
if(type != loc::FILE && type != loc::SUB_FILE) {
|
|
return false;
|
|
}
|
|
|
|
// The insertion will fail if there is already an element in the cache
|
|
// and this will point to the existing element.
|
|
auto iter = image_existence_map.begin();
|
|
bool success;
|
|
|
|
std::tie(iter, success) = image_existence_map.emplace(i_locator.get_filename(), false);
|
|
|
|
bool& cache = iter->second;
|
|
if(success) {
|
|
if(i_locator.is_data_uri()) {
|
|
cache = parsed_data_URI{i_locator.get_filename()}.good;
|
|
} else {
|
|
cache = !filesystem::get_binary_file_location("images", i_locator.get_filename()).empty();
|
|
}
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
static void precache_file_existence_internal(const std::string& dir, const std::string& subdir)
|
|
{
|
|
const std::string checked_dir = dir + "/" + subdir;
|
|
if(precached_dirs.find(checked_dir) != precached_dirs.end()) {
|
|
return;
|
|
}
|
|
|
|
precached_dirs.insert(checked_dir);
|
|
|
|
if(!filesystem::is_directory(checked_dir)) {
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> files_found;
|
|
std::vector<std::string> dirs_found;
|
|
filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found, filesystem::FILE_NAME_ONLY,
|
|
filesystem::NO_FILTER, filesystem::DONT_REORDER);
|
|
|
|
for(const auto& f : files_found) {
|
|
image_existence_map[subdir + f] = true;
|
|
}
|
|
|
|
for(const auto& d : dirs_found) {
|
|
precache_file_existence_internal(dir, subdir + d + "/");
|
|
}
|
|
}
|
|
|
|
void precache_file_existence(const std::string& subdir)
|
|
{
|
|
const std::vector<std::string>& paths = filesystem::get_binary_paths("images");
|
|
|
|
for(const auto& p : paths) {
|
|
precache_file_existence_internal(p, subdir);
|
|
}
|
|
}
|
|
|
|
bool precached_file_exists(const std::string& file)
|
|
{
|
|
const auto b = image_existence_map.find(file);
|
|
if(b != image_existence_map.end()) {
|
|
return b->second;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
save_result save_image(const locator& i_locator, const std::string& filename)
|
|
{
|
|
return save_image(get_image(i_locator), filename);
|
|
}
|
|
|
|
save_result save_image(const surface& surf, const std::string& filename)
|
|
{
|
|
if(surf.null()) {
|
|
return save_result::no_image;
|
|
}
|
|
|
|
#ifdef SDL_IMAGE_VERSION_ATLEAST
|
|
#if SDL_IMAGE_VERSION_ATLEAST(2, 0, 2)
|
|
if(filesystem::ends_with(filename, ".jpeg") || filesystem::ends_with(filename, ".jpg") || filesystem::ends_with(filename, ".jpe")) {
|
|
LOG_DP << "Writing a JPG image to " << filename << std::endl;
|
|
|
|
const int err = IMG_SaveJPG_RW(surf, filesystem::make_write_RWops(filename).release(), true, 75); // SDL takes ownership of the RWops
|
|
return err == 0 ? save_result::success : save_result::save_failed;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if(filesystem::ends_with(filename, ".png")) {
|
|
LOG_DP << "Writing a PNG image to " << filename << std::endl;
|
|
|
|
const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
|
|
return err == 0 ? save_result::success : save_result::save_failed;
|
|
}
|
|
|
|
if(filesystem::ends_with(filename, ".bmp")) {
|
|
LOG_DP << "Writing a BMP image to " << filename << std::endl;
|
|
const int err = SDL_SaveBMP(surf, filename.c_str()) == 0;
|
|
return err == 0 ? save_result::success : save_result::save_failed;
|
|
}
|
|
|
|
return save_result::unsupported_format;
|
|
}
|
|
|
|
/*
|
|
* TEXTURE INTERFACE ======================================================================
|
|
*
|
|
* I'm keeping this seperate from the surface-based handling above since the two approaches
|
|
* are so different. Might move this to a file of its own in the future.
|
|
*/
|
|
|
|
/** Sets the texture scale quality hint. Must be called *before* creating textures! */
|
|
static void set_scale_quality_pre_texture_creation(SCALE_QUALITY quality)
|
|
{
|
|
static const std::string n_scale_str = "nearest";
|
|
static const std::string l_scale_str = "linear";
|
|
|
|
set_texture_scale_quality(quality == NEAREST ? n_scale_str : l_scale_str);
|
|
}
|
|
|
|
/** Loads a new texture directly from disk. */
|
|
static texture create_texture_from_file(const image::locator& loc)
|
|
{
|
|
texture res;
|
|
|
|
// We need the window renderer to load the texture.
|
|
SDL_Renderer* renderer = CVideo::get_singleton().get_renderer();
|
|
if(!renderer) {
|
|
return res;
|
|
}
|
|
|
|
std::string location = filesystem::get_binary_file_location("images", loc.get_filename());
|
|
|
|
if(!location.empty()) {
|
|
#if 0
|
|
// Check if there is a localized image.
|
|
const std::string loc_location = get_localized_path(location);
|
|
if(!loc_location.empty()) {
|
|
location = loc_location;
|
|
}
|
|
#endif
|
|
|
|
// TODO: if we need to use SDL_RWops we should use IMG_LoadTexture_RW here instead.
|
|
{
|
|
res.assign(IMG_LoadTexture(renderer, location.c_str()));
|
|
}
|
|
|
|
// TODO: decide what to do about this.
|
|
#if 0
|
|
// If there was no standalone localized image, check if there is an overlay.
|
|
if(!res.null() && loc_location.empty()) {
|
|
const std::string ovr_location = get_localized_path(location, "--overlay");
|
|
if(!ovr_location.empty()) {
|
|
add_localized_overlay(ovr_location, res);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(res.null() && !loc.get_filename().empty()) {
|
|
ERR_DP << "Could not load texture for image '" << loc.get_filename() << "'" << std::endl;
|
|
|
|
// Also decide what to do here.
|
|
#if 0
|
|
if(game_config::debug && loc.get_filename() != game_config::images::missing) {
|
|
return get_texture(game_config::images::missing, UNSCALED);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Handle IPF manipulation. Since we don't have shaders yet, we need to use the surface
|
|
* modification code for now. It appears each result is saved in the relevant cache,
|
|
* so this should hopefully only result in a small slowdown when first processing the
|
|
* results.
|
|
*/
|
|
static texture create_texture_from_sub_file(const image::locator& loc)
|
|
{
|
|
surface surf = get_image(loc.get_filename(), UNSCALED);
|
|
if(surf == nullptr) {
|
|
return texture();
|
|
}
|
|
|
|
modification_queue mods = modification::decode(loc.get_modifications());
|
|
|
|
while(!mods.empty()) {
|
|
modification* mod = mods.top();
|
|
|
|
try {
|
|
surf = (*mod)(surf);
|
|
} catch(const image::modification::imod_exception& e) {
|
|
std::ostringstream ss;
|
|
ss << "\n";
|
|
|
|
for(const std::string& mod2 : utils::parenthetical_split(loc.get_modifications(), '~')) {
|
|
ss << "\t" << mod2 << "\n";
|
|
}
|
|
|
|
ERR_CFG << "Failed to apply a modification to an image:\n"
|
|
<< "Image: " << loc.get_filename() << "\n"
|
|
<< "Modifications: " << ss.str() << "\n"
|
|
<< "Error: " << e.message << "\n";
|
|
}
|
|
|
|
// NOTE: do this *after* applying the mod or you'll get crashes!
|
|
mods.pop();
|
|
}
|
|
|
|
#if 0
|
|
if(loc.get_loc().valid()) {
|
|
SDL_Rect srcrect = sdl::create_rect(
|
|
((tile_size * 3) / 4) * loc.get_loc().x,
|
|
tile_size * loc.get_loc().y + (tile_size / 2) * (loc.get_loc().x % 2),
|
|
tile_size,
|
|
tile_size
|
|
);
|
|
|
|
if(loc.get_center_x() >= 0 && loc.get_center_y() >= 0) {
|
|
srcrect.x += surf->w / 2 - loc.get_center_x();
|
|
srcrect.y += surf->h / 2 - loc.get_center_y();
|
|
}
|
|
|
|
// cut and hex mask, but also check and cache if empty result
|
|
surface cut(cut_surface(surf, srcrect));
|
|
bool is_empty = false;
|
|
surf = mask_surface(cut, get_hexmask(), &is_empty);
|
|
|
|
// discard empty images to free memory
|
|
if(is_empty) {
|
|
// Safe because those images are only used by terrain rendering
|
|
// and it filters them out.
|
|
// A safer and more general way would be to keep only one copy of it
|
|
surf = nullptr;
|
|
}
|
|
|
|
loc.add_to_cache(is_empty_hex_, is_empty);
|
|
}
|
|
#endif
|
|
|
|
return texture(surf);
|
|
}
|
|
|
|
texture create_texture_from_disk(const locator& loc)
|
|
{
|
|
switch(loc.get_type()) {
|
|
case locator::FILE:
|
|
if(loc.is_data_uri()){
|
|
return texture(load_image_data_uri(loc));
|
|
} else {
|
|
return create_texture_from_file(loc);
|
|
}
|
|
case locator::SUB_FILE:
|
|
return create_texture_from_sub_file(loc);
|
|
default:
|
|
return texture();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Small wrapper for creating a texture after applying a specific type of surface op.
|
|
* Won't be necessary once we get shader support.
|
|
*/
|
|
static texture create_texture_post_surface_op(const image::locator& i_locator, TYPE type)
|
|
{
|
|
surface surf = get_image(i_locator, type);
|
|
if(!surf) {
|
|
return texture();
|
|
}
|
|
|
|
return texture(surf);
|
|
}
|
|
|
|
texture get_texture(const image::locator& i_locator, TYPE type)
|
|
{
|
|
return get_texture(i_locator, NEAREST, type);
|
|
}
|
|
|
|
/** Returns a texture for the corresponding image. */
|
|
texture get_texture(const image::locator& i_locator, SCALE_QUALITY quality, TYPE type)
|
|
{
|
|
texture res;
|
|
|
|
if(i_locator.is_void()) {
|
|
return res;
|
|
}
|
|
|
|
// FIXME
|
|
//type = simplify_type(i_locator, type);
|
|
|
|
//
|
|
// Select the appropriate cache. We don't need caches for every single image types,
|
|
// since some types can be handled by render-time operations.
|
|
//
|
|
texture_cache* cache = nullptr;
|
|
|
|
switch(type) {
|
|
case HEXED:
|
|
cache = &textures_hexed_[quality];
|
|
break;
|
|
case TOD_COLORED:
|
|
cache = &texture_tod_colored_[quality];
|
|
break;
|
|
default:
|
|
cache = &textures_[quality];
|
|
}
|
|
|
|
//
|
|
// Now attempt to find a cached texture. If found, return it.
|
|
//
|
|
bool in_cache = i_locator.in_cache(*cache);
|
|
|
|
if(in_cache) {
|
|
res = i_locator.locate_in_cache(*cache);
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// No texture was cached. In that case, create a new one. The explicit cases require special
|
|
// handling with surfaces in order to generate the desired effect. This shouldn't be the case
|
|
// once we get OGL and shader support.
|
|
//
|
|
set_scale_quality_pre_texture_creation(quality);
|
|
|
|
switch(type) {
|
|
case TOD_COLORED:
|
|
case HEXED:
|
|
res = create_texture_post_surface_op(i_locator, type);
|
|
break;
|
|
default:
|
|
res = create_texture_from_disk(i_locator);
|
|
}
|
|
|
|
// If the texture is null at this point, return without any further action (like caching).
|
|
if(!res) {
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// Apply the appropriate render flags. (TODO)
|
|
//
|
|
#if 0
|
|
switch(type) {
|
|
case SCALED_TO_ZOOM:
|
|
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
|
|
break;
|
|
case BRIGHTENED:
|
|
|
|
break;
|
|
default:
|
|
// Ignore other types.
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// And finally add the texture to the cache.
|
|
//
|
|
i_locator.add_to_cache(*cache, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
} // end namespace image
|