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

Merge pull request #5306 from IvanSavenko/xbrz_foreground

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -381,12 +381,12 @@ Point CVideoInstance::size()
return dimensions / GH.screenHandler().getScalingFactor(); 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) if(sws == nullptr)
throw std::runtime_error("No video to show!"); 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 double FFMpegStream::getCurrentFrameEndTime() const

View File

@ -98,7 +98,7 @@ public:
bool videoEnded() final; bool videoEnded() final;
Point size() 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 tick(uint32_t msPassed) final;
void activate() final; void activate() final;
void deactivate() final; void deactivate() final;

View File

@ -11,7 +11,7 @@
#include "../lib/filesystem/ResourcePath.h" #include "../lib/filesystem/ResourcePath.h"
class Canvas; struct SDL_Surface;
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class Point; class Point;
@ -30,7 +30,7 @@ public:
virtual Point size() = 0; virtual Point size() = 0;
/// Displays current frame at specified position /// 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 /// Advances video playback by specified duration
virtual void tick(uint32_t msPassed) = 0; virtual void tick(uint32_t msPassed) = 0;

View File

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

View File

@ -30,7 +30,7 @@ bool CAnimation::loadFrame(size_t frame, size_t group, bool verbose)
if(auto image = getImageImpl(frame, group, false)) if(auto image = getImageImpl(frame, group, false))
return true; 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) 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() ); throw std::runtime_error("Frame " + std::to_string(frame) + " of group " + std::to_string(group) + " is missing from animation " + name.getOriginalName() );
} }
return ImageLocator(name, frame, group); return ImageLocator(name, frame, group, mode);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -62,21 +62,28 @@ enum class EImageBlitMode : uint8_t
ONLY_OVERLAY, 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. /// Base class for images for use in client code.
/// This class represents current state of image, with potential transformations applied, such as player coloring /// This class represents current state of image, with potential transformations applied, such as player coloring
class IImage class IImage
{ {
public: public:
//draws image on surface "where" at position //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 scaleTo(const Point & size, EScalingAlgorithm algorithm) = 0;
virtual void scaleInteger(int factor) = 0;
virtual void exportBitmap(const boost::filesystem::path & path) const = 0; virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
//Change palette to specific player //Change palette to specific player
virtual void playerColored(PlayerColor player) = 0; virtual void playerColored(const PlayerColor & player) = 0;
//test transparency of specific pixel //test transparency of specific pixel
virtual bool isTransparent(const Point & coords) const = 0; 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 adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0;
virtual void setAlpha(uint8_t value) = 0; virtual void setAlpha(uint8_t value) = 0;
virtual void setBlitMode(EImageBlitMode mode) = 0;
//only indexed bitmaps with 7 special colors //only indexed bitmaps with 7 special colors
virtual void setOverlayColor(const ColorRGBA & color) = 0; virtual void setOverlayColor(const ColorRGBA & color) = 0;
virtual std::shared_ptr<const ISharedImage> getSharedImage() const = 0;
virtual ~IImage() = default; virtual ~IImage() = default;
}; };
@ -112,15 +116,20 @@ public:
virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0; virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0;
virtual bool isTransparent(const Point & coords) const = 0; virtual bool isTransparent(const Point & coords) const = 0;
virtual Rect contentRect() 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; 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> horizontalFlip() const = 0;
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> verticalFlip() 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> 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; [[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
virtual ~ISharedImage() = default;
}; };

View File

@ -20,7 +20,10 @@ struct SDL_Surface;
class IFont; class IFont;
class IImage; class IImage;
class CAnimation; class CAnimation;
class CanvasImage;
class SDLImageShared;
enum class EImageBlitMode : uint8_t; enum class EImageBlitMode : uint8_t;
enum class CanvasScalingPolicy;
enum EFonts : int8_t; enum EFonts : int8_t;
class IRenderHandler : public boost::noncopyable class IRenderHandler : public boost::noncopyable
@ -32,13 +35,15 @@ public:
virtual void onLibraryLoadingFinished(const Services * services) = 0; virtual void onLibraryLoadingFinished(const Services * services) = 0;
/// Loads image using given path /// 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 ImagePath & path, EImageBlitMode mode) = 0;
virtual std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, 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 /// Loads single upscaled image without auto-scaling support
/// Surface will be shared, caller must still free it with SDL_FreeSurface virtual std::shared_ptr<SDLImageShared> loadScaledImage(const ImageLocator & locator) = 0;
virtual std::shared_ptr<IImage> createImage(SDL_Surface * source) = 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 /// Loads animation using given path
virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0; virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0;

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
#include "CursorHardware.h" #include "CursorHardware.h"
#include "SDL_Extensions.h" #include "SDL_Extensions.h"
#include "SDLImageScaler.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.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)); CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
image->draw(cursorSurface, Point(0,0)); image->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor());
auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y );
SDLImageScaler scaler(cursorSurface);
scaler.scaleSurface(cursorDimensionsScaled, EScalingAlgorithm::BILINEAR);
SDL_Surface * cursorSurfaceScaled = scaler.acquireResultSurface();
auto oldCursor = cursor; auto oldCursor = cursor;
cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y); cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);

