
Kill set_wm_icon function, since SDL_WM_SetIcon can be called safely only at one place. Clearly hardcode the image's url instead of pretending that we use game_config. Rename the image to "game-icon.png" (since you are forced to use it).
1249 lines
33 KiB
C++
1249 lines
33 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2010 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://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 version 2
|
|
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 "global.hpp"
|
|
|
|
#include "color_range.hpp"
|
|
#include "config.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "foreach.hpp"
|
|
#include "game_config.hpp"
|
|
#include "image.hpp"
|
|
#include "image_function.hpp"
|
|
#include "log.hpp"
|
|
#include "gettext.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
|
|
#include "SDL_image.h"
|
|
|
|
#include <boost/functional/hash.hpp>
|
|
|
|
#include <list>
|
|
#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)
|
|
|
|
/**
|
|
* Iterators from this dummy list are needed to ensure that iterator member
|
|
* of cache_item is always non-singular iterator thus avoiding
|
|
* "Copy-contruct from singular iterator" error when libstdc++ debug mode
|
|
* is enabled. Note copying a singular iterator is undefined behaviour by
|
|
* the C++ standard.
|
|
*/
|
|
static std::list<int> dummy_list;
|
|
|
|
template<typename T>
|
|
struct cache_item
|
|
{
|
|
cache_item(): loaded(false), item(), position(dummy_list.end())
|
|
{
|
|
}
|
|
|
|
cache_item(const T &item): loaded(true), item(item), position(dummy_list.end())
|
|
{
|
|
}
|
|
|
|
bool loaded;
|
|
T item;
|
|
std::list<int>::iterator position;
|
|
};
|
|
|
|
namespace image {
|
|
|
|
template<typename T>
|
|
class cache_type
|
|
{
|
|
public:
|
|
cache_type(): cache_size_(0), cache_max_size_(2000), lru_list_(), content_()
|
|
{
|
|
}
|
|
|
|
cache_item<T> &get_element(int index);
|
|
void on_load(int index);
|
|
|
|
void flush()
|
|
{
|
|
content_.clear();
|
|
lru_list_.clear();
|
|
cache_size_ = 0;
|
|
}
|
|
|
|
private:
|
|
int cache_size_;
|
|
int cache_max_size_;
|
|
std::list<int> lru_list_;
|
|
std::vector<cache_item<T> > content_;
|
|
};
|
|
|
|
template <typename T>
|
|
bool locator::in_cache(cache_type<T> &cache) const
|
|
{
|
|
return index_ == -1 ? 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_ == -1 ? dummy : cache.get_element(index_).item;
|
|
}
|
|
|
|
template <typename T>
|
|
void locator::add_to_cache(cache_type<T> &cache, const T &data) const
|
|
{
|
|
if (index_ != -1 ) cache.get_element(index_) = cache_item<T>(data);
|
|
cache.on_load(index_);
|
|
}
|
|
|
|
}
|
|
|
|
namespace {
|
|
|
|
image::locator::locator_finder_t locator_finder;
|
|
|
|
/** Definition of all image maps */
|
|
image::image_cache images_,hexed_images_,scaled_to_hex_images_,scaled_to_zoom_,unmasked_images_;
|
|
image::image_cache brightened_images_,semi_brightened_images_;
|
|
|
|
// cache storing if each image fit in a hex
|
|
image::bool_cache in_hex_info_;
|
|
|
|
// 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;
|
|
|
|
/** List of colors used by the TC image modification */
|
|
std::vector<std::string> team_colors;
|
|
|
|
int zoom = image::tile_size;
|
|
int cached_zoom = 0;
|
|
|
|
} // end anon namespace
|
|
|
|
namespace image {
|
|
|
|
std::list<int> dummy_list;
|
|
|
|
mini_terrain_cache_map mini_terrain_cache;
|
|
mini_terrain_cache_map mini_fogged_terrain_cache;
|
|
|
|
static int last_index_ = 0;
|
|
|
|
void flush_cache()
|
|
{
|
|
images_.flush();
|
|
hexed_images_.flush();
|
|
scaled_to_hex_images_.flush();
|
|
scaled_to_zoom_.flush();
|
|
unmasked_images_.flush();
|
|
brightened_images_.flush();
|
|
semi_brightened_images_.flush();
|
|
in_hex_info_.flush();
|
|
mini_terrain_cache.clear();
|
|
mini_fogged_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()
|
|
{
|
|
std::map<value, int>& finder = locator_finder[hash_value(val_)];
|
|
std::map<value, int>::iterator i = finder.find(val_);
|
|
|
|
if(i == finder.end()) {
|
|
index_ = last_index_++;
|
|
finder.insert(std::make_pair(val_, index_));
|
|
} else {
|
|
index_ = i->second;
|
|
}
|
|
}
|
|
|
|
void locator::parse_arguments()
|
|
{
|
|
std::string& fn = val_.filename_;
|
|
if(fn.empty()) {
|
|
return;
|
|
}
|
|
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.size()){
|
|
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_), filename_(a.filename_), loc_(a.loc_),
|
|
modifications_(a.modifications_),
|
|
center_x_(a.center_x_), center_y_(a.center_y_)
|
|
{
|
|
}
|
|
|
|
locator::value::value() :
|
|
type_(NONE), filename_(), loc_(), modifications_(),
|
|
center_x_(0), center_y_(0)
|
|
|
|
{}
|
|
|
|
locator::value::value(const char *filename) :
|
|
type_(FILE), filename_(filename), loc_(), modifications_(),
|
|
center_x_(0), center_y_(0)
|
|
|
|
{
|
|
}
|
|
|
|
locator::value::value(const std::string& filename) :
|
|
type_(FILE), filename_(filename), loc_(), modifications_(),
|
|
center_x_(0), center_y_(0)
|
|
|
|
{
|
|
}
|
|
|
|
locator::value::value(const std::string& filename, const std::string& modifications) :
|
|
type_(SUB_FILE), 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), 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_;
|
|
} else {
|
|
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_);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
size_t hash_value(const locator::value& val) {
|
|
using boost::hash_value;
|
|
using boost::hash_combine;
|
|
|
|
size_t hash = hash_value(val.type_);
|
|
if (val.type_ == locator::FILE || val.type_ == locator::SUB_FILE) {
|
|
hash_combine(hash, val.filename_);
|
|
}
|
|
if (val.type_ == locator::SUB_FILE) {
|
|
hash_combine(hash, val.loc_.x);
|
|
hash_combine(hash, val.loc_.y);
|
|
hash_combine(hash, val.center_x_);
|
|
hash_combine(hash, val.center_y_);
|
|
hash_combine(hash, val.modifications_);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
// Check if localized file is uptodate 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.size() == 0) {
|
|
// First call, parse track index to collect fuzzy files by path.
|
|
std::string fsep = "\xC2\xA6"; // UTF-8 for "broken bar"
|
|
std::string trackpath = get_binary_file_location("", "l10n-track");
|
|
std::string contents = read_file(trackpath);
|
|
std::vector<std::string> lines = utils::split(contents, '\n');
|
|
foreach (const std::string &line, lines) {
|
|
size_t p1 = line.find(fsep);
|
|
if (p1 == std::string::npos)
|
|
continue;
|
|
std::string state = line.substr(0, p1);
|
|
utils::strip(state);
|
|
if (state == "fuzzy") {
|
|
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 = directory_name(file);
|
|
std::string base = file_name(file);
|
|
const 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");
|
|
foreach (const std::string &lang, langs) {
|
|
std::string loc_file = dir + "l10n" + "/" + lang + "/" + loc_base;
|
|
if (file_exists(loc_file) && localized_file_uptodate(loc_file)) {
|
|
return loc_file;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Load overlay image and compose it with the original surface.
|
|
static void add_localized_overlay (const std::string& ovr_file, const surface &orig_surf)
|
|
{
|
|
surface ovr_surf = IMG_Load(ovr_file.c_str());
|
|
if (ovr_surf.null()) {
|
|
return;
|
|
}
|
|
SDL_Rect area;
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.w = ovr_surf->w;
|
|
area.h = ovr_surf->h;
|
|
SDL_BlitSurface(ovr_surf, 0, orig_surf, &area);
|
|
}
|
|
|
|
surface locator::load_image_file() const
|
|
{
|
|
surface res;
|
|
|
|
std::string location = get_binary_file_location("images", val_.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;
|
|
}
|
|
res = IMG_Load(location.c_str());
|
|
// 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() && !val_.filename_.empty()) {
|
|
ERR_DP << "could not open image '" << val_.filename_ << "'\n";
|
|
static const std::string missing = "misc/missing-image.png";
|
|
if (game_config::debug && val_.filename_ != missing)
|
|
return get_image(missing, UNSCALED);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
surface locator::load_image_sub_file() const
|
|
{
|
|
const surface mother_surface(get_image(val_.filename_, UNSCALED));
|
|
static const image::locator terrain_mask(game_config::images::terrain_mask);
|
|
const surface mask(get_image(terrain_mask, UNSCALED));
|
|
|
|
if(mother_surface == NULL)
|
|
return surface(NULL);
|
|
if(mask == NULL)
|
|
return surface(NULL);
|
|
|
|
surface surf=mother_surface;
|
|
if(val_.loc_.x>-1 && val_.loc_.y>-1 && val_.center_x_>-1 && val_.center_y_>-1){
|
|
int offset_x = mother_surface->w/2 - val_.center_x_;
|
|
int offset_y = mother_surface->h/2 - val_.center_y_;
|
|
SDL_Rect srcrect = create_rect(
|
|
((tile_size*3) / 4) * val_.loc_.x + offset_x
|
|
, tile_size * val_.loc_.y + (tile_size / 2) * (val_.loc_.x % 2) + offset_y
|
|
, tile_size
|
|
, tile_size);
|
|
|
|
surface tmp(cut_surface(mother_surface, srcrect));
|
|
surf=mask_surface(tmp, mask);
|
|
}
|
|
else if(val_.loc_.x>-1 && val_.loc_.y>-1 ){
|
|
SDL_Rect srcrect = create_rect(
|
|
((tile_size*3) / 4) * val_.loc_.x
|
|
, tile_size * val_.loc_.y + (tile_size / 2) * (val_.loc_.x % 2)
|
|
, tile_size
|
|
, tile_size);
|
|
|
|
surface tmp(cut_surface(mother_surface, srcrect));
|
|
surf=mask_surface(tmp, mask);
|
|
}
|
|
|
|
if(val_.modifications_.size()){
|
|
// The RC functor is very special; it must be applied
|
|
// before anything else, and it is not accumulative.
|
|
rc_function rc;
|
|
// The FL functor is delayed until the end of the sequence.
|
|
// This allows us to ignore things like ~FL(horiz)~FL(horiz)
|
|
fl_function fl;
|
|
// Regular functors
|
|
std::vector< image::function_base* > functor_queue;
|
|
|
|
const std::vector<std::string> modlist = utils::parenthetical_split(val_.modifications_,'~');
|
|
|
|
foreach(const std::string& s, modlist) {
|
|
const std::vector<std::string> tmpmod = utils::parenthetical_split(s);
|
|
std::vector<std::string>::const_iterator j = tmpmod.begin();
|
|
while(j!= tmpmod.end()){
|
|
const std::string function = *j++;
|
|
if(j == tmpmod.end()){
|
|
if(function.size()){
|
|
ERR_DP << "error parsing image modifications: "
|
|
<< val_.modifications_<< "\n";
|
|
}
|
|
break;
|
|
}
|
|
const std::string field = *j++;
|
|
typedef std::pair<Uint32,Uint32> rc_entry_type;
|
|
|
|
// Team color (TC), a subset of RC's functionality
|
|
if("TC" == function) {
|
|
std::vector<std::string> param = utils::split(field,',');
|
|
if(param.size() < 2) {
|
|
ERR_DP << "too few arguments passed to the ~TC() function\n";
|
|
break;
|
|
}
|
|
|
|
int side_n = lexical_cast_default<int>(param[0], -1);
|
|
std::string team_color;
|
|
if (side_n < 1) {
|
|
ERR_DP << "invalid team (" << side_n << ") passed to the ~TC() function\n";
|
|
break;
|
|
}
|
|
else if (side_n < static_cast<int>(team_colors.size())) {
|
|
team_color = team_colors[side_n - 1];
|
|
}
|
|
else {
|
|
// This side is not initialized; use default "n"
|
|
try {
|
|
team_color = lexical_cast<std::string>(side_n);
|
|
} catch(bad_lexical_cast const&) {
|
|
ERR_DP << "bad things happen\n";
|
|
}
|
|
}
|
|
|
|
//
|
|
// Pass parameters for RC functor
|
|
//
|
|
if(game_config::tc_info(param[1]).size()){
|
|
std::map<Uint32, Uint32> tmp_map;
|
|
try {
|
|
color_range const& new_color =
|
|
game_config::color_info(team_color);
|
|
std::vector<Uint32> const& old_color =
|
|
game_config::tc_info(param[1]);
|
|
|
|
tmp_map = recolor_range(new_color,old_color);
|
|
}
|
|
catch(config::error const& e) {
|
|
ERR_DP
|
|
<< "caught config::error while processing TC: "
|
|
<< e.message
|
|
<< '\n';
|
|
ERR_DP
|
|
<< "bailing out from TC\n";
|
|
tmp_map.clear();
|
|
}
|
|
|
|
foreach(const rc_entry_type& rc_entry, tmp_map) {
|
|
rc.map()[rc_entry.first] = rc_entry.second;
|
|
}
|
|
}
|
|
else {
|
|
ERR_DP
|
|
<< "could not load TC info for '" << param[1] << "' palette\n";
|
|
ERR_DP
|
|
<< "bailing out from TC\n";
|
|
}
|
|
|
|
}
|
|
// Palette recolor (RC)
|
|
else if("RC" == function) {
|
|
const std::vector<std::string> recolor_params = utils::split(field,'>');
|
|
if(recolor_params.size()>1){
|
|
//
|
|
// recolor source palette to color range
|
|
//
|
|
std::map<Uint32, Uint32> tmp_map;
|
|
try {
|
|
color_range const& new_color =
|
|
game_config::color_info(recolor_params[1]);
|
|
std::vector<Uint32> const& old_color =
|
|
game_config::tc_info(recolor_params[0]);
|
|
|
|
tmp_map = recolor_range(new_color,old_color);
|
|
}
|
|
catch (config::error& e) {
|
|
ERR_DP
|
|
<< "caught config::error while processing color-range RC: "
|
|
<< e.message
|
|
<< '\n';
|
|
ERR_DP
|
|
<< "bailing out from RC\n";
|
|
tmp_map.clear();
|
|
}
|
|
|
|
foreach(const rc_entry_type& rc_entry, tmp_map) {
|
|
rc.map()[rc_entry.first] = rc_entry.second;
|
|
}
|
|
}
|
|
else {
|
|
///@Deprecated 1.6 palette switch syntax
|
|
if(field.find('=') != std::string::npos) {
|
|
lg::wml_error << "the ~RC() image function cannot be used for palette switch (A=B) in 1.7.x; use ~PAL(A>B) instead\n";
|
|
}
|
|
}
|
|
}
|
|
// Palette switch (PAL)
|
|
else if("PAL" == function) {
|
|
const std::vector<std::string> remap_params = utils::split(field,'>');
|
|
if(remap_params.size() > 1) {
|
|
std::map<Uint32, Uint32> tmp_map;
|
|
try {
|
|
std::vector<Uint32> const& old_palette =
|
|
game_config::tc_info(remap_params[0]);
|
|
std::vector<Uint32> const& new_palette =
|
|
game_config::tc_info(remap_params[1]);
|
|
|
|
for(size_t i = 0; i < old_palette.size() && i < new_palette.size(); ++i) {
|
|
tmp_map[old_palette[i]] = new_palette[i];
|
|
}
|
|
}
|
|
catch(config::error& e) {
|
|
ERR_DP
|
|
<< "caught config::error while processing PAL function: "
|
|
<< e.message
|
|
<< '\n';
|
|
ERR_DP
|
|
<< "bailing out from PAL\n";
|
|
tmp_map.clear();
|
|
}
|
|
|
|
foreach(const rc_entry_type& rc_entry, tmp_map) {
|
|
rc.map()[rc_entry.first] = rc_entry.second;
|
|
}
|
|
}
|
|
}
|
|
// Flip-flop (FL)
|
|
else if("FL" == function) {
|
|
if(field.empty() || field.find("horiz") != std::string::npos) {
|
|
fl.toggle_horiz();
|
|
}
|
|
if(field.find("vert") != std::string::npos) {
|
|
fl.toggle_vert();
|
|
}
|
|
}
|
|
// Grayscale (GS)
|
|
else if("GS" == function) {
|
|
functor_queue.push_back(new gs_function());
|
|
}
|
|
// Color-shift (CS)
|
|
else if("CS" == function) {
|
|
std::vector<std::string> const factors = utils::split(field, ',');
|
|
const size_t s = factors.size();
|
|
if(s) {
|
|
int r = 0, g = 0, b = 0;
|
|
|
|
r = lexical_cast_default<int>(factors[0]);
|
|
if( s > 1 ) {
|
|
g = lexical_cast_default<int>(factors[1]);
|
|
}
|
|
if( s > 2 ) {
|
|
b = lexical_cast_default<int>(factors[2]);
|
|
}
|
|
|
|
functor_queue.push_back(new cs_function(r,g,b));
|
|
}
|
|
else {
|
|
LOG_DP << "no arguments passed to the ~CS() function\n";
|
|
}
|
|
}
|
|
// Crop/slice (CROP)
|
|
else if("CROP" == function) {
|
|
std::vector<std::string> const& slice_params = utils::split(field, ',', utils::STRIP_SPACES);
|
|
const size_t s = slice_params.size();
|
|
if(s) {
|
|
SDL_Rect slice_rect = { 0, 0, 0, 0 };
|
|
|
|
slice_rect.x = lexical_cast_default<Sint16, const std::string&>(slice_params[0]);
|
|
if(s > 1) {
|
|
slice_rect.y = lexical_cast_default<Sint16, const std::string&>(slice_params[1]);
|
|
}
|
|
if(s > 2) {
|
|
slice_rect.w = lexical_cast_default<Uint16, const std::string&>(slice_params[2]);
|
|
}
|
|
if(s > 3) {
|
|
slice_rect.h = lexical_cast_default<Uint16, const std::string&>(slice_params[3]);
|
|
}
|
|
|
|
functor_queue.push_back(new crop_function(slice_rect));
|
|
}
|
|
else {
|
|
ERR_DP << "no arguments passed to the ~CROP() function\n";
|
|
}
|
|
}
|
|
// LOC function
|
|
else if("LOC" == function) {
|
|
//FIXME: WIP, don't use it yet
|
|
std::vector<std::string> const& params = utils::split(field);
|
|
int x = lexical_cast<int>(params[0]);
|
|
int y = lexical_cast<int>(params[1]);
|
|
int cx = lexical_cast<int>(params[2]);
|
|
int cy = lexical_cast<int>(params[3]);
|
|
image::locator new_loc(val_.filename_, map_location(x,y), cx, cy, "");//TODO remove only ~LOC
|
|
surf = get_image(new_loc, SCALED_TO_HEX);
|
|
}
|
|
// BLIT function
|
|
else if("BLIT" == function) {
|
|
std::vector<std::string> param = utils::parenthetical_split(field, ',');
|
|
const size_t s = param.size();
|
|
if(s > 0){
|
|
int x = 0, y = 0;
|
|
if(s == 3) {
|
|
x = lexical_cast_default<int>(param[1]);
|
|
y = lexical_cast_default<int>(param[2]);
|
|
}
|
|
if(x >= 0 && y >= 0) { //required by blit_surface
|
|
surface surf = get_image(param[0]);
|
|
functor_queue.push_back(new blit_function(surf, x, y));
|
|
} else {
|
|
ERR_DP << "negative position arguments in ~BLIT() function\n";
|
|
}
|
|
} else {
|
|
ERR_DP << "no arguments passed to the ~BLIT() function\n";
|
|
}
|
|
}
|
|
else if("L" == function) {
|
|
if(!field.empty()){
|
|
surface surf = get_image(field);
|
|
functor_queue.push_back(new light_function(surf));
|
|
} else {
|
|
ERR_DP << "no arguments passed to the ~L() function\n";
|
|
}
|
|
}
|
|
// Scale (SCALE)
|
|
else if("SCALE" == function) {
|
|
std::vector<std::string> const& scale_params = utils::split(field, ',', utils::STRIP_SPACES);
|
|
const size_t s = scale_params.size();
|
|
if(s) {
|
|
int w = 0, h = 0;
|
|
|
|
w = lexical_cast_default<int, const std::string&>(scale_params[0]);
|
|
if(s > 1) {
|
|
h = lexical_cast_default<int, const std::string&>(scale_params[1]);
|
|
}
|
|
|
|
functor_queue.push_back(new scale_function(w, h));
|
|
}
|
|
else {
|
|
ERR_DP << "no arguments passed to the ~SCALE() function\n";
|
|
}
|
|
}
|
|
// Gaussian-like blur (BL)
|
|
else if("BL" == function) {
|
|
const int depth = std::max<int>(0, lexical_cast_default<int>(field));
|
|
functor_queue.push_back(new bl_function(depth));
|
|
}
|
|
// Opacity-shift (O)
|
|
else if("O" == function) {
|
|
const std::string::size_type p100_pos = field.find('%');
|
|
float num = 0.0f;
|
|
if(p100_pos == std::string::npos)
|
|
num = lexical_cast_default<float,const std::string&>(field);
|
|
else {
|
|
// make multiplier
|
|
const std::string parsed_field = field.substr(0, p100_pos);
|
|
num = lexical_cast_default<float,const std::string&>(parsed_field);
|
|
num /= 100.0f;
|
|
}
|
|
functor_queue.push_back(new o_function(num));
|
|
}
|
|
//
|
|
// ~R(), ~G() and ~B() are the children of ~CS(). Merely syntatic sugar.
|
|
// Hence they are at the end of the evaluation.
|
|
//
|
|
// Red component color-shift (R)
|
|
else if("R" == function) {
|
|
const int r = lexical_cast_default<int>(field);
|
|
functor_queue.push_back(new cs_function(r,0,0));
|
|
}
|
|
// Green component color-shift (G)
|
|
else if("G" == function) {
|
|
const int g = lexical_cast_default<int>(field);
|
|
functor_queue.push_back(new cs_function(0,g,0));
|
|
}
|
|
// Blue component color-shift (B)
|
|
else if("B" == function) {
|
|
const int b = lexical_cast_default<int>(field);
|
|
functor_queue.push_back(new cs_function(0,0,b));
|
|
}
|
|
else if("NOP" == function) {
|
|
}
|
|
// Fake image function used by GUI2 portraits until
|
|
// Mordante gets rid of it. *tsk* *tsk*
|
|
else if("RIGHT" == function) {
|
|
}
|
|
// Add a bright overlay.
|
|
else if (function == "BRIGHTEN") {
|
|
functor_queue.push_back(new brighten_function());
|
|
}
|
|
// Add a dark overlay.
|
|
else if (function == "DARKEN") {
|
|
functor_queue.push_back(new darken_function());
|
|
}
|
|
else {
|
|
ERR_DP << "unknown image function in path: " << function << '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!rc.no_op()) {
|
|
surf = rc(surf);
|
|
}
|
|
|
|
if(!fl.no_op()) {
|
|
surf = fl(surf);
|
|
}
|
|
|
|
foreach(function_base* f, functor_queue) {
|
|
surf = (*f)(surf);
|
|
delete f;
|
|
}
|
|
}
|
|
|
|
return surf;
|
|
}
|
|
|
|
bool locator::file_exists()
|
|
{
|
|
return !get_binary_file_location("images", val_.filename_).empty();
|
|
}
|
|
|
|
surface locator::load_from_disk() const
|
|
{
|
|
switch(val_.type_) {
|
|
case FILE:
|
|
return load_image_file();
|
|
case SUB_FILE:
|
|
return load_image_sub_file();
|
|
default:
|
|
return surface(NULL);
|
|
}
|
|
}
|
|
|
|
|
|
manager::manager() {}
|
|
|
|
manager::~manager()
|
|
{
|
|
flush_cache();
|
|
}
|
|
|
|
SDL_PixelFormat last_pixel_format;
|
|
|
|
void set_pixel_format(SDL_PixelFormat* format)
|
|
{
|
|
assert(format != NULL);
|
|
|
|
SDL_PixelFormat &f = *format;
|
|
SDL_PixelFormat &l = last_pixel_format;
|
|
// if the pixel format change, we clear the cache,
|
|
// because some images are now optimized for the wrong display format
|
|
// FIXME: 8 bpp use palette, need to compare them. For now assume a change
|
|
if (format->BitsPerPixel == 8 ||
|
|
f.BitsPerPixel != l.BitsPerPixel || f.BytesPerPixel != l.BytesPerPixel ||
|
|
f.Rmask != l.Rmask || f.Gmask != l.Gmask || f.Bmask != l.Bmask ||
|
|
f.Rloss != l.Rloss || f.Gloss != l.Gloss || f.Bloss != l.Bloss ||
|
|
f.Rshift != l.Rshift || f.Gshift != l.Gshift || f.Bshift != l.Bshift ||
|
|
f.colorkey != l.colorkey || f.alpha != l.alpha)
|
|
{
|
|
LOG_DP << "detected a new display format\n";
|
|
flush_cache();
|
|
}
|
|
last_pixel_format = *format;
|
|
}
|
|
|
|
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;
|
|
scaled_to_hex_images_.flush();
|
|
brightened_images_.flush();
|
|
semi_brightened_images_.flush();
|
|
reversed_images_.clear();
|
|
}
|
|
}
|
|
|
|
color_adjustment_resetter::color_adjustment_resetter()
|
|
: r_(red_adjust), g_(green_adjust), b_(blue_adjust)
|
|
{
|
|
}
|
|
|
|
void color_adjustment_resetter::reset()
|
|
{
|
|
set_color_adjustment(r_, g_, b_);
|
|
}
|
|
|
|
void set_team_colors(const std::vector<std::string>* colors)
|
|
{
|
|
if (colors == NULL)
|
|
team_colors.clear();
|
|
else {
|
|
team_colors = *colors;
|
|
}
|
|
}
|
|
|
|
void set_zoom(int amount)
|
|
{
|
|
if(amount != zoom) {
|
|
zoom = amount;
|
|
scaled_to_hex_images_.flush();
|
|
brightened_images_.flush();
|
|
semi_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();
|
|
unmasked_images_.flush();
|
|
cached_zoom = zoom;
|
|
}
|
|
}
|
|
}
|
|
|
|
static surface get_hexed(const locator& i_locator)
|
|
{
|
|
surface image(get_image(i_locator, UNSCALED));
|
|
// Re-cut scaled tiles according to a mask.
|
|
static const image::locator terrain_mask(game_config::images::terrain_mask);
|
|
const surface hex(get_image(terrain_mask, UNSCALED));
|
|
return mask_surface(image, hex);
|
|
}
|
|
|
|
static surface get_unmasked(const locator& i_locator)
|
|
{
|
|
// If no scaling needed at this zoom level,
|
|
// we just use the hexed image.
|
|
surface image(get_image(i_locator, HEXED));
|
|
if (zoom != tile_size)
|
|
return scale_surface(image, zoom, zoom);
|
|
else
|
|
return image;
|
|
}
|
|
|
|
static surface get_scaled_to_hex(const locator& i_locator)
|
|
{
|
|
surface res(get_image(i_locator, UNMASKED));
|
|
|
|
// Adjusts color if necessary.
|
|
if (red_adjust != 0 ||
|
|
green_adjust != 0 || blue_adjust != 0) {
|
|
res = surface(adjust_surface_color(res,
|
|
red_adjust, green_adjust, blue_adjust));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
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(res, ((res.get()->w * zoom) / tile_size), ((res.get()->h * zoom) / tile_size));
|
|
} else {
|
|
return surface(NULL);
|
|
}
|
|
}
|
|
|
|
static surface get_brightened(const locator& i_locator)
|
|
{
|
|
surface image(get_image(i_locator, SCALED_TO_HEX));
|
|
return surface(brighten_image(image, ftofxp(game_config::hex_brightening)));
|
|
}
|
|
|
|
static surface get_semi_brightened(const locator& i_locator)
|
|
{
|
|
surface image(get_image(i_locator, SCALED_TO_HEX));
|
|
return surface(brighten_image(image, ftofxp(game_config::hex_semi_brightening)));
|
|
}
|
|
|
|
surface get_image(const image::locator& i_locator, TYPE type)
|
|
{
|
|
surface res(NULL);
|
|
image_cache *imap;
|
|
|
|
if(i_locator.is_void())
|
|
return surface(NULL);
|
|
|
|
//translate type to a simpler one when possible
|
|
switch(type) {
|
|
case SCALED_TO_ZOOM:
|
|
if(zoom == tile_size)
|
|
type = UNSCALED;
|
|
break;
|
|
case UNMASKED:
|
|
if(zoom == tile_size)
|
|
type = HEXED;
|
|
break;
|
|
case BRIGHTENED:
|
|
if(ftofxp(game_config::hex_brightening) == ftofxp(1.0))
|
|
type = SCALED_TO_HEX;
|
|
break;
|
|
case SEMI_BRIGHTENED:
|
|
if(ftofxp(game_config::hex_semi_brightening) == ftofxp(1.0))
|
|
type = SCALED_TO_HEX;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// select associated cache
|
|
switch(type) {
|
|
case UNSCALED:
|
|
imap = &images_;
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
imap = &scaled_to_hex_images_;
|
|
break;
|
|
case SCALED_TO_ZOOM:
|
|
imap = &scaled_to_zoom_;
|
|
break;
|
|
case HEXED:
|
|
imap = &hexed_images_;
|
|
break;
|
|
case UNMASKED:
|
|
imap = &unmasked_images_;
|
|
break;
|
|
case BRIGHTENED:
|
|
imap = &brightened_images_;
|
|
break;
|
|
case SEMI_BRIGHTENED:
|
|
imap = &semi_brightened_images_;
|
|
break;
|
|
default:
|
|
return surface(NULL);
|
|
}
|
|
|
|
// return the image if already cached
|
|
if(i_locator.in_cache(*imap))
|
|
return i_locator.locate_in_cache(*imap);
|
|
|
|
// not cached, generate it
|
|
switch(type) {
|
|
case UNSCALED:
|
|
// If type is unscaled, directly load the image from the disk.
|
|
res = i_locator.load_from_disk();
|
|
break;
|
|
case SCALED_TO_HEX:
|
|
res = get_scaled_to_hex(i_locator);
|
|
break;
|
|
case SCALED_TO_ZOOM:
|
|
res = get_scaled_to_zoom(i_locator);
|
|
break;
|
|
case HEXED:
|
|
res = get_hexed(i_locator);
|
|
break;
|
|
case UNMASKED:
|
|
res = get_unmasked(i_locator);
|
|
break;
|
|
case BRIGHTENED:
|
|
res = get_brightened(i_locator);
|
|
break;
|
|
case SEMI_BRIGHTENED:
|
|
res = get_semi_brightened(i_locator);
|
|
break;
|
|
default:
|
|
return surface(NULL);
|
|
}
|
|
|
|
// Optimizes surface before storing it
|
|
if(res)
|
|
res = create_optimized_surface(res);
|
|
|
|
i_locator.add_to_cache(*imap, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
bool is_in_hex(const locator& i_locator)
|
|
{
|
|
if(i_locator.in_cache(in_hex_info_)) {
|
|
return i_locator.locate_in_cache(in_hex_info_);
|
|
} else {
|
|
const surface mask(get_image(game_config::images::terrain_mask, UNSCALED));
|
|
const surface image(get_image(i_locator, UNSCALED));
|
|
|
|
bool res = in_mask_surface(image, mask);
|
|
|
|
i_locator.add_to_cache(in_hex_info_, res);
|
|
|
|
//std::cout << "in_hex : " << i_locator.get_filename()
|
|
// << " " << (res ? "yes" : "no") << "\n";
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
surface reverse_image(const surface& surf)
|
|
{
|
|
if(surf == NULL) {
|
|
return surface(NULL);
|
|
}
|
|
|
|
const std::map<surface,surface>::iterator 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 == NULL) {
|
|
return surface(NULL);
|
|
}
|
|
|
|
reversed_images_.insert(std::pair<surface,surface>(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
|
|
std::pair< std::map< std::string, bool >::iterator, bool >
|
|
it = image_existence_map.insert(std::make_pair(i_locator.get_filename(), false));
|
|
bool &cache = it.first->second;
|
|
if (it.second)
|
|
cache = !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);
|
|
|
|
std::vector<std::string> files_found;
|
|
std::vector<std::string> dirs_found;
|
|
get_files_in_dir(checked_dir, &files_found, &dirs_found,
|
|
FILE_NAME_ONLY, NO_FILTER, DONT_REORDER);
|
|
|
|
for(std::vector<std::string>::const_iterator f = files_found.begin();
|
|
f != files_found.end(); ++f) {
|
|
image_existence_map[subdir + *f] = true;
|
|
}
|
|
|
|
for(std::vector<std::string>::const_iterator d = dirs_found.begin();
|
|
d != dirs_found.end(); ++d) {
|
|
precache_file_existence_internal(dir, subdir + *d + "/");
|
|
}
|
|
}
|
|
|
|
void precache_file_existence(const std::string& subdir)
|
|
{
|
|
const std::vector<std::string>& paths = get_binary_paths("images");
|
|
|
|
for(std::vector<std::string>::const_iterator p = paths.begin();
|
|
p != paths.end(); ++p) {
|
|
|
|
const std::string dir = *p + "/" + subdir;
|
|
precache_file_existence_internal(*p, subdir);
|
|
}
|
|
}
|
|
|
|
bool precached_file_exists(const std::string& file)
|
|
{
|
|
std::map<std::string, bool>::const_iterator b = image_existence_map.find(file);
|
|
if (b != image_existence_map.end())
|
|
return b->second;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
template<typename T>
|
|
cache_item<T>& cache_type<T>::get_element(int index)
|
|
{
|
|
assert (index != -1);
|
|
if (unsigned(index) >= content_.size()) content_.resize(index + 1);
|
|
cache_item<T>& elt = content_[index];
|
|
if(elt.loaded) {
|
|
assert(*elt.position == index);
|
|
lru_list_.erase(elt.position);
|
|
lru_list_.push_front(index);
|
|
elt.position = lru_list_.begin();
|
|
}
|
|
return elt;
|
|
}
|
|
template<typename T>
|
|
void cache_type<T>::on_load(int index){
|
|
if(index == -1) return ;
|
|
cache_item<T>& elt = content_[index];
|
|
if(!elt.loaded) return ;
|
|
lru_list_.push_front(index);
|
|
elt.position = lru_list_.begin();
|
|
while(cache_size_ > cache_max_size_-100) {
|
|
cache_item<T>& elt = content_[lru_list_.back()];
|
|
elt.loaded=false;
|
|
elt.item = T();
|
|
lru_list_.pop_back();
|
|
cache_size_--;
|
|
}
|
|
}
|
|
|
|
} // end namespace image
|
|
|