summaryrefslogtreecommitdiff
path: root/tools/dyecmd
diff options
context:
space:
mode:
Diffstat (limited to 'tools/dyecmd')
-rw-r--r--tools/dyecmd/README12
-rw-r--r--tools/dyecmd/dyecmd.cbp56
-rw-r--r--tools/dyecmd/run.cmd1
-rw-r--r--tools/dyecmd/src/dye.cpp214
-rw-r--r--tools/dyecmd/src/dye.h94
-rw-r--r--tools/dyecmd/src/dyecmd.cpp121
-rw-r--r--tools/dyecmd/src/imagewriter.cpp113
-rw-r--r--tools/dyecmd/src/imagewriter.h31
8 files changed, 642 insertions, 0 deletions
diff --git a/tools/dyecmd/README b/tools/dyecmd/README
new file mode 100644
index 00000000..c2981bb0
--- /dev/null
+++ b/tools/dyecmd/README
@@ -0,0 +1,12 @@
+DYECMD
+=======
+
+This tool is used to dye item graphics used by the tmw client according to the
+specification described here: http://wiki.themanaworld.org/index.php/Image_dyeing
+
+The tool expects 3 parameters:
+
+dyecmd <source_image> <target_image> <dye_string>
+e.g.:
+dyecmd "armor-legs-shorts.png" "armor-legs-shorts2.png" "W:#222255,6666ff"
+
diff --git a/tools/dyecmd/dyecmd.cbp b/tools/dyecmd/dyecmd.cbp
new file mode 100644
index 00000000..b3d1bb55
--- /dev/null
+++ b/tools/dyecmd/dyecmd.cbp
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_project_file>
+ <FileVersion major="1" minor="6" />
+ <Project>
+ <Option title="dyecmd" />
+ <Option pch_mode="2" />
+ <Option compiler="gcc" />
+ <Build>
+ <Target title="Debug">
+ <Option output="bin\Debug\dyecmd" prefix_auto="1" extension_auto="1" />
+ <Option object_output="obj\Debug\" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ </Target>
+ <Target title="Release">
+ <Option output="bin\Release\dyecmd" prefix_auto="1" extension_auto="1" />
+ <Option object_output="obj\Release\" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-O2" />
+ </Compiler>
+ <Linker>
+ <Add option="-s" />
+ </Linker>
+ </Target>
+ </Build>
+ <Compiler>
+ <Add option="-Wall" />
+ <Add option="-fexceptions" />
+ <Add directory="include" />
+ </Compiler>
+ <Linker>
+ <Add library="mingw32" />
+ <Add library="SDLmain" />
+ <Add library="SDL" />
+ <Add library="SDL_image" />
+ <Add library="png12.dll" />
+ <Add directory="lib" />
+ </Linker>
+ <Unit filename="src\dye.cpp" />
+ <Unit filename="src\dye.h" />
+ <Unit filename="src\dyecmd.cpp" />
+ <Unit filename="src\imagewriter.cpp" />
+ <Unit filename="src\imagewriter.h" />
+ <Extensions>
+ <code_completion />
+ <envvars />
+ <debugger />
+ <lib_finder disable_auto="1" />
+ </Extensions>
+ </Project>
+</CodeBlocks_project_file>
diff --git a/tools/dyecmd/run.cmd b/tools/dyecmd/run.cmd
new file mode 100644
index 00000000..86726215
--- /dev/null
+++ b/tools/dyecmd/run.cmd
@@ -0,0 +1 @@
+bin\debug\dyecmd "armor-legs-shorts.png" "armor-legs-shorts2.png" "W:#222255,6666ff" \ No newline at end of file
diff --git a/tools/dyecmd/src/dye.cpp b/tools/dyecmd/src/dye.cpp
new file mode 100644
index 00000000..058e0515
--- /dev/null
+++ b/tools/dyecmd/src/dye.cpp
@@ -0,0 +1,214 @@
+/*
+ * The Mana World
+ * Copyright 2007 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <algorithm>
+#include <sstream>
+
+#include "dye.h"
+
+Palette::Palette(const std::string &description)
+{
+ int size = description.length();
+ if (size == 0) return;
+ if (description[0] != '#')
+ {
+ throw;
+ }
+
+ 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
+ throw;
+
+ v = (v << 4) | n;
+ }
+ Color c = { { v >> 16, v >> 8, v } };
+ mColors.push_back(c);
+ pos += 6;
+ if (pos == size) return;
+ if (description[pos] != ',') break;
+ ++pos;
+ }
+}
+
+void Palette::getColor(int intensity, int color[3]) const
+{
+ 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)
+{
+ 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] != ':')
+ {
+ throw;
+ }
+ 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:
+ throw;
+ }
+ mPalettes[i] = new Palette(description.substr(pos + 2, next_pos - pos - 2));
+ ++next_pos;
+ }
+ while (next_pos < length);
+}
+
+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);
+}
+
+void Dye::instantiate(std::string &target, const std::string &palettes)
+{
+ std::string::size_type next_pos = target.find('|');
+ if (next_pos == std::string::npos || palettes.empty()) return;
+ ++next_pos;
+
+ std::ostringstream s;
+ s << target.substr(0, next_pos);
+ std::string::size_type last_pos = target.length(), pal_pos = 0;
+ do
+ {
+ std::string::size_type pos = next_pos;
+ next_pos = target.find(';', pos);
+ if (next_pos == std::string::npos) next_pos = last_pos;
+ if (next_pos == pos + 1 && pal_pos != std::string::npos)
+ {
+ std::string::size_type pal_next_pos = palettes.find(';', pal_pos);
+ s << target[pos] << ':';
+ if (pal_next_pos == std::string::npos)
+ {
+ s << palettes.substr(pal_pos);
+ s << target.substr(next_pos);
+ pal_pos = std::string::npos;
+ break;
+ }
+ s << palettes.substr(pal_pos, pal_next_pos - pal_pos);
+ pal_pos = pal_next_pos + 1;
+ }
+ else if (next_pos > pos + 2)
+ {
+ s << target.substr(pos, next_pos - pos);
+ }
+ else
+ {
+ throw;
+ }
+ s << target[next_pos];
+ ++next_pos;
+ }
+ while (next_pos < last_pos);
+
+ target = s.str();
+}
diff --git a/tools/dyecmd/src/dye.h b/tools/dyecmd/src/dye.h
new file mode 100644
index 00000000..f0bd7aab
--- /dev/null
+++ b/tools/dyecmd/src/dye.h
@@ -0,0 +1,94 @@
+/*
+ * The Mana World
+ * Copyright 2007 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _TMW_DYE_H
+#define _TMW_DYE_H
+
+#include <vector>
+
+/**
+ * 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;
+
+ private:
+
+ struct Color { unsigned char value[3]; };
+
+ std::vector< Color > mColors;
+};
+
+/**
+ * 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();
+
+ /**
+ * Modifies a pixel color.
+ */
+ void update(int color[3]) const;
+
+ /**
+ * Fills the blank in a dye placeholder with some palette names.
+ */
+ static void instantiate(std::string &target,
+ const std::string &palettes);
+
+ 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];
+};
+
+#endif
diff --git a/tools/dyecmd/src/dyecmd.cpp b/tools/dyecmd/src/dyecmd.cpp
new file mode 100644
index 00000000..8938aea5
--- /dev/null
+++ b/tools/dyecmd/src/dyecmd.cpp
@@ -0,0 +1,121 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <iostream>
+#include <SDL/SDL.h>
+#include <SDL/SDL_image.h>
+
+
+
+#include "dye.h"
+#include "imagewriter.h"
+
+using namespace std;
+
+// return values
+#define RETURN_OK 1
+#define INVALID_PARAMETER_LIST 100
+#define INVALID_INPUT_IMAGE 101
+#define INVALID_OUTPUT_IMAGE 102
+#define 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);
+
+ Uint32 *pixels = static_cast< Uint32 * >(surf->pixels);
+ for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels)
+ {
+ int alpha = *pixels & 255;
+ if (!alpha) continue;
+ int v[3];
+ v[0] = (*pixels >> 24) & 255;
+ v[1] = (*pixels >> 16) & 255;
+ v[2] = (*pixels >> 8 ) & 255;
+ dye->update(v);
+ *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha;
+ }
+
+ return surf;
+}
+
+int main(int argc, char* argv[])
+{
+ Dye* dye = NULL;
+ SDL_Surface* source = NULL;
+
+ // not enough or to many parameters
+ if (argc != 4)
+ {
+ cout << INVALID_PARAMETER_LIST << " - INVALID_PARAMETER_LIST";
+ exit(INVALID_PARAMETER_LIST);
+ }
+
+ try
+ {
+ dye = new Dye(argv[3]);
+ }
+ catch (exception &e)
+ {
+ cout << INVALID_DYE_PARAMETER << " - INVALID_DYE_PARAMETER";
+ exit(INVALID_DYE_PARAMETER);
+ }
+
+ try
+ {
+ source = IMG_Load(argv[1]);
+ if (!source)
+ {
+ throw;
+ }
+ }
+ catch (exception &e)
+ {
+ cout << INVALID_INPUT_IMAGE << " - INVALID_INPUT_IMAGE";
+ exit(INVALID_INPUT_IMAGE);
+ }
+
+ SDL_Surface* target = recolor(source, dye);
+
+ if (!ImageWriter::writePNG(target, argv[2]))
+ {
+ cout << INVALID_OUTPUT_IMAGE << " - INVALID_OUTPUT_IMAGE";
+ exit(INVALID_OUTPUT_IMAGE);
+ }
+
+ SDL_FreeSurface(source);
+ SDL_FreeSurface(target);
+ delete dye;
+
+ return 0;
+}
diff --git a/tools/dyecmd/src/imagewriter.cpp b/tools/dyecmd/src/imagewriter.cpp
new file mode 100644
index 00000000..9b4b10dc
--- /dev/null
+++ b/tools/dyecmd/src/imagewriter.cpp
@@ -0,0 +1,113 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "imagewriter.h"
+
+#include <png.h>
+#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)
+ {
+ // todo
+ // logger->log("could not open file %s for writing", filename.c_str());
+ 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)
+ {
+ // todo
+ // logger->log("Had trouble creating png_structp");
+ return false;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ // todo
+ // logger->log("Could not create png_info");
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ // todo
+ // logger->log("problem writing to %s", filename.c_str());
+ 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)
+ {
+ // todo
+ // logger->log("Had trouble converting surface to row pointers");
+ 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 100644
index 00000000..632e2ae4
--- /dev/null
+++ b/tools/dyecmd/src/imagewriter.h
@@ -0,0 +1,31 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <iosfwd>
+
+struct SDL_Surface;
+
+class ImageWriter
+{
+ public:
+ static bool writePNG(SDL_Surface *surface,
+ const std::string &filename);
+};