View File

@ -65,7 +65,7 @@ void CursorSoftware::updateTexture()
CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); 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); SDL_UpdateTexture(cursorTexture, nullptr, cursorSurface->pixels, cursorSurface->pitch);
needUpdate = false; needUpdate = false;
} }

View File

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

View File

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

View File

@ -11,19 +11,22 @@
#include "RenderHandler.h" #include "RenderHandler.h"
#include "SDLImage.h" #include "SDLImage.h"
#include "ImageScaled.h" #include "ScalableImage.h"
#include "FontChain.h" #include "FontChain.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
#include "../render/CanvasImage.h"
#include "../render/CDefFile.h" #include "../render/CDefFile.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../render/ColorFilter.h" #include "../render/ColorFilter.h"
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../../lib/json/JsonUtils.h" #include "../../lib/json/JsonUtils.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"
#include "../../lib/VCMIDirs.h" #include "../../lib/VCMIDirs.h"
#include "../../lib/constants/StringConstants.h"
#include <vcmi/ArtifactService.h> #include <vcmi/ArtifactService.h>
#include <vcmi/CreatureService.h> #include <vcmi/CreatureService.h>
@ -55,60 +58,7 @@ std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath &
return result; return result;
} }
std::optional<ResourcePath> RenderHandler::getPathForScaleFactor(const ResourcePath & path, const std::string & factor) void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config, EImageBlitMode mode)
{
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)
{ {
std::string basepath; std::string basepath;
basepath = config["basepath"].String(); basepath = config["basepath"].String();
@ -128,7 +78,7 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c
JsonNode toAdd = frame; JsonNode toAdd = frame;
JsonUtils::inherit(toAdd, base); JsonUtils::inherit(toAdd, base);
toAdd["file"].String() = basepath + frame.String(); 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")) if (toAdd.Struct().count("defFile"))
toAdd["defFile"].String() = basepath + node["defFile"].String(); 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); static constexpr std::array scaledSpritesPath = {
auto animPath = AnimationPath::builtin(tmp.first.getName()); "", // 0x
AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/"); "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); auto it = animationLayouts.find(actualPath);
@ -184,15 +145,11 @@ RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const Anim
std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]); std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
stream->read(textData.get(), 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; animationLayouts[actualPath] = result;
return animationLayouts[actualPath]; return animationLayouts[actualPath];
} }
@ -202,209 +159,174 @@ int RenderHandler::getScalingFactor() const
return GH.screenHandler().getScalingFactor(); 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)) if (!layout.count(group))
return ImageLocator(ImagePath::builtin("DEFAULT")); return ImageLocator();
if (frame >= layout.at(group).size()) if (frame >= layout.at(group).size())
return ImageLocator(ImagePath::builtin("DEFAULT")); return ImageLocator();
const auto & locator = layout.at(group).at(frame); const auto & locator = layout.at(group).at(frame);
if (locator.image || locator.defFile) if (locator.image || locator.defFile)
return locator; 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); auto it = imageFiles.find(locator);
if (it != imageFiles.end()) if (it != imageFiles.end())
return it->second; return it->second;
// TODO: order should be different: auto sdlImage = loadImageFromFileUncached(locator);
// 1) try to find correctly scaled image auto scaledImage = std::make_shared<ScalableImageShared>(locator, sdlImage);
// 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);
storeCachedImage(locator, scaledImage);
return 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) if(locator.image)
{ {
// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load // 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) if(locator.defFile)
{ {
auto defFile = getAnimationFile(*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)) 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 else
{ {
logGlobal->error("Frame %d in group %d not found in file: %s", logGlobal->error("Frame %d in group %d not found in file: %s",
locator.defFrame, locator.defGroup, locator.defFile->getName().c_str()); 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!"); 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; 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)) static constexpr std::array scaledDataPath = {
return imageFiles.at(locator); "", // 0x
"DATA/",
"DATA2X/",
"DATA3X/",
"DATA4X/",
};
auto result = loadImageFromFileUncached(locator); static constexpr std::array scaledSpritesPath = {
storeCachedImage(locator, result); "", // 0x
return result; "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) std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator)
{
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)
{ {
ImageLocator adjustedLocator = locator; ImageLocator adjustedLocator = locator;
if(adjustedLocator.image) std::shared_ptr<ScalableImageInstance> result;
{
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);
}
if (adjustedLocator.scalingFactor == 0) if (adjustedLocator.scalingFactor == 0)
{ {
auto scaledLocator = adjustedLocator; auto scaledLocator = adjustedLocator;
scaledLocator.scalingFactor = getScalingFactor(); scaledLocator.scalingFactor = getScalingFactor();
return loadImageImpl(scaledLocator)->createImageReference(mode); result = loadImageImpl(scaledLocator)->createImageReference();
} }
else 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) std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
{ {
auto tmp = getScalePath(path); ImageLocator locator = getLocatorForAnimationFrame(path, frame, group, 1, mode);
ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group); if (!locator.empty())
locator.preScaledFactor = tmp.second; return loadImage(locator);
return loadImage(locator, mode); else
return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode));
} }
std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
{ {
ImageLocator locator(path); ImageLocator locator(path, mode);
return loadImage(locator, 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) 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) void RenderHandler::addImageListEntries(const EntityService * service)
@ -416,7 +338,7 @@ void RenderHandler::addImageListEntries(const EntityService * service)
if (imageName.empty()) if (imageName.empty())
return; return;
auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName)); auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::SIMPLE);
JsonNode entry; JsonNode entry;
entry["file"].String() = imageName; entry["file"].String() = imageName;
@ -424,7 +346,7 @@ void RenderHandler::addImageListEntries(const EntityService * service)
if (index >= layout[group].size()) if (index >= layout[group].size())
layout[group].resize(index + 1); layout[group].resize(index + 1);
layout[group][index] = ImageLocator(entry); layout[group][index] = ImageLocator(entry, EImageBlitMode::SIMPLE);
}); });
}); });
} }

