瀏覽代碼

xBRZ-upscaled images now support common palette-transform effects:

- Player coloring
- Flag color for map
- Glue selection for combat
Ivan Savenko 1 年之前
父節點
當前提交
ffba847f40

+ 5 - 31
client/battle/CreatureAnimation.cpp

@@ -323,33 +323,6 @@ static ColorRGBA genBorderColor(ui8 alpha, const ColorRGBA & base)
 	return ColorRGBA(base.r, base.g, base.b, ui8(base.a * alpha / 256));
 }
 
-static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2)
-{
-	return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256;
-}
-
-static ColorRGBA addColors(const ColorRGBA & base, const ColorRGBA & over)
-{
-	return ColorRGBA(
-			mixChannels(over.r, base.r, over.a, base.a),
-			mixChannels(over.g, base.g, over.a, base.a),
-			mixChannels(over.b, base.b, over.a, base.a),
-			ui8(over.a + base.a * (255 - over.a) / 256)
-			);
-}
-
-void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
-{
-	target.resize(8);
-	target[0] = genShadow(0);
-	target[1] = genShadow(shadowAlpha / 2);
-	// colors 2 & 3 are not used in creatures
-	target[4] = genShadow(shadowAlpha);
-	target[5] = genBorderColor(getBorderStrength(elapsedTime), border);
-	target[6] = addColors(genShadow(shadowAlpha),     genBorderColor(getBorderStrength(elapsedTime), border));
-	target[7] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
-}
-
 void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight)
 {
 	ColorRGBA shadowTest = shifter.shiftColor(genShadow(128));
@@ -366,11 +339,12 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
 
 	if(image)
 	{
-		IImage::SpecialPalette SpecialPalette;
-		genSpecialPalette(SpecialPalette);
+		image->setShadowEnabled(true);
+		image->setOverlayEnabled(isIdle());
+		if (isIdle())
+			image->setOverlayColor(genBorderColor(getBorderStrength(elapsedTime), border));
 
-		image->setSpecialPalette(SpecialPalette, IImage::SPECIAL_PALETTE_MASK_CREATURES);
-		image->adjustPalette(shifter, IImage::SPECIAL_PALETTE_MASK_CREATURES);
+		image->adjustPalette(shifter, 0);
 
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
 

+ 0 - 2
client/battle/CreatureAnimation.h

@@ -107,9 +107,7 @@ private:
 
 	void endAnimation();
 
-	void genSpecialPalette(IImage::SpecialPalette & target);
 public:
-
 	/// function(s) that will be called when animation ends, after reset to 1st frame
 	/// NOTE that these functions will be fired only once
 	CFunctionList<void()> onAnimationReset;

+ 9 - 11
client/mapView/MapRenderer.cpp

@@ -21,6 +21,7 @@
 #include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
 #include "../render/Colors.h"
+#include "../render/Graphics.h"
 
 #include "../../CCallback.h"
 
@@ -477,23 +478,20 @@ void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & tar
 		return;
 
 	image->setAlpha(transparency);
-	image->setFlagColor(object->tempOwner);
+	image->setShadowEnabled(true);
+	image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL);
+
+	if (object->getOwner().isValidPlayer())
+		image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
+
+	if (object->getOwner() == PlayerColor::NEUTRAL)
+		image->setOverlayColor(graphics->neutralColor);
 
 	Point offsetPixels = context.objectImageOffset(object->id, coordinates);
 
 	if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
 	{
 		Point imagePos = image->dimensions() - offsetPixels - Point(32, 32);
-
-		//if (transparency == 255)
-		//{
-		//	Canvas intermediate(Point(32,32));
-		//	intermediate.enableTransparency(true);
-		//	image->setBlitMode(EImageBlitMode::OPAQUE);
-		//	intermediate.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
-		//	target.draw(intermediate, Point(0,0));
-		//	return;
-		//}
 		target.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
 	}
 }

+ 1 - 1
client/mapView/MapRenderer.h

@@ -23,7 +23,7 @@ class CAnimation;
 class IImage;
 class Canvas;
 class IMapRendererContext;
-enum class EImageBlitMode;
+enum class EImageBlitMode : uint8_t;
 
 class MapTileStorage
 {

+ 1 - 0
client/render/CAnimation.cpp

@@ -13,6 +13,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
+#include "../render/IScreenHandler.h"
 
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/json/JsonUtils.h"

+ 0 - 50
client/render/CDefFile.cpp

@@ -36,48 +36,10 @@ enum class DefType : uint32_t
  *  DefFile, class used for def loading                                  *
  *************************************************************************/
 
-static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs)
-{
-	// it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow
-	// exact logic is not clear and requires extensive testing with image editing
-	// potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component
-	static const int threshold = 8;
-
-	int diffR = static_cast<int>(lhs.r) - rhs.r;
-	int diffG = static_cast<int>(lhs.g) - rhs.g;
-	int diffB = static_cast<int>(lhs.b) - rhs.b;
-	int diffA = static_cast<int>(lhs.a) - rhs.a;
-
-	return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold;
-}
-
 CDefFile::CDefFile(const AnimationPath & Name):
 	data(nullptr),
 	palette(nullptr)
 {
-	//First 8 colors in def palette used for transparency
-	static const SDL_Color sourcePalette[8] = {
-		{0,   255, 255, SDL_ALPHA_OPAQUE},
-		{255, 150, 255, SDL_ALPHA_OPAQUE},
-		{255, 100, 255, SDL_ALPHA_OPAQUE},
-		{255, 50,  255, SDL_ALPHA_OPAQUE},
-		{255, 0,   255, SDL_ALPHA_OPAQUE},
-		{255, 255, 0,   SDL_ALPHA_OPAQUE},
-		{180, 0,   255, SDL_ALPHA_OPAQUE},
-		{0,   255, 0,   SDL_ALPHA_OPAQUE}
-	};
-
-	static const SDL_Color targetPalette[8] = {
-		{0, 0, 0, 0  }, // transparency                  ( used in most images )
-		{0, 0, 0, 64 }, // shadow border                 ( used in battle, adventure map def's )
-		{0, 0, 0, 64 }, // shadow border                 ( used in fog-of-war def's )
-		{0, 0, 0, 128}, // shadow body                   ( used in fog-of-war def's )
-		{0, 0, 0, 128}, // shadow body                   ( used in battle, adventure map def's )
-		{0, 0, 0, 0  }, // selection / owner flag        ( used in battle, adventure map def's )
-		{0, 0, 0, 128}, // shadow body   below selection ( used in battle def's )
-		{0, 0, 0, 64 }  // shadow border below selection ( used in battle def's )
-	};
-
 	data = CResourceHandler::get()->load(Name)->readAll().first;
 
 	palette = std::unique_ptr<SDL_Color[]>(new SDL_Color[256]);
@@ -99,18 +61,6 @@ CDefFile::CDefFile(const AnimationPath & Name):
 		palette[i].a = SDL_ALPHA_OPAQUE;
 	}
 
