diff options
Diffstat (limited to 'tools/dyecmd/src')
-rw-r--r-- | tools/dyecmd/src/CMakeLists.txt | 59 | ||||
-rwxr-xr-x | tools/dyecmd/src/dye.cpp | 202 | ||||
-rwxr-xr-x | tools/dyecmd/src/dye.h | 105 | ||||
-rwxr-xr-x | tools/dyecmd/src/dyecmd.cpp | 155 | ||||
-rwxr-xr-x | tools/dyecmd/src/imagewriter.cpp | 113 | ||||
-rwxr-xr-x | tools/dyecmd/src/imagewriter.h | 31 |
6 files changed, 665 insertions, 0 deletions
diff --git a/tools/dyecmd/src/CMakeLists.txt b/tools/dyecmd/src/CMakeLists.txt new file mode 100644 index 000000000..f8e717b78 --- /dev/null +++ b/tools/dyecmd/src/CMakeLists.txt @@ -0,0 +1,59 @@ +FIND_PACKAGE(SDL REQUIRED) +FIND_PACKAGE(SDL_image REQUIRED) +FIND_PACKAGE(PNG REQUIRED) + +IF (CMAKE_COMPILER_IS_GNUCXX) + # Help getting compilation warnings + SET(CMAKE_CXX_FLAGS "-Wall") + IF (WIN32) + # This includes enough debug information to get something useful + # from Dr. Mingw while keeping binary size down. Almost useless + # with gdb, though. + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -ggdb0 -gstabs2") + ENDIF() +ENDIF() + +SET(FLAGS "-DPACKAGE_VERSION=\\\"${VERSION}\\\"") +SET(FLAGS "${FLAGS} -DPKG_DATADIR=\\\"${PKG_DATADIR}/\\\"") +SET(FLAGS "${FLAGS} -DLOCALEDIR=\\\"${LOCALEDIR}/\\\"") + +IF (CMAKE_BUILD_TYPE) + STRING(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + IF((CMAKE_BUILD_TYPE_TOLOWER MATCHES debug) OR + (CMAKE_BUILD_TYPE_TOLOWER MATCHES relwithdebinfo)) + SET(FLAGS "${FLAGS} -DDEBUG") + ENDIF() +ENDIF() + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${SDL_INCLUDE_DIR} + ${SDLIMAGE_INCLUDE_DIR} + ${PNG_INCLUDE_DIR} + ) + +# Fix some stuff that gets not hidden by mainline modules +MARK_AS_ADVANCED(SDLIMAGE_INCLUDE_DIR) +MARK_AS_ADVANCED(SDLIMAGE_LIBRARY) +MARK_AS_ADVANCED(SDLMAIN_LIBRARY) +MARK_AS_ADVANCED(SDL_INCLUDE_DIR) +MARK_AS_ADVANCED(SDL_LIBRARY) + +SET(SRCS + dye.cpp + dye.h + dyecmd.cpp + imagewriter.cpp + imagewriter.h + ) + +SET (PROGRAMS dyecmd) + +ADD_EXECUTABLE(dyecmd WIN32 ${SRCS}) + +TARGET_LINK_LIBRARIES(dyecmd + ${SDL_LIBRARY} + ${SDLIMAGE_LIBRARY} + ${PNG_LIBRARIES}) + +SET_TARGET_PROPERTIES(dyecmd PROPERTIES COMPILE_FLAGS "${FLAGS}") diff --git a/tools/dyecmd/src/dye.cpp b/tools/dyecmd/src/dye.cpp new file mode 100755 index 000000000..77da2ebbb --- /dev/null +++ b/tools/dyecmd/src/dye.cpp @@ -0,0 +1,202 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <algorithm> +#include <sstream> +#include <iostream> + +#include "dye.h" + +Palette::Palette(const std::string &description) +{ + mLoaded = false; + int size = description.length(); + if (size == 0) return; + if (description[0] != '#') + { + std::cout << "Missing # in the palette description " + << "in the third parameter." << std::endl; + return; + } + + int pos = 1; + for (;;) + { + if (pos + 6 > size) break; + int v = 0; + for (int i = 0; i < 6; ++i) + { + char c = description[pos + i]; + int n; + if ('0' <= c && c <= '9') + n = c - '0'; + else if ('A' <= c && c <= 'F') + n = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + n = c - 'a' + 10; + else + { + std::cout << "invalid Hexadecimal description: " + << description << std::endl; + return; + } + + v = (v << 4) | n; + } + Color c = { { v >> 16, v >> 8, v } }; + mColors.push_back(c); + pos += 6; + if (pos == size) + { + mLoaded = true; + return; + } + if (description[pos] != ',') + break; + + ++pos; + } + + mLoaded = true; +} + +void Palette::getColor(int intensity, int color[3]) const +{ + // Return implicit black + if (intensity == 0) + { + color[0] = 0; + color[1] = 0; + color[2] = 0; + return; + } + + int last = mColors.size(); + if (last == 0) return; + + int i = intensity * last / 255; + int t = intensity * last % 255; + + int j = t != 0 ? i : i - 1; + // Get the exact color if any, the next color otherwise. + int r2 = mColors[j].value[0], + g2 = mColors[j].value[1], + b2 = mColors[j].value[2]; + + if (t == 0) + { + // Exact color. + color[0] = r2; + color[1] = g2; + color[2] = b2; + return; + } + + // Get the previous color. First color is implicitly black. + int r1 = 0, g1 = 0, b1 = 0; + if (i > 0) + { + r1 = mColors[i - 1].value[0]; + g1 = mColors[i - 1].value[1]; + b1 = mColors[i - 1].value[2]; + } + + // Perform a linear interpolation. + color[0] = ((255 - t) * r1 + t * r2) / 255; + color[1] = ((255 - t) * g1 + t * g2) / 255; + color[2] = ((255 - t) * b1 + t * b2) / 255; +} + +Dye::Dye(const std::string &description) +{ + mLoaded = false; + for (int i = 0; i < 7; ++i) + mPalettes[i] = 0; + + if (description.empty()) return; + + std::string::size_type next_pos = 0, length = description.length(); + do + { + std::string::size_type pos = next_pos; + next_pos = description.find(';', pos); + if (next_pos == std::string::npos) + next_pos = length; + if (next_pos <= pos + 3 || description[pos + 1] != ':') + { + std::cout << "Dyeing: Missing ':' in channel description." + << std::endl; + return; + } + int i = 0; + switch (description[pos]) + { + case 'R': i = 0; break; + case 'G': i = 1; break; + case 'Y': i = 2; break; + case 'B': i = 3; break; + case 'M': i = 4; break; + case 'C': i = 5; break; + case 'W': i = 6; break; + default: + std::cout << "Dyeing: Invalid channel. Not in [R,G,Y,B,M,C,W]" + << std::endl; + return; + } + mPalettes[i] = new Palette( + description.substr(pos + 2, next_pos - pos - 2)); + + if (!mPalettes[i]->loaded()) + return; + + ++next_pos; + } + while (next_pos < length); + + mLoaded = true; +} + +Dye::~Dye() +{ + for (int i = 0; i < 7; ++i) + delete mPalettes[i]; +} + +void Dye::update(int color[3]) const +{ + int cmax = std::max(color[0], std::max(color[1], color[2])); + if (cmax == 0) return; + + int cmin = std::min(color[0], std::min(color[1], color[2])); + int intensity = color[0] + color[1] + color[2]; + + if (cmin != cmax && + (cmin != 0 || (intensity != cmax && intensity != 2 * cmax))) + { + // not pure + return; + } + + int i = (color[0] != 0) | ((color[1] != 0) << 1) | ((color[2] != 0) << 2); + + if (mPalettes[i - 1]) + mPalettes[i - 1]->getColor(cmax, color); +} diff --git a/tools/dyecmd/src/dye.h b/tools/dyecmd/src/dye.h new file mode 100755 index 000000000..922f3370e --- /dev/null +++ b/tools/dyecmd/src/dye.h @@ -0,0 +1,105 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DYE_H +#define DYE_H + +#include <vector> + +#include <string> + +/** + * Class for performing a linear interpolation between colors. + */ +class Palette +{ + public: + + /** + * Creates a palette based on the given string. + * The string is either a file name or a sequence of hexadecimal RGB + * values separated by ',' and starting with '#'. + */ + Palette(const std::string &); + + /** + * Gets a pixel color depending on its intensity. + */ + void getColor(int intensity, int color[3]) const; + + /** + * Tells if the palette was successfully loaded. + */ + bool loaded() const + { return mLoaded; } + + private: + + struct Color { unsigned char value[3]; }; + + std::vector< Color > mColors; + + bool mLoaded; +}; + +/** + * Class for dispatching pixel-recoloring amongst several palettes. + */ +class Dye +{ + public: + + /** + * Creates a set of palettes based on the given string. + * + * The parts of string are separated by semi-colons. Each part starts + * by an uppercase letter, followed by a colon and then a palette name. + */ + Dye(const std::string &); + + /** + * Destroys the associated palettes. + */ + ~Dye(); + + /** + * Tells if the dye description was successfully loaded. + */ + bool loaded() const + { return mLoaded; } + + /** + * Modifies a pixel color. + */ + void update(int color[3]) const; + + private: + + /** + * The order of the palettes, as well as their uppercase letter, is: + * + * Red, Green, Yellow, Blue, Magenta, White (or rather gray). + */ + Palette *mPalettes[7]; + bool mLoaded; +}; + +#endif diff --git a/tools/dyecmd/src/dyecmd.cpp b/tools/dyecmd/src/dyecmd.cpp new file mode 100755 index 000000000..5e06e500d --- /dev/null +++ b/tools/dyecmd/src/dyecmd.cpp @@ -0,0 +1,155 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <SDL/SDL.h> +#include <SDL/SDL_image.h> + +#include "dye.h" +#include "imagewriter.h" + +using namespace std; + +// return values +enum ReturnValues +{ + RETURN_OK = 0, + INVALID_PARAMETER_LIST = 100, + INVALID_INPUT_IMAGE = 101, + INVALID_OUTPUT_IMAGE = 102, + INVALID_DYE_PARAMETER = 105 +}; + +SDL_Surface* recolor(SDL_Surface* tmpImage, Dye* dye) +{ + SDL_PixelFormat rgba; + rgba.palette = NULL; + rgba.BitsPerPixel = 32; + rgba.BytesPerPixel = 4; + rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24; + rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16; + rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8; + rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0; + rgba.colorkey = 0; + rgba.alpha = 255; + + SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, SDL_SWSURFACE); + //SDL_FreeSurface(tmpImage); <-- We'll free the surface later. + + Uint32 *pixels = static_cast< Uint32 * >(surf->pixels); + for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) + { + int alpha = (*pixels >> rgba.Ashift) & 255; + if (!alpha) continue; + int v[3]; + + v[0] = (*pixels >> rgba.Rshift) & 255; + v[1] = (*pixels >> rgba.Gshift) & 255; + v[2] = (*pixels >> rgba.Bshift) & 255; + dye->update(v); + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha; +#else + *pixels = v[0] | (v[1] << 8) | (v[2] << 16) | (alpha << 24); +#endif + } + + return surf; +} + +void printHelp() +{ + cout << endl + << "This tool is used to dye item graphics used by the Mana client " + << "according to the specification described here: " + << endl << "http://doc.manasource.org/image_dyeing_system" + << endl << endl << + "The tool expects 3 parameters:" << endl + << "dyecmd <source_image> <target_image> <dye_string>" << endl + << "e.g.:" << endl + << "dyecmd \"armor-legs-shorts.png\" " + <<"\"armor-legs-shorts2.png\" \"W:#222255,6666ff\"" << std::endl; +} + +int main(int argc, char* argv[]) +{ + Dye* dye = NULL; + SDL_Surface* source = NULL, *target = NULL; + ReturnValues returnValue = RETURN_OK; + + if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) + { + printHelp(); + } + // not enough or to many parameters + else if (argc != 4) + { + cout << INVALID_PARAMETER_LIST << " - INVALID_PARAMETER_LIST"; + printHelp(); + returnValue = INVALID_PARAMETER_LIST; + } + else + { + // Start dyeing process. + string inputFile = argv[1]; + string outputFile = argv[2]; + string dyeDescription = argv[3]; + + dye = new Dye(dyeDescription); + if (!dye->loaded()) + { + cout << INVALID_DYE_PARAMETER << " - INVALID_DYE_PARAMETER"; + printHelp(); + returnValue = INVALID_DYE_PARAMETER; + } + else + { + source = IMG_Load(inputFile.c_str()); + if (!source) + { + cout << INVALID_INPUT_IMAGE << " - INVALID_INPUT_IMAGE"; + printHelp(); + returnValue = INVALID_INPUT_IMAGE; + } + else + { + target = recolor(source, dye); + + if (!ImageWriter::writePNG(target, outputFile)) + { + cout << INVALID_OUTPUT_IMAGE << " - INVALID_OUTPUT_IMAGE"; + printHelp(); + returnValue = INVALID_OUTPUT_IMAGE; + } + } // Valid source image file + } // Valid dye parameter + } // Parameters ok + + if (source) + SDL_FreeSurface(source); + if (target) + SDL_FreeSurface(target); + if (dye) + delete dye; + + return returnValue; +} diff --git a/tools/dyecmd/src/imagewriter.cpp b/tools/dyecmd/src/imagewriter.cpp new file mode 100755 index 000000000..d237abbfb --- /dev/null +++ b/tools/dyecmd/src/imagewriter.cpp @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "imagewriter.h" + +#include <png.h> +#include <iostream> +#include <string> +#include <SDL/SDL.h> + +bool ImageWriter::writePNG(SDL_Surface *surface, + const std::string &filename) +{ + // TODO Maybe someone can make this look nice? + FILE *fp = fopen(filename.c_str(), "wb"); + if (!fp) + { + std::cout << "PNG writer: Could not open file for writing: " + << filename << std::endl; + return false; + } + + png_structp png_ptr; + png_infop info_ptr; + png_bytep *row_pointers; + int colortype; + + if (SDL_MUSTLOCK(surface)) { + SDL_LockSurface(surface); + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png_ptr) + { + std::cout << "PNG writer: Had trouble creating png_structp" + << std::endl; + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + std::cout << "PNG writer: Could not create png_info" << std::endl; + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + std::cout << "PNG writer: problem writing to : " + << filename << std::endl; + return false; + } + + png_init_io(png_ptr, fp); + + colortype = (surface->format->BitsPerPixel == 24) ? + PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + + png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png_ptr, info_ptr); + + png_set_packing(png_ptr); + + row_pointers = new png_bytep[surface->h]; + if (!row_pointers) + { + std::cout + << "PNG writer: Had trouble converting surface to row pointers" + << std::endl; + return false; + } + + for (int i = 0; i < surface->h; i++) + { + row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels + i * surface->pitch; + } + + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, info_ptr); + + fclose(fp); + + delete [] row_pointers; + + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + + if (SDL_MUSTLOCK(surface)) + SDL_UnlockSurface(surface); + + return true; +} diff --git a/tools/dyecmd/src/imagewriter.h b/tools/dyecmd/src/imagewriter.h new file mode 100755 index 000000000..a8bcdf57d --- /dev/null +++ b/tools/dyecmd/src/imagewriter.h @@ -0,0 +1,31 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iosfwd> + +struct SDL_Surface; + +class ImageWriter +{ + public: + static bool writePNG(SDL_Surface *surface, + const std::string &filename); +}; |