View File

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

View File

@ -11,76 +11,24 @@
#include "SDLImage.h" #include "SDLImage.h"
#include "SDLImageLoader.h" #include "SDLImageLoader.h"
#include "SDLImageScaler.h"
#include "SDL_Extensions.h" #include "SDL_Extensions.h"
#include "../render/ColorFilter.h" #include "../render/ColorFilter.h"
#include "../render/Colors.h"
#include "../render/CBitmapHandler.h" #include "../render/CBitmapHandler.h"
#include "../render/CDefFile.h" #include "../render/CDefFile.h"
#include "../render/Graphics.h"
#include "../xBRZ/xbrz.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <SDL_surface.h> #include <tbb/task_arena.h>
#include <SDL_image.h> #include <SDL_image.h>
#include <SDL_surface.h>
#include <SDL_version.h>
class SDLImageLoader; 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 int IImage::width() const
{ {
return dimensions().x; return dimensions().x;
@ -91,12 +39,11 @@ int IImage::height() const
return dimensions().y; 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), : surf(nullptr),
margins(0, 0), margins(0, 0),
fullSize(0, 0), fullSize(0, 0),
originalPalette(nullptr), originalPalette(nullptr)
preScaleFactor(preScaleFactor)
{ {
SDLImageLoader loader(this); SDLImageLoader loader(this);
data->loadFrame(frame, group, loader); data->loadFrame(frame, group, loader);
@ -104,12 +51,11 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group
savePalette(); savePalette();
} }
SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor) SDLImageShared::SDLImageShared(SDL_Surface * from)
: surf(nullptr), : surf(nullptr),
margins(0, 0), margins(0, 0),
fullSize(0, 0), fullSize(0, 0),
originalPalette(nullptr), originalPalette(nullptr)
preScaleFactor(preScaleFactor)
{ {
surf = from; surf = from;
if (surf == nullptr) if (surf == nullptr)
@ -122,12 +68,11 @@ SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor)
fullSize.y = surf->h; fullSize.y = surf->h;
} }
SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor) SDLImageShared::SDLImageShared(const ImagePath & filename)
: surf(nullptr), : surf(nullptr),
margins(0, 0), margins(0, 0),
fullSize(0, 0), fullSize(0, 0),
originalPalette(nullptr), originalPalette(nullptr)
preScaleFactor(preScaleFactor)
{ {
surf = BitmapHandler::loadBitmap(filename); 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 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) if (!surf)
return; return;
@ -199,89 +205,23 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin
void SDLImageShared::optimizeSurface() void SDLImageShared::optimizeSurface()
{ {
assert(upscalingInProgress == false);
if (!surf) if (!surf)
return; return;
int left = surf->w; SDLImageOptimizer optimizer(surf, Rect(margins, fullSize));
int top = surf->h;
int right = 0;
int bottom = 0;
// locate fully-transparent area around image optimizer.optimizeSurface(surf);
// H3 hadles this on format level, but mods or images scaled in runtime do not SDL_FreeSurface(surf);
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) surf = optimizer.acquireResultSurface();
{ margins = optimizer.getResultDimensions().topLeft();
// opaque fullSize = optimizer.getResultDimensions().dimensions();
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;
}
} }
std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const
{ {
assert(upscalingInProgress == false);
if (factor <= 0) if (factor <= 0)
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); 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) if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, palette); SDL_SetSurfacePalette(surf, palette);
SDL_Surface * scaled = nullptr; // simple heuristics to differentiate tileable UI elements from map object / combat assets
if(preScaleFactor == factor) EScalingAlgorithm algorithm;
return shared_from_this(); if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
else if(preScaleFactor == 1) algorithm = EScalingAlgorithm::XBRZ_OPAQUE;
{
// 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);
}
else 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); auto result = std::make_shared<SDLImageShared>(this, factor, algorithm);
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);
if (surf->format->palette) if (surf->format->palette)
SDL_SetSurfacePalette(surf, originalPalette); 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 std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
{ {
float scaleX = static_cast<float>(size.x) / fullSize.x; assert(upscalingInProgress == false);
float scaleY = static_cast<float>(size.y) / fullSize.y;
if (palette && surf->format->palette) if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, 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 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]); CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]);
@ -340,13 +291,9 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size,
else else
CSDL_Ext::setDefaultColorKey(scaled);//just in case CSDL_Ext::setDefaultColorKey(scaled);//just in case
auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor); auto ret = std::make_shared<SDLImageShared>(scaled);
ret->fullSize = scaler.getResultDimensions().dimensions();
ret->fullSize.x = (int) round((float)fullSize.x * scaleX); ret->margins = scaler.getResultDimensions().topLeft();
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);
// erase our own reference // erase our own reference
SDL_FreeSurface(scaled); 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 void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const
{ {
assert(upscalingInProgress == false);
if (!surf) if (!surf)
return; return;
@ -369,13 +317,9 @@ void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palet
SDL_SetSurfacePalette(surf, originalPalette); SDL_SetSurfacePalette(surf, originalPalette);
} }
void SDLImageIndexed::playerColored(PlayerColor player)
{
graphics->setPlayerPalette(currentPalette, player);
}
bool SDLImageShared::isTransparent(const Point & coords) const bool SDLImageShared::isTransparent(const Point & coords) const
{ {
assert(upscalingInProgress == false);
if (surf) if (surf)
return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y); return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y);
else else
@ -384,31 +328,34 @@ bool SDLImageShared::isTransparent(const Point & coords) const
Rect SDLImageShared::contentRect() const Rect SDLImageShared::contentRect() const
{ {
auto tmpMargins = margins / preScaleFactor; assert(upscalingInProgress == false);
auto tmpSize = Point(surf->w, surf->h) / preScaleFactor; auto tmpMargins = margins;
auto tmpSize = Point(surf->w, surf->h);
return Rect(tmpMargins, tmpSize); 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 Point SDLImageShared::dimensions() const
{ {
return fullSize / preScaleFactor; assert(upscalingInProgress == false);
} return fullSize;
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);
} }
std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const
{ {
assert(upscalingInProgress == false);
if (!surf) if (!surf)
return shared_from_this(); return shared_from_this();
SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf); 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->fullSize = fullSize;
ret->margins.x = margins.x; ret->margins.x = margins.x;
ret->margins.y = fullSize.y - surf->h - margins.y; 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 std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
{ {
assert(upscalingInProgress == false);
if (!surf) if (!surf)
return shared_from_this(); return shared_from_this();
SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf); 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->fullSize = fullSize;
ret->margins.x = fullSize.x - surf->w - margins.x; ret->margins.x = fullSize.x - surf->w - margins.x;
ret->margins.y = margins.y; 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 // Keep the original palette, in order to do color switching operation
void SDLImageShared::savePalette() void SDLImageShared::savePalette()
{ {
assert(upscalingInProgress == false);
// For some images that don't have palette, skip this // For some images that don't have palette, skip this
if(surf->format->palette == nullptr) if(surf->format->palette == nullptr)
return; return;
@ -445,219 +394,8 @@ void SDLImageShared::savePalette()
SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors); 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() SDLImageShared::~SDLImageShared()
{ {
SDL_FreeSurface(surf); SDL_FreeSurface(surf);
SDL_FreePalette(originalPalette); SDL_FreePalette(originalPalette);
} }
SDLImageBase::SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode)
:image(image)
, alphaValue(SDL_ALPHA_OPAQUE)
, blitMode(mode)
{}
std::shared_ptr<const ISharedImage> SDLImageBase::getSharedImage() const
{
return image;
}
void SDLImageRGB::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
{
image->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
}
void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
{
image->draw(where, currentPalette, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
}
void SDLImageIndexed::exportBitmap(const boost::filesystem::path & path) const
{
image->exportBitmap(path, currentPalette);
}
void SDLImageIndexed::scaleTo(const Point & size)
{
image = image->scaleTo(size, currentPalette);
}
void SDLImageRGB::scaleTo(const Point & size)
{
image = image->scaleTo(size, nullptr);
}
void SDLImageIndexed::scaleInteger(int factor)
{
image = image->scaleInteger(factor, currentPalette, blitMode);
}
void SDLImageRGB::scaleInteger(int factor)
{
image = image->scaleInteger(factor, nullptr, blitMode);
}
void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const
{
image->exportBitmap(path, nullptr);
}
bool SDLImageBase::isTransparent(const Point & coords) const
{
return image->isTransparent(coords);
}
Rect SDLImageBase::contentRect() const
{
return image->contentRect();
}
Point SDLImageBase::dimensions() const
{
return image->dimensions();
}
void SDLImageBase::setAlpha(uint8_t value)
{
alphaValue = value;
}
void SDLImageBase::setBlitMode(EImageBlitMode mode)
{
blitMode = mode;
}
void SDLImageRGB::setOverlayColor(const ColorRGBA & color)
{}
void SDLImageRGB::playerColored(PlayerColor player)
{}
void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
{}
void SDLImageRGB::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
{}

View File

@ -27,16 +27,15 @@ struct SDL_Palette;
class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this<SDLImageShared>, boost::noncopyable class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this<SDLImageShared>, boost::noncopyable
{ {
//Surface without empty borders //Surface without empty borders
SDL_Surface * surf; SDL_Surface * surf = nullptr;
SDL_Palette * originalPalette; SDL_Palette * originalPalette = nullptr;
//size of left and top borders //size of left and top borders
Point margins; Point margins;
//total size including borders //total size including borders
Point fullSize; Point fullSize;
//pre scaled image std::atomic_bool upscalingInProgress = false;
int preScaleFactor;
// Keep the original palette, in order to do color switching operation // Keep the original palette, in order to do color switching operation
void savePalette(); void savePalette();
@ -45,20 +44,27 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
public: public:
//Load image from def file //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 //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 //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(); ~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 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; void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override;
Point dimensions() const override; Point dimensions() const override;
bool isTransparent(const Point & coords) const override; bool isTransparent(const Point & coords) const override;
Rect contentRect() 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> horizontalFlip() const override;
[[nodiscard]] std::shared_ptr<const ISharedImage> verticalFlip() 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; [[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
@ -66,58 +72,3 @@ public:
friend class SDLImageLoader; friend class SDLImageLoader;
}; };
class SDLImageBase : public IImage, boost::noncopyable
{
protected:
std::shared_ptr<const ISharedImage> image;
uint8_t alphaValue;
EImageBlitMode blitMode;
public:
SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode);
bool isTransparent(const Point & coords) const override;
Rect contentRect() const override;
Point dimensions() const override;
void setAlpha(uint8_t value) override;
void setBlitMode(EImageBlitMode mode) override;
std::shared_ptr<const ISharedImage> getSharedImage() const override;
};
class SDLImageIndexed final : public SDLImageBase
{
SDL_Palette * currentPalette = nullptr;
SDL_Palette * originalPalette = nullptr;
void setShadowTransparency(float factor);
void preparePalette();
public:
SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
~SDLImageIndexed();
void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
void setOverlayColor(const ColorRGBA & color) override;
void playerColored(PlayerColor player) override;
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
void scaleInteger(int factor) override;
void scaleTo(const Point & size) override;
void exportBitmap(const boost::filesystem::path & path) const override;
};
class SDLImageRGB final : public SDLImageBase
{
public:
using SDLImageBase::SDLImageBase;
void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
void setOverlayColor(const ColorRGBA & color) override;
void playerColored(PlayerColor player) override;
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
void scaleInteger(int factor) override;
void scaleTo(const Point & size) override;
void exportBitmap(const boost::filesystem::path & path) const override;
};

View File

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

View File

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

View File

@ -14,6 +14,7 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/Graphics.h" #include "../render/Graphics.h"
#include "../render/IImage.h"
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../CMT.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) void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint)
{ {
SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput); 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); 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<3>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);

View File

@ -27,14 +27,6 @@ class Point;
VCMI_LIB_NAMESPACE_END 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 namespace CSDL_Ext
{ {
@ -98,10 +90,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
template<int bpp> template<int bpp>
SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value 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> template<int bpp>
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect); void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
void convertToGrayscale(SDL_Surface * surf, const Rect & rect); void convertToGrayscale(SDL_Surface * surf, const Rect & rect);

View File

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

View File

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

View File

@ -11,13 +11,14 @@
#include "StdInc.h" #include "StdInc.h"
#include "ScreenHandler.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/CConfigHandler.h"
#include "../../lib/constants/StringConstants.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 #ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h" #include "../lib/CAndroidVMHelper.h"

View File

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

View File

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

View File

@ -18,7 +18,6 @@
#include "../windows/CMessage.h" #include "../windows/CMessage.h"
#include "../windows/InfoWindows.h" #include "../windows/InfoWindows.h"
#include "../adventureMap/CInGameConsole.h" #include "../adventureMap/CInGameConsole.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/Canvas.h" #include "../render/Canvas.h"
#include "../render/Graphics.h" #include "../render/Graphics.h"
#include "../render/IFont.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 lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * fontPtr->getLineHeight());
Point lineSize = Point(getTextLocation().w, 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++) for(int i = beginLine; i < std::min(totalLines, endLine); i++)
{ {

View File

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

View File

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

View File

@ -20,7 +20,7 @@
#include "../widgets/TextControls.h" #include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h" #include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h" #include "../windows/InfoWindows.h"
#include "../render/Canvas.h" #include "../render/CanvasImage.h"
#include "../render/IImage.h" #include "../render/IImage.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../render/Graphics.h" #include "../render/Graphics.h"
@ -58,9 +58,10 @@ CMapOverview::CMapOverview(const std::string & mapName, const std::string & file
fitToScreen(10); 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 y = 0; y < map->height; ++y)
for (int x = 0; x < map->width; ++x) 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); 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; CMapService mapService;
std::unique_ptr<CMap> map; std::unique_ptr<CMap> map;
@ -113,9 +114,9 @@ std::vector<Canvas> CMapOverviewWidget::createMinimaps(ResourcePath resource) co
return createMinimaps(map); 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++) for(int i = 0; i < (map->twoLevel ? 2 : 1); i++)
ret.push_back(createMinimapForLayer(map, i)); ret.push_back(createMinimapForLayer(map, i));
@ -133,17 +134,15 @@ std::shared_ptr<CPicture> CMapOverviewWidget::buildDrawMinimap(const JsonNode &
if(id >= minimaps.size()) if(id >= minimaps.size())
return nullptr; return nullptr;
Rect minimapRect = minimaps[id].getRenderArea(); Point minimapRect = minimaps[id]->dimensions();
double maxSideLengthSrc = std::max(minimapRect.w, minimapRect.h); double maxSideLengthSrc = std::max(minimapRect.x, minimapRect.y);
double maxSideLengthDst = std::max(rect.w, rect.h); double maxSideLengthDst = std::max(rect.w, rect.h);
double resize = maxSideLengthSrc / maxSideLengthDst; 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); minimaps[id]->scaleTo(newMinimapSize, EScalingAlgorithm::NEAREST); // for sharp-looking minimap
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());
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): CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):