-	// these colors seems to be used unconditionally
-	palette[0] = targetPalette[0];
-	palette[1] = targetPalette[1];
-	palette[4] = targetPalette[4];
-
-	// rest of special colors are used only if their RGB values are close to H3
-	for (uint32_t i = 0; i < 8; ++i)
-	{
-		if (colorsSimilar(sourcePalette[i], palette[i]))
-			palette[i] = targetPalette[i];
-	}
-
 	for (ui32 i=0; i<totalBlocks; i++)
 	{
 		size_t blockID = read_le_u32(data.get() + it);

+ 1 - 0
client/render/Colors.cpp

@@ -15,6 +15,7 @@
 
 const ColorRGBA Colors::YELLOW = { 229, 215, 123, ColorRGBA::ALPHA_OPAQUE };
 const ColorRGBA Colors::WHITE = { 255, 243, 222, ColorRGBA::ALPHA_OPAQUE };
+const ColorRGBA Colors::WHITE_TRUE = { 255, 255, 255, ColorRGBA::ALPHA_OPAQUE };
 const ColorRGBA Colors::METALLIC_GOLD = { 173, 142, 66, ColorRGBA::ALPHA_OPAQUE };
 const ColorRGBA Colors::GREEN = { 0, 255, 0, ColorRGBA::ALPHA_OPAQUE };
 const ColorRGBA Colors::CYAN = { 0, 255, 255, ColorRGBA::ALPHA_OPAQUE };

+ 3 - 0
client/render/Colors.h

@@ -23,6 +23,9 @@ public:
 	/** the standard h3 white color */
 	static const ColorRGBA WHITE;
 
+	/** actual 100% white color */
+	static const ColorRGBA WHITE_TRUE;
+
 	/** the metallic gold color used mostly as a border around buttons */
 	static const ColorRGBA METALLIC_GOLD;
 

+ 15 - 12
client/render/IImage.h

@@ -23,9 +23,10 @@ VCMI_LIB_NAMESPACE_END
 struct SDL_Surface;
 struct SDL_Palette;
 class ColorFilter;
+class ISharedImage;
 
 /// Defines which blit method will be selected when image is used for rendering
-enum class EImageBlitMode
+enum class EImageBlitMode : uint8_t
 {
 	/// Preferred for images that don't need any background
 	/// Indexed or RGBA: Image can have no transparency and can be only used as background
@@ -41,15 +42,11 @@ enum class EImageBlitMode
 	ALPHA
 };
 
-/*
- * Base class for images, can be used for non-animation pictures as well
- */
+/// Base class for images for use in client code.
+/// This class represents current state of image, with potential transformations applied, such as player coloring
 class IImage
 {
 public:
-	using SpecialPalette = std::vector<ColorRGBA>;
-	static constexpr int32_t SPECIAL_PALETTE_MASK_CREATURES = 0b11110011;
-
 	//draws image on surface "where" at position
 	virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src = nullptr) const = 0;
 
@@ -60,9 +57,6 @@ public:
 	//Change palette to specific player
 	virtual void playerColored(PlayerColor player) = 0;
 
-	//set special color for flag
-	virtual void setFlagColor(PlayerColor player) = 0;
-
 	//test transparency of specific pixel
 	virtual bool isTransparent(const Point & coords) const = 0;
 
@@ -78,23 +72,32 @@ public:
 	virtual void setBlitMode(EImageBlitMode mode) = 0;
 
 	//only indexed bitmaps with 7 special colors
-	virtual void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) = 0;
+	virtual void setOverlayColor(const ColorRGBA & color) = 0;
+
+	virtual void setShadowEnabled(bool on) = 0;
+	virtual void setBodyEnabled(bool on) = 0;
+	virtual void setOverlayEnabled(bool on) = 0;
+	virtual std::shared_ptr<ISharedImage> getSharedImage() const = 0;
 
 	virtual ~IImage() = default;
 };
 
+/// Base class for image data, mostly for internal use
+/// Represents unmodified pixel data, usually loaded from file
+/// This image can be shared between multiple image handlers (IImage instances)
 class ISharedImage
 {
 public:
 	virtual Point dimensions() const = 0;
 	virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
 	virtual bool isTransparent(const Point & coords) const = 0;
-	virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const = 0;
+	virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0;
 
 	virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) = 0;
 
 	virtual std::shared_ptr<ISharedImage> horizontalFlip() const = 0;
 	virtual std::shared_ptr<ISharedImage> verticalFlip() const = 0;
+	virtual std::shared_ptr<ISharedImage> scaleFast(const Point & size, SDL_Palette * palette) const = 0;
 
 
 	virtual ~ISharedImage() = default;

