Added wesmage tool.

This tool in intended to make it easier to test SDL image manipulation
functions and thus make it easier to test changes to the algorithms.

loonycyborg please add SCons support.
This commit is contained in:
Mark de Wever 2012-02-16 20:54:32 +00:00
parent 3638a897ef
commit de058ef1a2
9 changed files with 755 additions and 0 deletions

View file

@ -100,6 +100,7 @@ Version 1.11.0-svn:
it is compressed when the Compressed Saves option is enabled in Advanced
Preferences (patch #3115)
* Show base terrain description if none for overlay (bug #19411)
* Added wesmage tool to test SDL image manipulation functions.
Version 1.10.0:
* Campaigns:

View file

@ -862,6 +862,23 @@ set_target_properties(schema_validator PROPERTIES OUTPUT_NAME ${BINARY_PREFIX}sc
install(TARGETS schema_validator DESTINATION ${BINDIR})
set(wesmage_SRC
wesmage/wesmage.cpp
wesmage/exit.cpp
wesmage/filter.cpp
wesmage/options.cpp
tools/dummy_video.cpp
tools/exploder_utils.cpp
sdl_utils.cpp
loadscreen_empty.cpp
)
add_executable(wesmage ${wesmage_SRC})
target_link_libraries(wesmage wesnoth-core png ${tools-external-libs})
set_target_properties(wesmage PROPERTIES OUTPUT_NAME ${BINARY_PREFIX}wesmage${BINARY_SUFFIX})
install(TARGETS wesmage DESTINATION ${BINDIR})
endif(ENABLE_TOOLS)
########### Unit tests ###############

21
src/wesmage/exit.cpp Normal file
View file

@ -0,0 +1,21 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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.
*/
#include "wesmage/exit.hpp"
texit::texit(const int status__)
: status(status__)
{
}

39
src/wesmage/exit.hpp Normal file
View file

@ -0,0 +1,39 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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
* Exit exception.
*/
#ifndef WESMAGE_EXIT_HPP_INCLUDED
#define WESMAGE_EXIT_HPP_INCLUDED
/**
* This exception when throw should terminate the application.
*
* The application should terminate with @ref texit::status as its exit
* status.
*/
struct texit
{
texit(const int status__);
/** The exit status for the application. */
int status;
};
#endif

217
src/wesmage/filter.cpp Normal file
View file

@ -0,0 +1,217 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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.
*/
#include "wesmage/filter.hpp"
#include "foreach.hpp"
#include "serialization/string_utils.hpp"
#include "wesmage/exit.hpp"
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <iostream>
/** Contains the definition of a filter. */
struct tfilter
{
/**
* The functor to call for the filter.
*
* @param surf The surface to apply the filter to.
* @param parameters A string with the parameters for the
* functor. The code is expected to be
* supplied on the command line. So it should
* be validated.
*/
typedef boost::function<void(
surface& surf
, const std::string& parameters
)>
tfunctor;
tfilter(
const std::string& name__
, const std::string& description__
, const tfunctor& functor__)
: name(name__)
, description(description__)
, functor(functor__)
{
}
/**
* The name of the filter.
*
* This value must be unique.
*/
std::string name;
/**
* Description of the filter.
*
* The exact format of the string is documented at @ref filter_list.
*/
tfilter_description description;
/** The functor to call for the filter. */
tfunctor functor;
};
/**
* The list of the available filters.
*
* The map contains:
* * @p first The name of the filter.
* * @p second The filter itself.
*/
static std::map<std::string, tfilter> filters;
/** Helper structure to register a filter to the @ref filters. */
struct tregister_filter
{
tregister_filter(const std::pair<std::string, tfilter>& filter)
{
filters.insert(filter);
}
};
/**
* Register macro for a filter.
*
* @param name The name of the filter.
* @param description A pipe-symbol separated list with the
* description. The name is automatically
* prefixed, so the item should start with a
* pipe-symbol. When the list is splitted in
* to a vector of string its contents should
* be compatible with the constructor of
* @ref tfilter_description.
*/
#define REGISTER(name, description) \
tregister_filter register_filter_##name(std::make_pair( \
#name \
, tfilter(#name, #name""description, boost::bind(name, _1, _2))));
static void
scale(surface& surf, const std::string& parameters)
{
unsigned width, height;
const int count = sscanf(parameters.c_str(), "%u,%u", &width, &height);
if(count != 2) {
std::cerr << "Error: Arguments to scale »"
<< parameters
<< "« are not compatible.\n";
throw texit(EXIT_FAILURE);
}
surf = scale_surface(surf, width, height);
}
REGISTER(scale,
"|Scales the size of an image."
"|new_width"
"|unsigned"
"|The width in pixel of the image after scaling."
"|new_height"
"|unsigned"
"|The height in pixel of the image after scaling.");
static void
brighten(surface& surf, const std::string& parameters)
{
float amount;
const int count = sscanf(parameters.c_str(), "%f", &amount);
if(count != 1) {
std::cerr << "Error: Arguments to brighten »"
<< parameters
<< "« are not compatible.\n";
throw texit(EXIT_FAILURE);
}
surf = brighten_image(surf, amount);
}
REGISTER(brighten,
"|Brightens an image."
"|amount"
"|float"
"|The amount the image should be brightened. The value of the every "
"colour channel is multiplied by this value. Value less than zero "
"are set to zero. The alpha channel is not modified.");
void
filter_apply(surface& surf, const std::string& filter)
{
std::vector<std::string> f = utils::split(filter, ':', utils::STRIP_SPACES);
if(f.size() != 2) {
std::cerr << "Error: Filter »"
<< filter
<< "« doesn't contain the expected separator »:«\n";
throw texit(EXIT_FAILURE);
}
std::map<std::string, tfilter>::iterator itor = filters.find(f[0]);
if(itor == filters.end()) {
std::cerr << "Error: Filter »" << f[0] << "« is unknown.\n";
throw texit(EXIT_FAILURE);
}
itor->second.functor(surf, f[1]);
}
tfilter_description::tfilter_description(const std::string& fmt)
: name()
, description()
, parameters()
{
std::vector<std::string> elements(utils::split(fmt, '|'));
/* Validate there is at least a header part. */
assert(elements.size() >= 2);
name = elements[0];
description = elements[1];
/* Validate every parameter indeed has three fields. */
assert((elements.size() - 2) % 3 == 0);
for(size_t i = 2; i < elements.size(); i += 3) {
tfilter_description::tparameter parameter =
{
elements[i]
, elements[i + 1]
, elements[i + 2]
};
parameters.push_back(parameter);
}
}
std::vector<tfilter_description>
filter_list()
{
std::vector<tfilter_description> result;
typedef std::pair<std::string, tfilter> thack;
BOOST_FOREACH(const thack& filter, filters) {
result.push_back(filter.second.description);
}
return result;
}

93
src/wesmage/filter.hpp Normal file
View file

@ -0,0 +1,93 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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
* Filters for wesmage
*/
#ifndef WESMAGE_FILTER_HPP_INCLUDED
#define WESMAGE_FILTER_HPP_INCLUDED
#include "sdl_utils.hpp"
#include <string>
#include <vector>
void
filter_apply(surface& surf, const std::string& filter);
/**
* Helper structure to describe what a filter does.
*
* This structure should make it easier to create help messages.
*/
struct tfilter_description
{
/**
* Constructor.
*
* Creates an object from a specially formated string. The string
* contains of a number of fields seperated by a pipe-symbol. The number
* of fields should be 2 + 3 * params, where params is the number of
* parameters of the filter. The fields are:
* * 1 The @ref tfilter_description::name
* * 2 The @ref tfilter_description::description
* After these two fields there are three fields per parameter, the
* fields are:
* * 1 The @ref tfilter_description::tparameter::name
* * 2 The @ref tfilter_description::tparameter::type
* * 3 The @ref tfilter_description::tparameter::description
*
* @param fmt The format string as described above.
*/
explicit tfilter_description(const std::string& fmt);
/**
* Name of the filter.
*
* This is the ID parameter given on the command line.
*/
std::string name;
/**
* Description of the filter.
*
* Shortly describes what the filter does.
*/
std::string description;
/** Describes a filter parameter. */
struct tparameter
{
/** The name of the parameter. */
std::string name;
/** The C type of the parameter. */
std::string type;
/** Describes what the parameter does. */
std::string descripton;
};
/** The list of filter parameters. */
std::vector<tparameter> parameters;
};
/** Returns the list of available filters. */
std::vector<tfilter_description>
filter_list();
#endif

218
src/wesmage/options.cpp Normal file
View file

@ -0,0 +1,218 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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.
*/
#include "wesmage/options.hpp"
#include "foreach.hpp"
#include "wesmage/exit.hpp"
#include "wesmage/filter.hpp"
#include <cassert>
#include <cstdlib>
#include <iostream>
toptions::toptions()
: input_filename()
, output_filename()
{
}
/*
* This function prints the option and its description in a nice fashion.
*
* * The description is indented at column tab_offset.
* * If the option text is short enough the description starts after it,
* properly indented. Else starts at the next line, again properly
* indented.
* * If the text of the description doesn't fit at a single line it split at
* space and continues on the next line, obviously indented again.
*/
void
print_option(
std::ostream& stream
, const std::string& option
, std::string description)
{
const unsigned line_length = 80;
const unsigned tab_offset = 25;
const unsigned description_length = line_length - tab_offset;
const std::string tab_filler(tab_offset - 1, ' ');
assert(!option.empty());
assert(!description.empty());
stream << option;
if(option.length() < tab_offset - 1) {
stream << std::string(tab_offset - 1 - option.length(), ' ');
} else {
stream << '\n' << tab_filler;
}
while(!description.empty()) {
if(description.size() <= description_length) {
stream << description;
description.clear();
} else {
int offset = description_length + 1;
while(description[offset] != ' ' && offset >= 0) {
--offset;
}
assert(offset != 0);
assert(description[offset] == ' ');
stream << description.substr(0, offset);
description.erase(0, offset + 1);
}
stream << '\n';
if(!description.empty()) {
stream << tab_filler;
}
}
}
static std::ostream&
operator<<(
std::ostream& stream
, const tfilter_description& fd)
{
print_option(stream, fd.name, fd.description);
BOOST_FOREACH(const tfilter_description::tparameter& p, fd.parameters) {
print_option(
stream
, " * " + p.name + " (" + p.type + ")"
, p.descripton);
}
return stream;
}
static void
print_help(const int exit_status)
{
std::cout <<
"Usage wesmage [OPTION...] [FILE]\n"
"Helper programme to test image manipulation algorithms.\n"
"\n"
"The FILE is the name of the input file to be converted.\n"
"OPTIONS:\n"
"-o, --output FILE The name of the output file to be written.\n"
"-f, --filter FILTER Filters to be applied to the image. See FILTERS.\n"
"-h, --help Show this help and terminate the programme.\n"
"\n"
"FILTERS:\n"
"A filter applies a modification to an image. The programme can handle\n"
"multiple filters. They are applied from the command line. The are applied\n"
"in the left to right order they appear on the command line.\n"
"A filter has the following syntax ID:PARAMETERS where:\n"
"ID The id of the filter.\n"
"PARAMETERS Zero or more parameters. Multiple parameters are\n"
" separated by a comma. The number parameters required\n"
" depend on the filter.\n"
"\n"
"The following filters are currently implemented:\n"
;
BOOST_FOREACH(const tfilter_description& filter, filter_list()) {
std::cout << filter;
}
throw texit(exit_status);
}
#define VALIDATE_NOT_PAST_END \
do { \
if(i >= argc) { \
std::cerr << "Error: Required argument for the option »" \
<< option \
<< "« is not supplied.\n"; \
\
throw texit(EXIT_FAILURE); \
} \
} while(0)
const toptions&
toptions::parse(int argc, char* argv[])
{
toptions& result = singleton(false);
bool help = false;
/* argv[0] is the name of the programme, not a command-line argument. */
for(int i = 1; i < argc; ++i) {
const std::string option(argv[i]);
if(option == "-h" || option == "--help") {
help = true;
} else if(option == "-o" || option == "--output") {
++i;
VALIDATE_NOT_PAST_END;
result.output_filename = argv[i];
} else if(option.substr(0, 2) == "-o") {
result.output_filename = option.substr(2);
} else if(option == "-f" || option == "--filter") {
++i;
VALIDATE_NOT_PAST_END;
result.filters.push_back(argv[i]);
} else if(option.substr(0, 2) == "-f") {
result.filters.push_back(option.substr(2));
} else {
if(!result.input_filename.empty()) {
std::cerr << "Error: Command line argument »"
<< option
<< "« is not recognised.\n";
print_help(EXIT_FAILURE);
}
result.input_filename = option;
}
}
if(help) {
print_help(EXIT_SUCCESS);
}
if(result.input_filename.empty()) {
std::cerr << "Error: Input filename omitted.\n";
print_help(EXIT_FAILURE);
}
if(result.output_filename.empty()) {
std::cerr << "Error: Output filename omitted.\n";
print_help(EXIT_FAILURE);
}
/*
* No filter implies a copy, or conversion to png, which is a valid
* way to use the programme, so do not complain.
*/
return result;
}
const toptions&
toptions::options()
{
return singleton(true);
}
toptions&
toptions::singleton(const bool is_initialized)
{
static bool initialized = false;
assert(is_initialized == initialized);
initialized = true;
static toptions result;
return result;
}

87
src/wesmage/options.hpp Normal file
View file

@ -0,0 +1,87 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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
* Command line parameters for wesmage.
*/
#ifndef WESMAGE_OPTIONS_HPP_INCLUDED
#define WESMAGE_OPTIONS_HPP_INCLUDED
#include <boost/noncopyable.hpp>
#include <string>
#include <vector>
/** A signleton class containing the parsed command line parameters. */
struct toptions
: private boost::noncopyable
{
private:
toptions();
public:
/**
* Parses the command line.
*
* This function shall be called once at the beginning of the programme.
*
* @param argc The @p argc to @ref main.
* @param argv The @p argv to @ref main.
*
* @returns The parsed options.
*/
static const toptions&
parse(int argc, char* argv[]);
/**
* Returns the cached parsed command line parameters.
*
* This function shall only be called after @ref toptions::parse has
* been called.
*
* @returns The parsed options.
*/
static const toptions&
options();
/** The filename of the input file. */
std::string input_filename;
/** The filename of the output file. */
std::string output_filename;
/** The filters to apply to the input file. */
std::vector<std::string> filters;
private:
/**
* Helper which contains the single instance of this class.
*
* @param is_initialized Helper variable to track whether
* @ref toptions::parse is only called once
* and whether @ref toptions::options isn't
* called before @ref toptions::parse.
*
* @returns The single instance of this class.
*/
static toptions&
singleton(const bool is_initialized);
};
#endif

62
src/wesmage/wesmage.cpp Normal file
View file

@ -0,0 +1,62 @@
/* $Id$ */
/*
Copyright (C) 2012 by Mark de Wever <koraq@xs4all.nl>
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 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
* Tool to test the image conversion functions.
*/
#include "foreach.hpp"
#include "tools/exploder_utils.hpp"
#include "wesmage/exit.hpp"
#include "wesmage/filter.hpp"
#include "wesmage/options.hpp"
#include <SDL_image.h>
#include <iostream>
int
main(int argc, char* argv[])
{
try {
const toptions& options = toptions::parse(argc, argv);
surface surf(make_neutral_surface(
IMG_Load(options.input_filename.c_str())));
if(!surf) {
std::cerr << "Error: Failed to load input file »"
<< options.input_filename
<< "«.\n";
return EXIT_FAILURE;
}
BOOST_FOREACH(const std::string& filter, options.filters) {
filter_apply(surf, filter);
}
save_image(surf, options.output_filename);
} catch(const texit& exit) {
return exit.status;
} catch(exploder_failure& err) {
std::cerr << "Error: Failed with error »" << err.message << "«.\n";
return EXIT_FAILURE;
}
return 0;
}