View File

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

View File

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

View File

@ -23,6 +23,7 @@
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../render/Canvas.h" #include "../render/Canvas.h"
#include "../render/CanvasImage.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
@ -87,8 +88,7 @@ std::shared_ptr<CPicture> CWindowObject::createBg(const ImagePath & imageName, b
if(imageName.empty()) if(imageName.empty())
return nullptr; return nullptr;
auto image = std::make_shared<CPicture>(imageName); auto image = std::make_shared<CPicture>(imageName, Point(0,0), EImageBlitMode::OPAQUE);
image->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
if(playerColored) if(playerColored)
image->setPlayerColor(LOCPLINT->playerID); image->setPlayerColor(LOCPLINT->playerID);
return image; return image;
@ -116,8 +116,7 @@ void CWindowObject::updateShadow()
void CWindowObject::setShadow(bool on) void CWindowObject::setShadow(bool on)
{ {
//size of shadow //size of shadow
int sizeOriginal = 8; int size = 8;
int size = sizeOriginal * GH.screenHandler().getScalingFactor();
if(on == !shadowParts.empty()) if(on == !shadowParts.empty())
return; return;
@ -130,61 +129,12 @@ void CWindowObject::setShadow(bool on)
if(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 //FIXME: do something with this points
Point shadowStart; Point shadowStart;
if (options & BORDERED) if (options & BORDERED)
shadowStart = Point(sizeOriginal - 14, sizeOriginal - 14); shadowStart = Point(size - 14, size - 14);
else else
shadowStart = Point(sizeOriginal, sizeOriginal); shadowStart = Point(size, size);
Point shadowPos; Point shadowPos;
if (options & BORDERED) if (options & BORDERED)
@ -198,26 +148,36 @@ void CWindowObject::setShadow(bool on)
else else
fullsize = Point(pos.w, pos.h); fullsize = Point(pos.w, pos.h);
//create base 8x8 piece of shadow Point sizeCorner(size, size);
SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); Point sizeRight(fullsize.x - size, size);
SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, (fullsize.x - sizeOriginal) * GH.screenHandler().getScalingFactor(), size); Point sizeBottom(size, fullsize.y - size);
SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, (fullsize.y - sizeOriginal) * GH.screenHandler().getScalingFactor());
blitAlphaCol(shadowBottom, 0); //create base 8x8 piece of shadow
blitAlphaRow(shadowRight, 0); 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 //generate "shadow" object with these 3 pieces in it
{ {
OBJECT_CONSTRUCTION; 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>( imageCorner, 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>( imageRight, Point(shadowStart.x, shadowPos.y)));
shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowBottom), Point(shadowStart.x, shadowPos.y))); shadowParts.push_back(std::make_shared<CPicture>( imageBottom, Point(shadowPos.x, shadowStart.y)));
} }
SDL_FreeSurface(shadowCorner);
SDL_FreeSurface(shadowBottom);
SDL_FreeSurface(shadowRight);
} }
} }