+ 1 - 1
client/render/IRenderHandler.h

@@ -19,7 +19,7 @@ struct SDL_Surface;
 
 class IImage;
 class CAnimation;
-enum class EImageBlitMode;
+enum class EImageBlitMode : uint8_t;
 
 class IRenderHandler : public boost::noncopyable
 {

+ 40 - 1
client/render/ImageLocator.cpp

@@ -10,6 +10,9 @@
 #include "StdInc.h"
 #include "ImageLocator.h"
 
+#include "../gui/CGuiHandler.h"
+#include "IScreenHandler.h"
+
 #include "../../lib/json/JsonNode.h"
 
 
@@ -47,10 +50,46 @@ bool ImageLocator::operator<(const ImageLocator & other) const
 		return defFrame < other.defFrame;
 	if(verticalFlip != other.verticalFlip)
 		return verticalFlip < other.verticalFlip;
-	return horizontalFlip < other.horizontalFlip;
+	if(horizontalFlip != other.horizontalFlip)
+		return horizontalFlip < other.horizontalFlip;
+	if(scalingFactor != other.scalingFactor)
+		return scalingFactor < other.scalingFactor;
+	if(playerColored != other.playerColored)
+		return playerColored < other.playerColored;
+	if(layerShadow != other.layerShadow)
+		return layerShadow < other.layerShadow;
+	if(layerBody != other.layerBody)
+		return layerBody < other.layerBody;
+	if (layerOverlay != other.layerOverlay)
+		return layerOverlay < other.layerOverlay;
+
+	return false;
 }
 
 bool ImageLocator::empty() const
 {
 	return !image.has_value() && !defFile.has_value();
 }
+
+ImageLocator ImageLocator::copyFile() const
+{
+	ImageLocator result;
+	result.image = image;
+	result.defFile = defFile;
+	result.defFrame = defFrame;
+	result.defGroup = defGroup;
+	return result;
+}
+
+ImageLocator ImageLocator::copyFileTransform() const
+{
+	ImageLocator result = copyFile();
+	result.horizontalFlip = horizontalFlip;
+	result.verticalFlip = verticalFlip;
+	return result;
+}
+
+ImageLocator ImageLocator::copyFileTransformScale() const
+{
+	return *this; // full copy
+}

+ 10 - 0
client/render/ImageLocator.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/filesystem/ResourcePath.h"
+#include "../../lib/constants/EntityIdentifiers.h"
 
 struct ImageLocator
 {
@@ -20,6 +21,11 @@ struct ImageLocator
 
 	bool verticalFlip = false;
 	bool horizontalFlip = false;
+	int8_t scalingFactor = 1;
+	PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE;
+	bool layerShadow = false;
+	bool layerBody = true;
+	bool layerOverlay = false;
 
 	ImageLocator() = default;
 	ImageLocator(const AnimationPath & path, int frame, int group);
@@ -28,4 +34,8 @@ struct ImageLocator
 
 	bool operator < (const ImageLocator & other) const;
 	bool empty() const;
+
+	ImageLocator copyFile() const;
+	ImageLocator copyFileTransform() const;
+	ImageLocator copyFileTransformScale() const;
 };

+ 74 - 63
client/renderSDL/ImageScaled.cpp

@@ -13,88 +13,49 @@
 #include "SDLImage.h"
 #include "SDL_Extensions.h"
 
+#include "../gui/CGuiHandler.h"
+#include "../render/IScreenHandler.h"
+#include "../render/Colors.h"
+
 #include "../../lib/constants/EntityIdentifiers.h"
 
 #include <SDL_surface.h>
 
-ImageSharedScaled::ImageSharedScaled(std::shared_ptr<SDLImageShared> sourceImage)
-	:sourceImage(sourceImage)
-{
-	scaledImage = sourceImage->scaleFast(sourceImage->dimensions() * getScalingFactor());
-}
-
-int ImageSharedScaled::getScalingFactor() const
-{
-	return 2;
-}
-
-void ImageSharedScaled::draw(SDL_Surface *where, SDL_Palette * palette, const Point &dest, const Rect *src, uint8_t alpha, EImageBlitMode mode) const
-{
-	scaledImage->draw(where, nullptr, dest, src, alpha, mode);
-}
-
-void ImageSharedScaled::exportBitmap(const boost::filesystem::path &path) const
-{
-	sourceImage->exportBitmap(path);
-}
-
-Point ImageSharedScaled::dimensions() const
-{
-	return sourceImage->dimensions();
-}
-
-bool ImageSharedScaled::isTransparent(const Point &coords) const
-{
-	return sourceImage->isTransparent(coords);
-}
-
-std::shared_ptr<IImage> ImageSharedScaled::createImageReference(EImageBlitMode mode)
-{
-	return std::make_shared<ImageScaled>(shared_from_this(), mode);
-}
-
-std::shared_ptr<ISharedImage> ImageSharedScaled::horizontalFlip() const
-{
-	return std::make_shared<ImageSharedScaled>(std::dynamic_pointer_cast<SDLImageShared>(sourceImage->horizontalFlip()));
-}
-
-std::shared_ptr<ISharedImage> ImageSharedScaled::verticalFlip() const
-{
-	return std::make_shared<ImageSharedScaled>(std::dynamic_pointer_cast<SDLImageShared>(sourceImage->verticalFlip()));
-}
-
-std::shared_ptr<ImageSharedScaled> ImageSharedScaled::scaleFast(const Point &size) const
+ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_ptr<ISharedImage> & source, EImageBlitMode mode)
+	: source(source)
+	, locator(inputLocator)
+	, colorMultiplier(Colors::WHITE_TRUE)
+	, alphaValue(SDL_ALPHA_OPAQUE)
+	, blitMode(mode)
 {
-	return std::make_shared<ImageSharedScaled>(sourceImage->scaleFast(size));
+	locator.scalingFactor = GH.screenHandler().getScalingFactor();
+	setBodyEnabled(true);
 }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-ImageScaled::ImageScaled(const std::shared_ptr<ImageSharedScaled> &image, EImageBlitMode mode)
