1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Merge pull request #5306 from IvanSavenko/xbrz_foreground

[1.6.4?] Move xbrz upscaling from foreground thread to background
This commit is contained in:
Ivan Savenko 2025-01-25 17:23:46 +02:00 committed by GitHub
commit 722d68643f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1590 additions and 1312 deletions

View File

@ -88,6 +88,7 @@ set(vcmiclientcommon_SRCS
render/CBitmapHandler.cpp
render/CDefFile.cpp
render/Canvas.cpp
render/CanvasImage.cpp
render/ColorFilter.cpp
render/Colors.cpp
render/Graphics.cpp
@ -99,10 +100,11 @@ set(vcmiclientcommon_SRCS
renderSDL/CursorHardware.cpp
renderSDL/CursorSoftware.cpp
renderSDL/FontChain.cpp
renderSDL/ImageScaled.cpp
renderSDL/ScalableImage.cpp
renderSDL/RenderHandler.cpp
renderSDL/SDLImage.cpp
renderSDL/SDLImageLoader.cpp
renderSDL/SDLImageScaler.cpp
renderSDL/SDLRWwrapper.cpp
renderSDL/ScreenHandler.cpp
renderSDL/SDL_Extensions.cpp
@ -290,6 +292,7 @@ set(vcmiclientcommon_HEADERS
render/CBitmapHandler.h
render/CDefFile.h
render/Canvas.h
render/CanvasImage.h
render/ColorFilter.h
render/Colors.h
render/EFont.h
@ -307,10 +310,11 @@ set(vcmiclientcommon_HEADERS
renderSDL/CursorHardware.h
renderSDL/CursorSoftware.h
renderSDL/FontChain.h
renderSDL/ImageScaled.h
renderSDL/ScalableImage.h
renderSDL/RenderHandler.h
renderSDL/SDLImage.h
renderSDL/SDLImageLoader.h
renderSDL/SDLImageScaler.h
renderSDL/SDLRWwrapper.h
renderSDL/ScreenHandler.h
renderSDL/SDL_Extensions.h

View File

@ -1171,7 +1171,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
if(t)
{
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
image->scaleTo(Point(35, 23));
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
images.push_back(image);
}
}

View File

@ -22,7 +22,6 @@
#include "../render/Colors.h"
#include "../render/Canvas.h"
#include "../render/Graphics.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h"
@ -178,7 +177,7 @@ void CMinimap::mouseDragged(const Point & cursorPosition, const Point & lastUpda
void CMinimap::showAll(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), aiShield->pos);
CanvasClipRectGuard guard(to, aiShield->pos);
CIntObject::showAll(to);
if(minimap)

View File

@ -26,7 +26,6 @@
#include "../render/CAnimation.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/IRenderHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CursorHandler.h"
@ -857,7 +856,7 @@ void BattleFieldController::tick(uint32_t msPassed)
void BattleFieldController::show(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos);
CanvasClipRectGuard guard(to, pos);
renderBattlefield(to);

View File

@ -24,7 +24,6 @@
#include "../render/CAnimation.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../eventsSDL/InputHandler.h"
#include "../../CCallback.h"
@ -76,7 +75,7 @@ void BasicMapView::tick(uint32_t msPassed)
void BasicMapView::show(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos);
CanvasClipRectGuard guard(to, pos);
render(to, false);
controller->afterRender();
@ -84,7 +83,7 @@ void BasicMapView::show(Canvas & to)
void BasicMapView::showAll(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos);
CanvasClipRectGuard guard(to, pos);
render(to, true);
}

View File

@ -381,12 +381,12 @@ Point CVideoInstance::size()
return dimensions / GH.screenHandler().getScalingFactor();
}
void CVideoInstance::show(const Point & position, Canvas & canvas)
void CVideoInstance::show(const Point & position, SDL_Surface * to)
{
if(sws == nullptr)
throw std::runtime_error("No video to show!");
CSDL_Ext::blitSurface(surface, canvas.getInternalSurface(), position * GH.screenHandler().getScalingFactor());
CSDL_Ext::blitSurface(surface, to, position * GH.screenHandler().getScalingFactor());
}
double FFMpegStream::getCurrentFrameEndTime() const

View File

@ -98,7 +98,7 @@ public:
bool videoEnded() final;
Point size() final;
void show(const Point & position, Canvas & canvas) final;
void show(const Point & position, SDL_Surface * to) final;
void tick(uint32_t msPassed) final;
void activate() final;
void deactivate() final;

View File

@ -11,7 +11,7 @@
#include "../lib/filesystem/ResourcePath.h"
class Canvas;
struct SDL_Surface;
VCMI_LIB_NAMESPACE_BEGIN
class Point;
@ -30,7 +30,7 @@ public:
virtual Point size() = 0;
/// Displays current frame at specified position
virtual void show(const Point & position, Canvas & canvas) = 0;
virtual void show(const Point & position, SDL_Surface * to) = 0;
/// Advances video playback by specified duration
virtual void tick(uint32_t msPassed) = 0;

View File

@ -14,6 +14,7 @@
#include "../render/IImage.h"
#include "../render/IImageLoader.h"
#include "../render/Canvas.h"
#include "../render/CanvasImage.h"
#include "../render/ColorFilter.h"
#include "../render/IRenderHandler.h"
#include "../render/CAnimation.h"
@ -58,12 +59,13 @@ void AssetGenerator::createAdventureOptionsCleanBackground()
return;
ResourcePath savePath(filename, EResType::IMAGE);
auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"));
locator.scalingFactor = 1;
auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(575, 585), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
Canvas canvas = Canvas(Point(575, 585), CanvasScalingPolicy::IGNORE);
canvas.draw(img, Point(0, 0), Rect(0, 0, 575, 585));
canvas.draw(img, Point(54, 121), Rect(54, 123, 335, 1));
canvas.draw(img, Point(158, 84), Rect(156, 84, 2, 37));
@ -72,8 +74,6 @@ void AssetGenerator::createAdventureOptionsCleanBackground()
canvas.draw(img, Point(53, 567), Rect(53, 520, 339, 3));
canvas.draw(img, Point(53, 520), Rect(53, 264, 339, 47));
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
}
@ -88,11 +88,11 @@ void AssetGenerator::createBigSpellBook()
return;
ResourcePath savePath(filename, EResType::IMAGE);
auto locator = ImageLocator(ImagePath::builtin("SpelBack"));
locator.scalingFactor = 1;
auto locator = ImageLocator(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
// edges
canvas.draw(img, Point(0, 0), Rect(15, 38, 90, 45));
canvas.draw(img, Point(0, 460), Rect(15, 400, 90, 141));
@ -135,8 +135,6 @@ void AssetGenerator::createBigSpellBook()
canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45));
canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47));
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
}
@ -152,10 +150,9 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player)
ResourcePath savePath(filename, EResType::IMAGE);
auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"));
locator.scalingFactor = 1;
auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator);
// transform to make color of brown DIBOX.PCX texture match color of specified player
auto filterSettings = VLC->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"];
@ -199,10 +196,10 @@ void AssetGenerator::createCombatUnitNumberWindow()
!CResourceHandler::get("local")->createResource(savePathNegative.getOriginalName() + ".png"))
return;
auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"));
locator.scalingFactor = 1;
auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"), EImageBlitMode::OPAQUE);
locator.layer = EImageBlitMode::OPAQUE;
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator);
static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f );
static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f );
@ -233,12 +230,12 @@ void AssetGenerator::createCampaignBackground()
return;
ResourcePath savePath(filename, EResType::IMAGE);
auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"));
locator.scalingFactor = 1;
auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE);
canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600));
// left image
@ -263,13 +260,10 @@ void AssetGenerator::createCampaignBackground()
canvas.draw(img, Point(404, 414), Rect(313, 74, 197, 114));
// skull
auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC"));
locatorSkull.scalingFactor = 1;
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE);
auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull);
canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19));
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
}
@ -290,11 +284,11 @@ void AssetGenerator::createChroniclesCampaignImages()
continue;
ResourcePath savePath(filename, EResType::IMAGE);
auto locator = ImageLocator(imgPathBg);
locator.scalingFactor = 1;
auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
Canvas canvas = Canvas(Point(200, 116), CanvasScalingPolicy::IGNORE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
switch (i)
{
@ -323,9 +317,8 @@ void AssetGenerator::createChroniclesCampaignImages()
canvas.draw(img, Point(0, 0), Rect(268, 210, 200, 116));
//skull
auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"));
locatorSkull.scalingFactor = 1;
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE);
auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull);
canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22));
canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4));
canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4));
@ -334,8 +327,6 @@ void AssetGenerator::createChroniclesCampaignImages()
break;
}
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
}
}
@ -403,8 +394,7 @@ void AssetGenerator::createPaletteShiftedSprites()
return;
auto imgLoc = anim->getImageLocator(j, 0);
imgLoc.scalingFactor = 1;
auto img = GH.renderHandler().loadImage(imgLoc, EImageBlitMode::COLORKEY);
auto img = GH.renderHandler().loadImage(imgLoc);
for(int k = 0; k < paletteAnimations[i].size(); k++)
{
auto element = paletteAnimations[i][k];
@ -420,9 +410,9 @@ void AssetGenerator::createPaletteShiftedSprites()
}
}
Canvas canvas = Canvas(Point(32, 32), CanvasScalingPolicy::IGNORE);
auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2));
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
JsonNode node(JsonMap{

View File

@ -30,7 +30,7 @@ bool CAnimation::loadFrame(size_t frame, size_t group, bool verbose)
if(auto image = getImageImpl(frame, group, false))
return true;
std::shared_ptr<IImage> image = GH.renderHandler().loadImage(getImageLocator(frame, group), mode);
std::shared_ptr<IImage> image = GH.renderHandler().loadImage(getImageLocator(frame, group));
if(image)
{
@ -224,5 +224,5 @@ ImageLocator CAnimation::getImageLocator(size_t frame, size_t group) const
throw std::runtime_error("Frame " + std::to_string(frame) + " of group " + std::to_string(group) + " is missing from animation " + name.getOriginalName() );
}
return ImageLocator(name, frame, group);
return ImageLocator(name, frame, group, mode);
}

View File

@ -11,6 +11,7 @@
#include "Canvas.h"
#include "../gui/CGuiHandler.h"
#include "../media/IVideoPlayer.h"
#include "../render/IRenderHandler.h"
#include "../render/IScreenHandler.h"
#include "../renderSDL/SDL_Extensions.h"
@ -102,11 +103,21 @@ Canvas::~Canvas()
SDL_FreeSurface(surface);
}
void Canvas::draw(IVideoInstance & video, const Point & pos)
{
video.show(pos, surface);
}
void Canvas::draw(const IImage& image, const Point & pos)
{
image.draw(surface, transformPos(pos), nullptr, getScalingFactor());
}
void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos)
{
assert(image);
if (image)
image->draw(surface, transformPos(pos));
image->draw(surface, transformPos(pos), nullptr, getScalingFactor());
}
void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect)
@ -114,7 +125,7 @@ void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const
Rect realSourceRect = sourceRect * getScalingFactor();
assert(image);
if (image)
image->draw(surface, transformPos(pos), &realSourceRect);
image->draw(surface, transformPos(pos), &realSourceRect, getScalingFactor());
}
void Canvas::draw(const Canvas & image, const Point & pos)
@ -218,16 +229,22 @@ void Canvas::fillTexture(const std::shared_ptr<IImage>& image)
for (int y=0; y < surface->h; y+= imageArea.h)
{
for (int x=0; x < surface->w; x+= imageArea.w)
image->draw(surface, Point(renderArea.x + x * getScalingFactor(), renderArea.y + y * getScalingFactor()));
image->draw(surface, Point(renderArea.x + x * getScalingFactor(), renderArea.y + y * getScalingFactor()), nullptr, getScalingFactor());
}
}
SDL_Surface * Canvas::getInternalSurface()
{
return surface;
}
Rect Canvas::getRenderArea() const
{
return renderArea;
}
CanvasClipRectGuard::CanvasClipRectGuard(Canvas & canvas, const Rect & rect): surf(canvas.surface)
{
CSDL_Ext::getClipRect(surf, oldRect);
CSDL_Ext::setClipRect(surf, rect * GH.screenHandler().getScalingFactor());
}
CanvasClipRectGuard::~CanvasClipRectGuard()
{
CSDL_Ext::setClipRect(surf, oldRect);
}

View File

@ -15,6 +15,7 @@
struct SDL_Surface;
class IImage;
class IVideoInstance;
enum EFonts : int8_t;
enum class CanvasScalingPolicy
@ -27,6 +28,8 @@ enum class CanvasScalingPolicy
/// Class that represents surface for drawing on
class Canvas
{
friend class CanvasClipRectGuard;
/// Upscaler awareness. Must be first member for initialization
CanvasScalingPolicy scalingPolicy;
@ -72,6 +75,9 @@ public:
/// renders image onto this canvas at specified position
void draw(const std::shared_ptr<IImage>& image, const Point & pos);
void draw(const IImage& image, const Point & pos);
void draw(IVideoInstance & video, const Point & pos);
/// renders section of image bounded by sourceRect at specified position
void draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect);
@ -114,9 +120,16 @@ public:
int getScalingFactor() const;
/// Compatibility method. AVOID USAGE. To be removed once SDL abstraction layer is finished.
SDL_Surface * getInternalSurface();
/// get the render area
Rect getRenderArea() const;
};
class CanvasClipRectGuard : boost::noncopyable
{
SDL_Surface * surf;
Rect oldRect;
public:
CanvasClipRectGuard(Canvas & canvas, const Rect & rect);
~CanvasClipRectGuard();
};

