1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

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

- Player coloring
- Flag color for map
- Glue selection for combat
This commit is contained in:
Ivan Savenko 2024-07-25 10:38:48 +00:00
parent 2d12cecded
commit ffba847f40
18 changed files with 464 additions and 291 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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)));
}
}

View File

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

View File

@ -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"

View File

@ -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);

View File

@ -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 };

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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
}

View File

@ -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;
};

View File

@ -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
{
return std::make_shared<ImageSharedScaled>(sourceImage->scaleFast(size));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
ImageScaled::ImageScaled(const std::shared_ptr<ImageSharedScaled> &image, EImageBlitMode mode)
:image(image)
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)
{
locator.scalingFactor = GH.screenHandler().getScalingFactor();
setBodyEnabled(true);
}
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)
{
}
void ImageScaled::setFlagColor(PlayerColor player)
{
playerColor = player;
if (body)
setBodyEnabled(true); // regenerate
}
void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{
// TODO: implement
}
void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSkipMask)
{
// TODO: implement
}
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;
}

View File

@ -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
{
std::shared_ptr<SDLImageShared> sourceImage;
std::shared_ptr<SDLImageShared> scaledImage;
int getScalingFactor() const;
public:
ImageSharedScaled(std::shared_ptr<SDLImageShared> sourceImage);
void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const override;
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 image with several mechanisms to emulate H3 palette effects
class ImageScaled final : public IImage
{
private:
std::shared_ptr<ImageSharedScaled> image;
/// Original unscaled image
std::shared_ptr<ISharedImage> source;
/// Upscaled shadow of our image, may be null
std::shared_ptr<ISharedImage> shadow;
/// Upscaled main part of our image, may be null
std::shared_ptr<ISharedImage> body;
/// 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;
};

View File

@ -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,47 +133,28 @@ 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);
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::loadImageFromSingleFile(const ImagePath & path)
{
auto result = createScaledImage(std::make_shared<SDLImageShared>(path));
imageFiles[ImageLocator(path)] = result;
return result;
}
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)
{
return loadImageImpl(locator);
}
else
{
auto defFile = getAnimationFile(path);
return createScaledImage(std::make_shared<SDLImageShared>(defFile.get(), frame, group));
}
}
if (locator.image || locator.defFile)
return locator;
std::shared_ptr<ISharedImage> RenderHandler::loadImageFromAnimationFile(const AnimationPath & path, int frame, int group)
{
auto result = loadImageFromAnimationFileUncached(path, frame, group);
imageFiles[ImageLocator(path, frame, group)] = result;
return result;
return ImageLocator(path, frame, group);
}
std::shared_ptr<ISharedImage> RenderHandler::loadImageImpl(const ImageLocator & locator)
@ -180,15 +163,52 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageImpl(const ImageLocator &
if (it != imageFiles.end())
return it->second;
std::shared_ptr<ISharedImage> result;
// 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)
result = loadImageFromSingleFile(*locator.image);
else if (locator.defFile)
result = loadImageFromAnimationFile(*locator.defFile, locator.defFrame, locator.defGroup);
{
// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load
return std::make_shared<SDLImageShared>(*locator.image);
}
if (!result)
result = loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
if (locator.defFile)
{
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::loadImageFromFile(const ImageLocator & locator)
{
if (imageFiles.count(locator))
return imageFiles.at(locator);
auto result = loadImageFromFileUncached(locator);
imageFiles[locator] = result;
return result;
}
std::shared_ptr<ISharedImage> RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image)
{
if (imageFiles.count(locator))
return imageFiles.at(locator);
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)

View File

@ -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

View File

@ -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);
}
if (surf->format->palette)
SDL_SetSurfacePalette(surf, originalPalette);
}
const SDL_Palette * SDLImageShared::getPalette() const
{
if (originalPalette == nullptr)
throw std::runtime_error("Palette not found!");
return 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);
for (size_t i = 0; i < last; ++i)
// 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);
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 SDLImageBase::scaleFast(const Point & size)
void SDLImageIndexed::scaleFast(const Point & size)
{
image = image->scaleFast(size);
image = image->scaleFast(size, currentPalette);
}
void SDLImageRGB::scaleFast(const Point & size)
{
image = image->scaleFast(size, nullptr);
}
void SDLImageBase::exportBitmap(const boost::filesystem::path & path) const
@ -342,15 +448,27 @@ 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::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::playerColored(PlayerColor player)
{}
void SDLImageRGB::setFlagColor(PlayerColor player)
{}
void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{}

View File

@ -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;
};