-	:image(image)
-	, alphaValue(SDL_ALPHA_OPAQUE)
-	, blitMode(mode)
+std::shared_ptr<ISharedImage> ImageScaled::getSharedImage() const
 {
+	return body;
 }
 
 void ImageScaled::scaleFast(const Point &size)
 {
-	image = image->scaleFast(size);
+	if (body)
+		body = body->scaleFast(size, nullptr); // FIXME: adjust for scaling
 }
 
 void ImageScaled::exportBitmap(const boost::filesystem::path &path) const
 {
-	image->exportBitmap(path);
+	source->exportBitmap(path);
 }
 
 bool ImageScaled::isTransparent(const Point &coords) const
 {
-	return image->isTransparent(coords);
+	return source->isTransparent(coords);
 }
 
 Point ImageScaled::dimensions() const
 {
-	return image->dimensions();
+	return source->dimensions();
 }
 
 void ImageScaled::setAlpha(uint8_t value)
@@ -109,25 +70,75 @@ void ImageScaled::setBlitMode(EImageBlitMode mode)
 
 void ImageScaled::draw(SDL_Surface *where, const Point &pos, const Rect *src) const
 {
-	image->draw(where, pos, src, alphaValue, blitMode);
+	if (shadow)
+		shadow->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
+	if (body)
+		body->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
+	if (overlay)
+		overlay->draw(where, nullptr, pos, src, colorMultiplier, colorMultiplier.a * alphaValue / 255, blitMode);
 }
 
-void ImageScaled::setSpecialPalette(const SpecialPalette &SpecialPalette, uint32_t colorsToSkipMask)
+void ImageScaled::setOverlayColor(const ColorRGBA & color)
 {
+	colorMultiplier = color;
 }
 
 void ImageScaled::playerColored(PlayerColor player)
 {
+	playerColor = player;
+	if (body)
+		setBodyEnabled(true); // regenerate
 }
 
-void ImageScaled::setFlagColor(PlayerColor player)
+void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
 {
+	// TODO: implement
 }
 
-void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
+void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSkipMask)
 {
+	// TODO: implement
 }
 
-void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSkipMask)
+void ImageScaled::setShadowEnabled(bool on)
+{
+	if (on)
+	{
+		locator.layerBody = false;
+		locator.layerShadow = true;
+		locator.layerOverlay = false;
+		locator.playerColored = PlayerColor::CANNOT_DETERMINE;
+		shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+	}
+	else
+		shadow = nullptr;
+}
+
+void ImageScaled::setBodyEnabled(bool on)
+{
+	if (on)
+	{
+		locator.layerBody = true;
+		locator.layerShadow = false;
+		locator.layerOverlay = false;
+		locator.playerColored = playerColor;
+		body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+	}
+	else
+		body = nullptr;
+}
+
+
+void ImageScaled::setOverlayEnabled(bool on)
 {
+	if (on)
+	{
+		locator.layerBody = false;
+		locator.layerShadow = false;
+		locator.layerOverlay = true;
+		locator.playerColored = PlayerColor::CANNOT_DETERMINE;
+		overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+	}
+	else
+		overlay = nullptr;
 }

+ 26 - 22
client/renderSDL/ImageScaled.h

@@ -12,40 +12,40 @@
 #include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
 
+#include "../../lib/Color.h"
+#include "../../lib/constants/EntityIdentifiers.h"
+
 struct SDL_Palette;
 
 class SDLImageShared;
 
-class ImageSharedScaled final : public ISharedImage, public std::enable_shared_from_this<ImageSharedScaled>, boost::noncopyable
+// Upscaled image with several mechanisms to emulate H3 palette effects
+class ImageScaled final : public IImage
 {
-	std::shared_ptr<SDLImageShared> sourceImage;
-	std::shared_ptr<SDLImageShared> scaledImage;
+private:
 
-	int getScalingFactor() const;
-public:
-	ImageSharedScaled(std::shared_ptr<SDLImageShared> sourceImage);
+	/// Original unscaled image
+	std::shared_ptr<ISharedImage> source;
 
-	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const override;
+	/// Upscaled shadow of our image, may be null
+	std::shared_ptr<ISharedImage> shadow;
 
-	void exportBitmap(const boost::filesystem::path & path) const override;
-	Point dimensions() const override;
-	bool isTransparent(const Point & coords) const override;
-	std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) override;
-	std::shared_ptr<ISharedImage> horizontalFlip() const override;
-	std::shared_ptr<ISharedImage> verticalFlip() const override;
-	std::shared_ptr<ImageSharedScaled> scaleFast(const Point & size) const;
-};
+	/// Upscaled main part of our image, may be null
+	std::shared_ptr<ISharedImage> body;
 
-class ImageScaled final : public IImage
-{
-private:
-	std::shared_ptr<ImageSharedScaled> image;
+	/// Upscaled overlay (player color, selection highlight) of our image, may be null
+	std::shared_ptr<ISharedImage> overlay;
+
+	ImageLocator locator;
+
+	ColorRGBA colorMultiplier;
+	PlayerColor playerColor = PlayerColor::CANNOT_DETERMINE;
 
 	uint8_t alphaValue;
 	EImageBlitMode blitMode;
 
 public:
-	ImageScaled(const std::shared_ptr<ImageSharedScaled> & image, EImageBlitMode mode);
+	ImageScaled(const ImageLocator & locator, const std::shared_ptr<ISharedImage> & source, EImageBlitMode mode);
 
 	void scaleFast(const Point & size) override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
@@ -54,9 +54,13 @@ public:
 	void setAlpha(uint8_t value) override;
 	void setBlitMode(EImageBlitMode mode) override;
 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
-	void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
+	void setOverlayColor(const ColorRGBA & color) override;
 	void playerColored(PlayerColor player) override;
-	void setFlagColor(PlayerColor player) override;
 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
+
+	void setShadowEnabled(bool on) override;
+	void setBodyEnabled(bool on) override;
+	void setOverlayEnabled(bool on) override;
+	std::shared_ptr<ISharedImage> getSharedImage() const override;
 };

