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:
commit
722d68643f
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
63
client/render/CanvasImage.cpp
Normal file
63
client/render/CanvasImage.cpp
Normal 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};
|
||||
}
|
41
client/render/CanvasImage.h
Normal file
41
client/render/CanvasImage.h
Normal 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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
233
client/renderSDL/SDLImageScaler.cpp
Normal file
233
client/renderSDL/SDLImageScaler.cpp
Normal 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;
|
||||
}
|
63
client/renderSDL/SDLImageScaler.h
Normal file
63
client/renderSDL/SDLImageScaler.h
Normal 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;
|
||||
};
|
@ -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);
|
||||
|
@ -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);
|
||||
|
532
client/renderSDL/ScalableImage.cpp
Normal file
532
client/renderSDL/ScalableImage.cpp
Normal 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);
|
||||
}
|
125
client/renderSDL/ScalableImage.h
Normal file
125
client/renderSDL/ScalableImage.h
Normal 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();
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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++)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user