summaryrefslogtreecommitdiff
path: root/tools/dyecmd/src
diff options
context:
space:
mode:
Diffstat (limited to 'tools/dyecmd/src')
-rw-r--r--tools/dyecmd/src/CMakeLists.txt59
-rwxr-xr-xtools/dyecmd/src/dye.cpp202
-rwxr-xr-xtools/dyecmd/src/dye.h105
-rwxr-xr-xtools/dyecmd/src/dyecmd.cpp155
-rwxr-xr-xtools/dyecmd/src/imagewriter.cpp113
-rwxr-xr-xtools/dyecmd/src/imagewriter.h31
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);
+};