+ 85 - 39
client/renderSDL/RenderHandler.cpp

@@ -15,6 +15,8 @@
 
 #include "../render/CAnimation.h"
 #include "../render/CDefFile.h"
+#include "../render/Colors.h"
+#include "../render/ColorFilter.h"
 
 #include "../../lib/json/JsonUtils.h"
 #include "../../lib/filesystem/Filesystem.h"
@@ -131,64 +133,82 @@ int RenderHandler::getScalingFactor() const
 	return 2;
 }
 
-std::shared_ptr<ISharedImage> RenderHandler::createScaledImage(std::shared_ptr<SDLImageShared> input)
+std::shared_ptr<IImage> RenderHandler::createImageReference(const ImageLocator & locator, std::shared_ptr<ISharedImage> input, EImageBlitMode mode)
 {
-	if (getScalingFactor() == 1)
-		return input;
-
-	return std::make_shared<ImageSharedScaled>(input);
-}
-
-std::shared_ptr<ISharedImage> RenderHandler::loadImageFromSingleFile(const ImagePath & path)
-{
-	auto result = createScaledImage(std::make_shared<SDLImageShared>(path));
-	imageFiles[ImageLocator(path)] = result;
-	return result;
+	if (getScalingFactor() == 1 || locator.scalingFactor != 1 || locator.empty())
+		return input->createImageReference(mode);
+	else
+		return std::make_shared<ImageScaled>(locator, input, mode);
 }
 
-std::shared_ptr<ISharedImage> RenderHandler::loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group)
+ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group)
 {
 	const auto & layout = getAnimationLayout(path);
 	if (!layout.count(group))
-		return loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+		return ImageLocator(ImagePath::builtin("DEFAULT"));
 
 	if (frame >= layout.at(group).size())
-		return loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+		return ImageLocator(ImagePath::builtin("DEFAULT"));
 
 	const auto & locator = layout.at(group).at(frame);
+	if (locator.image || locator.defFile)
+		return locator;
+
+	return ImageLocator(path, frame, group);
+}
+
+std::shared_ptr<ISharedImage> RenderHandler::loadImageImpl(const ImageLocator & locator)
+{
+	auto it = imageFiles.find(locator);
+	if (it != imageFiles.end())
+		return it->second;
+
+	// TODO: order should be different:
+	// 1) try to find correctly scaled image
+	// 2) if fails -> try to find correctly transformed
+	// 3) if also fails -> try to find image from correct file
+	// 4) load missing part of the sequence
+	// TODO: check whether (load -> transform -> scale) or (load -> scale -> transform) order should be used for proper loading of pre-scaled data
+	auto imageFromFile = loadImageFromFile(locator.copyFile());
+	auto transformedImage = transformImage(locator.copyFileTransform(), imageFromFile);
+	auto scaledImage = scaleImage(locator.copyFileTransformScale(), transformedImage);
+
+	return scaledImage;
+}
+
+std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
+{
 	if (locator.image)
 	{
-		return loadImageImpl(locator);
+		// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load
+		return std::make_shared<SDLImageShared>(*locator.image);
 	}
-	else
+
+	if (locator.defFile)
 	{
-		auto defFile = getAnimationFile(path);
-		return createScaledImage(std::make_shared<SDLImageShared>(defFile.get(), frame, group));
+		auto defFile = getAnimationFile(*locator.defFile);
+		return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup);
 	}
+
+	throw std::runtime_error("Invalid image locator received!");
 }
 
-std::shared_ptr<ISharedImage> RenderHandler::loadImageFromAnimationFile(const AnimationPath & path, int frame, int group)
+std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFile(const ImageLocator & locator)
 {
-	auto result = loadImageFromAnimationFileUncached(path, frame, group);
-	imageFiles[ImageLocator(path, frame, group)] = result;
+	if (imageFiles.count(locator))
+		return imageFiles.at(locator);
+
+	auto result = loadImageFromFileUncached(locator);
+	imageFiles[locator] = result;
 	return result;
 }
 
