diff options
author | Guillaume Melquiond <guillaume.melquiond@gmail.com> | 2007-11-03 21:04:51 +0000 |
---|---|---|
committer | Guillaume Melquiond <guillaume.melquiond@gmail.com> | 2007-11-03 21:04:51 +0000 |
commit | 47386d1d11ba26270edcfe15af832d7c127163f9 (patch) | |
tree | 6f9fc59bc69c29a2e3192d1bdca17c9faab27458 | |
parent | 8a60e11684e84807f3526b37afa8cbf6f103b8fd (diff) | |
download | mana-client-47386d1d11ba26270edcfe15af832d7c127163f9.tar.gz mana-client-47386d1d11ba26270edcfe15af832d7c127163f9.tar.bz2 mana-client-47386d1d11ba26270edcfe15af832d7c127163f9.tar.xz mana-client-47386d1d11ba26270edcfe15af832d7c127163f9.zip |
Added automatic recoloring of images. Inspired by fungos' ideas (PR #41).
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/resources/dye.cpp | 220 | ||||
-rw-r--r-- | src/resources/dye.h | 96 | ||||
-rw-r--r-- | src/resources/image.cpp | 47 | ||||
-rw-r--r-- | src/resources/image.h | 16 | ||||
-rw-r--r-- | src/resources/resourcemanager.cpp | 49 | ||||
-rw-r--r-- | src/resources/resourcemanager.h | 8 |
8 files changed, 427 insertions, 17 deletions
@@ -3,6 +3,12 @@ * src/player.cpp, src/player.h, src/gui/char_select.cpp: Factored code. * src/net/beinghandler.cpp, src/net/charserverhandler.cpp: Fixed double load of hair graphics. + * src/Makefile.am, src/resources/dye.cpp, src/resources/dye.h: Added + palette holder and linear interpolator of colors. + * src/resources/image.h, src/resources/image.cpp: Added palette-based + recoloring of images. + * src/resources/resourcemanager.h, src/resources/resourcemanager.cpp: + Added automatic recoloring of images depending on their names. 2007-10-28 Matthias Hartmann <hartmann.matthias@gmail.com> diff --git a/src/Makefile.am b/src/Makefile.am index 6e6cdfa4..5e2b62fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -204,6 +204,8 @@ tmw_SOURCES = gui/widgets/dropdown.cpp \ resources/ambientoverlay.h \ resources/animation.cpp \ resources/animation.h \ + resources/dye.cpp \ + resources/dye.h \ resources/image.cpp \ resources/image.h \ resources/imageloader.cpp \ diff --git a/src/resources/dye.cpp b/src/resources/dye.cpp new file mode 100644 index 00000000..3c4bfecc --- /dev/null +++ b/src/resources/dye.cpp @@ -0,0 +1,220 @@ +/* + * 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 + * + * $Id$ + */ + +#include <algorithm> +#include <sstream> + +#include "dye.h" + +#include "../log.h" + +Palette::Palette(std::string const &description) +{ + int size = description.length(); + if (size == 0) return; + if (description[0] != '#') + { + // TODO: load palette from file. + 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 goto error; + 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; + } + + error: + logger->log("Error, invalid embedded palette: %s", description.c_str()); +} + +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(std::string const &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] != ':') + { + logger->log("Error, invalid dye: %s", description.c_str()); + 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: + logger->log("Error, invalid dye: %s", description.c_str()); + return; + } + 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, std::string const &palettes) +{ + std::string::size_type last_pos = target.find('>'); + if (last_pos == std::string::npos || palettes.empty()) return; + ++last_pos; + + std::ostringstream s; + std::string::size_type next_pos = 0, pal_pos = 0; + do + { + std::string::size_type pos = next_pos; + next_pos = target.find_first_of(">;", pos); + if (next_pos == pos + 1) + { + std::string::size_type pal_next_pos = palettes.find(';'); + s << target[pos] << ':'; + if (pal_next_pos == std::string::npos) + { + s << palettes.substr(pal_pos); + break; + } + s << palettes.substr(pal_pos, pal_next_pos - pal_pos); + pal_pos = pal_next_pos; + } + else if (next_pos > pos + 2) + { + s << target.substr(pos, next_pos - pos); + } + else + { + logger->log("Error, invalid dye placeholder: %s", target.c_str()); + return; + } + s << target[next_pos]; + ++next_pos; + } + while (next_pos != last_pos); + + s << target.substr(next_pos); + target = s.str(); +} diff --git a/src/resources/dye.h b/src/resources/dye.h new file mode 100644 index 00000000..a11e3365 --- /dev/null +++ b/src/resources/dye.h @@ -0,0 +1,96 @@ +/* + * 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 + * + * $Id$ + */ + +#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(std::string const &); + + /** + * 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(std::string const &); + + /** + * 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, + std::string const &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/src/resources/image.cpp b/src/resources/image.cpp index 7a394edb..fb441bbf 100644 --- a/src/resources/image.cpp +++ b/src/resources/image.cpp @@ -21,9 +21,11 @@ * $Id$ */ +#include <SDL_image.h> + #include "image.h" -#include <SDL_image.h> +#include "dye.h" #include "../log.h" @@ -85,6 +87,49 @@ Resource *Image::load(void *buffer, unsigned bufferSize) return image; } +Resource *Image::load(void *buffer, unsigned bufferSize, Dye const &dye) +{ + SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize); + SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); + + if (!tmpImage) + { + logger->log("Error, image load failed: %s", IMG_GetError()); + return NULL; + } + + 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 (int i = 0, i_end = surf->w * surf->h; i != i_end; ++i) + { + int v[4]; + v[0] = (*pixels >> 24) & 255; + v[1] = (*pixels >> 16) & 255; + v[2] = (*pixels >> 8 ) & 255; + v[3] = (*pixels ) & 255; + dye.update(v); + *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | v[3]; + ++pixels; + } + + Image *image = load(surf); + SDL_FreeSurface(surf); + return image; +} + Image *Image::load(SDL_Surface *tmpImage) { #ifdef USE_OPENGL diff --git a/src/resources/image.h b/src/resources/image.h index 485ca227..52f286f8 100644 --- a/src/resources/image.h +++ b/src/resources/image.h @@ -40,6 +40,8 @@ #include "resource.h" +class Dye; + /** * Defines a class for loading and storing images. */ @@ -63,12 +65,24 @@ class Image : public Resource * @param buffer The memory buffer containing the image data. * @param bufferSize The size of the memory buffer in bytes. * - * @return <code>NULL</code> if the an error occurred, a valid pointer + * @return <code>NULL</code> if an error occurred, a valid pointer * otherwise. */ static Resource *load(void *buffer, unsigned bufferSize); /** + * Loads an image from a buffer in memory and recolors it. + * + * @param buffer The memory buffer containing the image data. + * @param bufferSize The size of the memory buffer in bytes. + * @param dye The dye used to recolor the image. + * + * @return <code>NULL</code> if an error occurred, a valid pointer + * otherwise. + */ + static Resource *load(void *buffer, unsigned bufferSize, Dye const &dye); + + /** * Loads an image from an SDL surface. */ static Image *load(SDL_Surface *); diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp index e507835a..8079e424 100644 --- a/src/resources/resourcemanager.cpp +++ b/src/resources/resourcemanager.cpp @@ -28,6 +28,7 @@ #include <physfs.h> #include <SDL_image.h> +#include "dye.h" #include "image.h" #include "music.h" #include "soundeffect.h" @@ -204,12 +205,6 @@ Resource *ResourceManager::load(std::string const &path, loader fun) return get(path, ResourceLoader::load, &l); } -Image* -ResourceManager::getImage(const std::string &idPath) -{ - return static_cast<Image*>(load(idPath, Image::load)); -} - Music* ResourceManager::getMusic(const std::string &idPath) { @@ -222,6 +217,38 @@ ResourceManager::getSoundEffect(const std::string &idPath) return static_cast<SoundEffect*>(load(idPath, SoundEffect::load)); } +struct DyedImageLoader +{ + ResourceManager *manager; + std::string path; + static Resource *load(void *v) + { + DyedImageLoader *l = static_cast< DyedImageLoader * >(v); + std::string path = l->path; + std::string::size_type p = path.find('>'); + Dye *d = NULL; + if (p != std::string::npos) + { + d = new Dye(path.substr(0, p)); + path = path.substr(p + 1); + } + int fileSize; + void *buffer = l->manager->loadFile(path, fileSize); + if (!buffer) return NULL; + Resource *res = d ? Image::load(buffer, fileSize, *d) + : Image::load(buffer, fileSize); + free(buffer); + delete d; + return res; + } +}; + +Image *ResourceManager::getImage(std::string const &idPath) +{ + DyedImageLoader l = { this, idPath }; + return static_cast<Image*>(get(idPath, DyedImageLoader::load, &l)); +} + struct ImageSetLoader { ResourceManager *manager; @@ -249,19 +276,19 @@ ResourceManager::getImageSet(const std::string &imagePath, int w, int h) struct SpriteDefLoader { - std::string path; + std::string path, palettes; int variant; static Resource *load(void *v) { SpriteDefLoader *l = static_cast< SpriteDefLoader * >(v); - return SpriteDef::load(l->path, l->variant); + return SpriteDef::load(l->path, l->variant /*, l->palettes*/); } }; -SpriteDef* -ResourceManager::getSprite(const std::string &path, int variant) +SpriteDef *ResourceManager::getSprite + (std::string const &path, int variant, std::string const &palettes) { - SpriteDefLoader l = { path, variant }; + SpriteDefLoader l = { path, palettes, variant }; std::stringstream ss; ss << path << "[" << variant << "]"; return static_cast<SpriteDef*>(get(ss.str(), SpriteDefLoader::load, &l)); diff --git a/src/resources/resourcemanager.h b/src/resources/resourcemanager.h index 150b773c..eb883687 100644 --- a/src/resources/resourcemanager.h +++ b/src/resources/resourcemanager.h @@ -24,10 +24,9 @@ #ifndef _TMW_RESOURCE_MANAGER_H #define _TMW_RESOURCE_MANAGER_H -#include <iosfwd> #include <map> +#include <string> #include <vector> -#include <SDL.h> class Resource; class Image; @@ -35,6 +34,7 @@ class Music; class SoundEffect; class ImageSet; class SpriteDef; +struct SDL_Surface; /** * A class for loading and managing resources. @@ -151,8 +151,8 @@ class ResourceManager * Creates a sprite definition based on a given path and the supplied * variant. */ - SpriteDef* - getSprite(const std::string &path, int variant = 0); + SpriteDef *getSprite(std::string const &path, int variant = 0, + std::string const &palettes = std::string()); /** * Releases a resource, removing it from the set of loaded resources. |