View File

@ -236,7 +236,7 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst)
{ {
assert(artInst.getScrollSpellID().num >= 0); assert(artInst.getScrollSpellID().num >= 0);
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("spellscr"), artInst.getScrollSpellID().num, 0, EImageBlitMode::COLORKEY); 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); CCS->curh->dragAndDropCursor(image);
} }

View File

@ -20,7 +20,7 @@ For upscaled images you have to use following folders (next to `sprites`, `data`
The sprites should have the same name and folder structure as in `sprites`, `data` and `video` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images. 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`. 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. 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 - 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 - Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover
For images that are used for player-colored interface, it is possible to provide custom images for each player. For example `HeroScr4-red.png` will be used for hero window of red player.
- Currently needed for all UI elements that are player-colored in HoMM3.
- Can NOT be used for player-owned adventure objects. Use `-overlay` images for such objects.
- Possible suffixes are `red`, `blue`, `tan`, `green`, `orange`, `purple`, `teal`, `pink`, `neutral` (used only for turn order queue in combat)
It is possible to use such additional images for both upscaled (xbrz) graphics, as well as for original / 1x images. When using this feature for original / 1x image, make sure that your base image (without suffix) is rgb/rgba image, and not indexed / with palette

View File

@ -54,6 +54,11 @@ public:
return Point(x*mul, y*mul); 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 constexpr Point operator*(const Point &b) const
{ {
return Point(x*b.x,y*b.y); return Point(x*b.x,y*b.y);