-std::shared_ptr<ISharedImage> RenderHandler::loadImageImpl(const ImageLocator & locator)
+std::shared_ptr<ISharedImage> RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image)
 {
-	auto it = imageFiles.find(locator);
-	if (it != imageFiles.end())
-		return it->second;
+	if (imageFiles.count(locator))
+		return imageFiles.at(locator);
 
-	std::shared_ptr<ISharedImage> result;
-
-	if (locator.image)
-		result = loadImageFromSingleFile(*locator.image);
-	else if (locator.defFile)
-		result = loadImageFromAnimationFile(*locator.defFile, locator.defFrame, locator.defGroup);
-
-	if (!result)
-		result = loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+	auto result = image;
 
 	if (locator.verticalFlip)
 		result = result->verticalFlip();
@@ -200,24 +220,50 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageImpl(const ImageLocator &
 	return result;
 }
 
+std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image)
+{
+	if (imageFiles.count(locator))
+		return imageFiles.at(locator);
+
+	auto handle = image->createImageReference(EImageBlitMode::OPAQUE);
+
+	assert(locator.scalingFactor != 1); // should be filtered-out before
+
+
+
+	handle->setOverlayEnabled(locator.layerOverlay);
+	handle->setBodyEnabled(locator.layerBody);
+	handle->setShadowEnabled(locator.layerShadow);
+	if (locator.layerBody && locator.playerColored != PlayerColor::CANNOT_DETERMINE)
+		handle->playerColored(locator.playerColored);
+
+	handle->scaleFast(handle->dimensions() * locator.scalingFactor);
+
+	// TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent
+	auto result = handle->getSharedImage();
+	imageFiles[locator] = result;
+	return result;
+}
+
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
 {
-	return loadImageImpl(locator)->createImageReference(mode);
+	return createImageReference(locator, loadImageImpl(locator), mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
 {
-	return loadImageFromAnimationFile(path, frame, group)->createImageReference(mode);
+	auto locator = getLocatorForAnimationFrame(path, frame, group);
+	return loadImage(locator, mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
 {
-	return loadImageImpl(ImageLocator(path))->createImageReference(mode);
+	return loadImage(ImageLocator(path), mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
 {
-	return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::ALPHA);
+	return createImageReference(ImageLocator(), std::make_shared<SDLImageShared>(source), EImageBlitMode::ALPHA);
 }
 
 std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)

+ 9 - 4
client/renderSDL/RenderHandler.h

@@ -34,14 +34,19 @@ class RenderHandler : public IRenderHandler
 	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
 	void addImageListEntries(const EntityService * service);
 
-	std::shared_ptr<ISharedImage> loadImageFromSingleFile(const ImagePath & path);
-	std::shared_ptr<ISharedImage> loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group);
-	std::shared_ptr<ISharedImage> loadImageFromAnimationFile(const AnimationPath & path, int frame, int group);
 	std::shared_ptr<ISharedImage> loadImageImpl(const ImageLocator & config);
 
+	std::shared_ptr<ISharedImage> loadImageFromFileUncached(const ImageLocator & locator);
+	std::shared_ptr<ISharedImage> loadImageFromFile(const ImageLocator & locator);
+
+	std::shared_ptr<ISharedImage> transformImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image);
+	std::shared_ptr<ISharedImage> scaleImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image);
+
+	ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group);
+
 	int getScalingFactor() const;
 
-	std::shared_ptr<ISharedImage> createScaledImage(std::shared_ptr<SDLImageShared> input);
+	std::shared_ptr<IImage> createImageReference(const ImageLocator & locator, std::shared_ptr<ISharedImage> input, EImageBlitMode mode);
 public:
 
 	// IRenderHandler implementation

+ 154 - 36
client/renderSDL/SDLImage.cpp

@@ -14,6 +14,7 @@
 #include "SDL_Extensions.h"
 
 #include "../render/ColorFilter.h"
+#include "../render/Colors.h"
 #include "../render/CBitmapHandler.h"
 #include "../render/CDefFile.h"
 #include "../render/Graphics.h"
@@ -22,6 +23,59 @@
 
 class SDLImageLoader;
 
+//First 8 colors in def palette used for transparency
+static const SDL_Color sourcePalette[8] = {
+	{0,   255, 255, SDL_ALPHA_OPAQUE},
+	{255, 150, 255, SDL_ALPHA_OPAQUE},
+	{255, 100, 255, SDL_ALPHA_OPAQUE},
+	{255, 50,  255, SDL_ALPHA_OPAQUE},
+	{255, 0,   255, SDL_ALPHA_OPAQUE},
+	{255, 255, 0,   SDL_ALPHA_OPAQUE},
+	{180, 0,   255, SDL_ALPHA_OPAQUE},
+	{0,   255, 0,   SDL_ALPHA_OPAQUE}
+};
+
+static const ColorRGBA targetPalette[8] = {
+	{0, 0, 0, 0  }, // 0 - transparency                  ( used in most images )
+	{0, 0, 0, 64 }, // 1 - shadow border                 ( used in battle, adventure map def's )
+	{0, 0, 0, 64 }, // 2 - shadow border                 ( used in fog-of-war def's )
+	{0, 0, 0, 128}, // 3 - shadow body                   ( used in fog-of-war def's )
+	{0, 0, 0, 128}, // 4 - shadow body                   ( used in battle, adventure map def's )
+	{0, 0, 0, 0  }, // 5 - selection / owner flag        ( used in battle, adventure map def's )
+	{0, 0, 0, 128}, // 6 - shadow body   below selection ( used in battle def's )
+	{0, 0, 0, 64 }  // 7 - shadow border below selection ( used in battle def's )
+};
+
+static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2)
+{
+	return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256;
+}
+
+static ColorRGBA addColors(const ColorRGBA & base, const ColorRGBA & over)
+{
+	return ColorRGBA(
+		mixChannels(over.r, base.r, over.a, base.a),
+		mixChannels(over.g, base.g, over.a, base.a),
+		mixChannels(over.b, base.b, over.a, base.a),
+		ui8(over.a + base.a * (255 - over.a) / 256)
+		);
+}
+
+static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs)
+{
+	// it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow
+	// exact logic is not clear and requires extensive testing with image editing
+	// potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component
+	static const int threshold = 8;
+
+	int diffR = static_cast<int>(lhs.r) - rhs.r;
+	int diffG = static_cast<int>(lhs.g) - rhs.g;
+	int diffB = static_cast<int>(lhs.b) - rhs.b;
+	int diffA = static_cast<int>(lhs.a) - rhs.a;
+
+	return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold;
+}
+
 int IImage::width() const
 {
 	return dimensions().x;
@@ -83,7 +137,7 @@ SDLImageShared::SDLImageShared(const ImagePath & filename)
 }
 
 
-void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const
+void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const
 {
 	if (!surf)
 		return;
@@ -109,6 +163,7 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin
 
 	destShift += dest;
 
+	SDL_SetSurfaceColorMod(surf, colorMultiplier.r, colorMultiplier.g, colorMultiplier.b);
 	SDL_SetSurfaceAlphaMod(surf, alpha);
 
 	if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0))
