From 7c7909350565c3506f8b24645cac10f3197e1fc5 Mon Sep 17 00:00:00 2001 From: Bjørn Lindeijer Date: Wed, 21 Nov 2007 19:43:11 +0000 Subject: Merged revisions 3705-3711,3714,3718,3721-3722,3729-3731,3735,3742 via svnmerge from https://themanaworld.svn.sourceforge.net/svnroot/themanaworld/tmw/trunk (dynamic recoloring of sprites and related changes) ........ r3705 | gmelquio | 2007-11-03 10:58:25 +0100 (Sat, 03 Nov 2007) | 1 line Fixed double load of hair graphics. ........ r3706 | gmelquio | 2007-11-03 22:04:51 +0100 (Sat, 03 Nov 2007) | 1 line Added automatic recoloring of images. Inspired by fungos' ideas (PR #41). ........ r3707 | gmelquio | 2007-11-03 22:08:21 +0100 (Sat, 03 Nov 2007) | 1 line Experimented recoloring on scorpions. ........ r3708 | gmelquio | 2007-11-04 12:52:44 +0100 (Sun, 04 Nov 2007) | 1 line Tightened palette handling. ........ r3709 | gmelquio | 2007-11-04 12:54:31 +0100 (Sun, 04 Nov 2007) | 1 line Experimented with scorpions again. ........ r3710 | gmelquio | 2007-11-04 16:40:37 +0100 (Sun, 04 Nov 2007) | 1 line Applied recoloring to hair styles. ........ r3711 | gmelquio | 2007-11-04 17:50:37 +0100 (Sun, 04 Nov 2007) | 1 line Converted slimes to recoloring. ........ r3742 | gmelquio | 2007-11-16 14:16:00 +0100 (Fri, 16 Nov 2007) | 1 line Sped up recoloring of transparent pixels. ........ --- src/resources/dye.cpp | 222 ++++++++++++++++++++++++++++++++++++++ src/resources/dye.h | 96 +++++++++++++++++ src/resources/image.cpp | 47 +++++++- src/resources/image.h | 16 ++- src/resources/resourcemanager.cpp | 43 ++++++-- src/resources/resourcemanager.h | 7 +- src/resources/spritedef.cpp | 25 +++-- src/resources/spritedef.h | 3 +- 8 files changed, 434 insertions(+), 25 deletions(-) create mode 100644 src/resources/dye.cpp create mode 100644 src/resources/dye.h (limited to 'src/resources') diff --git a/src/resources/dye.cpp b/src/resources/dye.cpp new file mode 100644 index 00000000..2ce4a48d --- /dev/null +++ b/src/resources/dye.cpp @@ -0,0 +1,222 @@ +/* + * 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: dye.cpp 3708 2007-11-04 11:52:44Z gmelquio $ + */ + +#include +#include + +#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 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) + { + std::string::size_type pal_next_pos = palettes.find(';'); + s << target[pos] << ':'; + if (pal_next_pos == std::string::npos) + { + s << palettes.substr(pal_pos); + s << target.substr(next_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); + + target = s.str(); +} diff --git a/src/resources/dye.h b/src/resources/dye.h new file mode 100644 index 00000000..fe8669bb --- /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: dye.h 3706 2007-11-03 21:04:51Z gmelquio $ + */ + +#ifndef _TMW_DYE_H +#define _TMW_DYE_H + +#include + +/** + * 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..d0dae462 100644 --- a/src/resources/image.cpp +++ b/src/resources/image.cpp @@ -21,9 +21,11 @@ * $Id$ */ +#include + #include "image.h" -#include +#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 (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; + } + + 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,11 +65,23 @@ class Image : public Resource * @param buffer The memory buffer containing the image data. * @param bufferSize The size of the memory buffer in bytes. * - * @return NULL if the an error occurred, a valid pointer + * @return NULL 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 NULL 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. */ diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp index e507835a..3368d05b 100644 --- a/src/resources/resourcemanager.cpp +++ b/src/resources/resourcemanager.cpp @@ -28,6 +28,7 @@ #include #include +#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(load(idPath, Image::load)); -} - Music* ResourceManager::getMusic(const std::string &idPath) { @@ -222,6 +217,38 @@ ResourceManager::getSoundEffect(const std::string &idPath) return static_cast(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(p + 1)); + path = path.substr(0, p); + } + 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(get(idPath, DyedImageLoader::load, &l)); +} + struct ImageSetLoader { ResourceManager *manager; @@ -258,8 +285,8 @@ struct SpriteDefLoader } }; -SpriteDef* -ResourceManager::getSprite(const std::string &path, int variant) +SpriteDef *ResourceManager::getSprite + (std::string const &path, int variant) { SpriteDefLoader l = { path, variant }; std::stringstream ss; diff --git a/src/resources/resourcemanager.h b/src/resources/resourcemanager.h index 150b773c..c52248b1 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 #include +#include #include -#include 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,7 @@ 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); /** * Releases a resource, removing it from the set of loaded resources. diff --git a/src/resources/spritedef.cpp b/src/resources/spritedef.cpp index 45a52d2e..de6f8d0b 100644 --- a/src/resources/spritedef.cpp +++ b/src/resources/spritedef.cpp @@ -25,14 +25,14 @@ #include "spritedef.h" -#include "../log.h" - -#include "animation.h" #include "action.h" -#include "resourcemanager.h" -#include "imageset.h" +#include "animation.h" +#include "dye.h" #include "image.h" +#include "imageset.h" +#include "resourcemanager.h" +#include "../log.h" #include "../utils/xml.h" Action* @@ -53,7 +53,14 @@ SpriteDef *SpriteDef::load(std::string const &animationFile, int variant) { int size; ResourceManager *resman = ResourceManager::getInstance(); - char *data = (char*) resman->loadFile(animationFile.c_str(), size); + + std::string::size_type pos = animationFile.find('|'); + std::string palettes; + if (pos != std::string::npos) + palettes = animationFile.substr(pos + 1); + + char *data = (char*) resman->loadFile + (animationFile.substr(0, pos).c_str(), size); if (!data) return NULL; @@ -89,7 +96,7 @@ SpriteDef *SpriteDef::load(std::string const &animationFile, int variant) { if (xmlStrEqual(node->name, BAD_CAST "imageset")) { - def->loadImageSet(node); + def->loadImageSet(node, palettes); } else if (xmlStrEqual(node->name, BAD_CAST "action")) { @@ -125,13 +132,13 @@ void SpriteDef::substituteActions() substituteAction(ACTION_DEAD, ACTION_HURT); } -void -SpriteDef::loadImageSet(xmlNodePtr node) +void SpriteDef::loadImageSet(xmlNodePtr node, std::string const &palettes) { int width = XML::getProperty(node, "width", 0); int height = XML::getProperty(node, "height", 0); std::string name = XML::getProperty(node, "name", ""); std::string imageSrc = XML::getProperty(node, "src", ""); + Dye::instantiate(imageSrc, palettes); ResourceManager *resman = ResourceManager::getInstance(); ImageSet *imageSet = resman->getImageSet(imageSrc, width, height); diff --git a/src/resources/spritedef.h b/src/resources/spritedef.h index 121f23cc..4f316875 100644 --- a/src/resources/spritedef.h +++ b/src/resources/spritedef.h @@ -95,8 +95,7 @@ class SpriteDef : public Resource /** * Loads an imageset element. */ - void - loadImageSet(xmlNodePtr node); + void loadImageSet(xmlNodePtr node, std::string const &palettes); /** * Loads an action element. -- cgit v1.2.3-70-g09d2