View File

@ -0,0 +1,63 @@
/*
* CanvasImage.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CanvasImage.h"
#include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/SDLImageScaler.h"
#include <SDL_image.h>
#include <SDL_surface.h>
CanvasImage::CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy)
: surface(CSDL_Ext::newSurface(scalingPolicy == CanvasScalingPolicy::IGNORE ? size : (size * GH.screenHandler().getScalingFactor())))
, scalingPolicy(scalingPolicy)
{
}
void CanvasImage::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const
{
if(src)
CSDL_Ext::blitSurface(surface, *src, where, pos);
else
CSDL_Ext::blitSurface(surface, where, pos);
}
void CanvasImage::scaleTo(const Point & size, EScalingAlgorithm algorithm)
{
Point scaledSize = size * GH.screenHandler().getScalingFactor();
SDLImageScaler scaler(surface);
scaler.scaleSurface(scaledSize, algorithm);
SDL_FreeSurface(surface);
surface = scaler.acquireResultSurface();
}
void CanvasImage::exportBitmap(const boost::filesystem::path & path) const
{
IMG_SavePNG(surface, path.string().c_str());
}
Canvas CanvasImage::getCanvas()
{
return Canvas::createFromSurface(surface, scalingPolicy);
}
Rect CanvasImage::contentRect() const
{
return Rect(Point(0, 0), dimensions());
}
Point CanvasImage::dimensions() const
{
return {surface->w, surface->h};
}

View File

@ -0,0 +1,41 @@
/*
* CanvasImage.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "IImage.h"
#include "Canvas.h"
class CanvasImage : public IImage
{
public:
CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy);
Canvas getCanvas();
void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override;
void scaleTo(const Point & size, EScalingAlgorithm algorithm) override;
void exportBitmap(const boost::filesystem::path & path) const override;
Rect contentRect() const override;
Point dimensions() const override;
//no-op methods
bool isTransparent(const Point & coords) const override{ return false;};
void setAlpha(uint8_t value) override{};
void playerColored(const PlayerColor & player) override{};
void setOverlayColor(const ColorRGBA & color) override{};
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{};
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{};
private:
SDL_Surface * surface;
CanvasScalingPolicy scalingPolicy;
};

View File

@ -62,21 +62,28 @@ enum class EImageBlitMode : uint8_t
ONLY_OVERLAY,
};
enum class EScalingAlgorithm : int8_t
{
NEAREST,
BILINEAR,
XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image. Only for integer scaling
XBRZ_ALPHA // xbrz, image edges are considered to be transparent. Only for integer scaling
};
/// 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:
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src = nullptr) const = 0;
virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const = 0;
virtual void scaleTo(const Point & size) = 0;
virtual void scaleInteger(int factor) = 0;
virtual void scaleTo(const Point & size, EScalingAlgorithm algorithm) = 0;
virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
//Change palette to specific player
virtual void playerColored(PlayerColor player) = 0;
virtual void playerColored(const PlayerColor & player) = 0;
//test transparency of specific pixel
virtual bool isTransparent(const Point & coords) const = 0;
@ -92,13 +99,10 @@ public:
virtual void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0;
virtual void setAlpha(uint8_t value) = 0;
virtual void setBlitMode(EImageBlitMode mode) = 0;
//only indexed bitmaps with 7 special colors
virtual void setOverlayColor(const ColorRGBA & color) = 0;
virtual std::shared_ptr<const ISharedImage> getSharedImage() const = 0;
virtual ~IImage() = default;
};
@ -112,15 +116,20 @@ public:
virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0;
virtual bool isTransparent(const Point & coords) const = 0;
virtual Rect contentRect() const = 0;
virtual void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, 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;
[[nodiscard]] virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0;
/// Returns true if this image is still loading and can't be used
virtual bool isLoading() const = 0;
virtual ~ISharedImage() = default;
virtual const SDL_Palette * getPalette() const = 0;
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0;
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0;
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0;
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
virtual ~ISharedImage() = default;
};

View File

@ -20,7 +20,10 @@ struct SDL_Surface;
class IFont;
class IImage;
class CAnimation;
class CanvasImage;
class SDLImageShared;
enum class EImageBlitMode : uint8_t;
enum class CanvasScalingPolicy;
enum EFonts : int8_t;
class IRenderHandler : public boost::noncopyable
@ -32,13 +35,15 @@ public:
virtual void onLibraryLoadingFinished(const Services * services) = 0;
/// Loads image using given path
virtual std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) = 0;
virtual std::shared_ptr<IImage> loadImage(const ImageLocator & locator) = 0;
virtual std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) = 0;
virtual std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) = 0;
/// temporary compatibility method. Creates IImage from existing SDL_Surface
/// Surface will be shared, caller must still free it with SDL_FreeSurface
virtual std::shared_ptr<IImage> createImage(SDL_Surface * source) = 0;
/// Loads single upscaled image without auto-scaling support
virtual std::shared_ptr<SDLImageShared> loadScaledImage(const ImageLocator & locator) = 0;
/// Creates image which can be used as target for drawing on
virtual std::shared_ptr<CanvasImage> createImage(const Point & size, CanvasScalingPolicy scalingPolicy) = 0;
/// Loads animation using given path
virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0;

View File

@ -15,11 +15,10 @@
#include "../../lib/json/JsonNode.h"
ImageLocator::ImageLocator(const JsonNode & config)
SharedImageLocator::SharedImageLocator(const JsonNode & config, EImageBlitMode mode)
: defFrame(config["defFrame"].Integer())
, defGroup(config["defGroup"].Integer())
, verticalFlip(config["verticalFlip"].Bool())
, horizontalFlip(config["horizontalFlip"].Bool())
, layer(mode)
{
if(!config["file"].isNull())
image = ImagePath::fromJson(config["file"]);
@ -28,19 +27,28 @@ ImageLocator::ImageLocator(const JsonNode & config)
defFile = AnimationPath::fromJson(config["defFile"]);
}
ImageLocator::ImageLocator(const ImagePath & path)
SharedImageLocator::SharedImageLocator(const ImagePath & path, EImageBlitMode mode)
: image(path)
, layer(mode)
{
}
ImageLocator::ImageLocator(const AnimationPath & path, int frame, int group)
SharedImageLocator::SharedImageLocator(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
: defFile(path)
, defFrame(frame)
, defGroup(group)
, layer(mode)
{
}
bool ImageLocator::operator<(const ImageLocator & other) const
ImageLocator::ImageLocator(const JsonNode & config, EImageBlitMode mode)
: SharedImageLocator(config, mode)
, verticalFlip(config["verticalFlip"].Bool())
, horizontalFlip(config["horizontalFlip"].Bool())
{
}
bool SharedImageLocator::operator < (const SharedImageLocator & other) const
{
if(image != other.image)
return image < other.image;
@ -50,14 +58,6 @@ bool ImageLocator::operator<(const ImageLocator & other) const
return defGroup < other.defGroup;
if(defFrame != other.defFrame)
return defFrame < other.defFrame;
if(verticalFlip != other.verticalFlip)
return verticalFlip < other.verticalFlip;
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(layer != other.layer)
return layer < other.layer;
@ -68,70 +68,3 @@ bool ImageLocator::empty() const
{
return !image.has_value() && !defFile.has_value();
}
ImageLocator ImageLocator::copyFile() const
{
ImageLocator result;
result.scalingFactor = 1;
result.preScaledFactor = preScaledFactor;
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
}
std::string ImageLocator::toString() const
{
std::string result;
if (empty())
return "invalid";
if (image)
{
result += image->getOriginalName();
assert(!result.empty());
}
if (defFile)
{
result += defFile->getOriginalName();
assert(!result.empty());
result += "-" + std::to_string(defGroup);
result += "-" + std::to_string(defFrame);
}
if (verticalFlip)
result += "-vflip";
if (horizontalFlip)
result += "-hflip";
if (scalingFactor > 1)
result += "-scale" + std::to_string(scalingFactor);
if (playerColored.isValidPlayer())
result += "-player" + playerColored.toString();
if (layer == EImageBlitMode::ONLY_OVERLAY)
result += "-overlay";
if (layer == EImageBlitMode::ONLY_SHADOW)
result += "-shadow";
return result;
}

View File

@ -14,35 +14,32 @@
#include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/constants/EntityIdentifiers.h"
struct ImageLocator
struct SharedImageLocator
{
std::optional<ImagePath> image;
std::optional<AnimationPath> defFile;
int defFrame = -1;
int defGroup = -1;
EImageBlitMode layer = EImageBlitMode::OPAQUE;
PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; // FIXME: treat as identical to blue to avoid double-loading?
SharedImageLocator() = default;
SharedImageLocator(const AnimationPath & path, int frame, int group, EImageBlitMode layer);
SharedImageLocator(const JsonNode & config, EImageBlitMode layer);
SharedImageLocator(const ImagePath & path, EImageBlitMode layer);
bool operator < (const SharedImageLocator & other) const;
};
struct ImageLocator : SharedImageLocator
{
PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE;
bool verticalFlip = false;
bool horizontalFlip = false;
int8_t scalingFactor = 0; // 0 = auto / use default scaling
int8_t preScaledFactor = 1;
EImageBlitMode layer = EImageBlitMode::OPAQUE;
ImageLocator() = default;
ImageLocator(const AnimationPath & path, int frame, int group);
explicit ImageLocator(const JsonNode & config);
explicit ImageLocator(const ImagePath & path);
using SharedImageLocator::SharedImageLocator;
ImageLocator(const JsonNode & config, EImageBlitMode layer);
bool operator < (const ImageLocator & other) const;
bool empty() const;
ImageLocator copyFile() const;
ImageLocator copyFileTransform() const;
ImageLocator copyFileTransformScale() const;
// generates string representation of this image locator
// guaranteed to be a valid file path with no extension
// but may contain '/' if source file is in directory
std::string toString() const;
};

View File

@ -11,9 +11,12 @@
#include "CBitmapFont.h"
#include "SDL_Extensions.h"
#include "SDLImageScaler.h"
#include "../CGameInfo.h"
#include "../gui/CGuiHandler.h"
#include "../render/Colors.h"
#include "../render/IImage.h"
#include "../render/IScreenHandler.h"
#include "../../lib/CConfigHandler.h"
@ -206,9 +209,10 @@ CBitmapFont::CBitmapFont(const std::string & filename):
auto filterName = settings["video"]["fontUpscalingFilter"].String();
EScalingAlgorithm algorithm = filterNameToEnum.at(filterName);
auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm);
SDLImageScaler scaler(atlasImage);
scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm);
SDL_FreeSurface(atlasImage);
atlasImage = scaledSurface;
atlasImage = scaler.acquireResultSurface();
}
logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d",

View File

@ -12,6 +12,7 @@
#include "CursorHardware.h"
#include "SDL_Extensions.h"
#include "SDLImageScaler.h"
#include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h"
@ -59,8 +60,11 @@ void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivot
CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
image->draw(cursorSurface, Point(0,0));
auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y );
image->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor());
SDLImageScaler scaler(cursorSurface);
scaler.scaleSurface(cursorDimensionsScaled, EScalingAlgorithm::BILINEAR);
SDL_Surface * cursorSurfaceScaled = scaler.acquireResultSurface();
auto oldCursor = cursor;
cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);

View File

@ -65,7 +65,7 @@ void CursorSoftware::updateTexture()
CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
cursorImage->draw(cursorSurface, Point(0,0));
cursorImage->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor());
SDL_UpdateTexture(cursorTexture, nullptr, cursorSurface->pixels, cursorSurface->pitch);
needUpdate = false;
}

View File

@ -1,172 +0,0 @@
/*
* ImageScaled.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ImageScaled.h"
#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>
ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode)
: source(source)
, locator(inputLocator)
, colorMultiplier(Colors::WHITE_TRUE)
, alphaValue(SDL_ALPHA_OPAQUE)
, blitMode(mode)
{
prepareImages();
}
std::shared_ptr<const ISharedImage> ImageScaled::getSharedImage() const
{
return body;
}
void ImageScaled::scaleInteger(int factor)
{
assert(0);
}
void ImageScaled::scaleTo(const Point & size)
{
if (source)
source = source->scaleTo(size, nullptr);
if (body)
body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr);
}
void ImageScaled::exportBitmap(const boost::filesystem::path &path) const
{
source->exportBitmap(path, nullptr);
}
bool ImageScaled::isTransparent(const Point &coords) const
{
return source->isTransparent(coords);
}
Rect ImageScaled::contentRect() const
{
return source->contentRect();
}
Point ImageScaled::dimensions() const
{
return source->dimensions();
}
void ImageScaled::setAlpha(uint8_t value)
{
alphaValue = value;
}
void ImageScaled::setBlitMode(EImageBlitMode mode)
{
blitMode = mode;
}
void ImageScaled::draw(SDL_Surface *where, const Point &pos, const Rect *src) const
{
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::setOverlayColor(const ColorRGBA & color)
{
colorMultiplier = color;
}
void ImageScaled::playerColored(PlayerColor player)
{
playerColor = player;
prepareImages();
}
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::prepareImages()
{
switch(blitMode)
{
case EImageBlitMode::OPAQUE:
case EImageBlitMode::COLORKEY:
case EImageBlitMode::SIMPLE:
locator.layer = blitMode;
locator.playerColored = playerColor;
body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
break;
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
case EImageBlitMode::ONLY_BODY:
locator.layer = EImageBlitMode::ONLY_BODY;
locator.playerColored = playerColor;
body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
break;
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
locator.layer = EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY;
locator.playerColored = playerColor;
body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
break;
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::ONLY_OVERLAY:
body = nullptr;
break;
}
switch(blitMode)
{
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
locator.layer = EImageBlitMode::ONLY_SHADOW;
locator.playerColored = PlayerColor::CANNOT_DETERMINE;
shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
break;
default:
shadow = nullptr;
break;
}
switch(blitMode)
{
case EImageBlitMode::ONLY_OVERLAY:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
locator.layer = EImageBlitMode::ONLY_OVERLAY;
locator.playerColored = PlayerColor::CANNOT_DETERMINE;
overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
break;
default:
overlay = nullptr;
break;
}
}

View File

@ -1,66 +0,0 @@
/*
* ImageScaled.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../render/IImage.h"
#include "../render/IRenderHandler.h"
#include "../../lib/Color.h"
#include "../../lib/constants/EntityIdentifiers.h"
struct SDL_Palette;
class SDLImageShared;
// Upscaled image with several mechanisms to emulate H3 palette effects
class ImageScaled final : public IImage
{
private:
/// Original unscaled image
std::shared_ptr<const ISharedImage> source;
/// Upscaled shadow of our image, may be null
std::shared_ptr<const ISharedImage> shadow;
/// Upscaled main part of our image, may be null
std::shared_ptr<const ISharedImage> body;
/// Upscaled overlay (player color, selection highlight) of our image, may be null
std::shared_ptr<const ISharedImage> overlay;
ImageLocator locator;
ColorRGBA colorMultiplier;
PlayerColor playerColor = PlayerColor::CANNOT_DETERMINE;
uint8_t alphaValue;
EImageBlitMode blitMode;
void prepareImages();
public:
ImageScaled(const ImageLocator & locator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode);
void scaleInteger(int factor) override;
void scaleTo(const Point & size) override;
void exportBitmap(const boost::filesystem::path & path) const override;
bool isTransparent(const Point & coords) const override;
Rect contentRect() const override;
Point dimensions() const override;
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 setOverlayColor(const ColorRGBA & color) override;
void playerColored(PlayerColor player) override;
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
std::shared_ptr<const ISharedImage> getSharedImage() const override;
};

View File

@ -11,19 +11,22 @@
#include "RenderHandler.h"
#include "SDLImage.h"
#include "ImageScaled.h"
#include "ScalableImage.h"
#include "FontChain.h"
#include "../gui/CGuiHandler.h"
#include "../render/CAnimation.h"
#include "../render/CanvasImage.h"
#include "../render/CDefFile.h"
#include "../render/Colors.h"
#include "../render/ColorFilter.h"
#include "../render/IScreenHandler.h"
#include "../../lib/json/JsonUtils.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/VCMIDirs.h"
#include "../../lib/constants/StringConstants.h"
#include <vcmi/ArtifactService.h>
#include <vcmi/CreatureService.h>
@ -55,60 +58,7 @@ std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath &
return result;
}
std::optional<ResourcePath> RenderHandler::getPathForScaleFactor(const ResourcePath & path, const std::string & factor)
{
if(path.getType() == EResType::IMAGE)
{
auto p = ImagePath::builtin(path.getName());
if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("DATA" + factor + "X/"));
}
else
{
auto p = AnimationPath::builtin(path.getName());
auto pJson = p.toType<EResType::JSON>();
if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
if(CResourceHandler::get()->existsResource(pJson))
return std::optional<ResourcePath>(p);
if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
}
return std::nullopt;
}
std::pair<ResourcePath, int> RenderHandler::getScalePath(const ResourcePath & p)
{
auto path = p;
int scaleFactor = 1;
if(getScalingFactor() > 1)
{
std::vector<int> factorsToCheck = {getScalingFactor(), 4, 3, 2};
for(auto factorToCheck : factorsToCheck)
{
std::string name = boost::algorithm::to_upper_copy(p.getName());
boost::replace_all(name, "SPRITES/", std::string("SPRITES") + std::to_string(factorToCheck) + std::string("X/"));
boost::replace_all(name, "DATA/", std::string("DATA") + std::to_string(factorToCheck) + std::string("X/"));
ResourcePath scaledPath = ImagePath::builtin(name);
if(p.getType() != EResType::IMAGE)
scaledPath = AnimationPath::builtin(name);
auto tmpPath = getPathForScaleFactor(scaledPath, std::to_string(factorToCheck));
if(tmpPath)
{
path = *tmpPath;
scaleFactor = factorToCheck;
break;
}
}
}
return std::pair<ResourcePath, int>(path, scaleFactor);
};
void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config)
void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config, EImageBlitMode mode)
{
std::string basepath;
basepath = config["basepath"].String();
@ -128,7 +78,7 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c
JsonNode toAdd = frame;
JsonUtils::inherit(toAdd, base);
toAdd["file"].String() = basepath + frame.String();
source[groupID].emplace_back(toAdd);
source[groupID].emplace_back(toAdd, mode);
}
}
@ -149,15 +99,26 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c
if (toAdd.Struct().count("defFile"))
toAdd["defFile"].String() = basepath + node["defFile"].String();
source[group][frame] = ImageLocator(toAdd);
source[group][frame] = ImageLocator(toAdd, mode);
}
}
RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path)
RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode)
{
auto tmp = getScalePath(path);
auto animPath = AnimationPath::builtin(tmp.first.getName());
AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/");
static constexpr std::array scaledSpritesPath = {
"", // 0x
"SPRITES/",
"SPRITES2X/",
"SPRITES3X/",
"SPRITES4X/",
};
std::string pathString = path.getName();
if (boost::starts_with(pathString, "SPRITES/"))
pathString = pathString.substr(std::string("SPRITES/").length());
AnimationPath actualPath = AnimationPath::builtin(scaledSpritesPath.at(scalingFactor) + pathString);
auto it = animationLayouts.find(actualPath);
@ -184,15 +145,11 @@ RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const Anim
std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
stream->read(textData.get(), stream->getSize());
const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), animPath.getOriginalName());
const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), path.getOriginalName());
initFromJson(result, config);
initFromJson(result, config, mode);
}
for(auto & g : result)
for(auto & i : g.second)
i.preScaledFactor = tmp.second;
animationLayouts[actualPath] = result;
return animationLayouts[actualPath];
}
@ -202,209 +159,174 @@ int RenderHandler::getScalingFactor() const
return GH.screenHandler().getScalingFactor();
}
ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group)
ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode)
{
const auto & layout = getAnimationLayout(path);
const auto & layout = getAnimationLayout(path, scaling, mode);
if (!layout.count(group))
return ImageLocator(ImagePath::builtin("DEFAULT"));
return ImageLocator();
if (frame >= layout.at(group).size())
return ImageLocator(ImagePath::builtin("DEFAULT"));
return ImageLocator();
const auto & locator = layout.at(group).at(frame);
if (locator.image || locator.defFile)
return locator;
return ImageLocator(path, frame, group);
return ImageLocator(path, frame, group, mode);
}
std::shared_ptr<const ISharedImage> RenderHandler::loadImageImpl(const ImageLocator & locator)
std::shared_ptr<ScalableImageShared> 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);
auto sdlImage = loadImageFromFileUncached(locator);
auto scaledImage = std::make_shared<ScalableImageShared>(locator, sdlImage);
storeCachedImage(locator, scaledImage);
return scaledImage;
}
std::shared_ptr<const ISharedImage> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
std::shared_ptr<SDLImageShared> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
{
if(locator.image)
{
// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load
return std::make_shared<SDLImageShared>(*locator.image, locator.preScaledFactor);
return std::make_shared<SDLImageShared>(*locator.image);
}
if(locator.defFile)
{
auto defFile = getAnimationFile(*locator.defFile);
int preScaledFactor = locator.preScaledFactor;
if(!defFile) // no prescale for this frame
{
auto tmpPath = (*locator.defFile).getName();
boost::algorithm::replace_all(tmpPath, "SPRITES2X/", "SPRITES/");
boost::algorithm::replace_all(tmpPath, "SPRITES3X/", "SPRITES/");
boost::algorithm::replace_all(tmpPath, "SPRITES4X/", "SPRITES/");
preScaledFactor = 1;
defFile = getAnimationFile(AnimationPath::builtin(tmpPath));
}
if(defFile->hasFrame(locator.defFrame, locator.defGroup))
return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup, preScaledFactor);
return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup);
else
{
logGlobal->error("Frame %d in group %d not found in file: %s",
locator.defFrame, locator.defGroup, locator.defFile->getName().c_str());
return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"), locator.preScaledFactor);
return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"));
}
}
throw std::runtime_error("Invalid image locator received!");
}
void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image)
void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<ScalableImageShared> image)
{
imageFiles[locator] = image;
#if 0
const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "imageCache" / (locator.toString() + ".png");
boost::filesystem::path outDir = outPath;
outDir.remove_filename();
boost::filesystem::create_directories(outDir);
image->exportBitmap(outPath , nullptr);
#endif
}
std::shared_ptr<const ISharedImage> RenderHandler::loadImageFromFile(const ImageLocator & locator)
std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocator & locator)
{
if (imageFiles.count(locator))
return imageFiles.at(locator);
static constexpr std::array scaledDataPath = {
"", // 0x
"DATA/",
"DATA2X/",
"DATA3X/",
"DATA4X/",
};
auto result = loadImageFromFileUncached(locator);
storeCachedImage(locator, result);
return result;
static constexpr std::array scaledSpritesPath = {
"", // 0x
"SPRITES/",
"SPRITES2X/",
"SPRITES3X/",
"SPRITES4X/",
};
ImagePath pathToLoad;
if(locator.defFile)
{
auto remappedLocator = getLocatorForAnimationFrame(*locator.defFile, locator.defFrame, locator.defGroup, locator.scalingFactor, locator.layer);
// we expect that .def's are only used for 1x data, upscaled assets should use standalone images
if (!remappedLocator.image)
return nullptr;
pathToLoad = *remappedLocator.image;
}
if(locator.image)
pathToLoad = *locator.image;
if (pathToLoad.empty())
return nullptr;
std::string imagePathString = pathToLoad.getName();
if(locator.layer == EImageBlitMode::ONLY_OVERLAY)
imagePathString += "-OVERLAY";
if(locator.layer == EImageBlitMode::ONLY_SHADOW)
imagePathString += "-SHADOW";
if(locator.playerColored.isValidPlayer())
imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]);
if(locator.playerColored == PlayerColor::NEUTRAL)
imagePathString += "-NEUTRAL";
auto imagePath = ImagePath::builtin(imagePathString);
auto imagePathSprites = ImagePath::builtin(imagePathString).addPrefix(scaledSpritesPath.at(locator.scalingFactor));
auto imagePathData = ImagePath::builtin(imagePathString).addPrefix(scaledDataPath.at(locator.scalingFactor));
if(CResourceHandler::get()->existsResource(imagePathSprites))
return std::make_shared<SDLImageShared>(imagePathSprites);
if(CResourceHandler::get()->existsResource(imagePathData))
return std::make_shared<SDLImageShared>(imagePathData);
if(CResourceHandler::get()->existsResource(imagePath))
return std::make_shared<SDLImageShared>(imagePath);
return nullptr;
}
std::shared_ptr<const ISharedImage> RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image)
{
if (imageFiles.count(locator))
return imageFiles.at(locator);
auto result = image;
if (locator.verticalFlip)
result = result->verticalFlip();
if (locator.horizontalFlip)
result = result->horizontalFlip();
storeCachedImage(locator, result);
return result;
}
std::shared_ptr<const ISharedImage> RenderHandler::scaleImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image)
{
if (imageFiles.count(locator))
return imageFiles.at(locator);
auto handle = image->createImageReference(locator.layer);
assert(locator.scalingFactor != 1); // should be filtered-out before
if (locator.playerColored != PlayerColor::CANNOT_DETERMINE)
handle->playerColored(locator.playerColored);
handle->scaleInteger(locator.scalingFactor);
auto result = handle->getSharedImage();
storeCachedImage(locator, result);
return result;
}
std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator)
{
ImageLocator adjustedLocator = locator;
if(adjustedLocator.image)
{
std::string imgPath = (*adjustedLocator.image).getName();
if(adjustedLocator.layer == EImageBlitMode::ONLY_OVERLAY)
imgPath += "-OVERLAY";
if(adjustedLocator.layer == EImageBlitMode::ONLY_SHADOW)
imgPath += "-SHADOW";
if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) ||
CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("DATA/")) ||
CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("SPRITES/")))
adjustedLocator.image = ImagePath::builtin(imgPath);
}
if(adjustedLocator.defFile && adjustedLocator.scalingFactor == 0)
{
auto tmp = getScalePath(*adjustedLocator.defFile);
adjustedLocator.defFile = AnimationPath::builtin(tmp.first.getName());
adjustedLocator.preScaledFactor = tmp.second;
}
if(adjustedLocator.image && adjustedLocator.scalingFactor == 0)
{
auto tmp = getScalePath(*adjustedLocator.image);
adjustedLocator.image = ImagePath::builtin(tmp.first.getName());
adjustedLocator.preScaledFactor = tmp.second;
}
if (adjustedLocator.scalingFactor == 0 && getScalingFactor() != 1 )
{
auto unscaledLocator = adjustedLocator;
auto scaledLocator = adjustedLocator;
unscaledLocator.scalingFactor = 1;
scaledLocator.scalingFactor = getScalingFactor();
auto unscaledImage = loadImageImpl(unscaledLocator);
return std::make_shared<ImageScaled>(scaledLocator, unscaledImage, mode);
}
std::shared_ptr<ScalableImageInstance> result;
if (adjustedLocator.scalingFactor == 0)
{
auto scaledLocator = adjustedLocator;
scaledLocator.scalingFactor = getScalingFactor();
return loadImageImpl(scaledLocator)->createImageReference(mode);
result = loadImageImpl(scaledLocator)->createImageReference();
}
else
return loadImageImpl(adjustedLocator)->createImageReference(mode);
result = loadImageImpl(adjustedLocator)->createImageReference();
if (locator.horizontalFlip)
result->horizontalFlip();
if (locator.verticalFlip)
result->verticalFlip();
return result;
}
std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
{
auto tmp = getScalePath(path);
ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group);
locator.preScaledFactor = tmp.second;
return loadImage(locator, mode);
ImageLocator locator = getLocatorForAnimationFrame(path, frame, group, 1, mode);
if (!locator.empty())
return loadImage(locator);
else
return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode));
}
std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
{
ImageLocator locator(path);
return loadImage(locator, mode);
ImageLocator locator(path, mode);
return loadImage(locator);
}
std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
std::shared_ptr<CanvasImage> RenderHandler::createImage(const Point & size, CanvasScalingPolicy scalingPolicy)
{
return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::SIMPLE);
return std::make_shared<CanvasImage>(size, scalingPolicy);
}
std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)
{
return std::make_shared<CAnimation>(path, getAnimationLayout(path), mode);
return std::make_shared<CAnimation>(path, getAnimationLayout(path, 1, mode), mode);
}
void RenderHandler::addImageListEntries(const EntityService * service)
@ -416,7 +338,7 @@ void RenderHandler::addImageListEntries(const EntityService * service)
if (imageName.empty())
return;
auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName));
auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::SIMPLE);
JsonNode entry;
entry["file"].String() = imageName;
@ -424,7 +346,7 @@ void RenderHandler::addImageListEntries(const EntityService * service)
if (index >= layout[group].size())
layout[group].resize(index + 1);
layout[group][index] = ImageLocator(entry);
layout[group][index] = ImageLocator(entry, EImageBlitMode::SIMPLE);
});
});
}

View File

@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_END
class CDefFile;
class SDLImageShared;
class ISharedImage;
class ScalableImageShared;
class RenderHandler : public IRenderHandler
{
@ -25,28 +25,22 @@ class RenderHandler : public IRenderHandler
std::map<AnimationPath, std::shared_ptr<CDefFile>> animationFiles;
std::map<AnimationPath, AnimationLayoutMap> animationLayouts;
std::map<ImageLocator, std::shared_ptr<const ISharedImage>> imageFiles;
std::map<SharedImageLocator, std::shared_ptr<ScalableImageShared>> imageFiles;
std::map<EFonts, std::shared_ptr<const IFont>> fonts;
std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
std::optional<ResourcePath> getPathForScaleFactor(const ResourcePath & path, const std::string & factor);
std::pair<ResourcePath, int> getScalePath(const ResourcePath & p);
AnimationLayoutMap & getAnimationLayout(const AnimationPath & path);
void initFromJson(AnimationLayoutMap & layout, const JsonNode & config);
AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode);
void initFromJson(AnimationLayoutMap & layout, const JsonNode & config, EImageBlitMode mode);
void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
void addImageListEntries(const EntityService * service);
void storeCachedImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image);
void storeCachedImage(const ImageLocator & locator, std::shared_ptr<ScalableImageShared> image);
std::shared_ptr<const ISharedImage> loadImageImpl(const ImageLocator & config);
std::shared_ptr<ScalableImageShared> loadImageImpl(const ImageLocator & config);
std::shared_ptr<const ISharedImage> loadImageFromFileUncached(const ImageLocator & locator);
std::shared_ptr<const ISharedImage> loadImageFromFile(const ImageLocator & locator);
std::shared_ptr<SDLImageShared> loadImageFromFileUncached(const ImageLocator & locator);
std::shared_ptr<const ISharedImage> transformImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image);
std::shared_ptr<const ISharedImage> scaleImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image);
ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group);
ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode);
int getScalingFactor() const;
@ -55,13 +49,15 @@ public:
// IRenderHandler implementation
void onLibraryLoadingFinished(const Services * services) override;
std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) override;
std::shared_ptr<IImage> loadImage(const ImageLocator & locator) override;
std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) override;
std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) override;
std::shared_ptr<SDLImageShared> loadScaledImage(const ImageLocator & locator) override;
std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) override;
std::shared_ptr<IImage> createImage(SDL_Surface * source) override;
std::shared_ptr<CanvasImage> createImage(const Point & size, CanvasScalingPolicy scalingPolicy) override;
/// Returns font with specified identifer
std::shared_ptr<const IFont> loadFont(EFonts font) override;

View File

@ -11,76 +11,24 @@
#include "SDLImage.h"
#include "SDLImageLoader.h"
#include "SDLImageScaler.h"
#include "SDL_Extensions.h"
#include "../render/ColorFilter.h"
#include "../render/Colors.h"
#include "../render/CBitmapHandler.h"
#include "../render/CDefFile.h"
#include "../render/Graphics.h"
#include "../xBRZ/xbrz.h"
#include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h"
#include <tbb/parallel_for.h>
#include <SDL_surface.h>
#include <tbb/task_arena.h>
#include <SDL_image.h>
#include <SDL_surface.h>
#include <SDL_version.h>
class SDLImageLoader;
//First 8 colors in def palette used for transparency
static constexpr std::array<SDL_Color, 8> sourcePalette = {{
{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 constexpr std::array<ColorRGBA, 8> targetPalette = {{
{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),
static_cast<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;
@ -91,12 +39,11 @@ int IImage::height() const
return dimensions().y;
}
SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int preScaleFactor)
SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
originalPalette(nullptr)
{
SDLImageLoader loader(this);
data->loadFrame(frame, group, loader);
@ -104,12 +51,11 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group
savePalette();
}
SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor)
SDLImageShared::SDLImageShared(SDL_Surface * from)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
originalPalette(nullptr)
{
surf = from;
if (surf == nullptr)
@ -122,12 +68,11 @@ SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor)
fullSize.y = surf->h;
}
SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
SDLImageShared::SDLImageShared(const ImagePath & filename)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
originalPalette(nullptr)
{
surf = BitmapHandler::loadBitmap(filename);
@ -146,9 +91,70 @@ SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
}
}
void SDLImageShared::scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaleTo, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const
{
assert(upscalingInProgress == false);
if (!surf)
return;
Rect sourceRect(0, 0, surf->w, surf->h);
Point destShift(0, 0);
Point destScale = Point(surf->w, surf->h) * scaleTo / dimensions();
Point marginsScaled = margins * scaleTo / dimensions();
if(src)
{
Rect srcUnscaled(Point(src->topLeft() * dimensions() / scaleTo), Point(src->dimensions() * dimensions() / scaleTo));
if(srcUnscaled.x < margins.x)
destShift.x += marginsScaled.x - src->x;
if(srcUnscaled.y < margins.y)
destShift.y += marginsScaled.y - src->y;
sourceRect = Rect(srcUnscaled).intersect(Rect(margins.x, margins.y, surf->w, surf->h));
destScale.x = std::min(destScale.x, sourceRect.w * scaleTo.x / dimensions().x);
destScale.y = std::min(destScale.y, sourceRect.h * scaleTo.y / dimensions().y);
sourceRect -= margins;
}
else
destShift = marginsScaled;
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))
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
else
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, palette);
SDL_Rect srcRect = CSDL_Ext::toSDL(sourceRect);
SDL_Rect dstRect = CSDL_Ext::toSDL(Rect(destShift, destScale));
if (sourceRect.dimensions() * scaleTo / dimensions() != destScale)
logGlobal->info("???");
SDL_Surface * tempSurface = SDL_ConvertSurface(surf, where->format, 0);
int result = SDL_BlitScaled(tempSurface, &srcRect, where, &dstRect);
SDL_FreeSurface(tempSurface);
if (result != 0)
logGlobal->error("SDL_BlitScaled failed! %s", SDL_GetError());
if (surf->format->palette)
SDL_SetSurfacePalette(surf, originalPalette);
}
void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const
{
assert(upscalingInProgress == false);
if (!surf)
return;
@ -199,89 +205,23 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin
void SDLImageShared::optimizeSurface()
{
assert(upscalingInProgress == false);
if (!surf)
return;
int left = surf->w;
int top = surf->h;
int right = 0;
int bottom = 0;
SDLImageOptimizer optimizer(surf, Rect(margins, fullSize));
// locate fully-transparent area around image
// H3 hadles this on format level, but mods or images scaled in runtime do not
if (surf->format->palette)
{
for (int y = 0; y < surf->h; ++y)
{
const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch;
for (int x = 0; x < surf->w; ++x)
{
if (row[x] != 0)
{
// opaque or can be opaque (e.g. disabled shadow)
top = std::min(top, y);
left = std::min(left, x);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
}
}
}
else
{
for (int y = 0; y < surf->h; ++y)
{
for (int x = 0; x < surf->w; ++x)
{
ColorRGBA color;
SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a);
optimizer.optimizeSurface(surf);
SDL_FreeSurface(surf);
if (color.a != SDL_ALPHA_TRANSPARENT)
{
// opaque
top = std::min(top, y);
left = std::min(left, x);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
}
}
}
if (left == surf->w)
{
// empty image - simply delete it
SDL_FreeSurface(surf);
surf = nullptr;
return;
}
if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1)
{
// non-zero border found
Rect newDimensions(left, top, right - left + 1, bottom - top + 1);
SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions);
auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), surf);
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
if (SDL_HasColorKey(surf))
{
uint32_t colorKey;
SDL_GetColorKey(surf, &colorKey);
SDL_SetColorKey(newSurface, SDL_TRUE, colorKey);
}
SDL_FreeSurface(surf);
surf = newSurface;
margins.x += left;
margins.y += top;
}
surf = optimizer.acquireResultSurface();
margins = optimizer.getResultDimensions().topLeft();
fullSize = optimizer.getResultDimensions().dimensions();
}
std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const
{
assert(upscalingInProgress == false);
if (factor <= 0)
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
@ -291,47 +231,58 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL
if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, palette);
SDL_Surface * scaled = nullptr;
if(preScaleFactor == factor)
return shared_from_this();
else if(preScaleFactor == 1)
{
// dump heuristics to differentiate tileable UI elements from map object / combat assets
if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE);
else
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA);
}
// simple heuristics to differentiate tileable UI elements from map object / combat assets
EScalingAlgorithm algorithm;
if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
algorithm = EScalingAlgorithm::XBRZ_OPAQUE;
else
scaled = CSDL_Ext::scaleSurface(surf, (int) round((float)surf->w * factor / preScaleFactor), (int) round((float)surf->h * factor / preScaleFactor));
algorithm = EScalingAlgorithm::XBRZ_ALPHA;
auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor);
ret->fullSize.x = fullSize.x * factor;
ret->fullSize.y = fullSize.y * factor;
ret->margins.x = (int) round((float)margins.x * factor / preScaleFactor);
ret->margins.y = (int) round((float)margins.y * factor / preScaleFactor);
ret->optimizeSurface();
// erase our own reference
SDL_FreeSurface(scaled);
auto result = std::make_shared<SDLImageShared>(this, factor, algorithm);
if (surf->format->palette)
SDL_SetSurfacePalette(surf, originalPalette);
return ret;
return result;
}
SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm)
{
static tbb::task_arena upscalingArena;
upscalingInProgress = true;
auto scaler = std::make_shared<SDLImageScaler>(from->surf, Rect(from->margins, from->fullSize));
const auto & scalingTask = [this, algorithm, scaler]()
{
scaler->scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm);
surf = scaler->acquireResultSurface();
fullSize = scaler->getResultDimensions().dimensions();
margins = scaler->getResultDimensions().topLeft();
upscalingInProgress = false;
};
upscalingArena.enqueue(scalingTask);
}
bool SDLImageShared::isLoading() const
{
return upscalingInProgress;
}
std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
{
float scaleX = static_cast<float>(size.x) / fullSize.x;
float scaleY = static_cast<float>(size.y) / fullSize.y;
assert(upscalingInProgress == false);
if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, palette);
auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY));
SDLImageScaler scaler(surf, Rect(margins, fullSize));
scaler.scaleSurface(size, EScalingAlgorithm::XBRZ_ALPHA);
auto scaled = scaler.acquireResultSurface();
if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point
CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]);
@ -340,13 +291,9 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size,
else
CSDL_Ext::setDefaultColorKey(scaled);//just in case
auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor);
ret->fullSize.x = (int) round((float)fullSize.x * scaleX);
ret->fullSize.y = (int) round((float)fullSize.y * scaleY);
ret->margins.x = (int) round((float)margins.x * scaleX);
ret->margins.y = (int) round((float)margins.y * scaleY);
auto ret = std::make_shared<SDLImageShared>(scaled);
ret->fullSize = scaler.getResultDimensions().dimensions();
ret->margins = scaler.getResultDimensions().topLeft();
// erase our own reference
SDL_FreeSurface(scaled);
@ -359,6 +306,7 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size,
void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const
{
assert(upscalingInProgress == false);
if (!surf)
return;
@ -369,13 +317,9 @@ void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palet
SDL_SetSurfacePalette(surf, originalPalette);
}
void SDLImageIndexed::playerColored(PlayerColor player)
{
graphics->setPlayerPalette(currentPalette, player);
}
bool SDLImageShared::isTransparent(const Point & coords) const
{
assert(upscalingInProgress == false);
if (surf)
return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y);
else
@ -384,31 +328,34 @@ bool SDLImageShared::isTransparent(const Point & coords) const
Rect SDLImageShared::contentRect() const
{
auto tmpMargins = margins / preScaleFactor;
auto tmpSize = Point(surf->w, surf->h) / preScaleFactor;
assert(upscalingInProgress == false);
auto tmpMargins = margins;
auto tmpSize = Point(surf->w, surf->h);
return Rect(tmpMargins, tmpSize);
}
const SDL_Palette * SDLImageShared::getPalette() const
{
assert(upscalingInProgress == false);
if (!surf)
return nullptr;
return surf->format->palette;
}
Point SDLImageShared::dimensions() const
{
return fullSize / preScaleFactor;
}
std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode) const
{
if (surf && surf->format->palette)
return std::make_shared<SDLImageIndexed>(shared_from_this(), originalPalette, mode);
else
return std::make_shared<SDLImageRGB>(shared_from_this(), mode);
assert(upscalingInProgress == false);
return fullSize;
}
std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const
{
assert(upscalingInProgress == false);
if (!surf)
return shared_from_this();
SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
auto ret = std::make_shared<SDLImageShared>(flipped);
ret->fullSize = fullSize;
ret->margins.x = margins.x;
ret->margins.y = fullSize.y - surf->h - margins.y;
@ -419,11 +366,12 @@ std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const
std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
{
assert(upscalingInProgress == false);
if (!surf)
return shared_from_this();
SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
auto ret = std::make_shared<SDLImageShared>(flipped);
ret->fullSize = fullSize;
ret->margins.x = fullSize.x - surf->w - margins.x;
ret->margins.y = margins.y;
@ -435,6 +383,7 @@ std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
// Keep the original palette, in order to do color switching operation
void SDLImageShared::savePalette()
{
assert(upscalingInProgress == false);
// For some images that don't have palette, skip this
if(surf->format->palette == nullptr)
return;
@ -445,219 +394,8 @@ void SDLImageShared::savePalette()
SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors);
}
void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{
std::vector<SDL_Color> shifterColors(colorsToMove);
for(uint32_t i=0; i<colorsToMove; ++i)
shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
SDL_SetPaletteColors(currentPalette, shifterColors.data(), firstColorID, colorsToMove);
}
void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
{
// If shadow is enabled, following colors must be skipped unconditionally
if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY)
colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
// 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;
currentPalette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i])));
}
}
SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * originalPalette, EImageBlitMode mode)
:SDLImageBase::SDLImageBase(image, mode)
,originalPalette(originalPalette)
{
currentPalette = SDL_AllocPalette(originalPalette->ncolors);
SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
preparePalette();
}
SDLImageIndexed::~SDLImageIndexed()
{
SDL_FreePalette(currentPalette);
}
void SDLImageIndexed::setShadowTransparency(float factor)
{
ColorRGBA shadow50(0, 0, 0, 128 * factor);
ColorRGBA shadow25(0, 0, 0, 64 * factor);
std::array<SDL_Color, 5> colorsSDL = {
originalPalette->colors[0],
originalPalette->colors[1],
originalPalette->colors[2],
originalPalette->colors[3],
originalPalette->colors[4]
};
// seems to be used unconditionally
colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY);
colorsSDL[1] = CSDL_Ext::toSDL(shadow25);
colorsSDL[4] = CSDL_Ext::toSDL(shadow50);
// seems to be used only if color matches
if (colorsSimilar(originalPalette->colors[2], sourcePalette[2]))
colorsSDL[2] = CSDL_Ext::toSDL(shadow25);
if (colorsSimilar(originalPalette->colors[3], sourcePalette[3]))
colorsSDL[3] = CSDL_Ext::toSDL(shadow50);
SDL_SetPaletteColors(currentPalette, colorsSDL.data(), 0, colorsSDL.size());
}
void SDLImageIndexed::setOverlayColor(const ColorRGBA & color)
{
currentPalette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color));
for (int i : {6,7})
{
if (colorsSimilar(originalPalette->colors[i], sourcePalette[i]))
currentPalette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
}
}
void SDLImageIndexed::preparePalette()
{
switch(blitMode)
{
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::ONLY_OVERLAY:
adjustPalette(ColorFilter::genAlphaShifter(0), 0);
break;
}
switch(blitMode)
{
case EImageBlitMode::SIMPLE:
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
setShadowTransparency(1.0);
break;
case EImageBlitMode::ONLY_BODY:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
case EImageBlitMode::ONLY_OVERLAY:
setShadowTransparency(0.0);
break;
}
switch(blitMode)
{
case EImageBlitMode::ONLY_OVERLAY:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
setOverlayColor(Colors::WHITE_TRUE);
break;
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::ONLY_BODY:
setOverlayColor(Colors::TRANSPARENCY);
break;
}
}
SDLImageShared::~SDLImageShared()
{
SDL_FreeSurface(surf);
SDL_FreePalette(originalPalette);
}
SDLImageBase::SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode)
:image(image)
, alphaValue(SDL_ALPHA_OPAQUE)
, blitMode(mode)
{}
std::shared_ptr<const 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, Colors::WHITE_TRUE, alphaValue, blitMode);
}
void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
{
image->draw(where, currentPalette, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
}
void SDLImageIndexed::exportBitmap(const boost::filesystem::path & path) const
{
image->exportBitmap(path, currentPalette);
}
void SDLImageIndexed::scaleTo(const Point & size)
{
image = image->scaleTo(size, currentPalette);
}
void SDLImageRGB::scaleTo(const Point & size)
{
image = image->scaleTo(size, nullptr);
}
void SDLImageIndexed::scaleInteger(int factor)
{
image = image->scaleInteger(factor, currentPalette, blitMode);
}
void SDLImageRGB::scaleInteger(int factor)
{
image = image->scaleInteger(factor, nullptr, blitMode);
}
void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const
{
image->exportBitmap(path, nullptr);
}
bool SDLImageBase::isTransparent(const Point & coords) const
{
return image->isTransparent(coords);
}
Rect SDLImageBase::contentRect() const
{
return image->contentRect();
}
Point SDLImageBase::dimensions() const
{
return image->dimensions();
}
void SDLImageBase::setAlpha(uint8_t value)
{
alphaValue = value;
}
void SDLImageBase::setBlitMode(EImageBlitMode mode)
{
blitMode = mode;
}
void SDLImageRGB::setOverlayColor(const ColorRGBA & color)
{}
void SDLImageRGB::playerColored(PlayerColor player)
{}
void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{}
void SDLImageRGB::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
{}

View File

@ -27,16 +27,15 @@ struct SDL_Palette;
class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this<SDLImageShared>, boost::noncopyable
{
//Surface without empty borders
SDL_Surface * surf;
SDL_Surface * surf = nullptr;
SDL_Palette * originalPalette;
SDL_Palette * originalPalette = nullptr;
//size of left and top borders
Point margins;
//total size including borders
Point fullSize;
//pre scaled image
int preScaleFactor;
std::atomic_bool upscalingInProgress = false;
// Keep the original palette, in order to do color switching operation
void savePalette();
@ -45,20 +44,27 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
public:
//Load image from def file
SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int preScaleFactor=1);
SDLImageShared(const CDefFile *data, size_t frame, size_t group=0);
//Load from bitmap file
SDLImageShared(const ImagePath & filename, int preScaleFactor=1);
SDLImageShared(const ImagePath & filename);
//Create using existing surface, extraRef will increase refcount on SDL_Surface
SDLImageShared(SDL_Surface * from, int preScaleFactor=1);
SDLImageShared(SDL_Surface * from);
/// Creates image at specified scaling factor from source image
SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm);
~SDLImageShared();
void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, 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, SDL_Palette * palette) const override;
Point dimensions() const override;
bool isTransparent(const Point & coords) const override;
Rect contentRect() const override;
[[nodiscard]] std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override;
bool isLoading() const override;
const SDL_Palette * getPalette() const override;
[[nodiscard]] std::shared_ptr<const ISharedImage> horizontalFlip() const override;
[[nodiscard]] std::shared_ptr<const ISharedImage> verticalFlip() const override;
[[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
@ -66,58 +72,3 @@ public:
friend class SDLImageLoader;
};
class SDLImageBase : public IImage, boost::noncopyable
{
protected:
std::shared_ptr<const ISharedImage> image;
uint8_t alphaValue;
EImageBlitMode blitMode;
public:
SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode);
bool isTransparent(const Point & coords) const override;
Rect contentRect() const override;
Point dimensions() const override;
void setAlpha(uint8_t value) override;
void setBlitMode(EImageBlitMode mode) override;
std::shared_ptr<const ISharedImage> getSharedImage() const override;
};
class SDLImageIndexed final : public SDLImageBase
{
SDL_Palette * currentPalette = nullptr;
SDL_Palette * originalPalette = nullptr;
void setShadowTransparency(float factor);
void preparePalette();
public:
SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
~SDLImageIndexed();
void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
void setOverlayColor(const ColorRGBA & color) override;
void playerColored(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 scaleInteger(int factor) override;
void scaleTo(const Point & size) override;
void exportBitmap(const boost::filesystem::path & path) const override;
};
class SDLImageRGB final : public SDLImageBase
{
public:
using SDLImageBase::SDLImageBase;
void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
void setOverlayColor(const ColorRGBA & color) override;
void playerColored(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 scaleInteger(int factor) override;
void scaleTo(const Point & size) override;
void exportBitmap(const boost::filesystem::path & path) const override;
};

View File

@ -0,0 +1,233 @@
/*
* SDLImageScaler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "SDLImageScaler.h"
#include "SDL_Extensions.h"
#include "../CMT.h"
#include "../xBRZ/xbrz.h"
#include <tbb/parallel_for.h>
#include <SDL_surface.h>
SDLImageOptimizer::SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions)
: surf(surf)
, virtualDimensions(virtualDimensions)
{
}
void SDLImageOptimizer::optimizeSurface(SDL_Surface * formatSourceSurface)
{
if (!surf)
return;
int left = surf->w;
int top = surf->h;
int right = 0;
int bottom = 0;
// locate fully-transparent area around image
// H3 hadles this on format level, but mods or images scaled in runtime do not
if (surf->format->palette)
{
for (int y = 0; y < surf->h; ++y)
{
const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch;
for (int x = 0; x < surf->w; ++x)
{
if (row[x] != 0)
{
// opaque or can be opaque (e.g. disabled shadow)
top = std::min(top, y);
left = std::min(left, x);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
}
}
}
else
{
for (int y = 0; y < surf->h; ++y)
{
for (int x = 0; x < surf->w; ++x)
{
ColorRGBA color;
SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a);
if (color.a != SDL_ALPHA_TRANSPARENT)
{
// opaque
top = std::min(top, y);
left = std::min(left, x);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
}
}
}
// empty image
if (left == surf->w)
return;
if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1)
{
// non-zero border found
Rect newDimensions(left, top, right - left + 1, bottom - top + 1);
SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions);
auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), formatSourceSurface);
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
if (SDL_HasColorKey(surf))
{
uint32_t colorKey;
SDL_GetColorKey(surf, &colorKey);
SDL_SetColorKey(newSurface, SDL_TRUE, colorKey);
}
output = newSurface;
virtualDimensions.x += left;
virtualDimensions.y += top;
}
else
{
output = surf;
output->refcount += 1;
}
}
SDL_Surface * SDLImageOptimizer::acquireResultSurface()
{
SDL_Surface * result = output;
output = nullptr;
return result;
}
const Rect & SDLImageOptimizer::getResultDimensions() const
{
return virtualDimensions;
}
void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm)
{
if(!targetDimensions.x || !targetDimensions.y)
throw std::runtime_error("invalid scaling dimensions!");
Point inputSurfaceSize(intermediate->w, intermediate->h);
Point outputSurfaceSize = targetDimensions * inputSurfaceSize / virtualDimensionsInput.dimensions();
Point outputMargins = targetDimensions * virtualDimensionsInput.topLeft() / virtualDimensionsInput.dimensions();
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
// Need to investigate which is optimal for performance and for visuals
ret = CSDL_Ext::newSurface(Point(outputSurfaceSize.x, outputSurfaceSize.y), intermediate);
virtualDimensionsOutput = Rect(outputMargins, targetDimensions); // TODO: account for input virtual size
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
if (algorithm == EScalingAlgorithm::NEAREST)
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
else
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
}
void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm)
{
if(factor == 0)
throw std::runtime_error("invalid scaling factor!");
int newWidth = intermediate->w * factor;
int newHight = intermediate->h * factor;
virtualDimensionsOutput = virtualDimensionsInput * factor;
ret = CSDL_Ext::newSurface(Point(newWidth, newHight), intermediate);
assert(intermediate->pitch == intermediate->w * 4);
assert(ret->pitch == ret->w * 4);
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
switch (algorithm)
{
case EScalingAlgorithm::NEAREST:
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
break;
case EScalingAlgorithm::BILINEAR:
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
break;
case EScalingAlgorithm::XBRZ_ALPHA:
case EScalingAlgorithm::XBRZ_OPAQUE:
{
auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB;
if(intermediate->h < 32)
{
// for tiny images tbb incurs too high overhead
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {});
}
else
{
// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option
const int granulation = intermediate->h > 400 ? 16 : 4;
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [this, factor, srcPixels, dstPixels, format](const tbb::blocked_range<size_t> & r)
{
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end());
});
}
break;
}
default:
throw std::runtime_error("invalid scaling algorithm!");
}
}
SDLImageScaler::SDLImageScaler(SDL_Surface * surf)
:SDLImageScaler(surf, Rect(0,0,surf->w, surf->h))
{
}
SDLImageScaler::SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions)
{
SDLImageOptimizer optimizer(surf, virtualDimensions);
optimizer.optimizeSurface(screen);
intermediate = optimizer.acquireResultSurface();
virtualDimensionsInput = optimizer.getResultDimensions();
if (intermediate == surf)
{
SDL_FreeSurface(intermediate);
intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
}
}
SDLImageScaler::~SDLImageScaler()
{
SDL_FreeSurface(intermediate);
SDL_FreeSurface(ret);
}
SDL_Surface * SDLImageScaler::acquireResultSurface()
{
SDL_Surface * result = ret;
ret = nullptr;
return result;
}
const Rect & SDLImageScaler::getResultDimensions() const
{
return virtualDimensionsOutput;
}

View File

@ -0,0 +1,63 @@
/*
* SDLImageScaler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../render/IImage.h"
#include "../../lib/Rect.h"
class SDLImageOptimizer : boost::noncopyable
{
SDL_Surface * surf = nullptr;
SDL_Surface * output = nullptr;
Rect virtualDimensions = Rect(0,0,0,0);
public:
SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions);
void optimizeSurface(SDL_Surface * formatSourceSurface);
/// Aquires resulting surface and transfers surface ownership to the caller
/// May return nullptr if input image was empty
SDL_Surface * acquireResultSurface();
/// Returns adjusted virtual dimensions of resulting surface
const Rect & getResultDimensions() const;
};
/// Class that performs scaling of SDL surfaces
/// Object construction MUST be performed while holding UI lock
/// but task execution can be performed asynchronously if needed
class SDLImageScaler : boost::noncopyable
{
SDL_Surface * intermediate = nullptr;
SDL_Surface * ret = nullptr;
Rect virtualDimensionsInput = Rect(0,0,0,0);
Rect virtualDimensionsOutput = Rect(0,0,0,0);
public:
SDLImageScaler(SDL_Surface * surf);
SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions);
~SDLImageScaler();
/// Performs upscaling or downscaling to a requested dimensions
/// Aspect ratio is NOT maintained.
/// xbrz algorithm is not supported in this mode
void scaleSurface(Point dimensions, EScalingAlgorithm algorithm);
/// Performs upscaling by specified integral factor, potentially using xbrz algorithm if requested
void scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm);
/// Aquires resulting surface and transfers surface ownership to the caller
/// May return nullptr if input image was empty
SDL_Surface * acquireResultSurface();
/// Returns adjusted virtual dimensions of resulting surface
const Rect & getResultDimensions() const;
};

View File

@ -14,6 +14,7 @@
#include "../gui/CGuiHandler.h"
#include "../render/Graphics.h"
#include "../render/IImage.h"
#include "../render/IScreenHandler.h"
#include "../render/Colors.h"
#include "../CMT.h"
@ -630,86 +631,6 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect )
}
}
// scaling via bilinear interpolation algorithm.
// NOTE: best results are for scaling in range 50%...200%.
// And upscaling looks awful right now - should be fixed somehow
SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
{
if(!surf || !width || !height)
return nullptr;
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
// Need to investigate which is optimal for performance and for visuals
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
SDL_Surface * ret = newSurface(Point(width, height), intermediate);
#if SDL_VERSION_ATLEAST(2,0,16)
SDL_SoftStretchLinear(intermediate, nullptr, ret, nullptr);
#else
SDL_SoftStretch(intermediate, nullptr, ret, nullptr);
#endif
SDL_FreeSurface(intermediate);
return ret;
}
SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm)
{
if(surf == nullptr || factor == 0)
return nullptr;
int newWidth = surf->w * factor;
int newHight = surf->h * factor;
SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate);
assert(intermediate->pitch == intermediate->w * 4);
assert(ret->pitch == ret->w * 4);
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
switch (algorithm)
{
case EScalingAlgorithm::NEAREST:
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
break;
case EScalingAlgorithm::BILINEAR:
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
break;
case EScalingAlgorithm::XBRZ_ALPHA:
case EScalingAlgorithm::XBRZ_OPAQUE:
{
auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB;
if(intermediate->h < 32)
{
// for tiny images tbb incurs too high overhead
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {});
}
else
{
// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option
const int granulation = intermediate->h > 400 ? 16 : 4;
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range<size_t> & r)
{
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end());
});
}
break;
}
default:
throw std::runtime_error("invalid scaling algorithm!");
}
SDL_FreeSurface(intermediate);
return ret;
}
void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint)
{
SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput);
@ -799,10 +720,5 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other)
other = CSDL_Ext::fromSDL(rect);
}
int CSDL_Ext::CClipRectGuard::getScalingFactor() const
{
return GH.screenHandler().getScalingFactor();
}
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);

View File

@ -27,14 +27,6 @@ class Point;
VCMI_LIB_NAMESPACE_END
enum class EScalingAlgorithm : int8_t
{
NEAREST,
BILINEAR,
XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image
XBRZ_ALPHA // xbrz, image edges are considered to be transparent
};
namespace CSDL_Ext
{
@ -98,10 +90,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
template<int bpp>
SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value
// bilinear filtering. Always returns rgba surface
SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height);
SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler);
template<int bpp>
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
void convertToGrayscale(SDL_Surface * surf, const Rect & rect);

View File

@ -0,0 +1,532 @@
/*
* ScalableImage.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ScalableImage.h"
#include "SDLImage.h"
#include "SDL_Extensions.h"
#include "../gui/CGuiHandler.h"
#include "../render/ColorFilter.h"
#include "../render/Colors.h"
#include "../render/Graphics.h"
#include "../render/IRenderHandler.h"
#include "../render/IScreenHandler.h"
#include "../render/CanvasImage.h"
#include "../../lib/constants/EntityIdentifiers.h"
#include <SDL_surface.h>
//First 8 colors in def palette used for transparency
static constexpr std::array<SDL_Color, 8> sourcePalette = {{
{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 constexpr std::array<ColorRGBA, 8> targetPalette = {{
{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),
static_cast<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;
}
ScalableImageParameters::ScalableImageParameters(const SDL_Palette * originalPalette, EImageBlitMode blitMode)
{
if (originalPalette)
{
palette = SDL_AllocPalette(originalPalette->ncolors);
SDL_SetPaletteColors(palette, originalPalette->colors, 0, originalPalette->ncolors);
preparePalette(originalPalette, blitMode);
}
}
ScalableImageParameters::~ScalableImageParameters()
{
SDL_FreePalette(palette);
}
void ScalableImageParameters::preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode)
{
switch(blitMode)
{
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::ONLY_OVERLAY:
adjustPalette(originalPalette, blitMode, ColorFilter::genAlphaShifter(0), 0);
break;
}
switch(blitMode)
{
case EImageBlitMode::SIMPLE:
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
setShadowTransparency(originalPalette, 1.0);
break;
case EImageBlitMode::ONLY_BODY:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
case EImageBlitMode::ONLY_OVERLAY:
setShadowTransparency(originalPalette, 0.0);
break;
}
switch(blitMode)
{
case EImageBlitMode::ONLY_OVERLAY:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
setOverlayColor(originalPalette, Colors::WHITE_TRUE);
break;
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::ONLY_BODY:
setOverlayColor(originalPalette, Colors::TRANSPARENCY);
break;
}
}
void ScalableImageParameters::setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color)
{
palette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color));
for (int i : {6,7})
{
if (colorsSimilar(originalPalette->colors[i], sourcePalette[i]))
palette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
}
}
void ScalableImageParameters::shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{
std::vector<SDL_Color> shifterColors(colorsToMove);
for(uint32_t i=0; i<colorsToMove; ++i)
shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
SDL_SetPaletteColors(palette, shifterColors.data(), firstColorID, colorsToMove);
}
void ScalableImageParameters::setShadowTransparency(const SDL_Palette * originalPalette, float factor)
{
ColorRGBA shadow50(0, 0, 0, 128 * factor);
ColorRGBA shadow25(0, 0, 0, 64 * factor);
std::array<SDL_Color, 5> colorsSDL = {
originalPalette->colors[0],
originalPalette->colors[1],
originalPalette->colors[2],
originalPalette->colors[3],
originalPalette->colors[4]
};
// seems to be used unconditionally
colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY);
colorsSDL[1] = CSDL_Ext::toSDL(shadow25);
colorsSDL[4] = CSDL_Ext::toSDL(shadow50);
// seems to be used only if color matches
if (colorsSimilar(originalPalette->colors[2], sourcePalette[2]))
colorsSDL[2] = CSDL_Ext::toSDL(shadow25);
if (colorsSimilar(originalPalette->colors[3], sourcePalette[3]))
colorsSDL[3] = CSDL_Ext::toSDL(shadow50);
SDL_SetPaletteColors(palette, colorsSDL.data(), 0, colorsSDL.size());
}
void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask)
{
// If shadow is enabled, following colors must be skipped unconditionally
if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY)
colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
// Note: here we skip first colors in the palette that are predefined in H3 images
for(int i = 0; i < palette->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;
palette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i])));
}
}
ScalableImageShared::ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & baseImage)
:locator(locator)
{
scaled[1].body[0] = baseImage;
assert(scaled[1].body[0] != nullptr);
loadScaledImages(GH.screenHandler().getScalingFactor(), PlayerColor::CANNOT_DETERMINE);
}
Point ScalableImageShared::dimensions() const
{
return scaled[1].body[0]->dimensions();
}
void ScalableImageShared::exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const
{
scaled[1].body[0]->exportBitmap(path, parameters.palette);
}
bool ScalableImageShared::isTransparent(const Point & coords) const
{
return scaled[1].body[0]->isTransparent(coords);
}
Rect ScalableImageShared::contentRect() const
{
return scaled[1].body[0]->contentRect();
}
void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor)
{
const auto & getFlippedImage = [&](FlippedImages & images){
int index = 0;
if (parameters.flipVertical)
{
if (!images[index|1])
images[index|1] = images[index]->verticalFlip();
index |= 1;
}
if (parameters.flipHorizontal)
{
if (!images[index|2])
images[index|2] = images[index]->horizontalFlip();
index |= 2;
}
return images[index];
};
const auto & flipAndDraw = [&](FlippedImages & images, const ColorRGBA & colorMultiplier, uint8_t alphaValue){
getFlippedImage(images)->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer);
};
bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading();
bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading();
bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading();
bool playerLoading = parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->isLoading();
if (shadowLoading || bodyLoading || overlayLoading || playerLoading)
{
getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer);
return;
}
if (scaled.at(scalingFactor).shadow.at(0))
flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue);
if (parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()))
{
scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer);
}
else
{
if (scaled.at(scalingFactor).body.at(0))
flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue);
}
if (scaled.at(scalingFactor).overlay.at(0))
flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast<int>(parameters.alphaValue) * parameters.ovelayColorMultiplier.a / 255);
}
const SDL_Palette * ScalableImageShared::getPalette() const
{
return scaled[1].body[0]->getPalette();
}
std::shared_ptr<ScalableImageInstance> ScalableImageShared::createImageReference()
{
return std::make_shared<ScalableImageInstance>(shared_from_this(), locator.layer);
}
ScalableImageInstance::ScalableImageInstance(const std::shared_ptr<ScalableImageShared> & image, EImageBlitMode blitMode)
:image(image)
,parameters(image->getPalette(), blitMode)
,blitMode(blitMode)
{
assert(image);
}
void ScalableImageInstance::scaleTo(const Point & size, EScalingAlgorithm algorithm)
{
scaledImage = nullptr;
auto newScaledImage = GH.renderHandler().createImage(dimensions(), CanvasScalingPolicy::AUTO);
newScaledImage->getCanvas().draw(*this, Point(0, 0));
newScaledImage->scaleTo(size, algorithm);
scaledImage = newScaledImage;
}
void ScalableImageInstance::exportBitmap(const boost::filesystem::path & path) const
{
image->exportBitmap(path, parameters);
}
bool ScalableImageInstance::isTransparent(const Point & coords) const
{
return image->isTransparent(coords);
}
Rect ScalableImageInstance::contentRect() const
{
return image->contentRect();
}
Point ScalableImageInstance::dimensions() const
{
if (scaledImage)
return scaledImage->dimensions() / GH.screenHandler().getScalingFactor();
return image->dimensions();
}
void ScalableImageInstance::setAlpha(uint8_t value)
{
parameters.alphaValue = value;
}
void ScalableImageInstance::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const
{
if (scaledImage)
scaledImage->draw(where, pos, src, scalingFactor);
else
image->draw(where, pos, src, parameters, scalingFactor);
}
void ScalableImageInstance::setOverlayColor(const ColorRGBA & color)
{
parameters.ovelayColorMultiplier = color;
if (parameters.palette)
parameters.setOverlayColor(image->getPalette(), color);
}
void ScalableImageInstance::playerColored(const PlayerColor & player)
{
parameters.player = player;
if (parameters.palette)
parameters.playerColored(player);
image->preparePlayerColoredImage(player);
}
void ScalableImageParameters::playerColored(PlayerColor player)
{
graphics->setPlayerPalette(palette, player);
}
void ScalableImageInstance::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{
if (parameters.palette)
parameters.shiftPalette(image->getPalette(),firstColorID, colorsToMove, distanceToMove);
}
void ScalableImageInstance::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
{
if (parameters.palette)
parameters.adjustPalette(image->getPalette(), blitMode, shifter, colorsToSkipMask);
}
void ScalableImageInstance::horizontalFlip()
{
parameters.flipHorizontal = !parameters.flipHorizontal;
}
void ScalableImageInstance::verticalFlip()
{
parameters.flipVertical = !parameters.flipVertical;
}
std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const
{
ImageLocator loadingLocator;
loadingLocator.image = locator.image;
loadingLocator.defFile = locator.defFile;
loadingLocator.defFrame = locator.defFrame;
loadingLocator.defGroup = locator.defGroup;
loadingLocator.layer = mode;
loadingLocator.scalingFactor = scalingFactor;
loadingLocator.playerColored = color;
// best case - requested image is already available in filesystem
auto loadedImage = GH.renderHandler().loadScaledImage(loadingLocator);
if (loadedImage)
return loadedImage;
if (scalingFactor == 1)
{
// optional images for 1x resolution - only try load them, don't attempt to generate
// this block should never be called for 'body' layer - that image is loaded unconditionally before construction
assert(mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE);
return nullptr;
}
// alternatively, find largest pre-scaled image, load it and rescale to desired scaling
for (int8_t scaling = 4; scaling > 0; --scaling)
{
loadingLocator.scalingFactor = scaling;
auto loadedImage = GH.renderHandler().loadScaledImage(loadingLocator);
if (loadedImage)
{
if (scaling == 1)
{
if (mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE)
{
ScalableImageParameters parameters(getPalette(), mode);
return loadedImage->scaleInteger(scalingFactor, parameters.palette, mode);
}
}
else
{
Point targetSize = scaled[1].body[0]->dimensions() * scalingFactor;
return loadedImage->scaleTo(targetSize, nullptr);
}
}
}
ScalableImageParameters parameters(getPalette(), mode);
// if all else fails - use base (presumably, indexed) image and convert it to desired form
if (color != PlayerColor::CANNOT_DETERMINE)
parameters.playerColored(color);
if (upscalingSource)
return upscalingSource->scaleInteger(scalingFactor, parameters.palette, mode);
else
return scaled[1].body[0]->scaleInteger(scalingFactor, parameters.palette, mode);
}
void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor color)
{
if (scaled[scalingFactor].body[0] == nullptr && scalingFactor != 1)
{
switch(locator.layer)
{
case EImageBlitMode::OPAQUE:
case EImageBlitMode::COLORKEY:
case EImageBlitMode::SIMPLE:
scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break;
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
case EImageBlitMode::ONLY_BODY:
scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break;
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break;
}
}
if (color != PlayerColor::CANNOT_DETERMINE && scaled[scalingFactor].playerColored[1+color.getNum()] == nullptr)
{
switch(locator.layer)
{
case EImageBlitMode::OPAQUE:
case EImageBlitMode::COLORKEY:
case EImageBlitMode::SIMPLE:
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break;
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
case EImageBlitMode::ONLY_BODY:
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break;
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break;
}
}
if (scaled[scalingFactor].shadow[0] == nullptr)
{
switch(locator.layer)
{
case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]);
break;
default:
break;
}
}
if (scaled[scalingFactor].overlay[0] == nullptr)
{
switch(locator.layer)
{
case EImageBlitMode::ONLY_OVERLAY:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]);
break;
default:
break;
}
}
}
void ScalableImageShared::preparePlayerColoredImage(PlayerColor color)
{
loadScaledImages(GH.screenHandler().getScalingFactor(), color);
}

View File

@ -0,0 +1,125 @@
/*
* ScalableImage.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../render/IImage.h"
#include "../render/ImageLocator.h"
#include "../render/Colors.h"
#include "../../lib/Color.h"
struct SDL_Palette;
class ScalableImageInstance;
class CanvasImage;
struct ScalableImageParameters : boost::noncopyable
{
SDL_Palette * palette = nullptr;
ColorRGBA colorMultiplier = Colors::WHITE_TRUE;
ColorRGBA ovelayColorMultiplier = Colors::WHITE_TRUE;
PlayerColor player = PlayerColor::CANNOT_DETERMINE;
uint8_t alphaValue = 255;
bool flipVertical = false;
bool flipHorizontal = false;
ScalableImageParameters(const SDL_Palette * originalPalette, EImageBlitMode blitMode);
~ScalableImageParameters();
void setShadowTransparency(const SDL_Palette * originalPalette, float factor);
void shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove);
void playerColored(PlayerColor player);
void setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color);
void preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode);
void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask);
};
class ScalableImageShared final : public std::enable_shared_from_this<ScalableImageShared>, boost::noncopyable
{
static constexpr int scalingSize = 5; // 0-4 range. TODO: switch to 1-4 since there is no '0' scaling
static constexpr int maxFlips = 4;
using ImageType = std::shared_ptr<const ISharedImage>;
using FlippedImages = std::array<ImageType, maxFlips>;
using PlayerColoredImages = std::array<ImageType, PlayerColor::PLAYER_LIMIT_I + 1>; // all valid colors+neutral
struct ScaledImage
{
/// Upscaled shadow of our image, may be null
FlippedImages shadow;
/// Upscaled main part of our image, may be null
FlippedImages body;
/// Upscaled overlay (player color, selection highlight) of our image, may be null
FlippedImages overlay;
/// player-colored images of this particular scale, mostly for UI. These are never flipped in h3
PlayerColoredImages playerColored;
};
/// 1x-4x images. body for 1x scaling is guaranteed to be loaded
std::array<ScaledImage, scalingSize> scaled;
/// Locator of this image, for loading additional (e.g. upscaled) images
const SharedImageLocator locator;
std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const;
void loadScaledImages(int8_t scalingFactor, PlayerColor color);
public:
ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & baseImage);
Point dimensions() const;
void exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const;
bool isTransparent(const Point & coords) const;
Rect contentRect() const;
void draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor);
const SDL_Palette * getPalette() const;
std::shared_ptr<ScalableImageInstance> createImageReference();
void preparePlayerColoredImage(PlayerColor color);
};
class ScalableImageInstance final : public IImage
{
friend class ScalableImageShared;
std::shared_ptr<ScalableImageShared> image;
std::shared_ptr<CanvasImage> scaledImage;
ScalableImageParameters parameters;
EImageBlitMode blitMode;
public:
ScalableImageInstance(const std::shared_ptr<ScalableImageShared> & image, EImageBlitMode blitMode);
void scaleTo(const Point & size, EScalingAlgorithm algorithm) override;
void exportBitmap(const boost::filesystem::path & path) const override;
bool isTransparent(const Point & coords) const override;
Rect contentRect() const override;
Point dimensions() const override;
void setAlpha(uint8_t value) override;
void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override;
void setOverlayColor(const ColorRGBA & color) override;
void playerColored(const 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 horizontalFlip();
void verticalFlip();
};

View File

@ -11,13 +11,14 @@
#include "StdInc.h"
#include "ScreenHandler.h"
#include "../eventsSDL/NotificationHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "CMT.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/constants/StringConstants.h"
#include "../gui/CGuiHandler.h"
#include "../eventsSDL/NotificationHandler.h"
#include "../gui/WindowHandler.h"
#include "CMT.h"
#include "SDL_Extensions.h"
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"

View File

@ -13,7 +13,6 @@
#include "MiscWidgets.h"
#include "../gui/CGuiHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/AssetGenerator.h"
#include "../render/IImage.h"
#include "../render/IRenderHandler.h"
@ -53,8 +52,8 @@ CPicture::CPicture( const ImagePath & bmpname )
: CPicture(bmpname, Point(0,0))
{}
CPicture::CPicture( const ImagePath & bmpname, const Point & position )
: bg(GH.renderHandler().loadImage(bmpname, EImageBlitMode::COLORKEY))
CPicture::CPicture( const ImagePath & bmpname, const Point & position, EImageBlitMode mode )
: bg(GH.renderHandler().loadImage(bmpname, mode))
, needRefresh(false)
{
pos.x += position.x;
@ -74,6 +73,10 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position )
addUsedEvents(SHOW_POPUP);
}
CPicture::CPicture( const ImagePath & bmpname, const Point & position )
:CPicture(bmpname, position, EImageBlitMode::COLORKEY)
{}
CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y)
: CPicture(bmpname, Point(x,y))
{
@ -118,7 +121,7 @@ void CPicture::setAlpha(uint8_t value)
void CPicture::scaleTo(Point size)
{
bg->scaleTo(size);
bg->scaleTo(size, EScalingAlgorithm::BILINEAR);
pos.w = bg->width();
pos.h = bg->height();
@ -160,7 +163,7 @@ CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position, Rect
void CFilledTexture::showAll(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos);
CanvasClipRectGuard guard(to, pos);
for (int y=pos.top(); y < pos.bottom(); y+= imageArea.h)
{
@ -266,7 +269,7 @@ void CAnimImage::showAll(Canvas & to)
if(auto img = anim->getImage(targetFrame, group))
{
if(isScaled())
img->scaleTo(scaledSize);
img->scaleTo(scaledSize, EScalingAlgorithm::BILINEAR);
to.draw(img, pos.topLeft());
}

View File

@ -21,6 +21,7 @@ class CAnimImage;
class CLabel;
class CAnimation;
class IImage;
enum class EImageBlitMode : uint8_t;
// Image class
class CPicture : public CIntObject
@ -49,6 +50,7 @@ public:
/// Loads image from specified file name
CPicture(const ImagePath & bmpname);
CPicture(const ImagePath & bmpname, const Point & position, EImageBlitMode mode);
CPicture(const ImagePath & bmpname, const Point & position);
CPicture(const ImagePath & bmpname, int x, int y);

View File

@ -18,7 +18,6 @@
#include "../windows/CMessage.h"
#include "../windows/InfoWindows.h"
#include "../adventureMap/CInGameConsole.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/Canvas.h"
#include "../render/Graphics.h"
#include "../render/IFont.h"
@ -298,7 +297,7 @@ void CMultiLineLabel::showAll(Canvas & to)
Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * fontPtr->getLineHeight());
Point lineSize = Point(getTextLocation().w, fontPtr->getLineHeight());
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), getTextLocation()); // to properly trim text that is too big to fit
CanvasClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit
for(int i = beginLine; i < std::min(totalLines, endLine); i++)
{

View File

@ -86,7 +86,7 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay)
void VideoWidgetBase::show(Canvas & to)
{
if(videoInstance)
videoInstance->show(pos.topLeft(), to);
to.draw(*videoInstance, pos.topLeft());
if(subTitle)
subTitle->showAll(to);
}
@ -162,7 +162,7 @@ void VideoWidgetBase::deactivate()
void VideoWidgetBase::showAll(Canvas & to)
{
if(videoInstance)
videoInstance->show(pos.topLeft(), to);
to.draw(*videoInstance, pos.topLeft());
if(subTitle)
subTitle->showAll(to);
}

View File

@ -570,9 +570,8 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town):
{
OBJECT_CONSTRUCTION;
background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground);
background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground, Point(0,0), EImageBlitMode::OPAQUE);
background->needRefresh = true;
background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
pos.w = background->pos.w;
pos.h = background->pos.h;
@ -974,7 +973,7 @@ void CCastleBuildings::enterCastleGate(BuildingID building)
if(settings["general"]["enableUiEnhancements"].Bool())
{
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
image->scaleTo(Point(35, 23));
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
images.push_back(image);
}
}

View File

@ -20,7 +20,7 @@
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../render/Canvas.h"
#include "../render/CanvasImage.h"
#include "../render/IImage.h"
#include "../render/IRenderHandler.h"
#include "../render/Graphics.h"
@ -58,9 +58,10 @@ CMapOverview::CMapOverview(const std::string & mapName, const std::string & file
fitToScreen(10);
}
Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const
std::shared_ptr<CanvasImage> CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const
{
Canvas canvas = Canvas(Point(map->width, map->height), CanvasScalingPolicy::IGNORE);
auto canvasImage = GH.renderHandler().createImage(Point(map->width, map->height), CanvasScalingPolicy::IGNORE);
auto canvas = canvasImage->getCanvas();
for (int y = 0; y < map->height; ++y)
for (int x = 0; x < map->width; ++x)
@ -91,12 +92,12 @@ Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, in
canvas.drawPoint(Point(x, y), color);
}
return canvas;
return canvasImage;
}
std::vector<Canvas> CMapOverviewWidget::createMinimaps(ResourcePath resource) const
std::vector<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(ResourcePath resource) const
{
auto ret = std::vector<Canvas>();
std::vector<std::shared_ptr<CanvasImage>> ret;
CMapService mapService;
std::unique_ptr<CMap> map;
@ -113,9 +114,9 @@ std::vector<Canvas> CMapOverviewWidget::createMinimaps(ResourcePath resource) co
return createMinimaps(map);
}
std::vector<Canvas> CMapOverviewWidget::createMinimaps(std::unique_ptr<CMap> & map) const
std::vector<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(std::unique_ptr<CMap> & map) const
{
auto ret = std::vector<Canvas>();
std::vector<std::shared_ptr<CanvasImage>> ret;
for(int i = 0; i < (map->twoLevel ? 2 : 1); i++)
ret.push_back(createMinimapForLayer(map, i));
@ -133,17 +134,15 @@ std::shared_ptr<CPicture> CMapOverviewWidget::buildDrawMinimap(const JsonNode &
if(id >= minimaps.size())
return nullptr;
Rect minimapRect = minimaps[id].getRenderArea();
double maxSideLengthSrc = std::max(minimapRect.w, minimapRect.h);
Point minimapRect = minimaps[id]->dimensions();
double maxSideLengthSrc = std::max(minimapRect.x, minimapRect.y);
double maxSideLengthDst = std::max(rect.w, rect.h);
double resize = maxSideLengthSrc / maxSideLengthDst;
Point newMinimapSize = Point(minimapRect.w / resize, minimapRect.h / resize);
Point newMinimapSize = Point(minimapRect.x / resize, minimapRect.y / resize);
Canvas canvasScaled = Canvas(Point(rect.w, rect.h), CanvasScalingPolicy::AUTO);
canvasScaled.drawScaled(minimaps[id], Point((rect.w - newMinimapSize.x) / 2, (rect.h - newMinimapSize.y) / 2), newMinimapSize);
std::shared_ptr<IImage> img = GH.renderHandler().createImage(canvasScaled.getInternalSurface());
minimaps[id]->scaleTo(newMinimapSize, EScalingAlgorithm::NEAREST); // for sharp-looking minimap
return std::make_shared<CPicture>(img, Point(rect.x, rect.y));
return std::make_shared<CPicture>(minimaps[id], Point(rect.x, rect.y));
}
CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):

View File

@ -22,7 +22,7 @@ class CPicture;
class CFilledTexture;
class CTextBox;
class IImage;
class Canvas;
class CanvasImage;
class TransparentFilledRectangle;
enum class ESelectionScreen : ui8;
@ -33,11 +33,11 @@ class CMapOverviewWidget : public InterfaceObjectConfigurable
CMapOverview& p;
bool drawPlayerElements;
std::vector<Canvas> minimaps;
std::vector<std::shared_ptr<CanvasImage>> minimaps;
Canvas createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const;
std::vector<Canvas> createMinimaps(ResourcePath resource) const;
std::vector<Canvas> createMinimaps(std::unique_ptr<CMap> & map) const;
std::shared_ptr<CanvasImage> createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const;
std::vector<std::shared_ptr<CanvasImage>> createMinimaps(ResourcePath resource) const;
std::vector<std::shared_ptr<CanvasImage>> createMinimaps(std::unique_ptr<CMap> & map) const;
std::shared_ptr<CPicture> buildDrawMinimap(const JsonNode & config) const;
public:

View File

@ -21,7 +21,6 @@
#include "../adventureMap/AdventureMapInterface.h"
#include "../adventureMap/CMinimap.h"
#include "../render/Canvas.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../CCallback.h"
#include "../../lib/CArtHandler.h"
@ -61,7 +60,7 @@ void CQuestIcon::clickPressed(const Point & cursorPosition)
void CQuestIcon::showAll(Canvas & to)
{
CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), parent->pos);
CanvasClipRectGuard guard(to, parent->pos);
CAnimImage::showAll(to);
}

View File

@ -23,6 +23,7 @@
#include "../render/IScreenHandler.h"
#include "../render/IRenderHandler.h"
#include "../render/Canvas.h"
#include "../render/CanvasImage.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
@ -87,8 +88,7 @@ std::shared_ptr<CPicture> CWindowObject::createBg(const ImagePath & imageName, b
if(imageName.empty())
return nullptr;
auto image = std::make_shared<CPicture>(imageName);
image->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
auto image = std::make_shared<CPicture>(imageName, Point(0,0), EImageBlitMode::OPAQUE);
if(playerColored)
image->setPlayerColor(LOCPLINT->playerID);
return image;
@ -116,8 +116,7 @@ void CWindowObject::updateShadow()
void CWindowObject::setShadow(bool on)
{
//size of shadow
int sizeOriginal = 8;
int size = sizeOriginal * GH.screenHandler().getScalingFactor();
int size = 8;
if(on == !shadowParts.empty())
return;
@ -130,61 +129,12 @@ void CWindowObject::setShadow(bool on)
if(on)
{
//helper to set last row
auto blitAlphaRow = [](SDL_Surface *surf, size_t row)
{
uint8_t * ptr = (uint8_t*)surf->pixels + surf->pitch * (row);
for (size_t i=0; i< surf->w; i++)
{
Channels::px<4>::a.set(ptr, 128);
ptr+=4;
}
};
// helper to set last column
auto blitAlphaCol = [](SDL_Surface *surf, size_t col)
{
uint8_t * ptr = (uint8_t*)surf->pixels + 4 * (col);
for (size_t i=0; i< surf->h; i++)
{
Channels::px<4>::a.set(ptr, 128);
ptr+= surf->pitch;
}
};
static SDL_Surface * shadowCornerTempl = nullptr;
static SDL_Surface * shadowBottomTempl = nullptr;
static SDL_Surface * shadowRightTempl = nullptr;
//one-time initialization
if(!shadowCornerTempl)
{
//create "template" surfaces
shadowCornerTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, size);
shadowBottomTempl = CSDL_Ext::createSurfaceWithBpp<4>(1, size);
shadowRightTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, 1);
//fill with shadow body color
CSDL_Ext::fillSurface(shadowCornerTempl, { 0, 0, 0, 192 } );
CSDL_Ext::fillSurface(shadowBottomTempl, { 0, 0, 0, 192 } );
CSDL_Ext::fillSurface(shadowRightTempl, { 0, 0, 0, 192 } );
//fill last row and column with more transparent color
blitAlphaCol(shadowRightTempl , size-1);
blitAlphaCol(shadowCornerTempl, size-1);
blitAlphaRow(shadowBottomTempl, size-1);
blitAlphaRow(shadowCornerTempl, size-1);
}
//FIXME: do something with this points
Point shadowStart;
if (options & BORDERED)
shadowStart = Point(sizeOriginal - 14, sizeOriginal - 14);
shadowStart = Point(size - 14, size - 14);
else
shadowStart = Point(sizeOriginal, sizeOriginal);
shadowStart = Point(size, size);
Point shadowPos;
if (options & BORDERED)
@ -198,26 +148,36 @@ void CWindowObject::setShadow(bool on)
else
fullsize = Point(pos.w, pos.h);
//create base 8x8 piece of shadow
SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl);
SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, (fullsize.x - sizeOriginal) * GH.screenHandler().getScalingFactor(), size);
SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, (fullsize.y - sizeOriginal) * GH.screenHandler().getScalingFactor());
Point sizeCorner(size, size);
Point sizeRight(fullsize.x - size, size);
Point sizeBottom(size, fullsize.y - size);
blitAlphaCol(shadowBottom, 0);
blitAlphaRow(shadowRight, 0);
//create base 8x8 piece of shadow
auto imageCorner = GH.renderHandler().createImage(sizeCorner, CanvasScalingPolicy::AUTO);
auto imageRight = GH.renderHandler().createImage(sizeRight, CanvasScalingPolicy::AUTO);
auto imageBottom = GH.renderHandler().createImage(sizeBottom, CanvasScalingPolicy::AUTO);
Canvas canvasCorner = imageCorner->getCanvas();
Canvas canvasRight = imageRight->getCanvas();
Canvas canvasBottom = imageBottom->getCanvas();
canvasCorner.drawColor(Rect(Point(0,0), sizeCorner), { 0, 0, 0, 128 });
canvasRight.drawColor(Rect(Point(0,0), sizeRight), { 0, 0, 0, 128 });
canvasBottom.drawColor(Rect(Point(0,0), sizeBottom), { 0, 0, 0, 128 });
canvasCorner.drawColor(Rect(Point(0,0), sizeCorner - Point(1,1)), { 0, 0, 0, 192 });
canvasRight.drawColor(Rect(Point(0,0), sizeRight - Point(0,1)), { 0, 0, 0, 192 });
canvasBottom.drawColor(Rect(Point(0,0), sizeBottom - Point(1,0)), { 0, 0, 0, 192 });
//generate "shadow" object with these 3 pieces in it
{
OBJECT_CONSTRUCTION;
shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowCorner), Point(shadowPos.x, shadowPos.y)));
shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowRight ), Point(shadowPos.x, shadowStart.y)));
shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowBottom), Point(shadowStart.x, shadowPos.y)));
shadowParts.push_back(std::make_shared<CPicture>( imageCorner, Point(shadowPos.x, shadowPos.y)));
shadowParts.push_back(std::make_shared<CPicture>( imageRight, Point(shadowStart.x, shadowPos.y)));
shadowParts.push_back(std::make_shared<CPicture>( imageBottom, Point(shadowPos.x, shadowStart.y)));
}
SDL_FreeSurface(shadowCorner);
SDL_FreeSurface(shadowBottom);
SDL_FreeSurface(shadowRight);
}
}

View File

@ -236,7 +236,7 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst)
{
assert(artInst.getScrollSpellID().num >= 0);
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("spellscr"), artInst.getScrollSpellID().num, 0, EImageBlitMode::COLORKEY);
image->scaleTo(Point(44,34));
image->scaleTo(Point(44,34), EScalingAlgorithm::BILINEAR);
CCS->curh->dragAndDropCursor(image);
}

View File

@ -20,7 +20,7 @@ For upscaled images you have to use following folders (next to `sprites`, `data`
The sprites should have the same name and folder structure as in `sprites`, `data` and `video` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images.
### Shadows / Overlays
### Shadows / Overlays / Player-colored images
It's also possible (but not necessary) to add high-definition shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`.
In future, such shadows will likely become required to correctly exclude shadow from effects such as Clone spell.
@ -36,3 +36,11 @@ Currently needed for:
- Flaggable adventure map objects. Overlay must contain a transparent image with white flags on it and will be used to colorize flags to owning player
- Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover
For images that are used for player-colored interface, it is possible to provide custom images for each player. For example `HeroScr4-red.png` will be used for hero window of red player.
- Currently needed for all UI elements that are player-colored in HoMM3.
- Can NOT be used for player-owned adventure objects. Use `-overlay` images for such objects.
- Possible suffixes are `red`, `blue`, `tan`, `green`, `orange`, `purple`, `teal`, `pink`, `neutral` (used only for turn order queue in combat)
It is possible to use such additional images for both upscaled (xbrz) graphics, as well as for original / 1x images. When using this feature for original / 1x image, make sure that your base image (without suffix) is rgb/rgba image, and not indexed / with palette

View File

@ -54,6 +54,11 @@ public:
return Point(x*mul, y*mul);
}
constexpr Point operator/(const Point &b) const
{
return Point(x/b.x,y/b.y);
}
constexpr Point operator*(const Point &b) const
{
return Point(x*b.x,y*b.y);