@@ -127,21 +182,19 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin
 	{
 		CSDL_Ext::blitSurface(surf, sourceRect, where, destShift);
 	}
-}
 
-const SDL_Palette * SDLImageShared::getPalette() const
-{
-	if (originalPalette == nullptr)
-		throw std::runtime_error("Palette not found!");
-
-	return originalPalette;
+	if (surf->format->palette)
+		SDL_SetSurfacePalette(surf, originalPalette);
 }
 
-std::shared_ptr<SDLImageShared> SDLImageShared::scaleFast(const Point & size) const
+std::shared_ptr<ISharedImage> SDLImageShared::scaleFast(const Point & size, SDL_Palette * palette) const
 {
 	float scaleX = float(size.x) / dimensions().x;
 	float scaleY = float(size.y) / dimensions().y;
 
+	if (palette && surf->format->palette)
+		SDL_SetSurfacePalette(surf, palette);
+
 	auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY));
 
 	if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point
@@ -162,6 +215,9 @@ std::shared_ptr<SDLImageShared> SDLImageShared::scaleFast(const Point & size) co
 	// erase our own reference
 	SDL_FreeSurface(scaled);
 
+	if (surf->format->palette)
+		SDL_SetSurfacePalette(surf, originalPalette);
+
 	return ret;
 }
 
@@ -175,12 +231,6 @@ void SDLImageIndexed::playerColored(PlayerColor player)
 	graphics->setPlayerPalette(currentPalette, player);
 }
 
-void SDLImageIndexed::setFlagColor(PlayerColor player)
-{
-	if(player.isValidPlayer() || player==PlayerColor::NEUTRAL)
-		graphics->setPlayerFlagColor(currentPalette, player);
-}
-
 bool SDLImageShared::isTransparent(const Point & coords) const
 {
 	if (surf)
@@ -197,7 +247,7 @@ Point SDLImageShared::dimensions() const
 std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode)
 {
 	if (surf && surf->format->palette)
-		return std::make_shared<SDLImageIndexed>(shared_from_this(), mode);
+		return std::make_shared<SDLImageIndexed>(shared_from_this(), originalPalette, mode);
 	else
 		return std::make_shared<SDLImageRGB>(shared_from_this(), mode);
 }
@@ -241,8 +291,6 @@ void SDLImageShared::savePalette()
 
 void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
 {
-	const SDL_Palette * originalPalette = image->getPalette();
-
 	std::vector<SDL_Color> shifterColors(colorsToMove);
 
 	for(uint32_t i=0; i<colorsToMove; ++i)
@@ -253,11 +301,12 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove,
 
 void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
 {
-	const SDL_Palette * originalPalette = image->getPalette();
-
 	// Note: here we skip first colors in the palette that are predefined in H3 images
 	for(int i = 0; i < currentPalette->ncolors; i++)
 	{
+		if (i < std::size(sourcePalette) && colorsSimilar(sourcePalette[i], originalPalette->colors[i]))
+			continue;
+
 		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
 			continue;
 
@@ -265,13 +314,16 @@ void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colors
 	}
 }
 
-SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<SDLImageShared> & image, EImageBlitMode mode)
+SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<ISharedImage> & image, SDL_Palette * originalPalette, EImageBlitMode mode)
 	:SDLImageBase::SDLImageBase(image, mode)
+	,originalPalette(originalPalette)
 {
-	auto originalPalette = image->getPalette();
 
 	currentPalette = SDL_AllocPalette(originalPalette->ncolors);
 	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
+
+	setOverlayColor(Colors::TRANSPARENCY);
+	setShadowTransparency(0);
 }
 
 SDLImageIndexed::~SDLImageIndexed()
@@ -279,42 +331,96 @@ SDLImageIndexed::~SDLImageIndexed()
 	SDL_FreePalette(currentPalette);
 }
 
-void SDLImageIndexed::setSpecialPalette(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask)
+void SDLImageIndexed::setShadowTransparency(float factor)
 {
-	size_t last = std::min<size_t>(specialPalette.size(), currentPalette->ncolors);
+	ColorRGBA shadow50(0, 0, 0, 128 * factor);
+	ColorRGBA shadow25(0, 0, 0,  64 * factor);
+
+	// seems to be used unconditionally
+	currentPalette->colors[1] = CSDL_Ext::toSDL(shadow25);
+	currentPalette->colors[4] = CSDL_Ext::toSDL(shadow50);
+
+	// seems to be used only if color matches
+	if (colorsSimilar(originalPalette->colors[2], sourcePalette[2]))
+		currentPalette->colors[2] = CSDL_Ext::toSDL(shadow25);
 
-	for (size_t i = 0; i < last; ++i)
+	if (colorsSimilar(originalPalette->colors[3], sourcePalette[3]))
+		currentPalette->colors[3] = CSDL_Ext::toSDL(shadow50);
+}
+
+void SDLImageIndexed::setOverlayColor(const ColorRGBA & color)
+{
+	for (int i : {5,6,7})
 	{
-		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
-			currentPalette->colors[i] = CSDL_Ext::toSDL(specialPalette[i]);
+		if (colorsSimilar(originalPalette->colors[i], sourcePalette[i]))
+			currentPalette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
 	}
 }
 
+void SDLImageIndexed::setShadowEnabled(bool on)
+{
+	if (on)
+		setShadowTransparency(1.0);
+	else
+		setShadowTransparency(0);
+
+	shadowEnabled = on;
+}
+
+void SDLImageIndexed::setBodyEnabled(bool on)
+{
+	if (on)
+		adjustPalette(ColorFilter::genEmptyShifter(), 0);
+	else
+		adjustPalette(ColorFilter::genAlphaShifter(0), 0);
+
+	bodyEnabled = on;
+}
+
+void SDLImageIndexed::setOverlayEnabled(bool on)
+{
+	if (on)
+		setOverlayColor(Colors::WHITE_TRUE);
+	else
+		setOverlayColor(Colors::TRANSPARENCY);
+	overlayEnabled = on;
+}
+
 SDLImageShared::~SDLImageShared()
 {
 	SDL_FreeSurface(surf);
 	SDL_FreePalette(originalPalette);
 }
 
-SDLImageBase::SDLImageBase(const std::shared_ptr<SDLImageShared> & image, EImageBlitMode mode)
+SDLImageBase::SDLImageBase(const std::shared_ptr<ISharedImage> & image, EImageBlitMode mode)
 	:image(image)
 	, alphaValue(SDL_ALPHA_OPAQUE)
 	, blitMode(mode)
 {}
 
+std::shared_ptr<ISharedImage> SDLImageBase::getSharedImage() const
+{
+	return image;
+}
+
 void SDLImageRGB::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
 {
-	image->draw(where, nullptr, pos, src, alphaValue, blitMode);
+	image->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
 }
 
 void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
 {
-	image->draw(where, currentPalette, pos, src, alphaValue, blitMode);
+	image->draw(where, currentPalette, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
+}
+
+void SDLImageIndexed::scaleFast(const Point & size)
+{
+	image = image->scaleFast(size, currentPalette);
 }
 
-void SDLImageBase::scaleFast(const Point & size)
+void SDLImageRGB::scaleFast(const Point & size)
 {
-	image = image->scaleFast(size);
+	image = image->scaleFast(size, nullptr);
 }
 
 void SDLImageBase::exportBitmap(const boost::filesystem::path & path) const
@@ -342,13 +448,25 @@ void SDLImageBase::setBlitMode(EImageBlitMode mode)
 	blitMode = mode;
 }
 
-void SDLImageRGB::setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask)
-{}
+void SDLImageRGB::setShadowEnabled(bool on)
+{
+	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
+}
 
-void SDLImageRGB::playerColored(PlayerColor player)
+void SDLImageRGB::setBodyEnabled(bool on)
+{
+	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
+}
+
+void SDLImageRGB::setOverlayEnabled(bool on)
+{
+	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
+}
+
+void SDLImageRGB::setOverlayColor(const ColorRGBA & color)
 {}
 
-void SDLImageRGB::setFlagColor(PlayerColor player)
+void SDLImageRGB::playerColored(PlayerColor player)
 {}
 
 void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)

+ 24 - 12
client/renderSDL/SDLImage.h

@@ -47,7 +47,7 @@ public:
 	SDLImageShared(SDL_Surface * from);
 	~SDLImageShared();
 
-	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const override;
+	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override;
 
 	void exportBitmap(const boost::filesystem::path & path) const override;
 	Point dimensions() const override;
@@ -55,9 +55,7 @@ public:
 	std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) override;
 	std::shared_ptr<ISharedImage> horizontalFlip() const override;
 	std::shared_ptr<ISharedImage> verticalFlip() const override;
-	std::shared_ptr<SDLImageShared> scaleFast(const Point & size) const;
-
-	const SDL_Palette * getPalette() const;
+	std::shared_ptr<ISharedImage> scaleFast(const Point & size, SDL_Palette * palette) const override;
 
 	friend class SDLImageLoader;
 };
@@ -65,36 +63,46 @@ public:
 class SDLImageBase : public IImage, boost::noncopyable
 {
 protected:
-	std::shared_ptr<SDLImageShared> image;
+	std::shared_ptr<ISharedImage> image;
 
 	uint8_t alphaValue;
 	EImageBlitMode blitMode;
 
 public:
-	SDLImageBase(const std::shared_ptr<SDLImageShared> & image, EImageBlitMode mode);
+	SDLImageBase(const std::shared_ptr<ISharedImage> & image, EImageBlitMode mode);
 
-	void scaleFast(const Point & size) override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
 	bool isTransparent(const Point & coords) const override;
 	Point dimensions() const override;
 	void setAlpha(uint8_t value) override;
 	void setBlitMode(EImageBlitMode mode) override;
+	std::shared_ptr<ISharedImage> getSharedImage() const override;
 };
 
 class SDLImageIndexed final : public SDLImageBase
 {
 	SDL_Palette * currentPalette = nullptr;
+	SDL_Palette * originalPalette = nullptr;
 
+	bool bodyEnabled = true;
+	bool shadowEnabled = false;
+	bool overlayEnabled = false;
+
+	void setShadowTransparency(float factor);
 public:
-	SDLImageIndexed(const std::shared_ptr<SDLImageShared> & image, EImageBlitMode mode);
+	SDLImageIndexed(const std::shared_ptr<ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
 	~SDLImageIndexed();
 
 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
-	void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
+	void setOverlayColor(const ColorRGBA & color) override;
 	void playerColored(PlayerColor player) override;
-	void setFlagColor(PlayerColor player) override;
 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
+	void scaleFast(const Point & size) override;
+
+	void setShadowEnabled(bool on) override;
+	void setBodyEnabled(bool on) override;
+	void setOverlayEnabled(bool on) override;
 };
 
 class SDLImageRGB final : public SDLImageBase
@@ -103,9 +111,13 @@ public:
 	using SDLImageBase::SDLImageBase;
 
 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
-	void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
+	void setOverlayColor(const ColorRGBA & color) override;
 	void playerColored(PlayerColor player) override;
-	void setFlagColor(PlayerColor player) override;
 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
+	void scaleFast(const Point & size) override;
+
+	void setShadowEnabled(bool on) override;
+	void setBodyEnabled(bool on) override;
+	void setOverlayEnabled(bool on) override;
 };