mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +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:
		| @@ -88,6 +88,7 @@ set(vcmiclientcommon_SRCS | ||||
| 	render/CBitmapHandler.cpp | ||||
| 	render/CDefFile.cpp | ||||
| 	render/Canvas.cpp | ||||
| 	render/CanvasImage.cpp | ||||
| 	render/ColorFilter.cpp | ||||
| 	render/Colors.cpp | ||||
| 	render/Graphics.cpp | ||||
| @@ -99,10 +100,11 @@ set(vcmiclientcommon_SRCS | ||||
| 	renderSDL/CursorHardware.cpp | ||||
| 	renderSDL/CursorSoftware.cpp | ||||
| 	renderSDL/FontChain.cpp | ||||
| 	renderSDL/ImageScaled.cpp | ||||
| 	renderSDL/ScalableImage.cpp | ||||
| 	renderSDL/RenderHandler.cpp | ||||
| 	renderSDL/SDLImage.cpp | ||||
| 	renderSDL/SDLImageLoader.cpp | ||||
| 	renderSDL/SDLImageScaler.cpp | ||||
| 	renderSDL/SDLRWwrapper.cpp | ||||
| 	renderSDL/ScreenHandler.cpp | ||||
| 	renderSDL/SDL_Extensions.cpp | ||||
| @@ -290,6 +292,7 @@ set(vcmiclientcommon_HEADERS | ||||
| 	render/CBitmapHandler.h | ||||
| 	render/CDefFile.h | ||||
| 	render/Canvas.h | ||||
| 	render/CanvasImage.h | ||||
| 	render/ColorFilter.h | ||||
| 	render/Colors.h | ||||
| 	render/EFont.h | ||||
| @@ -307,10 +310,11 @@ set(vcmiclientcommon_HEADERS | ||||
| 	renderSDL/CursorHardware.h | ||||
| 	renderSDL/CursorSoftware.h | ||||
| 	renderSDL/FontChain.h | ||||
| 	renderSDL/ImageScaled.h | ||||
| 	renderSDL/ScalableImage.h | ||||
| 	renderSDL/RenderHandler.h | ||||
| 	renderSDL/SDLImage.h | ||||
| 	renderSDL/SDLImageLoader.h | ||||
| 	renderSDL/SDLImageScaler.h | ||||
| 	renderSDL/SDLRWwrapper.h | ||||
| 	renderSDL/ScreenHandler.h | ||||
| 	renderSDL/SDL_Extensions.h | ||||
|   | ||||
| @@ -1171,7 +1171,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component | ||||
| 		if(t) | ||||
| 		{ | ||||
| 			auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); | ||||
| 			image->scaleTo(Point(35, 23)); | ||||
| 			image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST); | ||||
| 			images.push_back(image); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -22,7 +22,6 @@ | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../windows/InfoWindows.h" | ||||
|  | ||||
| #include "../../CCallback.h" | ||||
| @@ -178,7 +177,7 @@ void CMinimap::mouseDragged(const Point & cursorPosition, const Point & lastUpda | ||||
|  | ||||
| void CMinimap::showAll(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), aiShield->pos); | ||||
| 	CanvasClipRectGuard guard(to, aiShield->pos); | ||||
| 	CIntObject::showAll(to); | ||||
|  | ||||
| 	if(minimap) | ||||
|   | ||||
| @@ -26,7 +26,6 @@ | ||||
| #include "../render/CAnimation.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../gui/CursorHandler.h" | ||||
| @@ -857,7 +856,7 @@ void BattleFieldController::tick(uint32_t msPassed) | ||||
|  | ||||
| void BattleFieldController::show(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos); | ||||
| 	CanvasClipRectGuard guard(to, pos); | ||||
|  | ||||
| 	renderBattlefield(to); | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
| #include "../render/CAnimation.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../eventsSDL/InputHandler.h" | ||||
|  | ||||
| #include "../../CCallback.h" | ||||
| @@ -76,7 +75,7 @@ void BasicMapView::tick(uint32_t msPassed) | ||||
|  | ||||
| void BasicMapView::show(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos); | ||||
| 	CanvasClipRectGuard guard(to, pos); | ||||
| 	render(to, false); | ||||
|  | ||||
| 	controller->afterRender(); | ||||
| @@ -84,7 +83,7 @@ void BasicMapView::show(Canvas & to) | ||||
|  | ||||
| void BasicMapView::showAll(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos); | ||||
| 	CanvasClipRectGuard guard(to, pos); | ||||
| 	render(to, true); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -381,12 +381,12 @@ Point CVideoInstance::size() | ||||
| 	return dimensions / GH.screenHandler().getScalingFactor(); | ||||
| } | ||||
|  | ||||
| void CVideoInstance::show(const Point & position, Canvas & canvas) | ||||
| void CVideoInstance::show(const Point & position, SDL_Surface * to) | ||||
| { | ||||
| 	if(sws == nullptr) | ||||
| 		throw std::runtime_error("No video to show!"); | ||||
|  | ||||
| 	CSDL_Ext::blitSurface(surface, canvas.getInternalSurface(), position * GH.screenHandler().getScalingFactor()); | ||||
| 	CSDL_Ext::blitSurface(surface, to, position * GH.screenHandler().getScalingFactor()); | ||||
| } | ||||
|  | ||||
| double FFMpegStream::getCurrentFrameEndTime() const | ||||
|   | ||||
| @@ -98,7 +98,7 @@ public: | ||||
| 	bool videoEnded() final; | ||||
| 	Point size() final; | ||||
|  | ||||
| 	void show(const Point & position, Canvas & canvas) final; | ||||
| 	void show(const Point & position, SDL_Surface * to) final; | ||||
| 	void tick(uint32_t msPassed) final; | ||||
| 	void activate() final; | ||||
| 	void deactivate() final; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| #include "../lib/filesystem/ResourcePath.h" | ||||
|  | ||||
| class Canvas; | ||||
| struct SDL_Surface; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
| class Point; | ||||
| @@ -30,7 +30,7 @@ public: | ||||
| 	virtual Point size() = 0; | ||||
|  | ||||
| 	/// Displays current frame at specified position | ||||
| 	virtual void show(const Point & position, Canvas & canvas) = 0; | ||||
| 	virtual void show(const Point & position, SDL_Surface * to) = 0; | ||||
|  | ||||
| 	/// Advances video playback by specified duration | ||||
| 	virtual void tick(uint32_t msPassed) = 0; | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IImageLoader.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/CanvasImage.h" | ||||
| #include "../render/ColorFilter.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../render/CAnimation.h" | ||||
| @@ -58,12 +59,13 @@ void AssetGenerator::createAdventureOptionsCleanBackground() | ||||
| 		return; | ||||
| 	ResourcePath savePath(filename, EResType::IMAGE); | ||||
|  | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK")); | ||||
| 	locator.scalingFactor = 1; | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE); | ||||
|  | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); | ||||
|  | ||||
| 	auto image = GH.renderHandler().createImage(Point(575, 585), CanvasScalingPolicy::IGNORE); | ||||
| 	Canvas canvas = image->getCanvas(); | ||||
|  | ||||
| 	Canvas canvas = Canvas(Point(575, 585), CanvasScalingPolicy::IGNORE); | ||||
| 	canvas.draw(img, Point(0, 0), Rect(0, 0, 575, 585)); | ||||
| 	canvas.draw(img, Point(54, 121), Rect(54, 123, 335, 1)); | ||||
| 	canvas.draw(img, Point(158, 84), Rect(156, 84, 2, 37)); | ||||
| @@ -72,8 +74,6 @@ void AssetGenerator::createAdventureOptionsCleanBackground() | ||||
| 	canvas.draw(img, Point(53, 567), Rect(53, 520, 339, 3)); | ||||
| 	canvas.draw(img, Point(53, 520), Rect(53, 264, 339, 47)); | ||||
|  | ||||
| 	std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface()); | ||||
|  | ||||
| 	image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); | ||||
| } | ||||
|  | ||||
| @@ -88,11 +88,11 @@ void AssetGenerator::createBigSpellBook() | ||||
| 		return; | ||||
| 	ResourcePath savePath(filename, EResType::IMAGE); | ||||
|  | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("SpelBack")); | ||||
| 	locator.scalingFactor = 1; | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE); | ||||
|  | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 	Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE); | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); | ||||
| 	auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); | ||||
| 	Canvas canvas = image->getCanvas(); | ||||
| 	// edges | ||||
| 	canvas.draw(img, Point(0, 0), Rect(15, 38, 90, 45)); | ||||
| 	canvas.draw(img, Point(0, 460), Rect(15, 400, 90, 141)); | ||||
| @@ -135,8 +135,6 @@ void AssetGenerator::createBigSpellBook() | ||||
| 	canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45)); | ||||
| 	canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47)); | ||||
|  | ||||
| 	std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface()); | ||||
|  | ||||
| 	image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); | ||||
| } | ||||
|  | ||||
| @@ -152,10 +150,9 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) | ||||
|  | ||||
| 	ResourcePath savePath(filename, EResType::IMAGE); | ||||
|  | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("DiBoxBck")); | ||||
| 	locator.scalingFactor = 1; | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE); | ||||
|  | ||||
| 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator); | ||||
|  | ||||
| 	// transform to make color of brown DIBOX.PCX texture match color of specified player | ||||
| 	auto filterSettings = VLC->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"]; | ||||
| @@ -199,10 +196,10 @@ void AssetGenerator::createCombatUnitNumberWindow() | ||||
| 	   !CResourceHandler::get("local")->createResource(savePathNegative.getOriginalName() + ".png")) | ||||
| 		return; | ||||
|  | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN")); | ||||
| 	locator.scalingFactor = 1; | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"), EImageBlitMode::OPAQUE); | ||||
| 	locator.layer = EImageBlitMode::OPAQUE; | ||||
|  | ||||
| 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator); | ||||
|  | ||||
| 	static const auto shifterNormal   = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f ); | ||||
| 	static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f ); | ||||
| @@ -233,12 +230,12 @@ void AssetGenerator::createCampaignBackground() | ||||
| 		return; | ||||
| 	ResourcePath savePath(filename, EResType::IMAGE); | ||||
|  | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("CAMPBACK")); | ||||
| 	locator.scalingFactor = 1; | ||||
| 	auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE); | ||||
|  | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); | ||||
| 	auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); | ||||
| 	Canvas canvas = image->getCanvas(); | ||||
|  | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 	Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE); | ||||
| 	 | ||||
| 	canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); | ||||
|  | ||||
| 	// left image | ||||
| @@ -263,13 +260,10 @@ void AssetGenerator::createCampaignBackground() | ||||
| 	canvas.draw(img, Point(404, 414), Rect(313, 74, 197, 114)); | ||||
|  | ||||
| 	// skull | ||||
| 	auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC")); | ||||
| 	locatorSkull.scalingFactor = 1; | ||||
| 	std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE); | ||||
| 	auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC"), EImageBlitMode::OPAQUE); | ||||
| 	std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull); | ||||
| 	canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19)); | ||||
|  | ||||
| 	std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface()); | ||||
|  | ||||
| 	image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); | ||||
| } | ||||
|  | ||||
| @@ -290,11 +284,11 @@ void AssetGenerator::createChroniclesCampaignImages() | ||||
| 			continue; | ||||
| 		ResourcePath savePath(filename, EResType::IMAGE); | ||||
|  | ||||
| 		auto locator = ImageLocator(imgPathBg); | ||||
| 		locator.scalingFactor = 1; | ||||
| 		auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); | ||||
|  | ||||
| 		std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); | ||||
| 		Canvas canvas = Canvas(Point(200, 116), CanvasScalingPolicy::IGNORE); | ||||
| 		std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); | ||||
| 		auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); | ||||
| 		Canvas canvas = image->getCanvas(); | ||||
| 		 | ||||
| 		switch (i) | ||||
| 		{ | ||||
| @@ -323,9 +317,8 @@ void AssetGenerator::createChroniclesCampaignImages() | ||||
| 			canvas.draw(img, Point(0, 0), Rect(268, 210, 200, 116)); | ||||
|  | ||||
| 			//skull | ||||
| 			auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1")); | ||||
| 			locatorSkull.scalingFactor = 1; | ||||
| 			std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE); | ||||
| 			auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); | ||||
| 			std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull); | ||||
| 			canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); | ||||
| 			canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); | ||||
| 			canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); | ||||
| @@ -334,8 +327,6 @@ void AssetGenerator::createChroniclesCampaignImages() | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface()); | ||||
|  | ||||
| 		image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); | ||||
| 	} | ||||
| } | ||||
| @@ -403,8 +394,7 @@ void AssetGenerator::createPaletteShiftedSprites() | ||||
| 					return; | ||||
|  | ||||
| 				auto imgLoc = anim->getImageLocator(j, 0); | ||||
| 				imgLoc.scalingFactor = 1; | ||||
| 				auto img = GH.renderHandler().loadImage(imgLoc, EImageBlitMode::COLORKEY); | ||||
| 				auto img = GH.renderHandler().loadImage(imgLoc); | ||||
| 				for(int k = 0; k < paletteAnimations[i].size(); k++) | ||||
| 				{ | ||||
| 					auto element = paletteAnimations[i][k]; | ||||
| @@ -420,9 +410,9 @@ void AssetGenerator::createPaletteShiftedSprites() | ||||
| 					} | ||||
| 				} | ||||
| 				 | ||||
| 				Canvas canvas = Canvas(Point(32, 32), CanvasScalingPolicy::IGNORE); | ||||
| 				auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); | ||||
| 				Canvas canvas = image->getCanvas(); | ||||
| 				canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); | ||||
| 				std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface()); | ||||
| 				image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); | ||||
|  | ||||
| 				JsonNode node(JsonMap{ | ||||
|   | ||||
| @@ -30,7 +30,7 @@ bool CAnimation::loadFrame(size_t frame, size_t group, bool verbose) | ||||
| 	if(auto image = getImageImpl(frame, group, false)) | ||||
| 		return true; | ||||
|  | ||||
| 	std::shared_ptr<IImage> image = GH.renderHandler().loadImage(getImageLocator(frame, group), mode); | ||||
| 	std::shared_ptr<IImage> image = GH.renderHandler().loadImage(getImageLocator(frame, group)); | ||||
|  | ||||
| 	if(image) | ||||
| 	{ | ||||
| @@ -224,5 +224,5 @@ ImageLocator CAnimation::getImageLocator(size_t frame, size_t group) const | ||||
| 		throw std::runtime_error("Frame " + std::to_string(frame) + " of group " + std::to_string(group) + " is missing from animation " + name.getOriginalName() ); | ||||
| 	} | ||||
|  | ||||
| 	return ImageLocator(name, frame, group); | ||||
| 	return ImageLocator(name, frame, group, mode); | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "Canvas.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../media/IVideoPlayer.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| @@ -102,11 +103,21 @@ Canvas::~Canvas() | ||||
| 	SDL_FreeSurface(surface); | ||||
| } | ||||
|  | ||||
| void Canvas::draw(IVideoInstance & video, const Point & pos) | ||||
| { | ||||
| 	video.show(pos, surface); | ||||
| } | ||||
|  | ||||
| void Canvas::draw(const IImage& image, const Point & pos) | ||||
| { | ||||
| 	image.draw(surface, transformPos(pos), nullptr, getScalingFactor()); | ||||
| } | ||||
|  | ||||
| void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos) | ||||
| { | ||||
| 	assert(image); | ||||
| 	if (image) | ||||
| 		image->draw(surface, transformPos(pos)); | ||||
| 		image->draw(surface, transformPos(pos), nullptr, getScalingFactor()); | ||||
| } | ||||
|  | ||||
| void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect) | ||||
| @@ -114,7 +125,7 @@ void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const | ||||
| 	Rect realSourceRect = sourceRect * getScalingFactor(); | ||||
| 	assert(image); | ||||
| 	if (image) | ||||
| 		image->draw(surface, transformPos(pos), &realSourceRect); | ||||
| 		image->draw(surface, transformPos(pos), &realSourceRect, getScalingFactor()); | ||||
| } | ||||
|  | ||||
| void Canvas::draw(const Canvas & image, const Point & pos) | ||||
| @@ -218,16 +229,22 @@ void Canvas::fillTexture(const std::shared_ptr<IImage>& image) | ||||
| 	for (int y=0; y < surface->h; y+= imageArea.h) | ||||
| 	{ | ||||
| 		for (int x=0; x < surface->w; x+= imageArea.w) | ||||
| 			image->draw(surface, Point(renderArea.x + x * getScalingFactor(), renderArea.y + y * getScalingFactor())); | ||||
| 			image->draw(surface, Point(renderArea.x + x * getScalingFactor(), renderArea.y + y * getScalingFactor()), nullptr, getScalingFactor()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDL_Surface * Canvas::getInternalSurface() | ||||
| { | ||||
| 	return surface; | ||||
| } | ||||
|  | ||||
| Rect Canvas::getRenderArea() const | ||||
| { | ||||
| 	return renderArea; | ||||
| } | ||||
|  | ||||
| CanvasClipRectGuard::CanvasClipRectGuard(Canvas & canvas, const Rect & rect): surf(canvas.surface) | ||||
| { | ||||
| 	CSDL_Ext::getClipRect(surf, oldRect); | ||||
| 	CSDL_Ext::setClipRect(surf, rect * GH.screenHandler().getScalingFactor()); | ||||
| } | ||||
|  | ||||
| CanvasClipRectGuard::~CanvasClipRectGuard() | ||||
| { | ||||
| 	CSDL_Ext::setClipRect(surf, oldRect); | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|  | ||||
| struct SDL_Surface; | ||||
| class IImage; | ||||
| class IVideoInstance; | ||||
| enum EFonts : int8_t; | ||||
|  | ||||
| enum class CanvasScalingPolicy | ||||
| @@ -27,6 +28,8 @@ enum class CanvasScalingPolicy | ||||
| /// Class that represents surface for drawing on | ||||
| class Canvas | ||||
| { | ||||
| 	friend class CanvasClipRectGuard; | ||||
|  | ||||
| 	/// Upscaler awareness. Must be first member for initialization | ||||
| 	CanvasScalingPolicy scalingPolicy; | ||||
|  | ||||
| @@ -72,6 +75,9 @@ public: | ||||
|  | ||||
| 	/// renders image onto this canvas at specified position | ||||
| 	void draw(const std::shared_ptr<IImage>& image, const Point & pos); | ||||
| 	void draw(const IImage& image, const Point & pos); | ||||
|  | ||||
| 	void draw(IVideoInstance & video, const Point & pos); | ||||
|  | ||||
| 	/// renders section of image bounded by sourceRect at specified position | ||||
| 	void draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect); | ||||
| @@ -114,9 +120,16 @@ public: | ||||
|  | ||||
| 	int getScalingFactor() const; | ||||
|  | ||||
| 	/// Compatibility method. AVOID USAGE. To be removed once SDL abstraction layer is finished. | ||||
| 	SDL_Surface * getInternalSurface(); | ||||
|  | ||||
| 	/// get the render area | ||||
| 	Rect getRenderArea() const; | ||||
| }; | ||||
|  | ||||
| class CanvasClipRectGuard : boost::noncopyable | ||||
| { | ||||
| 	SDL_Surface * surf; | ||||
| 	Rect oldRect; | ||||
|  | ||||
| public: | ||||
| 	CanvasClipRectGuard(Canvas & canvas, const Rect & rect); | ||||
| 	~CanvasClipRectGuard(); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										63
									
								
								client/render/CanvasImage.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								client/render/CanvasImage.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * CanvasImage.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "CanvasImage.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../renderSDL/SDLImageScaler.h" | ||||
|  | ||||
| #include <SDL_image.h> | ||||
| #include <SDL_surface.h> | ||||
|  | ||||
| CanvasImage::CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy) | ||||
| 	: surface(CSDL_Ext::newSurface(scalingPolicy == CanvasScalingPolicy::IGNORE ? size : (size * GH.screenHandler().getScalingFactor()))) | ||||
| 	, scalingPolicy(scalingPolicy) | ||||
| { | ||||
| } | ||||
|  | ||||
| void CanvasImage::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const | ||||
| { | ||||
| 	if(src) | ||||
| 		CSDL_Ext::blitSurface(surface, *src, where, pos); | ||||
| 	else | ||||
| 		CSDL_Ext::blitSurface(surface, where, pos); | ||||
| } | ||||
|  | ||||
| void CanvasImage::scaleTo(const Point & size, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	Point scaledSize = size * GH.screenHandler().getScalingFactor(); | ||||
|  | ||||
| 	SDLImageScaler scaler(surface); | ||||
| 	scaler.scaleSurface(scaledSize, algorithm); | ||||
| 	SDL_FreeSurface(surface); | ||||
| 	surface = scaler.acquireResultSurface(); | ||||
| } | ||||
|  | ||||
| void CanvasImage::exportBitmap(const boost::filesystem::path & path) const | ||||
| { | ||||
| 	IMG_SavePNG(surface, path.string().c_str()); | ||||
| } | ||||
|  | ||||
| Canvas CanvasImage::getCanvas() | ||||
| { | ||||
| 	return Canvas::createFromSurface(surface, scalingPolicy); | ||||
| } | ||||
|  | ||||
| Rect CanvasImage::contentRect() const | ||||
| { | ||||
| 	return Rect(Point(0, 0), dimensions()); | ||||
| } | ||||
|  | ||||
| Point CanvasImage::dimensions() const | ||||
| { | ||||
| 	return {surface->w, surface->h}; | ||||
| } | ||||
							
								
								
									
										41
									
								
								client/render/CanvasImage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								client/render/CanvasImage.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| /* | ||||
|  * CanvasImage.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "IImage.h" | ||||
| #include "Canvas.h" | ||||
|  | ||||
| class CanvasImage : public IImage | ||||
| { | ||||
| public: | ||||
| 	CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy); | ||||
|  | ||||
| 	Canvas getCanvas(); | ||||
|  | ||||
| 	void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override; | ||||
| 	void scaleTo(const Point & size, EScalingAlgorithm algorithm) override; | ||||
| 	void exportBitmap(const boost::filesystem::path & path) const override; | ||||
| 	Rect contentRect() const override; | ||||
| 	Point dimensions() const override; | ||||
|  | ||||
| 	//no-op methods | ||||
|  | ||||
| 	bool isTransparent(const Point & coords) const override{ return false;}; | ||||
| 	void setAlpha(uint8_t value) override{}; | ||||
| 	void playerColored(const PlayerColor & player) override{}; | ||||
| 	void setOverlayColor(const ColorRGBA & color) override{}; | ||||
| 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{}; | ||||
| 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{}; | ||||
|  | ||||
| private: | ||||
| 	SDL_Surface * surface; | ||||
| 	CanvasScalingPolicy scalingPolicy; | ||||
| }; | ||||
|  | ||||
| @@ -62,21 +62,28 @@ enum class EImageBlitMode : uint8_t | ||||
| 	ONLY_OVERLAY, | ||||
| }; | ||||
|  | ||||
| enum class EScalingAlgorithm : int8_t | ||||
| { | ||||
| 	NEAREST, | ||||
| 	BILINEAR, | ||||
| 	XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image. Only for integer scaling | ||||
| 	XBRZ_ALPHA // xbrz, image edges are considered to be transparent. Only for integer scaling | ||||
| }; | ||||
|  | ||||
| /// Base class for images for use in client code. | ||||
| /// This class represents current state of image, with potential transformations applied, such as player coloring | ||||
| class IImage | ||||
| { | ||||
| public: | ||||
| 	//draws image on surface "where" at position | ||||
| 	virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src = nullptr) const = 0; | ||||
| 	virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const = 0; | ||||
|  | ||||
| 	virtual void scaleTo(const Point & size) = 0; | ||||
| 	virtual void scaleInteger(int factor) = 0; | ||||
| 	virtual void scaleTo(const Point & size, EScalingAlgorithm algorithm) = 0; | ||||
|  | ||||
| 	virtual void exportBitmap(const boost::filesystem::path & path) const = 0; | ||||
|  | ||||
| 	//Change palette to specific player | ||||
| 	virtual void playerColored(PlayerColor player) = 0; | ||||
| 	virtual void playerColored(const PlayerColor & player) = 0; | ||||
|  | ||||
| 	//test transparency of specific pixel | ||||
| 	virtual bool isTransparent(const Point & coords) const = 0; | ||||
| @@ -92,13 +99,10 @@ public: | ||||
| 	virtual void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0; | ||||
|  | ||||
| 	virtual void setAlpha(uint8_t value) = 0; | ||||
| 	virtual void setBlitMode(EImageBlitMode mode) = 0; | ||||
|  | ||||
| 	//only indexed bitmaps with 7 special colors | ||||
| 	virtual void setOverlayColor(const ColorRGBA & color) = 0; | ||||
|  | ||||
| 	virtual std::shared_ptr<const ISharedImage> getSharedImage() const = 0; | ||||
|  | ||||
| 	virtual ~IImage() = default; | ||||
| }; | ||||
|  | ||||
| @@ -112,15 +116,20 @@ public: | ||||
| 	virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0; | ||||
| 	virtual bool isTransparent(const Point & coords) const = 0; | ||||
| 	virtual Rect contentRect() const = 0; | ||||
|  | ||||
| 	virtual void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0; | ||||
| 	virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0; | ||||
|  | ||||
| 	[[nodiscard]] virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0; | ||||
| 	/// Returns true if this image is still loading and can't be used | ||||
| 	virtual bool isLoading() const = 0; | ||||
|  | ||||
| 	virtual ~ISharedImage() = default; | ||||
|  | ||||
| 	virtual const SDL_Palette * getPalette() const = 0; | ||||
|  | ||||
| 	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0; | ||||
| 	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0; | ||||
| 	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0; | ||||
| 	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0; | ||||
|  | ||||
|  | ||||
| 	virtual ~ISharedImage() = default; | ||||
| }; | ||||
|   | ||||
| @@ -20,7 +20,10 @@ struct SDL_Surface; | ||||
| class IFont; | ||||
| class IImage; | ||||
| class CAnimation; | ||||
| class CanvasImage; | ||||
| class SDLImageShared; | ||||
| enum class EImageBlitMode : uint8_t; | ||||
| enum class CanvasScalingPolicy; | ||||
| enum EFonts : int8_t; | ||||
|  | ||||
| class IRenderHandler : public boost::noncopyable | ||||
| @@ -32,13 +35,15 @@ public: | ||||
| 	virtual void onLibraryLoadingFinished(const Services * services) = 0; | ||||
|  | ||||
| 	/// Loads image using given path | ||||
| 	virtual std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) = 0; | ||||
| 	virtual std::shared_ptr<IImage> loadImage(const ImageLocator & locator) = 0; | ||||
| 	virtual std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) = 0; | ||||
| 	virtual std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) = 0; | ||||
|  | ||||
| 	/// temporary compatibility method. Creates IImage from existing SDL_Surface | ||||
| 	/// Surface will be shared, caller must still free it with SDL_FreeSurface | ||||
| 	virtual std::shared_ptr<IImage> createImage(SDL_Surface * source) = 0; | ||||
| 	/// Loads single upscaled image without auto-scaling support | ||||
| 	virtual std::shared_ptr<SDLImageShared> loadScaledImage(const ImageLocator & locator) = 0; | ||||
|  | ||||
| 	/// Creates image which can be used as target for drawing on | ||||
| 	virtual std::shared_ptr<CanvasImage> createImage(const Point & size, CanvasScalingPolicy scalingPolicy) = 0; | ||||
|  | ||||
| 	/// Loads animation using given path | ||||
| 	virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0; | ||||
|   | ||||
| @@ -15,11 +15,10 @@ | ||||
|  | ||||
| #include "../../lib/json/JsonNode.h" | ||||
|  | ||||
| ImageLocator::ImageLocator(const JsonNode & config) | ||||
| SharedImageLocator::SharedImageLocator(const JsonNode & config, EImageBlitMode mode) | ||||
| 	: defFrame(config["defFrame"].Integer()) | ||||
| 	, defGroup(config["defGroup"].Integer()) | ||||
| 	, verticalFlip(config["verticalFlip"].Bool()) | ||||
| 	, horizontalFlip(config["horizontalFlip"].Bool()) | ||||
| 	, layer(mode) | ||||
| { | ||||
| 	if(!config["file"].isNull()) | ||||
| 		image = ImagePath::fromJson(config["file"]); | ||||
| @@ -28,19 +27,28 @@ ImageLocator::ImageLocator(const JsonNode & config) | ||||
| 		defFile = AnimationPath::fromJson(config["defFile"]); | ||||
| } | ||||
|  | ||||
| ImageLocator::ImageLocator(const ImagePath & path) | ||||
| SharedImageLocator::SharedImageLocator(const ImagePath & path, EImageBlitMode mode) | ||||
| 	: image(path) | ||||
| 	, layer(mode) | ||||
| { | ||||
| } | ||||
|  | ||||
| ImageLocator::ImageLocator(const AnimationPath & path, int frame, int group) | ||||
| SharedImageLocator::SharedImageLocator(const AnimationPath & path, int frame, int group, EImageBlitMode mode) | ||||
| 	: defFile(path) | ||||
| 	, defFrame(frame) | ||||
| 	, defGroup(group) | ||||
| 	, layer(mode) | ||||
| { | ||||
| } | ||||
|  | ||||
| bool ImageLocator::operator<(const ImageLocator & other) const | ||||
| ImageLocator::ImageLocator(const JsonNode & config, EImageBlitMode mode) | ||||
| 	: SharedImageLocator(config, mode) | ||||
| 	, verticalFlip(config["verticalFlip"].Bool()) | ||||
| 	, horizontalFlip(config["horizontalFlip"].Bool()) | ||||
| { | ||||
| } | ||||
|  | ||||
| bool SharedImageLocator::operator < (const SharedImageLocator & other) const | ||||
| { | ||||
| 	if(image != other.image) | ||||
| 		return image < other.image; | ||||
| @@ -50,14 +58,6 @@ bool ImageLocator::operator<(const ImageLocator & other) const | ||||
| 		return defGroup < other.defGroup; | ||||
| 	if(defFrame != other.defFrame) | ||||
| 		return defFrame < other.defFrame; | ||||
| 	if(verticalFlip != other.verticalFlip) | ||||
| 		return verticalFlip < other.verticalFlip; | ||||
| 	if(horizontalFlip != other.horizontalFlip) | ||||
| 		return horizontalFlip < other.horizontalFlip; | ||||
| 	if(scalingFactor != other.scalingFactor) | ||||
| 		return scalingFactor < other.scalingFactor; | ||||
| 	if(playerColored != other.playerColored) | ||||
| 		return playerColored < other.playerColored; | ||||
| 	if(layer != other.layer) | ||||
| 		return layer < other.layer; | ||||
|  | ||||
| @@ -68,70 +68,3 @@ bool ImageLocator::empty() const | ||||
| { | ||||
| 	return !image.has_value() && !defFile.has_value(); | ||||
| } | ||||
|  | ||||
| ImageLocator ImageLocator::copyFile() const | ||||
| { | ||||
| 	ImageLocator result; | ||||
| 	result.scalingFactor = 1; | ||||
| 	result.preScaledFactor = preScaledFactor; | ||||
| 	result.image = image; | ||||
| 	result.defFile = defFile; | ||||
| 	result.defFrame = defFrame; | ||||
| 	result.defGroup = defGroup; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| ImageLocator ImageLocator::copyFileTransform() const | ||||
| { | ||||
| 	ImageLocator result = copyFile(); | ||||
| 	result.horizontalFlip = horizontalFlip; | ||||
| 	result.verticalFlip = verticalFlip; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| ImageLocator ImageLocator::copyFileTransformScale() const | ||||
| { | ||||
| 	return *this; // full copy | ||||
| } | ||||
|  | ||||
| std::string ImageLocator::toString() const | ||||
| { | ||||
| 	std::string result; | ||||
| 	if (empty()) | ||||
| 		return "invalid"; | ||||
|  | ||||
| 	if (image) | ||||
| 	{ | ||||
| 		result += image->getOriginalName(); | ||||
| 		assert(!result.empty()); | ||||
| 	} | ||||
|  | ||||
| 	if (defFile) | ||||
| 	{ | ||||
| 		result += defFile->getOriginalName(); | ||||
| 		assert(!result.empty()); | ||||
| 		result += "-" + std::to_string(defGroup); | ||||
| 		result += "-" + std::to_string(defFrame); | ||||
| 	} | ||||
|  | ||||
| 	if (verticalFlip) | ||||
| 		result += "-vflip"; | ||||
|  | ||||
| 	if (horizontalFlip) | ||||
| 		result += "-hflip"; | ||||
|  | ||||
| 	if (scalingFactor > 1) | ||||
| 		result += "-scale" + std::to_string(scalingFactor); | ||||
|  | ||||
| 	if (playerColored.isValidPlayer()) | ||||
| 		result += "-player" + playerColored.toString(); | ||||
|  | ||||
| 	if (layer == EImageBlitMode::ONLY_OVERLAY) | ||||
| 		result += "-overlay"; | ||||
|  | ||||
| 	if (layer == EImageBlitMode::ONLY_SHADOW) | ||||
| 		result += "-shadow"; | ||||
|  | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|   | ||||
| @@ -14,35 +14,32 @@ | ||||
| #include "../../lib/filesystem/ResourcePath.h" | ||||
| #include "../../lib/constants/EntityIdentifiers.h" | ||||
|  | ||||
| struct ImageLocator | ||||
| struct SharedImageLocator | ||||
| { | ||||
| 	std::optional<ImagePath> image; | ||||
| 	std::optional<AnimationPath> defFile; | ||||
| 	int defFrame = -1; | ||||
| 	int defGroup = -1; | ||||
| 	EImageBlitMode layer = EImageBlitMode::OPAQUE; | ||||
|  | ||||
| 	PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; // FIXME: treat as identical to blue to avoid double-loading? | ||||
| 	SharedImageLocator() = default; | ||||
| 	SharedImageLocator(const AnimationPath & path, int frame, int group, EImageBlitMode layer); | ||||
| 	SharedImageLocator(const JsonNode & config, EImageBlitMode layer); | ||||
| 	SharedImageLocator(const ImagePath & path, EImageBlitMode layer); | ||||
|  | ||||
| 	bool operator < (const SharedImageLocator & other) const; | ||||
| }; | ||||
|  | ||||
| struct ImageLocator : SharedImageLocator | ||||
| { | ||||
| 	PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; | ||||
|  | ||||
| 	bool verticalFlip = false; | ||||
| 	bool horizontalFlip = false; | ||||
| 	int8_t scalingFactor = 0; // 0 = auto / use default scaling | ||||
| 	int8_t preScaledFactor = 1; | ||||
| 	EImageBlitMode layer = EImageBlitMode::OPAQUE; | ||||
|  | ||||
| 	ImageLocator() = default; | ||||
| 	ImageLocator(const AnimationPath & path, int frame, int group); | ||||
| 	explicit ImageLocator(const JsonNode & config); | ||||
| 	explicit ImageLocator(const ImagePath & path); | ||||
| 	using SharedImageLocator::SharedImageLocator; | ||||
| 	 ImageLocator(const JsonNode & config, EImageBlitMode layer); | ||||
|  | ||||
| 	bool operator < (const ImageLocator & other) const; | ||||
| 	bool empty() const; | ||||
|  | ||||
| 	ImageLocator copyFile() const; | ||||
| 	ImageLocator copyFileTransform() const; | ||||
| 	ImageLocator copyFileTransformScale() const; | ||||
|  | ||||
| 	// generates string representation of this image locator | ||||
| 	// guaranteed to be a valid file path with no extension | ||||
| 	// but may contain '/' if source file is in directory | ||||
| 	std::string toString() const; | ||||
| }; | ||||
|   | ||||
| @@ -11,9 +11,12 @@ | ||||
| #include "CBitmapFont.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
|  | ||||
| #include "../../lib/CConfigHandler.h" | ||||
| @@ -206,9 +209,10 @@ CBitmapFont::CBitmapFont(const std::string & filename): | ||||
|  | ||||
| 		auto filterName = settings["video"]["fontUpscalingFilter"].String(); | ||||
| 		EScalingAlgorithm algorithm = filterNameToEnum.at(filterName); | ||||
| 		auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm); | ||||
| 		SDLImageScaler scaler(atlasImage); | ||||
| 		scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm); | ||||
| 		SDL_FreeSurface(atlasImage); | ||||
| 		atlasImage = scaledSurface; | ||||
| 		atlasImage = scaler.acquireResultSurface(); | ||||
| 	} | ||||
|  | ||||
| 	logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d", | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "CursorHardware.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| @@ -59,8 +60,11 @@ void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivot | ||||
|  | ||||
| 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); | ||||
|  | ||||
| 	image->draw(cursorSurface, Point(0,0)); | ||||
| 	auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y ); | ||||
| 	image->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor()); | ||||
|  | ||||
| 	SDLImageScaler scaler(cursorSurface); | ||||
| 	scaler.scaleSurface(cursorDimensionsScaled, EScalingAlgorithm::BILINEAR); | ||||
| 	SDL_Surface	* cursorSurfaceScaled = scaler.acquireResultSurface(); | ||||
|  | ||||
| 	auto oldCursor = cursor; | ||||
| 	cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y); | ||||
|   | ||||
| @@ -65,7 +65,7 @@ void CursorSoftware::updateTexture() | ||||
|  | ||||
| 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); | ||||
|  | ||||
| 	cursorImage->draw(cursorSurface, Point(0,0)); | ||||
| 	cursorImage->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor()); | ||||
| 	SDL_UpdateTexture(cursorTexture, nullptr, cursorSurface->pixels, cursorSurface->pitch); | ||||
| 	needUpdate = false; | ||||
| } | ||||
|   | ||||
| @@ -1,172 +0,0 @@ | ||||
| /* | ||||
|  * ImageScaled.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "ImageScaled.h" | ||||
|  | ||||
| #include "SDLImage.h" | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../render/Colors.h" | ||||
|  | ||||
| #include "../../lib/constants/EntityIdentifiers.h" | ||||
|  | ||||
| #include <SDL_surface.h> | ||||
|  | ||||
| ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode) | ||||
| 	: source(source) | ||||
| 	, locator(inputLocator) | ||||
| 	, colorMultiplier(Colors::WHITE_TRUE) | ||||
| 	, alphaValue(SDL_ALPHA_OPAQUE) | ||||
| 	, blitMode(mode) | ||||
| { | ||||
| 	prepareImages(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> ImageScaled::getSharedImage() const | ||||
| { | ||||
| 	return body; | ||||
| } | ||||
|  | ||||
| void ImageScaled::scaleInteger(int factor) | ||||
| { | ||||
| 	assert(0); | ||||
| } | ||||
|  | ||||
| void ImageScaled::scaleTo(const Point & size) | ||||
| { | ||||
| 	if (source) | ||||
| 		source = source->scaleTo(size, nullptr); | ||||
|  | ||||
| 	if (body) | ||||
| 		body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr); | ||||
| } | ||||
|  | ||||
| void ImageScaled::exportBitmap(const boost::filesystem::path &path) const | ||||
| { | ||||
| 	source->exportBitmap(path, nullptr); | ||||
| } | ||||
|  | ||||
| bool ImageScaled::isTransparent(const Point &coords) const | ||||
| { | ||||
| 	return source->isTransparent(coords); | ||||
| } | ||||
|  | ||||
| Rect ImageScaled::contentRect() const | ||||
| { | ||||
| 	return source->contentRect(); | ||||
| } | ||||
|  | ||||
| Point ImageScaled::dimensions() const | ||||
| { | ||||
| 	return source->dimensions(); | ||||
| } | ||||
|  | ||||
| void ImageScaled::setAlpha(uint8_t value) | ||||
| { | ||||
| 	alphaValue = value; | ||||
| } | ||||
|  | ||||
| void ImageScaled::setBlitMode(EImageBlitMode mode) | ||||
| { | ||||
| 	blitMode = mode; | ||||
| } | ||||
|  | ||||
| void ImageScaled::draw(SDL_Surface *where, const Point &pos, const Rect *src) const | ||||
| { | ||||
| 	if (shadow) | ||||
| 		shadow->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode); | ||||
| 	if (body) | ||||
| 		body->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode); | ||||
| 	if (overlay) | ||||
| 		overlay->draw(where, nullptr, pos, src, colorMultiplier, colorMultiplier.a * alphaValue / 255, blitMode); | ||||
| } | ||||
|  | ||||
| void ImageScaled::setOverlayColor(const ColorRGBA & color) | ||||
| { | ||||
| 	colorMultiplier = color; | ||||
| } | ||||
|  | ||||
| void ImageScaled::playerColored(PlayerColor player) | ||||
| { | ||||
| 	playerColor = player; | ||||
| 	prepareImages(); | ||||
| } | ||||
|  | ||||
| void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) | ||||
| { | ||||
| 	// TODO: implement | ||||
| } | ||||
|  | ||||
| void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSkipMask) | ||||
| { | ||||
| 	// TODO: implement | ||||
| } | ||||
|  | ||||
| void ImageScaled::prepareImages() | ||||
| { | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::OPAQUE: | ||||
| 		case EImageBlitMode::COLORKEY: | ||||
| 		case EImageBlitMode::SIMPLE: | ||||
| 			locator.layer = blitMode; | ||||
| 			locator.playerColored = playerColor; | ||||
| 			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); | ||||
| 			break; | ||||
|  | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 		case EImageBlitMode::ONLY_BODY: | ||||
| 			locator.layer = EImageBlitMode::ONLY_BODY; | ||||
| 			locator.playerColored = playerColor; | ||||
| 			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); | ||||
| 			break; | ||||
|  | ||||
| 		case EImageBlitMode::WITH_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: | ||||
| 			locator.layer = EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY; | ||||
| 			locator.playerColored = playerColor; | ||||
| 			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); | ||||
| 			break; | ||||
|  | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			body = nullptr; | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::WITH_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			locator.layer = EImageBlitMode::ONLY_SHADOW; | ||||
| 			locator.playerColored = PlayerColor::CANNOT_DETERMINE; | ||||
| 			shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); | ||||
| 			break; | ||||
| 		default: | ||||
| 			shadow = nullptr; | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			locator.layer = EImageBlitMode::ONLY_OVERLAY; | ||||
| 			locator.playerColored = PlayerColor::CANNOT_DETERMINE; | ||||
| 			overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); | ||||
| 			break; | ||||
| 		default: | ||||
| 			overlay = nullptr; | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
| @@ -1,66 +0,0 @@ | ||||
| /* | ||||
|  * ImageScaled.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
|  | ||||
| #include "../../lib/Color.h" | ||||
| #include "../../lib/constants/EntityIdentifiers.h" | ||||
|  | ||||
| struct SDL_Palette; | ||||
|  | ||||
| class SDLImageShared; | ||||
|  | ||||
| // Upscaled image with several mechanisms to emulate H3 palette effects | ||||
| class ImageScaled final : public IImage | ||||
| { | ||||
| private: | ||||
|  | ||||
| 	/// Original unscaled image | ||||
| 	std::shared_ptr<const ISharedImage> source; | ||||
|  | ||||
| 	/// Upscaled shadow of our image, may be null | ||||
| 	std::shared_ptr<const ISharedImage> shadow; | ||||
|  | ||||
| 	/// Upscaled main part of our image, may be null | ||||
| 	std::shared_ptr<const ISharedImage> body; | ||||
|  | ||||
| 	/// Upscaled overlay (player color, selection highlight) of our image, may be null | ||||
| 	std::shared_ptr<const ISharedImage> overlay; | ||||
|  | ||||
| 	ImageLocator locator; | ||||
|  | ||||
| 	ColorRGBA colorMultiplier; | ||||
| 	PlayerColor playerColor = PlayerColor::CANNOT_DETERMINE; | ||||
|  | ||||
| 	uint8_t alphaValue; | ||||
| 	EImageBlitMode blitMode; | ||||
|  | ||||
| 	void prepareImages(); | ||||
| public: | ||||
| 	ImageScaled(const ImageLocator & locator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode); | ||||
|  | ||||
| 	void scaleInteger(int factor) override; | ||||
| 	void scaleTo(const Point & size) override; | ||||
| 	void exportBitmap(const boost::filesystem::path & path) const override; | ||||
| 	bool isTransparent(const Point & coords) const override; | ||||
| 	Rect contentRect() const override; | ||||
| 	Point dimensions() const override; | ||||
| 	void setAlpha(uint8_t value) override; | ||||
| 	void setBlitMode(EImageBlitMode mode) override; | ||||
| 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; | ||||
| 	void setOverlayColor(const ColorRGBA & color) override; | ||||
| 	void playerColored(PlayerColor player) override; | ||||
| 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; | ||||
| 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> getSharedImage() const override; | ||||
| }; | ||||
| @@ -11,19 +11,22 @@ | ||||
| #include "RenderHandler.h" | ||||
|  | ||||
| #include "SDLImage.h" | ||||
| #include "ImageScaled.h" | ||||
| #include "ScalableImage.h" | ||||
| #include "FontChain.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
|  | ||||
| #include "../render/CAnimation.h" | ||||
| #include "../render/CanvasImage.h" | ||||
| #include "../render/CDefFile.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/ColorFilter.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../../lib/json/JsonUtils.h" | ||||
| #include "../../lib/CThreadHelper.h" | ||||
| #include "../../lib/filesystem/Filesystem.h" | ||||
| #include "../../lib/VCMIDirs.h" | ||||
| #include "../../lib/constants/StringConstants.h" | ||||
|  | ||||
| #include <vcmi/ArtifactService.h> | ||||
| #include <vcmi/CreatureService.h> | ||||
| @@ -55,60 +58,7 @@ std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath & | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::optional<ResourcePath> RenderHandler::getPathForScaleFactor(const ResourcePath & path, const std::string & factor) | ||||
| { | ||||
| 	if(path.getType() == EResType::IMAGE) | ||||
| 	{ | ||||
| 		auto p = ImagePath::builtin(path.getName()); | ||||
| 		if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/"))) | ||||
| 			return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/")); | ||||
| 		if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/"))) | ||||
| 			return std::optional<ResourcePath>(p.addPrefix("DATA" + factor + "X/")); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto p = AnimationPath::builtin(path.getName()); | ||||
| 		auto pJson = p.toType<EResType::JSON>(); | ||||
| 		if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/"))) | ||||
| 			return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/")); | ||||
| 		if(CResourceHandler::get()->existsResource(pJson)) | ||||
| 			return std::optional<ResourcePath>(p); | ||||
| 		if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES" + factor + "X/"))) | ||||
| 			return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/")); | ||||
| 	} | ||||
|  | ||||
| 	return std::nullopt; | ||||
| } | ||||
|  | ||||
| std::pair<ResourcePath, int> RenderHandler::getScalePath(const ResourcePath & p) | ||||
| { | ||||
| 	auto path = p; | ||||
| 	int scaleFactor = 1; | ||||
| 	if(getScalingFactor() > 1) | ||||
| 	{ | ||||
| 		std::vector<int> factorsToCheck = {getScalingFactor(), 4, 3, 2}; | ||||
| 		for(auto factorToCheck : factorsToCheck) | ||||
| 		{ | ||||
| 			std::string name = boost::algorithm::to_upper_copy(p.getName()); | ||||
| 			boost::replace_all(name, "SPRITES/", std::string("SPRITES") + std::to_string(factorToCheck) + std::string("X/")); | ||||
| 			boost::replace_all(name, "DATA/", std::string("DATA") + std::to_string(factorToCheck) + std::string("X/")); | ||||
| 			ResourcePath scaledPath = ImagePath::builtin(name); | ||||
| 			if(p.getType() != EResType::IMAGE) | ||||
| 				scaledPath = AnimationPath::builtin(name); | ||||
| 			auto tmpPath = getPathForScaleFactor(scaledPath, std::to_string(factorToCheck)); | ||||
| 			if(tmpPath) | ||||
| 			{ | ||||
| 				path = *tmpPath; | ||||
| 				scaleFactor = factorToCheck; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return std::pair<ResourcePath, int>(path, scaleFactor); | ||||
| }; | ||||
|  | ||||
| void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config) | ||||
| void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config, EImageBlitMode mode) | ||||
| { | ||||
| 	std::string basepath; | ||||
| 	basepath = config["basepath"].String(); | ||||
| @@ -128,7 +78,7 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c | ||||
| 			JsonNode toAdd = frame; | ||||
| 			JsonUtils::inherit(toAdd, base); | ||||
| 			toAdd["file"].String() = basepath + frame.String(); | ||||
| 			source[groupID].emplace_back(toAdd); | ||||
| 			source[groupID].emplace_back(toAdd, mode); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -149,15 +99,26 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c | ||||
| 		if (toAdd.Struct().count("defFile")) | ||||
| 			toAdd["defFile"].String() = basepath + node["defFile"].String(); | ||||
|  | ||||
| 		source[group][frame] = ImageLocator(toAdd); | ||||
| 		source[group][frame] = ImageLocator(toAdd, mode); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path) | ||||
| RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode) | ||||
| { | ||||
| 	auto tmp = getScalePath(path); | ||||
| 	auto animPath = AnimationPath::builtin(tmp.first.getName()); | ||||
| 	AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/"); | ||||
| 	static constexpr std::array scaledSpritesPath = { | ||||
| 		"", // 0x | ||||
| 		"SPRITES/", | ||||
| 		"SPRITES2X/", | ||||
| 		"SPRITES3X/", | ||||
| 		"SPRITES4X/", | ||||
| 	}; | ||||
|  | ||||
| 	std::string pathString = path.getName(); | ||||
|  | ||||
| 	if (boost::starts_with(pathString, "SPRITES/")) | ||||
| 		pathString = pathString.substr(std::string("SPRITES/").length()); | ||||
|  | ||||
| 	AnimationPath actualPath = AnimationPath::builtin(scaledSpritesPath.at(scalingFactor) + pathString); | ||||
|  | ||||
| 	auto it = animationLayouts.find(actualPath); | ||||
|  | ||||
| @@ -184,15 +145,11 @@ RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const Anim | ||||
| 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]); | ||||
| 		stream->read(textData.get(), stream->getSize()); | ||||
|  | ||||
| 		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), animPath.getOriginalName()); | ||||
| 		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), path.getOriginalName()); | ||||
|  | ||||
| 		initFromJson(result, config); | ||||
| 		initFromJson(result, config, mode); | ||||
| 	} | ||||
|  | ||||
| 	for(auto & g : result) | ||||
| 		for(auto & i : g.second) | ||||
| 			i.preScaledFactor = tmp.second; | ||||
|  | ||||
| 	animationLayouts[actualPath] = result; | ||||
| 	return animationLayouts[actualPath]; | ||||
| } | ||||
| @@ -202,209 +159,174 @@ int RenderHandler::getScalingFactor() const | ||||
| 	return GH.screenHandler().getScalingFactor(); | ||||
| } | ||||
|  | ||||
| ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group) | ||||
| ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode) | ||||
| { | ||||
| 	const auto & layout = getAnimationLayout(path); | ||||
| 	const auto & layout = getAnimationLayout(path, scaling, mode); | ||||
| 	if (!layout.count(group)) | ||||
| 		return ImageLocator(ImagePath::builtin("DEFAULT")); | ||||
| 		return ImageLocator(); | ||||
|  | ||||
| 	if (frame >= layout.at(group).size()) | ||||
| 		return ImageLocator(ImagePath::builtin("DEFAULT")); | ||||
| 		return ImageLocator(); | ||||
|  | ||||
| 	const auto & locator = layout.at(group).at(frame); | ||||
| 	if (locator.image || locator.defFile) | ||||
| 		return locator; | ||||
|  | ||||
| 	return ImageLocator(path, frame, group); | ||||
| 	return ImageLocator(path, frame, group, mode); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> RenderHandler::loadImageImpl(const ImageLocator & locator) | ||||
| std::shared_ptr<ScalableImageShared> RenderHandler::loadImageImpl(const ImageLocator & locator) | ||||
| { | ||||
| 	auto it = imageFiles.find(locator); | ||||
| 	if (it != imageFiles.end()) | ||||
| 		return it->second; | ||||
|  | ||||
| 	// TODO: order should be different: | ||||
| 	// 1) try to find correctly scaled image | ||||
| 	// 2) if fails -> try to find correctly transformed | ||||
| 	// 3) if also fails -> try to find image from correct file | ||||
| 	// 4) load missing part of the sequence | ||||
| 	// TODO: check whether (load -> transform -> scale) or (load -> scale -> transform) order should be used for proper loading of pre-scaled data | ||||
| 	auto imageFromFile = loadImageFromFile(locator.copyFile()); | ||||
| 	auto transformedImage = transformImage(locator.copyFileTransform(), imageFromFile); | ||||
| 	auto scaledImage = scaleImage(locator.copyFileTransformScale(), transformedImage); | ||||
| 	auto sdlImage = loadImageFromFileUncached(locator); | ||||
| 	auto scaledImage = std::make_shared<ScalableImageShared>(locator, sdlImage); | ||||
|  | ||||
| 	storeCachedImage(locator, scaledImage); | ||||
| 	return scaledImage; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) | ||||
| std::shared_ptr<SDLImageShared> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) | ||||
| { | ||||
| 	if(locator.image) | ||||
| 	{ | ||||
| 		// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load | ||||
| 		return std::make_shared<SDLImageShared>(*locator.image, locator.preScaledFactor); | ||||
| 		return std::make_shared<SDLImageShared>(*locator.image); | ||||
| 	} | ||||
|  | ||||
| 	if(locator.defFile) | ||||
| 	{ | ||||
| 		auto defFile = getAnimationFile(*locator.defFile); | ||||
| 		int preScaledFactor = locator.preScaledFactor; | ||||
| 		if(!defFile) // no prescale for this frame | ||||
| 		{ | ||||
| 			auto tmpPath = (*locator.defFile).getName(); | ||||
| 			boost::algorithm::replace_all(tmpPath, "SPRITES2X/", "SPRITES/"); | ||||
| 			boost::algorithm::replace_all(tmpPath, "SPRITES3X/", "SPRITES/"); | ||||
| 			boost::algorithm::replace_all(tmpPath, "SPRITES4X/", "SPRITES/"); | ||||
| 			preScaledFactor = 1; | ||||
| 			defFile = getAnimationFile(AnimationPath::builtin(tmpPath)); | ||||
| 		} | ||||
| 		if(defFile->hasFrame(locator.defFrame, locator.defGroup)) | ||||
| 			return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup, preScaledFactor); | ||||
| 			return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup); | ||||
| 		else | ||||
| 		{ | ||||
| 			logGlobal->error("Frame %d in group %d not found in file: %s",  | ||||
| 				locator.defFrame, locator.defGroup, locator.defFile->getName().c_str()); | ||||
| 			return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"), locator.preScaledFactor); | ||||
| 			return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	throw std::runtime_error("Invalid image locator received!"); | ||||
| } | ||||
|  | ||||
| void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image) | ||||
| void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<ScalableImageShared> image) | ||||
| { | ||||
| 	imageFiles[locator] = image; | ||||
|  | ||||
| #if 0 | ||||
| 	const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "imageCache" / (locator.toString() + ".png"); | ||||
| 	boost::filesystem::path outDir = outPath; | ||||
| 	outDir.remove_filename(); | ||||
| 	boost::filesystem::create_directories(outDir); | ||||
| 	image->exportBitmap(outPath , nullptr); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> RenderHandler::loadImageFromFile(const ImageLocator & locator) | ||||
| std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocator & locator) | ||||
| { | ||||
| 	if (imageFiles.count(locator)) | ||||
| 		return imageFiles.at(locator); | ||||
| 	static constexpr std::array scaledDataPath = { | ||||
| 		"", // 0x | ||||
| 		"DATA/", | ||||
| 		"DATA2X/", | ||||
| 		"DATA3X/", | ||||
| 		"DATA4X/", | ||||
| 	}; | ||||
|  | ||||
| 	auto result = loadImageFromFileUncached(locator); | ||||
| 	storeCachedImage(locator, result); | ||||
| 	return result; | ||||
| 	static constexpr std::array scaledSpritesPath = { | ||||
| 		"", // 0x | ||||
| 		"SPRITES/", | ||||
| 		"SPRITES2X/", | ||||
| 		"SPRITES3X/", | ||||
| 		"SPRITES4X/", | ||||
| 	}; | ||||
|  | ||||
| 	ImagePath pathToLoad; | ||||
|  | ||||
| 	if(locator.defFile) | ||||
| 	{ | ||||
| 		auto remappedLocator = getLocatorForAnimationFrame(*locator.defFile, locator.defFrame, locator.defGroup, locator.scalingFactor, locator.layer); | ||||
| 		// we expect that .def's are only used for 1x data, upscaled assets should use standalone images | ||||
| 		if (!remappedLocator.image) | ||||
| 			return nullptr; | ||||
|  | ||||
| 		pathToLoad = *remappedLocator.image; | ||||
| 	} | ||||
|  | ||||
| 	if(locator.image) | ||||
| 		pathToLoad = *locator.image; | ||||
|  | ||||
| 	if (pathToLoad.empty()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	std::string imagePathString = pathToLoad.getName(); | ||||
|  | ||||
| 	if(locator.layer == EImageBlitMode::ONLY_OVERLAY) | ||||
| 		imagePathString += "-OVERLAY"; | ||||
| 	if(locator.layer == EImageBlitMode::ONLY_SHADOW) | ||||
| 		imagePathString += "-SHADOW"; | ||||
| 	if(locator.playerColored.isValidPlayer()) | ||||
| 		imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]); | ||||
| 	if(locator.playerColored == PlayerColor::NEUTRAL) | ||||
| 		imagePathString += "-NEUTRAL"; | ||||
|  | ||||
| 	auto imagePath = ImagePath::builtin(imagePathString); | ||||
| 	auto imagePathSprites = ImagePath::builtin(imagePathString).addPrefix(scaledSpritesPath.at(locator.scalingFactor)); | ||||
| 	auto imagePathData = ImagePath::builtin(imagePathString).addPrefix(scaledDataPath.at(locator.scalingFactor)); | ||||
|  | ||||
| 	if(CResourceHandler::get()->existsResource(imagePathSprites)) | ||||
| 		return std::make_shared<SDLImageShared>(imagePathSprites); | ||||
|  | ||||
| 	if(CResourceHandler::get()->existsResource(imagePathData)) | ||||
| 		return std::make_shared<SDLImageShared>(imagePathData); | ||||
|  | ||||
| 	if(CResourceHandler::get()->existsResource(imagePath)) | ||||
| 		return std::make_shared<SDLImageShared>(imagePath); | ||||
|  | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image) | ||||
| { | ||||
| 	if (imageFiles.count(locator)) | ||||
| 		return imageFiles.at(locator); | ||||
|  | ||||
| 	auto result = image; | ||||
|  | ||||
| 	if (locator.verticalFlip) | ||||
| 		result = result->verticalFlip(); | ||||
|  | ||||
| 	if (locator.horizontalFlip) | ||||
| 		result = result->horizontalFlip(); | ||||
|  | ||||
| 	storeCachedImage(locator, result); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> RenderHandler::scaleImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image) | ||||
| { | ||||
| 	if (imageFiles.count(locator)) | ||||
| 		return imageFiles.at(locator); | ||||
|  | ||||
| 	auto handle = image->createImageReference(locator.layer); | ||||
|  | ||||
| 	assert(locator.scalingFactor != 1); // should be filtered-out before | ||||
| 	if (locator.playerColored != PlayerColor::CANNOT_DETERMINE) | ||||
| 		handle->playerColored(locator.playerColored); | ||||
|  | ||||
| 	handle->scaleInteger(locator.scalingFactor); | ||||
|  | ||||
| 	auto result = handle->getSharedImage(); | ||||
| 	storeCachedImage(locator, result); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) | ||||
| std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator) | ||||
| { | ||||
| 	ImageLocator adjustedLocator = locator; | ||||
|  | ||||
| 	if(adjustedLocator.image) | ||||
| 	{ | ||||
| 		std::string imgPath = (*adjustedLocator.image).getName(); | ||||
| 		if(adjustedLocator.layer == EImageBlitMode::ONLY_OVERLAY) | ||||
| 			imgPath += "-OVERLAY"; | ||||
| 		if(adjustedLocator.layer == EImageBlitMode::ONLY_SHADOW) | ||||
| 			imgPath += "-SHADOW"; | ||||
|  | ||||
| 		if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) || | ||||
| 		   CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("DATA/")) || | ||||
| 		   CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("SPRITES/"))) | ||||
| 			adjustedLocator.image = ImagePath::builtin(imgPath); | ||||
| 	} | ||||
|  | ||||
| 	if(adjustedLocator.defFile && adjustedLocator.scalingFactor == 0) | ||||
| 	{ | ||||
| 		auto tmp = getScalePath(*adjustedLocator.defFile); | ||||
| 		adjustedLocator.defFile = AnimationPath::builtin(tmp.first.getName()); | ||||
| 		adjustedLocator.preScaledFactor = tmp.second; | ||||
| 	} | ||||
| 	if(adjustedLocator.image && adjustedLocator.scalingFactor == 0) | ||||
| 	{ | ||||
| 		auto tmp = getScalePath(*adjustedLocator.image); | ||||
| 		adjustedLocator.image = ImagePath::builtin(tmp.first.getName()); | ||||
| 		adjustedLocator.preScaledFactor = tmp.second; | ||||
| 	} | ||||
|  | ||||
| 	if (adjustedLocator.scalingFactor == 0 && getScalingFactor() != 1 ) | ||||
| 	{ | ||||
| 		auto unscaledLocator = adjustedLocator; | ||||
| 		auto scaledLocator = adjustedLocator; | ||||
|  | ||||
| 		unscaledLocator.scalingFactor = 1; | ||||
| 		scaledLocator.scalingFactor = getScalingFactor(); | ||||
| 		auto unscaledImage = loadImageImpl(unscaledLocator); | ||||
|  | ||||
| 		return std::make_shared<ImageScaled>(scaledLocator, unscaledImage, mode); | ||||
| 	} | ||||
| 	std::shared_ptr<ScalableImageInstance> result; | ||||
|  | ||||
| 	if (adjustedLocator.scalingFactor == 0) | ||||
| 	{ | ||||
| 		auto scaledLocator = adjustedLocator; | ||||
| 		scaledLocator.scalingFactor = getScalingFactor(); | ||||
|  | ||||
| 		return loadImageImpl(scaledLocator)->createImageReference(mode); | ||||
| 		result = loadImageImpl(scaledLocator)->createImageReference(); | ||||
| 	} | ||||
| 	else | ||||
| 		return loadImageImpl(adjustedLocator)->createImageReference(mode); | ||||
| 		result = loadImageImpl(adjustedLocator)->createImageReference(); | ||||
|  | ||||
| 	if (locator.horizontalFlip) | ||||
| 		result->horizontalFlip(); | ||||
| 	if (locator.verticalFlip) | ||||
| 		result->verticalFlip(); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) | ||||
| { | ||||
| 	auto tmp = getScalePath(path); | ||||
| 	ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group); | ||||
| 	locator.preScaledFactor = tmp.second; | ||||
| 	return loadImage(locator, mode); | ||||
| 	ImageLocator locator = getLocatorForAnimationFrame(path, frame, group, 1, mode); | ||||
| 	if (!locator.empty()) | ||||
| 		return loadImage(locator); | ||||
| 	else | ||||
| 		return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode)); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) | ||||
| { | ||||
| 	ImageLocator locator(path); | ||||
| 	return loadImage(locator, mode); | ||||
| 	ImageLocator locator(path, mode); | ||||
| 	return loadImage(locator); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source) | ||||
| std::shared_ptr<CanvasImage> RenderHandler::createImage(const Point & size, CanvasScalingPolicy scalingPolicy) | ||||
| { | ||||
| 	return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::SIMPLE); | ||||
| 	return std::make_shared<CanvasImage>(size, scalingPolicy); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode) | ||||
| { | ||||
| 	return std::make_shared<CAnimation>(path, getAnimationLayout(path), mode); | ||||
| 	return std::make_shared<CAnimation>(path, getAnimationLayout(path, 1, mode), mode); | ||||
| } | ||||
|  | ||||
| void RenderHandler::addImageListEntries(const EntityService * service) | ||||
| @@ -416,7 +338,7 @@ void RenderHandler::addImageListEntries(const EntityService * service) | ||||
| 			if (imageName.empty()) | ||||
| 				return; | ||||
|  | ||||
| 			auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName)); | ||||
| 			auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::SIMPLE); | ||||
|  | ||||
| 			JsonNode entry; | ||||
| 			entry["file"].String() = imageName; | ||||
| @@ -424,7 +346,7 @@ void RenderHandler::addImageListEntries(const EntityService * service) | ||||
| 			if (index >= layout[group].size()) | ||||
| 				layout[group].resize(index + 1); | ||||
|  | ||||
| 			layout[group][index] = ImageLocator(entry); | ||||
| 			layout[group][index] = ImageLocator(entry, EImageBlitMode::SIMPLE); | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_END | ||||
|  | ||||
| class CDefFile; | ||||
| class SDLImageShared; | ||||
| class ISharedImage; | ||||
| class ScalableImageShared; | ||||
|  | ||||
| class RenderHandler : public IRenderHandler | ||||
| { | ||||
| @@ -25,28 +25,22 @@ class RenderHandler : public IRenderHandler | ||||
|  | ||||
| 	std::map<AnimationPath, std::shared_ptr<CDefFile>> animationFiles; | ||||
| 	std::map<AnimationPath, AnimationLayoutMap> animationLayouts; | ||||
| 	std::map<ImageLocator, std::shared_ptr<const ISharedImage>> imageFiles; | ||||
| 	std::map<SharedImageLocator, std::shared_ptr<ScalableImageShared>> imageFiles; | ||||
| 	std::map<EFonts, std::shared_ptr<const IFont>> fonts; | ||||
|  | ||||
| 	std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path); | ||||
| 	std::optional<ResourcePath> getPathForScaleFactor(const ResourcePath & path, const std::string & factor); | ||||
| 	std::pair<ResourcePath, int> getScalePath(const ResourcePath & p); | ||||
| 	AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); | ||||
| 	void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); | ||||
| 	AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode); | ||||
| 	void initFromJson(AnimationLayoutMap & layout, const JsonNode & config, EImageBlitMode mode); | ||||
|  | ||||
| 	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName); | ||||
| 	void addImageListEntries(const EntityService * service); | ||||
| 	void storeCachedImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image); | ||||
| 	void storeCachedImage(const ImageLocator & locator, std::shared_ptr<ScalableImageShared> image); | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> loadImageImpl(const ImageLocator & config); | ||||
| 	std::shared_ptr<ScalableImageShared> loadImageImpl(const ImageLocator & config); | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> loadImageFromFileUncached(const ImageLocator & locator); | ||||
| 	std::shared_ptr<const ISharedImage> loadImageFromFile(const ImageLocator & locator); | ||||
| 	std::shared_ptr<SDLImageShared> loadImageFromFileUncached(const ImageLocator & locator); | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> transformImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image); | ||||
| 	std::shared_ptr<const ISharedImage> scaleImage(const ImageLocator & locator, std::shared_ptr<const ISharedImage> image); | ||||
|  | ||||
| 	ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group); | ||||
| 	ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode); | ||||
|  | ||||
| 	int getScalingFactor() const; | ||||
|  | ||||
| @@ -55,13 +49,15 @@ public: | ||||
| 	// IRenderHandler implementation | ||||
| 	void onLibraryLoadingFinished(const Services * services) override; | ||||
|  | ||||
| 	std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) override; | ||||
| 	std::shared_ptr<IImage> loadImage(const ImageLocator & locator) override; | ||||
| 	std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) override; | ||||
| 	std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) override; | ||||
|  | ||||
| 	std::shared_ptr<SDLImageShared> loadScaledImage(const ImageLocator & locator) override; | ||||
|  | ||||
| 	std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) override; | ||||
|  | ||||
| 	std::shared_ptr<IImage> createImage(SDL_Surface * source) override; | ||||
| 	std::shared_ptr<CanvasImage> createImage(const Point & size, CanvasScalingPolicy scalingPolicy) override; | ||||
|  | ||||
| 	/// Returns font with specified identifer | ||||
| 	std::shared_ptr<const IFont> loadFont(EFonts font) override; | ||||
|   | ||||
| @@ -11,76 +11,24 @@ | ||||
| #include "SDLImage.h" | ||||
|  | ||||
| #include "SDLImageLoader.h" | ||||
| #include "SDLImageScaler.h" | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../render/ColorFilter.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/CBitmapHandler.h" | ||||
| #include "../render/CDefFile.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../xBRZ/xbrz.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
|  | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <SDL_surface.h> | ||||
| #include <tbb/task_arena.h> | ||||
|  | ||||
| #include <SDL_image.h> | ||||
| #include <SDL_surface.h> | ||||
| #include <SDL_version.h> | ||||
|  | ||||
| class SDLImageLoader; | ||||
|  | ||||
| //First 8 colors in def palette used for transparency | ||||
| static constexpr std::array<SDL_Color, 8> sourcePalette = {{ | ||||
| 	{0,   255, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 150, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 100, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 50,  255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 0,   255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 255, 0,   SDL_ALPHA_OPAQUE}, | ||||
| 	{180, 0,   255, SDL_ALPHA_OPAQUE}, | ||||
| 	{0,   255, 0,   SDL_ALPHA_OPAQUE} | ||||
| }}; | ||||
|  | ||||
| static constexpr std::array<ColorRGBA, 8> targetPalette = {{ | ||||
| 	{0, 0, 0, 0  }, // 0 - transparency                  ( used in most images ) | ||||
| 	{0, 0, 0, 64 }, // 1 - shadow border                 ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 64 }, // 2 - shadow border                 ( used in fog-of-war def's ) | ||||
| 	{0, 0, 0, 128}, // 3 - shadow body                   ( used in fog-of-war def's ) | ||||
| 	{0, 0, 0, 128}, // 4 - shadow body                   ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 0  }, // 5 - selection / owner flag        ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 128}, // 6 - shadow body   below selection ( used in battle def's ) | ||||
| 	{0, 0, 0, 64 }  // 7 - shadow border below selection ( used in battle def's ) | ||||
| }}; | ||||
|  | ||||
| static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2) | ||||
| { | ||||
| 	return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256; | ||||
| } | ||||
|  | ||||
| static ColorRGBA addColors(const ColorRGBA & base, const ColorRGBA & over) | ||||
| { | ||||
| 	return ColorRGBA( | ||||
| 		mixChannels(over.r, base.r, over.a, base.a), | ||||
| 		mixChannels(over.g, base.g, over.a, base.a), | ||||
| 		mixChannels(over.b, base.b, over.a, base.a), | ||||
| 		static_cast<ui8>(over.a + base.a * (255 - over.a) / 256) | ||||
| 		); | ||||
| } | ||||
|  | ||||
| static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs) | ||||
| { | ||||
| 	// it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow | ||||
| 	// exact logic is not clear and requires extensive testing with image editing | ||||
| 	// potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component | ||||
| 	static const int threshold = 8; | ||||
|  | ||||
| 	int diffR = static_cast<int>(lhs.r) - rhs.r; | ||||
| 	int diffG = static_cast<int>(lhs.g) - rhs.g; | ||||
| 	int diffB = static_cast<int>(lhs.b) - rhs.b; | ||||
| 	int diffA = static_cast<int>(lhs.a) - rhs.a; | ||||
|  | ||||
| 	return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold; | ||||
| } | ||||
|  | ||||
| int IImage::width() const | ||||
| { | ||||
| 	return dimensions().x; | ||||
| @@ -91,12 +39,11 @@ int IImage::height() const | ||||
| 	return dimensions().y; | ||||
| } | ||||
|  | ||||
| SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int preScaleFactor) | ||||
| SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group) | ||||
| 	: surf(nullptr), | ||||
| 	margins(0, 0), | ||||
| 	fullSize(0, 0), | ||||
| 	originalPalette(nullptr), | ||||
| 	preScaleFactor(preScaleFactor) | ||||
| 	originalPalette(nullptr) | ||||
| { | ||||
| 	SDLImageLoader loader(this); | ||||
| 	data->loadFrame(frame, group, loader); | ||||
| @@ -104,12 +51,11 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group | ||||
| 	savePalette(); | ||||
| } | ||||
|  | ||||
| SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor) | ||||
| SDLImageShared::SDLImageShared(SDL_Surface * from) | ||||
| 	: surf(nullptr), | ||||
| 	margins(0, 0), | ||||
| 	fullSize(0, 0), | ||||
| 	originalPalette(nullptr), | ||||
| 	preScaleFactor(preScaleFactor) | ||||
| 	originalPalette(nullptr) | ||||
| { | ||||
| 	surf = from; | ||||
| 	if (surf == nullptr) | ||||
| @@ -122,12 +68,11 @@ SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor) | ||||
| 	fullSize.y = surf->h; | ||||
| } | ||||
|  | ||||
| SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor) | ||||
| SDLImageShared::SDLImageShared(const ImagePath & filename) | ||||
| 	: surf(nullptr), | ||||
| 	margins(0, 0), | ||||
| 	fullSize(0, 0), | ||||
| 	originalPalette(nullptr), | ||||
| 	preScaleFactor(preScaleFactor) | ||||
| 	originalPalette(nullptr) | ||||
| { | ||||
| 	surf = BitmapHandler::loadBitmap(filename); | ||||
|  | ||||
| @@ -146,9 +91,70 @@ SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SDLImageShared::scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaleTo, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| 	Rect sourceRect(0, 0, surf->w, surf->h); | ||||
| 	Point destShift(0, 0); | ||||
| 	Point destScale = Point(surf->w, surf->h) * scaleTo / dimensions(); | ||||
| 	Point marginsScaled = margins * scaleTo / dimensions(); | ||||
|  | ||||
| 	if(src) | ||||
| 	{ | ||||
| 		Rect srcUnscaled(Point(src->topLeft() * dimensions() / scaleTo), Point(src->dimensions() * dimensions() / scaleTo)); | ||||
|  | ||||
| 		if(srcUnscaled.x < margins.x) | ||||
| 			destShift.x += marginsScaled.x - src->x; | ||||
|  | ||||
| 		if(srcUnscaled.y < margins.y) | ||||
| 			destShift.y += marginsScaled.y - src->y; | ||||
|  | ||||
| 		sourceRect = Rect(srcUnscaled).intersect(Rect(margins.x, margins.y, surf->w, surf->h)); | ||||
|  | ||||
| 		destScale.x = std::min(destScale.x, sourceRect.w * scaleTo.x / dimensions().x); | ||||
| 		destScale.y = std::min(destScale.y, sourceRect.h * scaleTo.y / dimensions().y); | ||||
|  | ||||
| 		sourceRect -= margins; | ||||
| 	} | ||||
| 	else | ||||
| 		destShift = marginsScaled; | ||||
|  | ||||
| 	destShift += dest; | ||||
|  | ||||
| 	SDL_SetSurfaceColorMod(surf, colorMultiplier.r, colorMultiplier.g, colorMultiplier.b); | ||||
| 	SDL_SetSurfaceAlphaMod(surf, alpha); | ||||
|  | ||||
| 	if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0)) | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND); | ||||
| 	else | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); | ||||
|  | ||||
| 	if (palette && surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, palette); | ||||
|  | ||||
| 	SDL_Rect srcRect = CSDL_Ext::toSDL(sourceRect); | ||||
| 	SDL_Rect dstRect = CSDL_Ext::toSDL(Rect(destShift, destScale)); | ||||
|  | ||||
| 	if (sourceRect.dimensions() * scaleTo / dimensions() != destScale) | ||||
| 		logGlobal->info("???"); | ||||
|  | ||||
| 	SDL_Surface * tempSurface = SDL_ConvertSurface(surf, where->format, 0); | ||||
| 	int result = SDL_BlitScaled(tempSurface, &srcRect, where, &dstRect); | ||||
|  | ||||
| 	SDL_FreeSurface(tempSurface); | ||||
| 	if (result != 0) | ||||
| 		logGlobal->error("SDL_BlitScaled failed! %s", SDL_GetError()); | ||||
|  | ||||
| 	if (surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, originalPalette); | ||||
| } | ||||
|  | ||||
| void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| @@ -199,89 +205,23 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin | ||||
|  | ||||
| void SDLImageShared::optimizeSurface() | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| 	int left = surf->w; | ||||
| 	int top = surf->h; | ||||
| 	int right = 0; | ||||
| 	int bottom = 0; | ||||
| 	SDLImageOptimizer optimizer(surf, Rect(margins, fullSize)); | ||||
|  | ||||
| 	// locate fully-transparent area around image | ||||
| 	// H3 hadles this on format level, but mods or images scaled in runtime do not | ||||
| 	if (surf->format->palette) | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch; | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				if (row[x] != 0) | ||||
| 				{ | ||||
| 					// opaque or can be opaque (e.g. disabled shadow) | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				ColorRGBA color; | ||||
| 				SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a); | ||||
| 	optimizer.optimizeSurface(surf); | ||||
| 	SDL_FreeSurface(surf); | ||||
|  | ||||
| 				if (color.a != SDL_ALPHA_TRANSPARENT) | ||||
| 				{ | ||||
| 					 // opaque | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (left == surf->w) | ||||
| 	{ | ||||
| 		// empty image - simply delete it | ||||
| 		SDL_FreeSurface(surf); | ||||
| 		surf = nullptr; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1) | ||||
| 	{ | ||||
| 		// non-zero border found | ||||
| 		Rect newDimensions(left, top, right - left + 1, bottom - top + 1); | ||||
| 		SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions); | ||||
| 		auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), surf); | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); | ||||
| 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr); | ||||
|  | ||||
| 		if (SDL_HasColorKey(surf)) | ||||
| 		{ | ||||
| 			uint32_t colorKey; | ||||
| 			SDL_GetColorKey(surf, &colorKey); | ||||
| 			SDL_SetColorKey(newSurface, SDL_TRUE, colorKey); | ||||
| 		} | ||||
|  | ||||
| 		SDL_FreeSurface(surf); | ||||
| 		surf = newSurface; | ||||
|  | ||||
| 		margins.x += left; | ||||
| 		margins.y += top; | ||||
| 	} | ||||
| 	surf = optimizer.acquireResultSurface(); | ||||
| 	margins = optimizer.getResultDimensions().topLeft(); | ||||
| 	fullSize = optimizer.getResultDimensions().dimensions(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (factor <= 0) | ||||
| 		throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); | ||||
|  | ||||
| @@ -291,47 +231,58 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL | ||||
| 	if (palette && surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, palette); | ||||
|  | ||||
| 	SDL_Surface * scaled = nullptr; | ||||
| 	if(preScaleFactor == factor) | ||||
| 		return shared_from_this(); | ||||
| 	else if(preScaleFactor == 1) | ||||
| 	{ | ||||
| 		// dump heuristics to differentiate tileable UI elements from map object / combat assets | ||||
| 		if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE) | ||||
| 			scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE); | ||||
| 		else | ||||
| 			scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA); | ||||
| 	} | ||||
| 	// simple heuristics to differentiate tileable UI elements from map object / combat assets | ||||
| 	EScalingAlgorithm algorithm; | ||||
| 	if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE) | ||||
| 		algorithm = EScalingAlgorithm::XBRZ_OPAQUE; | ||||
| 	else | ||||
| 		scaled = CSDL_Ext::scaleSurface(surf, (int) round((float)surf->w * factor / preScaleFactor), (int) round((float)surf->h * factor / preScaleFactor)); | ||||
| 		algorithm = EScalingAlgorithm::XBRZ_ALPHA; | ||||
|  | ||||
| 	auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor); | ||||
|  | ||||
| 	ret->fullSize.x = fullSize.x * factor; | ||||
| 	ret->fullSize.y = fullSize.y * factor; | ||||
|  | ||||
| 	ret->margins.x = (int) round((float)margins.x * factor / preScaleFactor); | ||||
| 	ret->margins.y = (int) round((float)margins.y * factor / preScaleFactor); | ||||
| 	ret->optimizeSurface(); | ||||
|  | ||||
| 	// erase our own reference | ||||
| 	SDL_FreeSurface(scaled); | ||||
| 	auto result = std::make_shared<SDLImageShared>(this, factor, algorithm); | ||||
|  | ||||
| 	if (surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, originalPalette); | ||||
|  | ||||
| 	return ret; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	static tbb::task_arena upscalingArena; | ||||
|  | ||||
| 	upscalingInProgress = true; | ||||
|  | ||||
| 	auto scaler = std::make_shared<SDLImageScaler>(from->surf, Rect(from->margins, from->fullSize)); | ||||
|  | ||||
| 	const auto & scalingTask = [this, algorithm, scaler]() | ||||
| 	{ | ||||
| 		scaler->scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm); | ||||
| 		surf = scaler->acquireResultSurface(); | ||||
| 		fullSize = scaler->getResultDimensions().dimensions(); | ||||
| 		margins = scaler->getResultDimensions().topLeft(); | ||||
|  | ||||
| 		upscalingInProgress = false; | ||||
| 	}; | ||||
|  | ||||
| 	upscalingArena.enqueue(scalingTask); | ||||
| } | ||||
|  | ||||
| bool SDLImageShared::isLoading() const | ||||
| { | ||||
| 	return upscalingInProgress; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const | ||||
| { | ||||
| 	float scaleX = static_cast<float>(size.x) / fullSize.x; | ||||
| 	float scaleY = static_cast<float>(size.y) / fullSize.y; | ||||
|  | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (palette && surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, palette); | ||||
|  | ||||
| 	auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY)); | ||||
| 	SDLImageScaler scaler(surf, Rect(margins, fullSize)); | ||||
|  | ||||
| 	scaler.scaleSurface(size, EScalingAlgorithm::XBRZ_ALPHA); | ||||
|  | ||||
| 	auto scaled = scaler.acquireResultSurface(); | ||||
|  | ||||
| 	if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point | ||||
| 		CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]); | ||||
| @@ -340,13 +291,9 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, | ||||
| 	else | ||||
| 		CSDL_Ext::setDefaultColorKey(scaled);//just in case | ||||
|  | ||||
| 	auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor); | ||||
|  | ||||
| 	ret->fullSize.x = (int) round((float)fullSize.x * scaleX); | ||||
| 	ret->fullSize.y = (int) round((float)fullSize.y * scaleY); | ||||
|  | ||||
| 	ret->margins.x = (int) round((float)margins.x * scaleX); | ||||
| 	ret->margins.y = (int) round((float)margins.y * scaleY); | ||||
| 	auto ret = std::make_shared<SDLImageShared>(scaled); | ||||
| 	ret->fullSize = scaler.getResultDimensions().dimensions(); | ||||
| 	ret->margins = scaler.getResultDimensions().topLeft(); | ||||
|  | ||||
| 	// erase our own reference | ||||
| 	SDL_FreeSurface(scaled); | ||||
| @@ -359,6 +306,7 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, | ||||
|  | ||||
| void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| @@ -369,13 +317,9 @@ void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palet | ||||
| 		SDL_SetSurfacePalette(surf, originalPalette); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::playerColored(PlayerColor player) | ||||
| { | ||||
| 	graphics->setPlayerPalette(currentPalette, player); | ||||
| } | ||||
|  | ||||
| bool SDLImageShared::isTransparent(const Point & coords) const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (surf) | ||||
| 		return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y	- margins.y); | ||||
| 	else | ||||
| @@ -384,31 +328,34 @@ bool SDLImageShared::isTransparent(const Point & coords) const | ||||
|  | ||||
| Rect SDLImageShared::contentRect() const | ||||
| { | ||||
| 	auto tmpMargins = margins / preScaleFactor; | ||||
| 	auto tmpSize = Point(surf->w, surf->h) / preScaleFactor; | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	auto tmpMargins = margins; | ||||
| 	auto tmpSize = Point(surf->w, surf->h); | ||||
| 	return Rect(tmpMargins, tmpSize); | ||||
| } | ||||
|  | ||||
| const SDL_Palette * SDLImageShared::getPalette() const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return nullptr; | ||||
| 	return surf->format->palette; | ||||
| } | ||||
|  | ||||
| Point SDLImageShared::dimensions() const | ||||
| { | ||||
| 	return fullSize / preScaleFactor; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode) const | ||||
| { | ||||
| 	if (surf && surf->format->palette) | ||||
| 		return std::make_shared<SDLImageIndexed>(shared_from_this(), originalPalette, mode); | ||||
| 	else | ||||
| 		return std::make_shared<SDLImageRGB>(shared_from_this(), mode); | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	return fullSize; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return shared_from_this(); | ||||
|  | ||||
| 	SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf); | ||||
| 	auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor); | ||||
| 	auto ret = std::make_shared<SDLImageShared>(flipped); | ||||
| 	ret->fullSize = fullSize; | ||||
| 	ret->margins.x = margins.x; | ||||
| 	ret->margins.y = fullSize.y - surf->h - margins.y; | ||||
| @@ -419,11 +366,12 @@ std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	if (!surf) | ||||
| 		return shared_from_this(); | ||||
|  | ||||
| 	SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf); | ||||
| 	auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor); | ||||
| 	auto ret = std::make_shared<SDLImageShared>(flipped); | ||||
| 	ret->fullSize = fullSize; | ||||
| 	ret->margins.x = fullSize.x - surf->w - margins.x; | ||||
| 	ret->margins.y = margins.y; | ||||
| @@ -435,6 +383,7 @@ std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const | ||||
| // Keep the original palette, in order to do color switching operation | ||||
| void SDLImageShared::savePalette() | ||||
| { | ||||
| 	assert(upscalingInProgress == false); | ||||
| 	// For some images that don't have palette, skip this | ||||
| 	if(surf->format->palette == nullptr) | ||||
| 		return; | ||||
| @@ -445,219 +394,8 @@ void SDLImageShared::savePalette() | ||||
| 	SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) | ||||
| { | ||||
| 	std::vector<SDL_Color> shifterColors(colorsToMove); | ||||
|  | ||||
| 	for(uint32_t i=0; i<colorsToMove; ++i) | ||||
| 		shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i]; | ||||
|  | ||||
| 	SDL_SetPaletteColors(currentPalette, shifterColors.data(), firstColorID, colorsToMove); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) | ||||
| { | ||||
| 	// If shadow is enabled, following colors must be skipped unconditionally | ||||
| 	if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY) | ||||
| 		colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4); | ||||
|  | ||||
| 	// Note: here we skip first colors in the palette that are predefined in H3 images | ||||
| 	for(int i = 0; i < currentPalette->ncolors; i++) | ||||
| 	{ | ||||
| 		if (i < std::size(sourcePalette) && colorsSimilar(sourcePalette[i], originalPalette->colors[i])) | ||||
| 			continue; | ||||
|  | ||||
| 		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1) | ||||
| 			continue; | ||||
|  | ||||
| 		currentPalette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i]))); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * originalPalette, EImageBlitMode mode) | ||||
| 	:SDLImageBase::SDLImageBase(image, mode) | ||||
| 	,originalPalette(originalPalette) | ||||
| { | ||||
| 	currentPalette = SDL_AllocPalette(originalPalette->ncolors); | ||||
| 	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors); | ||||
|  | ||||
| 	preparePalette(); | ||||
| } | ||||
|  | ||||
| SDLImageIndexed::~SDLImageIndexed() | ||||
| { | ||||
| 	SDL_FreePalette(currentPalette); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::setShadowTransparency(float factor) | ||||
| { | ||||
| 	ColorRGBA shadow50(0, 0, 0, 128 * factor); | ||||
| 	ColorRGBA shadow25(0, 0, 0,  64 * factor); | ||||
|  | ||||
| 	std::array<SDL_Color, 5> colorsSDL = { | ||||
| 		originalPalette->colors[0], | ||||
| 		originalPalette->colors[1], | ||||
| 		originalPalette->colors[2], | ||||
| 		originalPalette->colors[3], | ||||
| 		originalPalette->colors[4] | ||||
| 	}; | ||||
|  | ||||
| 	// seems to be used unconditionally | ||||
| 	colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY); | ||||
| 	colorsSDL[1] = CSDL_Ext::toSDL(shadow25); | ||||
| 	colorsSDL[4] = CSDL_Ext::toSDL(shadow50); | ||||
|  | ||||
| 	// seems to be used only if color matches | ||||
| 	if (colorsSimilar(originalPalette->colors[2], sourcePalette[2])) | ||||
| 		colorsSDL[2] = CSDL_Ext::toSDL(shadow25); | ||||
|  | ||||
| 	if (colorsSimilar(originalPalette->colors[3], sourcePalette[3])) | ||||
| 		colorsSDL[3] = CSDL_Ext::toSDL(shadow50); | ||||
|  | ||||
| 	SDL_SetPaletteColors(currentPalette, colorsSDL.data(), 0, colorsSDL.size()); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::setOverlayColor(const ColorRGBA & color) | ||||
| { | ||||
| 	currentPalette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color)); | ||||
|  | ||||
| 	for (int i : {6,7}) | ||||
| 	{ | ||||
| 		if (colorsSimilar(originalPalette->colors[i], sourcePalette[i])) | ||||
| 			currentPalette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::preparePalette() | ||||
| { | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			adjustPalette(ColorFilter::genAlphaShifter(0), 0); | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::SIMPLE: | ||||
| 		case EImageBlitMode::WITH_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			setShadowTransparency(1.0); | ||||
| 			break; | ||||
| 		case EImageBlitMode::ONLY_BODY: | ||||
| 		case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			setShadowTransparency(0.0); | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			setOverlayColor(Colors::WHITE_TRUE); | ||||
| 			break; | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_BODY: | ||||
| 			setOverlayColor(Colors::TRANSPARENCY); | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageShared::~SDLImageShared() | ||||
| { | ||||
| 	SDL_FreeSurface(surf); | ||||
| 	SDL_FreePalette(originalPalette); | ||||
| } | ||||
|  | ||||
| SDLImageBase::SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode) | ||||
| 	:image(image) | ||||
| 	, alphaValue(SDL_ALPHA_OPAQUE) | ||||
| 	, blitMode(mode) | ||||
| {} | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageBase::getSharedImage() const | ||||
| { | ||||
| 	return image; | ||||
| } | ||||
|  | ||||
| void SDLImageRGB::draw(SDL_Surface * where, const Point & pos, const Rect * src) const | ||||
| { | ||||
| 	image->draw(where, nullptr, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const | ||||
| { | ||||
| 	image->draw(where, currentPalette, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::exportBitmap(const boost::filesystem::path & path) const | ||||
| { | ||||
| 	image->exportBitmap(path, currentPalette); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::scaleTo(const Point & size) | ||||
| { | ||||
| 	image = image->scaleTo(size, currentPalette); | ||||
| } | ||||
|  | ||||
| void SDLImageRGB::scaleTo(const Point & size) | ||||
| { | ||||
| 	image = image->scaleTo(size, nullptr); | ||||
| } | ||||
|  | ||||
| void SDLImageIndexed::scaleInteger(int factor) | ||||
| { | ||||
| 	image = image->scaleInteger(factor, currentPalette, blitMode); | ||||
| } | ||||
|  | ||||
| void SDLImageRGB::scaleInteger(int factor) | ||||
| { | ||||
| 	image = image->scaleInteger(factor, nullptr, blitMode); | ||||
| } | ||||
|  | ||||
| void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const | ||||
| { | ||||
| 	image->exportBitmap(path, nullptr); | ||||
| } | ||||
|  | ||||
| bool SDLImageBase::isTransparent(const Point & coords) const | ||||
| { | ||||
| 	return image->isTransparent(coords); | ||||
| } | ||||
|  | ||||
| Rect SDLImageBase::contentRect() const | ||||
| { | ||||
| 	return image->contentRect(); | ||||
| } | ||||
|  | ||||
| Point SDLImageBase::dimensions() const | ||||
| { | ||||
| 	return image->dimensions(); | ||||
| } | ||||
|  | ||||
| void SDLImageBase::setAlpha(uint8_t value) | ||||
| { | ||||
| 	alphaValue = value; | ||||
| } | ||||
|  | ||||
| void SDLImageBase::setBlitMode(EImageBlitMode mode) | ||||
| { | ||||
| 	blitMode = mode; | ||||
| } | ||||
|  | ||||
| void SDLImageRGB::setOverlayColor(const ColorRGBA & color) | ||||
| {} | ||||
|  | ||||
| void SDLImageRGB::playerColored(PlayerColor player) | ||||
| {} | ||||
|  | ||||
| void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) | ||||
| {} | ||||
|  | ||||
| void SDLImageRGB::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) | ||||
| {} | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -27,16 +27,15 @@ struct SDL_Palette; | ||||
| class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this<SDLImageShared>, boost::noncopyable | ||||
| { | ||||
| 	//Surface without empty borders | ||||
| 	SDL_Surface * surf; | ||||
| 	SDL_Surface * surf = nullptr; | ||||
|  | ||||
| 	SDL_Palette * originalPalette; | ||||
| 	SDL_Palette * originalPalette = nullptr; | ||||
| 	//size of left and top borders | ||||
| 	Point margins; | ||||
| 	//total size including borders | ||||
| 	Point fullSize; | ||||
|  | ||||
| 	//pre scaled image | ||||
| 	int preScaleFactor; | ||||
| 	std::atomic_bool upscalingInProgress = false; | ||||
|  | ||||
| 	// Keep the original palette, in order to do color switching operation | ||||
| 	void savePalette(); | ||||
| @@ -45,20 +44,27 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from | ||||
|  | ||||
| public: | ||||
| 	//Load image from def file | ||||
| 	SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int preScaleFactor=1); | ||||
| 	SDLImageShared(const CDefFile *data, size_t frame, size_t group=0); | ||||
| 	//Load from bitmap file | ||||
| 	SDLImageShared(const ImagePath & filename, int preScaleFactor=1); | ||||
| 	SDLImageShared(const ImagePath & filename); | ||||
| 	//Create using existing surface, extraRef will increase refcount on SDL_Surface | ||||
| 	SDLImageShared(SDL_Surface * from, int preScaleFactor=1); | ||||
| 	SDLImageShared(SDL_Surface * from); | ||||
| 	/// Creates image at specified scaling factor from source image | ||||
| 	SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm); | ||||
| 	~SDLImageShared(); | ||||
|  | ||||
| 	void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override; | ||||
| 	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override; | ||||
|  | ||||
| 	void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override; | ||||
| 	Point dimensions() const override; | ||||
| 	bool isTransparent(const Point & coords) const override; | ||||
| 	Rect contentRect() const override; | ||||
| 	[[nodiscard]] std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override; | ||||
|  | ||||
| 	bool isLoading() const override; | ||||
|  | ||||
| 	const SDL_Palette * getPalette() const override; | ||||
|  | ||||
| 	[[nodiscard]] std::shared_ptr<const ISharedImage> horizontalFlip() const override; | ||||
| 	[[nodiscard]] std::shared_ptr<const ISharedImage> verticalFlip() const override; | ||||
| 	[[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override; | ||||
| @@ -66,58 +72,3 @@ public: | ||||
|  | ||||
| 	friend class SDLImageLoader; | ||||
| }; | ||||
|  | ||||
| class SDLImageBase : public IImage, boost::noncopyable | ||||
| { | ||||
| protected: | ||||
| 	std::shared_ptr<const ISharedImage> image; | ||||
|  | ||||
| 	uint8_t alphaValue; | ||||
| 	EImageBlitMode blitMode; | ||||
|  | ||||
| public: | ||||
| 	SDLImageBase(const std::shared_ptr<const ISharedImage> & image, EImageBlitMode mode); | ||||
|  | ||||
| 	bool isTransparent(const Point & coords) const override; | ||||
| 	Rect contentRect() const override; | ||||
| 	Point dimensions() const override; | ||||
| 	void setAlpha(uint8_t value) override; | ||||
| 	void setBlitMode(EImageBlitMode mode) override; | ||||
| 	std::shared_ptr<const ISharedImage> getSharedImage() const override; | ||||
| }; | ||||
|  | ||||
| class SDLImageIndexed final : public SDLImageBase | ||||
| { | ||||
| 	SDL_Palette * currentPalette = nullptr; | ||||
| 	SDL_Palette * originalPalette = nullptr; | ||||
|  | ||||
| 	void setShadowTransparency(float factor); | ||||
| 	void preparePalette(); | ||||
| public: | ||||
| 	SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode); | ||||
| 	~SDLImageIndexed(); | ||||
|  | ||||
| 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; | ||||
| 	void setOverlayColor(const ColorRGBA & color) override; | ||||
| 	void playerColored(PlayerColor player) override; | ||||
| 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; | ||||
| 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; | ||||
| 	void scaleInteger(int factor) override; | ||||
| 	void scaleTo(const Point & size) override; | ||||
| 	void exportBitmap(const boost::filesystem::path & path) const override; | ||||
| }; | ||||
|  | ||||
| class SDLImageRGB final : public SDLImageBase | ||||
| { | ||||
| public: | ||||
| 	using SDLImageBase::SDLImageBase; | ||||
|  | ||||
| 	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; | ||||
| 	void setOverlayColor(const ColorRGBA & color) override; | ||||
| 	void playerColored(PlayerColor player) override; | ||||
| 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; | ||||
| 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; | ||||
| 	void scaleInteger(int factor) override; | ||||
| 	void scaleTo(const Point & size) override; | ||||
| 	void exportBitmap(const boost::filesystem::path & path) const override; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										233
									
								
								client/renderSDL/SDLImageScaler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								client/renderSDL/SDLImageScaler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| /* | ||||
|  * SDLImageScaler.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../CMT.h" | ||||
| #include "../xBRZ/xbrz.h" | ||||
|  | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <SDL_surface.h> | ||||
|  | ||||
| SDLImageOptimizer::SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions) | ||||
| 	: surf(surf) | ||||
| 	, virtualDimensions(virtualDimensions) | ||||
| { | ||||
| } | ||||
|  | ||||
| void SDLImageOptimizer::optimizeSurface(SDL_Surface * formatSourceSurface) | ||||
| { | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| 	int left = surf->w; | ||||
| 	int top = surf->h; | ||||
| 	int right = 0; | ||||
| 	int bottom = 0; | ||||
|  | ||||
| 	// locate fully-transparent area around image | ||||
| 	// H3 hadles this on format level, but mods or images scaled in runtime do not | ||||
| 	if (surf->format->palette) | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch; | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				if (row[x] != 0) | ||||
| 				{ | ||||
| 					// opaque or can be opaque (e.g. disabled shadow) | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				ColorRGBA color; | ||||
| 				SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a); | ||||
|  | ||||
| 				if (color.a != SDL_ALPHA_TRANSPARENT) | ||||
| 				{ | ||||
| 					// opaque | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// empty image | ||||
| 	if (left == surf->w) | ||||
| 		return; | ||||
|  | ||||
| 	if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1) | ||||
| 	{ | ||||
| 		// non-zero border found | ||||
| 		Rect newDimensions(left, top, right - left + 1, bottom - top + 1); | ||||
| 		SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions); | ||||
| 		auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), formatSourceSurface); | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); | ||||
| 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr); | ||||
|  | ||||
| 		if (SDL_HasColorKey(surf)) | ||||
| 		{ | ||||
| 			uint32_t colorKey; | ||||
| 			SDL_GetColorKey(surf, &colorKey); | ||||
| 			SDL_SetColorKey(newSurface, SDL_TRUE, colorKey); | ||||
| 		} | ||||
| 		output = newSurface; | ||||
|  | ||||
| 		virtualDimensions.x += left; | ||||
| 		virtualDimensions.y += top; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		output = surf; | ||||
| 		output->refcount += 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDL_Surface * SDLImageOptimizer::acquireResultSurface() | ||||
| { | ||||
| 	SDL_Surface * result = output; | ||||
| 	output = nullptr; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const Rect & SDLImageOptimizer::getResultDimensions() const | ||||
| { | ||||
| 	return virtualDimensions; | ||||
| } | ||||
|  | ||||
| void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(!targetDimensions.x || !targetDimensions.y) | ||||
| 		throw std::runtime_error("invalid scaling dimensions!"); | ||||
|  | ||||
| 	Point inputSurfaceSize(intermediate->w, intermediate->h); | ||||
| 	Point outputSurfaceSize = targetDimensions * inputSurfaceSize / virtualDimensionsInput.dimensions(); | ||||
| 	Point outputMargins = targetDimensions * virtualDimensionsInput.topLeft() / virtualDimensionsInput.dimensions(); | ||||
|  | ||||
| 	// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%? | ||||
| 	// Need to investigate which is optimal	for performance and for visuals | ||||
| 	ret = CSDL_Ext::newSurface(Point(outputSurfaceSize.x, outputSurfaceSize.y), intermediate); | ||||
|  | ||||
| 	virtualDimensionsOutput = Rect(outputMargins, targetDimensions); // TODO: account for input virtual size | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	if (algorithm == EScalingAlgorithm::NEAREST) | ||||
| 		xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 	else | ||||
| 		xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| } | ||||
|  | ||||
| void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(factor == 0) | ||||
| 		throw std::runtime_error("invalid scaling factor!"); | ||||
|  | ||||
| 	int newWidth = intermediate->w * factor; | ||||
| 	int newHight = intermediate->h * factor; | ||||
|  | ||||
| 	virtualDimensionsOutput = virtualDimensionsInput * factor; | ||||
|  | ||||
| 	ret = CSDL_Ext::newSurface(Point(newWidth, newHight), intermediate); | ||||
|  | ||||
| 	assert(intermediate->pitch == intermediate->w * 4); | ||||
| 	assert(ret->pitch == ret->w * 4); | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	switch (algorithm) | ||||
| 	{ | ||||
| 		case EScalingAlgorithm::NEAREST: | ||||
| 			xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::BILINEAR: | ||||
| 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::XBRZ_ALPHA: | ||||
| 		case EScalingAlgorithm::XBRZ_OPAQUE: | ||||
| 		{ | ||||
| 			auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; | ||||
|  | ||||
| 			if(intermediate->h < 32) | ||||
| 			{ | ||||
| 				// for tiny images tbb incurs too high overhead | ||||
| 				xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option | ||||
| 				const int granulation = intermediate->h > 400 ? 16 : 4; | ||||
| 				tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [this, factor, srcPixels, dstPixels, format](const tbb::blocked_range<size_t> & r) | ||||
| 								  { | ||||
| 									  xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); | ||||
| 								  }); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			throw std::runtime_error("invalid scaling algorithm!"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageScaler::SDLImageScaler(SDL_Surface * surf) | ||||
| 	:SDLImageScaler(surf, Rect(0,0,surf->w, surf->h)) | ||||
| { | ||||
| } | ||||
|  | ||||
| SDLImageScaler::SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions) | ||||
| { | ||||
| 	SDLImageOptimizer optimizer(surf, virtualDimensions); | ||||
| 	optimizer.optimizeSurface(screen); | ||||
| 	intermediate = optimizer.acquireResultSurface(); | ||||
| 	virtualDimensionsInput = optimizer.getResultDimensions(); | ||||
|  | ||||
| 	if (intermediate == surf) | ||||
| 	{ | ||||
| 		SDL_FreeSurface(intermediate); | ||||
| 		intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageScaler::~SDLImageScaler() | ||||
| { | ||||
| 	SDL_FreeSurface(intermediate); | ||||
| 	SDL_FreeSurface(ret); | ||||
| } | ||||
|  | ||||
| SDL_Surface * SDLImageScaler::acquireResultSurface() | ||||
| { | ||||
| 	SDL_Surface * result = ret; | ||||
| 	ret = nullptr; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const Rect & SDLImageScaler::getResultDimensions() const | ||||
| { | ||||
| 	return virtualDimensionsOutput; | ||||
| } | ||||
							
								
								
									
										63
									
								
								client/renderSDL/SDLImageScaler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								client/renderSDL/SDLImageScaler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * SDLImageScaler.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../render/IImage.h" | ||||
| #include "../../lib/Rect.h" | ||||
|  | ||||
| class SDLImageOptimizer : boost::noncopyable | ||||
| { | ||||
| 	SDL_Surface * surf = nullptr; | ||||
| 	SDL_Surface * output = nullptr; | ||||
| 	Rect virtualDimensions = Rect(0,0,0,0); | ||||
| public: | ||||
| 	SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions); | ||||
|  | ||||
| 	void optimizeSurface(SDL_Surface * formatSourceSurface); | ||||
|  | ||||
| 	/// Aquires resulting surface and transfers surface ownership to the caller | ||||
| 	/// May return nullptr if input image was empty | ||||
| 	SDL_Surface * acquireResultSurface(); | ||||
|  | ||||
| 	/// Returns adjusted virtual dimensions of resulting surface | ||||
| 	const Rect & getResultDimensions() const; | ||||
| }; | ||||
|  | ||||
| /// Class that performs scaling of SDL surfaces | ||||
| /// Object construction MUST be performed while holding UI lock | ||||
| /// but task execution can be performed asynchronously if needed | ||||
| class SDLImageScaler : boost::noncopyable | ||||
| { | ||||
| 	SDL_Surface * intermediate = nullptr; | ||||
| 	SDL_Surface * ret = nullptr; | ||||
| 	Rect virtualDimensionsInput = Rect(0,0,0,0); | ||||
| 	Rect virtualDimensionsOutput = Rect(0,0,0,0); | ||||
|  | ||||
| public: | ||||
| 	SDLImageScaler(SDL_Surface * surf); | ||||
| 	SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions); | ||||
| 	~SDLImageScaler(); | ||||
|  | ||||
| 	/// Performs upscaling or downscaling to a requested dimensions | ||||
| 	/// Aspect ratio is NOT maintained. | ||||
| 	/// xbrz algorithm is not supported in this mode | ||||
| 	void scaleSurface(Point dimensions, EScalingAlgorithm algorithm); | ||||
|  | ||||
| 	/// Performs upscaling by specified integral factor, potentially using xbrz algorithm if requested | ||||
| 	void scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm); | ||||
|  | ||||
| 	/// Aquires resulting surface and transfers surface ownership to the caller | ||||
| 	/// May return nullptr if input image was empty | ||||
| 	SDL_Surface * acquireResultSurface(); | ||||
|  | ||||
| 	/// Returns adjusted virtual dimensions of resulting surface | ||||
| 	const Rect & getResultDimensions() const; | ||||
| }; | ||||
| @@ -14,6 +14,7 @@ | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../CMT.h" | ||||
| @@ -630,86 +631,6 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // scaling via bilinear interpolation algorithm. | ||||
| // NOTE: best results are for scaling in range 50%...200%. | ||||
| // And upscaling looks awful right now - should be fixed somehow | ||||
| SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height) | ||||
| { | ||||
| 	if(!surf || !width || !height) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%? | ||||
| 	// Need to investigate which is optimal	for performance and for visuals | ||||
|  | ||||
| 	SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0); | ||||
| 	SDL_Surface * ret = newSurface(Point(width, height), intermediate); | ||||
|  | ||||
| #if SDL_VERSION_ATLEAST(2,0,16) | ||||
| 	SDL_SoftStretchLinear(intermediate, nullptr, ret, nullptr); | ||||
| #else | ||||
| 	SDL_SoftStretch(intermediate, nullptr, ret, nullptr); | ||||
| #endif | ||||
| 	SDL_FreeSurface(intermediate); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(surf == nullptr || factor == 0) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	int newWidth = surf->w * factor; | ||||
| 	int newHight = surf->h * factor; | ||||
|  | ||||
| 	SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0); | ||||
| 	SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate); | ||||
|  | ||||
| 	assert(intermediate->pitch == intermediate->w * 4); | ||||
| 	assert(ret->pitch == ret->w * 4); | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	switch (algorithm) | ||||
| 	{ | ||||
| 		case EScalingAlgorithm::NEAREST: | ||||
| 			xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::BILINEAR: | ||||
| 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::XBRZ_ALPHA: | ||||
| 		case EScalingAlgorithm::XBRZ_OPAQUE: | ||||
| 		{ | ||||
| 			auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; | ||||
|  | ||||
| 			if(intermediate->h < 32) | ||||
| 			{ | ||||
| 				// for tiny images tbb incurs too high overhead | ||||
| 				xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option | ||||
| 				const int granulation = intermediate->h > 400 ? 16 : 4; | ||||
| 				tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range<size_t> & r) | ||||
| 				{ | ||||
| 					xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			throw std::runtime_error("invalid scaling algorithm!"); | ||||
| 	} | ||||
|  | ||||
| 	SDL_FreeSurface(intermediate); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint) | ||||
| { | ||||
| 	SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput); | ||||
| @@ -799,10 +720,5 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other) | ||||
| 	other = CSDL_Ext::fromSDL(rect); | ||||
| } | ||||
|  | ||||
| int CSDL_Ext::CClipRectGuard::getScalingFactor() const | ||||
| { | ||||
| 	return GH.screenHandler().getScalingFactor(); | ||||
| } | ||||
|  | ||||
| template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int); | ||||
| template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int); | ||||
|   | ||||
| @@ -27,14 +27,6 @@ class Point; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|  | ||||
| enum class EScalingAlgorithm : int8_t | ||||
| { | ||||
| 	NEAREST, | ||||
| 	BILINEAR, | ||||
| 	XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image | ||||
| 	XBRZ_ALPHA // xbrz, image edges are considered to be transparent | ||||
| }; | ||||
|  | ||||
| namespace CSDL_Ext | ||||
| { | ||||
|  | ||||
| @@ -98,10 +90,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, | ||||
| 	template<int bpp> | ||||
| 	SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value | ||||
|  | ||||
| 	// bilinear filtering. Always returns rgba surface | ||||
| 	SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height); | ||||
| 	SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler); | ||||
|  | ||||
| 	template<int bpp> | ||||
| 	void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect); | ||||
| 	void convertToGrayscale(SDL_Surface * surf, const Rect & rect); | ||||
|   | ||||
							
								
								
									
										532
									
								
								client/renderSDL/ScalableImage.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								client/renderSDL/ScalableImage.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,532 @@ | ||||
| /* | ||||
|  * ScalableImage.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "ScalableImage.h" | ||||
|  | ||||
| #include "SDLImage.h" | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
|  | ||||
| #include "../render/ColorFilter.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../render/CanvasImage.h" | ||||
|  | ||||
| #include "../../lib/constants/EntityIdentifiers.h" | ||||
|  | ||||
| #include <SDL_surface.h> | ||||
|  | ||||
| //First 8 colors in def palette used for transparency | ||||
| static constexpr std::array<SDL_Color, 8> sourcePalette = {{ | ||||
| 	{0,   255, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 150, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 100, 255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 50,  255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 0,   255, SDL_ALPHA_OPAQUE}, | ||||
| 	{255, 255, 0,   SDL_ALPHA_OPAQUE}, | ||||
| 	{180, 0,   255, SDL_ALPHA_OPAQUE}, | ||||
| 	{0,   255, 0,   SDL_ALPHA_OPAQUE} | ||||
| }}; | ||||
|  | ||||
| static constexpr std::array<ColorRGBA, 8> targetPalette = {{ | ||||
| 	{0, 0, 0, 0  }, // 0 - transparency                  ( used in most images ) | ||||
| 	{0, 0, 0, 64 }, // 1 - shadow border                 ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 64 }, // 2 - shadow border                 ( used in fog-of-war def's ) | ||||
| 	{0, 0, 0, 128}, // 3 - shadow body                   ( used in fog-of-war def's ) | ||||
| 	{0, 0, 0, 128}, // 4 - shadow body                   ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 0  }, // 5 - selection / owner flag        ( used in battle, adventure map def's ) | ||||
| 	{0, 0, 0, 128}, // 6 - shadow body   below selection ( used in battle def's ) | ||||
| 	{0, 0, 0, 64 }  // 7 - shadow border below selection ( used in battle def's ) | ||||
| }}; | ||||
|  | ||||
| static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2) | ||||
| { | ||||
| 	return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256; | ||||
| } | ||||
|  | ||||
| static ColorRGBA addColors(const ColorRGBA & base, const ColorRGBA & over) | ||||
| { | ||||
| 	return ColorRGBA( | ||||
| 		mixChannels(over.r, base.r, over.a, base.a), | ||||
| 		mixChannels(over.g, base.g, over.a, base.a), | ||||
| 		mixChannels(over.b, base.b, over.a, base.a), | ||||
| 		static_cast<ui8>(over.a + base.a * (255 - over.a) / 256) | ||||
| 		); | ||||
| } | ||||
| static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs) | ||||
| { | ||||
| 	// it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow | ||||
| 	// exact logic is not clear and requires extensive testing with image editing | ||||
| 	// potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component | ||||
| 	static const int threshold = 8; | ||||
|  | ||||
| 	int diffR = static_cast<int>(lhs.r) - rhs.r; | ||||
| 	int diffG = static_cast<int>(lhs.g) - rhs.g; | ||||
| 	int diffB = static_cast<int>(lhs.b) - rhs.b; | ||||
| 	int diffA = static_cast<int>(lhs.a) - rhs.a; | ||||
|  | ||||
| 	return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold; | ||||
| } | ||||
|  | ||||
| ScalableImageParameters::ScalableImageParameters(const SDL_Palette * originalPalette, EImageBlitMode blitMode) | ||||
| { | ||||
| 	if (originalPalette) | ||||
| 	{ | ||||
| 		palette = SDL_AllocPalette(originalPalette->ncolors); | ||||
| 		SDL_SetPaletteColors(palette, originalPalette->colors, 0, originalPalette->ncolors); | ||||
| 		preparePalette(originalPalette, blitMode); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ScalableImageParameters::~ScalableImageParameters() | ||||
| { | ||||
| 	SDL_FreePalette(palette); | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode) | ||||
| { | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			adjustPalette(originalPalette, blitMode, ColorFilter::genAlphaShifter(0), 0); | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::SIMPLE: | ||||
| 		case EImageBlitMode::WITH_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			setShadowTransparency(originalPalette, 1.0); | ||||
| 			break; | ||||
| 		case EImageBlitMode::ONLY_BODY: | ||||
| 		case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			setShadowTransparency(originalPalette, 0.0); | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	switch(blitMode) | ||||
| 	{ | ||||
| 		case EImageBlitMode::ONLY_OVERLAY: | ||||
| 		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			setOverlayColor(originalPalette, Colors::WHITE_TRUE); | ||||
| 			break; | ||||
| 		case EImageBlitMode::ONLY_SHADOW: | ||||
| 		case EImageBlitMode::ONLY_BODY: | ||||
| 			setOverlayColor(originalPalette, Colors::TRANSPARENCY); | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color) | ||||
| { | ||||
| 	palette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color)); | ||||
|  | ||||
| 	for (int i : {6,7}) | ||||
| 	{ | ||||
| 		if (colorsSimilar(originalPalette->colors[i], sourcePalette[i])) | ||||
| 			palette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) | ||||
| { | ||||
| 	std::vector<SDL_Color> shifterColors(colorsToMove); | ||||
|  | ||||
| 	for(uint32_t i=0; i<colorsToMove; ++i) | ||||
| 		shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i]; | ||||
|  | ||||
| 	SDL_SetPaletteColors(palette, shifterColors.data(), firstColorID, colorsToMove); | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::setShadowTransparency(const SDL_Palette * originalPalette, float factor) | ||||
| { | ||||
| 	ColorRGBA shadow50(0, 0, 0, 128 * factor); | ||||
| 	ColorRGBA shadow25(0, 0, 0,  64 * factor); | ||||
|  | ||||
| 	std::array<SDL_Color, 5> colorsSDL = { | ||||
| 		originalPalette->colors[0], | ||||
| 		originalPalette->colors[1], | ||||
| 		originalPalette->colors[2], | ||||
| 		originalPalette->colors[3], | ||||
| 		originalPalette->colors[4] | ||||
| 	}; | ||||
|  | ||||
| 	// seems to be used unconditionally | ||||
| 	colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY); | ||||
| 	colorsSDL[1] = CSDL_Ext::toSDL(shadow25); | ||||
| 	colorsSDL[4] = CSDL_Ext::toSDL(shadow50); | ||||
|  | ||||
| 	// seems to be used only if color matches | ||||
| 	if (colorsSimilar(originalPalette->colors[2], sourcePalette[2])) | ||||
| 		colorsSDL[2] = CSDL_Ext::toSDL(shadow25); | ||||
|  | ||||
| 	if (colorsSimilar(originalPalette->colors[3], sourcePalette[3])) | ||||
| 		colorsSDL[3] = CSDL_Ext::toSDL(shadow50); | ||||
|  | ||||
| 	SDL_SetPaletteColors(palette, colorsSDL.data(), 0, colorsSDL.size()); | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask) | ||||
| { | ||||
| 	// If shadow is enabled, following colors must be skipped unconditionally | ||||
| 	if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY) | ||||
| 		colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4); | ||||
|  | ||||
| 	// Note: here we skip first colors in the palette that are predefined in H3 images | ||||
| 	for(int i = 0; i < palette->ncolors; i++) | ||||
| 	{ | ||||
| 		if (i < std::size(sourcePalette) && colorsSimilar(sourcePalette[i], originalPalette->colors[i])) | ||||
| 			continue; | ||||
|  | ||||
| 		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1) | ||||
| 			continue; | ||||
|  | ||||
| 		palette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i]))); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ScalableImageShared::ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & baseImage) | ||||
| 	:locator(locator) | ||||
| { | ||||
| 	scaled[1].body[0] = baseImage; | ||||
| 	assert(scaled[1].body[0] != nullptr); | ||||
|  | ||||
| 	loadScaledImages(GH.screenHandler().getScalingFactor(), PlayerColor::CANNOT_DETERMINE); | ||||
| } | ||||
|  | ||||
| Point ScalableImageShared::dimensions() const | ||||
| { | ||||
| 	return scaled[1].body[0]->dimensions(); | ||||
| } | ||||
|  | ||||
| void ScalableImageShared::exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const | ||||
| { | ||||
| 	scaled[1].body[0]->exportBitmap(path, parameters.palette); | ||||
| } | ||||
|  | ||||
| bool ScalableImageShared::isTransparent(const Point & coords) const | ||||
| { | ||||
| 	return scaled[1].body[0]->isTransparent(coords); | ||||
| } | ||||
|  | ||||
| Rect ScalableImageShared::contentRect() const | ||||
| { | ||||
| 	return scaled[1].body[0]->contentRect(); | ||||
| } | ||||
|  | ||||
| void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor) | ||||
| { | ||||
| 	const auto & getFlippedImage = [&](FlippedImages & images){ | ||||
| 		int index = 0; | ||||
| 		if (parameters.flipVertical) | ||||
| 		{ | ||||
| 			if (!images[index|1]) | ||||
| 				images[index|1] = images[index]->verticalFlip(); | ||||
|  | ||||
| 			index |= 1; | ||||
| 		} | ||||
|  | ||||
| 		if (parameters.flipHorizontal) | ||||
| 		{ | ||||
| 			if (!images[index|2]) | ||||
| 				images[index|2] = images[index]->horizontalFlip(); | ||||
|  | ||||
| 			index |= 2; | ||||
| 		} | ||||
|  | ||||
| 		return images[index]; | ||||
| 	}; | ||||
|  | ||||
| 	const auto & flipAndDraw = [&](FlippedImages & images, const ColorRGBA & colorMultiplier, uint8_t alphaValue){ | ||||
|  | ||||
| 		getFlippedImage(images)->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer); | ||||
| 	}; | ||||
|  | ||||
| 	bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading(); | ||||
| 	bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading(); | ||||
| 	bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading(); | ||||
| 	bool playerLoading = parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->isLoading(); | ||||
|  | ||||
| 	if (shadowLoading || bodyLoading || overlayLoading || playerLoading) | ||||
| 	{ | ||||
| 		getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (scaled.at(scalingFactor).shadow.at(0)) | ||||
| 		flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue); | ||||
|  | ||||
| 	if (parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())) | ||||
| 	{ | ||||
| 		scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if (scaled.at(scalingFactor).body.at(0)) | ||||
| 			flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue); | ||||
| 	} | ||||
|  | ||||
| 	if (scaled.at(scalingFactor).overlay.at(0)) | ||||
| 		flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast<int>(parameters.alphaValue) * parameters.ovelayColorMultiplier.a / 255); | ||||
| } | ||||
|  | ||||
| const SDL_Palette * ScalableImageShared::getPalette() const | ||||
| { | ||||
| 	return scaled[1].body[0]->getPalette(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ScalableImageInstance> ScalableImageShared::createImageReference() | ||||
| { | ||||
| 	return std::make_shared<ScalableImageInstance>(shared_from_this(), locator.layer); | ||||
| } | ||||
|  | ||||
| ScalableImageInstance::ScalableImageInstance(const std::shared_ptr<ScalableImageShared> & image, EImageBlitMode blitMode) | ||||
| 	:image(image) | ||||
| 	,parameters(image->getPalette(), blitMode) | ||||
| 	,blitMode(blitMode) | ||||
| { | ||||
| 	assert(image); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::scaleTo(const Point & size, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	scaledImage = nullptr; | ||||
|  | ||||
| 	auto newScaledImage = GH.renderHandler().createImage(dimensions(), CanvasScalingPolicy::AUTO); | ||||
|  | ||||
| 	newScaledImage->getCanvas().draw(*this, Point(0, 0)); | ||||
| 	newScaledImage->scaleTo(size, algorithm); | ||||
| 	scaledImage = newScaledImage; | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::exportBitmap(const boost::filesystem::path & path) const | ||||
| { | ||||
| 	image->exportBitmap(path, parameters); | ||||
| } | ||||
|  | ||||
| bool ScalableImageInstance::isTransparent(const Point & coords) const | ||||
| { | ||||
| 	return image->isTransparent(coords); | ||||
| } | ||||
|  | ||||
| Rect ScalableImageInstance::contentRect() const | ||||
| { | ||||
| 	return image->contentRect(); | ||||
| } | ||||
|  | ||||
| Point ScalableImageInstance::dimensions() const | ||||
| { | ||||
| 	if (scaledImage) | ||||
| 		return scaledImage->dimensions() / GH.screenHandler().getScalingFactor(); | ||||
| 	return image->dimensions(); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::setAlpha(uint8_t value) | ||||
| { | ||||
| 	parameters.alphaValue = value; | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const | ||||
| { | ||||
| 	if (scaledImage) | ||||
| 		scaledImage->draw(where, pos, src, scalingFactor); | ||||
| 	else | ||||
| 		image->draw(where, pos, src, parameters, scalingFactor); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::setOverlayColor(const ColorRGBA & color) | ||||
| { | ||||
| 	parameters.ovelayColorMultiplier = color; | ||||
|  | ||||
| 	if (parameters.palette) | ||||
| 		parameters.setOverlayColor(image->getPalette(), color); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::playerColored(const PlayerColor & player) | ||||
| { | ||||
| 	parameters.player = player; | ||||
|  | ||||
| 	if (parameters.palette) | ||||
| 		parameters.playerColored(player); | ||||
|  | ||||
| 	image->preparePlayerColoredImage(player); | ||||
| } | ||||
|  | ||||
| void ScalableImageParameters::playerColored(PlayerColor player) | ||||
| { | ||||
| 	graphics->setPlayerPalette(palette, player); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) | ||||
| { | ||||
| 	if (parameters.palette) | ||||
| 		parameters.shiftPalette(image->getPalette(),firstColorID, colorsToMove, distanceToMove); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) | ||||
| { | ||||
| 	if (parameters.palette) | ||||
| 		parameters.adjustPalette(image->getPalette(), blitMode, shifter, colorsToSkipMask); | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::horizontalFlip() | ||||
| { | ||||
| 	parameters.flipHorizontal = !parameters.flipHorizontal; | ||||
| } | ||||
|  | ||||
| void ScalableImageInstance::verticalFlip() | ||||
| { | ||||
| 	parameters.flipVertical = !parameters.flipVertical; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const | ||||
| { | ||||
| 	ImageLocator loadingLocator; | ||||
|  | ||||
| 	loadingLocator.image = locator.image; | ||||
| 	loadingLocator.defFile = locator.defFile; | ||||
| 	loadingLocator.defFrame = locator.defFrame; | ||||
| 	loadingLocator.defGroup = locator.defGroup; | ||||
| 	loadingLocator.layer = mode; | ||||
| 	loadingLocator.scalingFactor = scalingFactor; | ||||
| 	loadingLocator.playerColored = color; | ||||
|  | ||||
| 	// best case - requested image is already available in filesystem | ||||
| 	auto loadedImage = GH.renderHandler().loadScaledImage(loadingLocator); | ||||
| 	if (loadedImage) | ||||
| 		return loadedImage; | ||||
|  | ||||
| 	if (scalingFactor == 1) | ||||
| 	{ | ||||
| 		// optional images for 1x resolution - only try load them, don't attempt to generate | ||||
| 		// this block should never be called for 'body' layer - that image is loaded unconditionally before construction | ||||
| 		assert(mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE); | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	// alternatively, find largest pre-scaled image, load it and rescale to desired scaling | ||||
| 	for (int8_t scaling = 4; scaling > 0; --scaling) | ||||
| 	{ | ||||
| 		loadingLocator.scalingFactor = scaling; | ||||
| 		auto loadedImage = GH.renderHandler().loadScaledImage(loadingLocator); | ||||
| 		if (loadedImage) | ||||
| 		{ | ||||
| 			if (scaling == 1) | ||||
| 			{ | ||||
| 				if (mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE) | ||||
| 				{ | ||||
| 					ScalableImageParameters parameters(getPalette(), mode); | ||||
| 					return loadedImage->scaleInteger(scalingFactor, parameters.palette, mode); | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				Point targetSize = scaled[1].body[0]->dimensions() * scalingFactor; | ||||
| 				return loadedImage->scaleTo(targetSize, nullptr); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ScalableImageParameters parameters(getPalette(), mode); | ||||
| 	// if all else fails - use base (presumably, indexed) image and convert it to desired form | ||||
| 	if (color != PlayerColor::CANNOT_DETERMINE) | ||||
| 		parameters.playerColored(color); | ||||
|  | ||||
| 	if (upscalingSource) | ||||
| 		return upscalingSource->scaleInteger(scalingFactor, parameters.palette, mode); | ||||
| 	else | ||||
| 		return scaled[1].body[0]->scaleInteger(scalingFactor, parameters.palette, mode); | ||||
| } | ||||
|  | ||||
| void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor color) | ||||
| { | ||||
| 	if (scaled[scalingFactor].body[0] == nullptr && scalingFactor != 1) | ||||
| 	{ | ||||
| 		switch(locator.layer) | ||||
| 		{ | ||||
| 			case EImageBlitMode::OPAQUE: | ||||
| 			case EImageBlitMode::COLORKEY: | ||||
| 			case EImageBlitMode::SIMPLE: | ||||
| 				scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); | ||||
| 				break; | ||||
|  | ||||
| 			case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			case EImageBlitMode::ONLY_BODY: | ||||
| 				scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); | ||||
| 				break; | ||||
|  | ||||
| 			case EImageBlitMode::WITH_SHADOW: | ||||
| 			case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: | ||||
| 				scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (color != PlayerColor::CANNOT_DETERMINE && scaled[scalingFactor].playerColored[1+color.getNum()] == nullptr) | ||||
| 	{ | ||||
| 		switch(locator.layer) | ||||
| 		{ | ||||
| 			case EImageBlitMode::OPAQUE: | ||||
| 			case EImageBlitMode::COLORKEY: | ||||
| 			case EImageBlitMode::SIMPLE: | ||||
| 				scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); | ||||
| 				break; | ||||
|  | ||||
| 			case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 			case EImageBlitMode::ONLY_BODY: | ||||
| 				scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); | ||||
| 				break; | ||||
|  | ||||
| 			case EImageBlitMode::WITH_SHADOW: | ||||
| 			case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: | ||||
| 				scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (scaled[scalingFactor].shadow[0] == nullptr) | ||||
| 	{ | ||||
| 		switch(locator.layer) | ||||
| 		{ | ||||
| 			case EImageBlitMode::WITH_SHADOW: | ||||
| 			case EImageBlitMode::ONLY_SHADOW: | ||||
| 			case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 				scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]); | ||||
| 				break; | ||||
| 			default: | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (scaled[scalingFactor].overlay[0] == nullptr) | ||||
| 	{ | ||||
| 		switch(locator.layer) | ||||
| 		{ | ||||
| 			case EImageBlitMode::ONLY_OVERLAY: | ||||
| 			case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: | ||||
| 				scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]); | ||||
| 				break; | ||||
| 			default: | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ScalableImageShared::preparePlayerColoredImage(PlayerColor color) | ||||
| { | ||||
| 	loadScaledImages(GH.screenHandler().getScalingFactor(), color); | ||||
| } | ||||
							
								
								
									
										125
									
								
								client/renderSDL/ScalableImage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								client/renderSDL/ScalableImage.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| /* | ||||
|  * ScalableImage.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/ImageLocator.h" | ||||
| #include "../render/Colors.h" | ||||
|  | ||||
| #include "../../lib/Color.h" | ||||
|  | ||||
| struct SDL_Palette; | ||||
|  | ||||
| class ScalableImageInstance; | ||||
| class CanvasImage; | ||||
|  | ||||
| struct ScalableImageParameters : boost::noncopyable | ||||
| { | ||||
| 	SDL_Palette * palette = nullptr; | ||||
|  | ||||
| 	ColorRGBA colorMultiplier = Colors::WHITE_TRUE; | ||||
| 	ColorRGBA ovelayColorMultiplier = Colors::WHITE_TRUE; | ||||
|  | ||||
| 	PlayerColor player = PlayerColor::CANNOT_DETERMINE; | ||||
| 	uint8_t alphaValue = 255; | ||||
|  | ||||
| 	bool flipVertical = false; | ||||
| 	bool flipHorizontal = false; | ||||
|  | ||||
| 	ScalableImageParameters(const SDL_Palette * originalPalette, EImageBlitMode blitMode); | ||||
| 	~ScalableImageParameters(); | ||||
|  | ||||
| 	void setShadowTransparency(const SDL_Palette * originalPalette, float factor); | ||||
| 	void shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove); | ||||
| 	void playerColored(PlayerColor player); | ||||
| 	void setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color); | ||||
| 	void preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode); | ||||
| 	void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask); | ||||
| }; | ||||
|  | ||||
| class ScalableImageShared final : public std::enable_shared_from_this<ScalableImageShared>, boost::noncopyable | ||||
| { | ||||
| 	static constexpr int scalingSize = 5; // 0-4 range. TODO: switch to 1-4 since there is no '0' scaling | ||||
| 	static constexpr int maxFlips = 4; | ||||
|  | ||||
| 	using ImageType = std::shared_ptr<const ISharedImage>; | ||||
| 	using FlippedImages = std::array<ImageType, maxFlips>; | ||||
| 	using PlayerColoredImages = std::array<ImageType, PlayerColor::PLAYER_LIMIT_I + 1>; // all valid colors+neutral | ||||
|  | ||||
| 	struct ScaledImage | ||||
| 	{ | ||||
| 		/// Upscaled shadow of our image, may be null | ||||
| 		FlippedImages shadow; | ||||
|  | ||||
| 		/// Upscaled main part of our image, may be null | ||||
| 		FlippedImages body; | ||||
|  | ||||
| 		/// Upscaled overlay (player color, selection highlight) of our image, may be null | ||||
| 		FlippedImages overlay; | ||||
|  | ||||
| 		/// player-colored images of this particular scale, mostly for UI. These are never flipped in h3 | ||||
| 		PlayerColoredImages playerColored; | ||||
| 	}; | ||||
|  | ||||
| 	/// 1x-4x images. body for 1x scaling is guaranteed to be loaded | ||||
| 	std::array<ScaledImage, scalingSize> scaled; | ||||
|  | ||||
| 	/// Locator of this image, for loading additional (e.g. upscaled) images | ||||
| 	const SharedImageLocator locator; | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const; | ||||
|  | ||||
| 	void loadScaledImages(int8_t scalingFactor, PlayerColor color); | ||||
|  | ||||
| public: | ||||
| 	ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & baseImage); | ||||
|  | ||||
| 	Point dimensions() const; | ||||
| 	void exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const; | ||||
| 	bool isTransparent(const Point & coords) const; | ||||
| 	Rect contentRect() const; | ||||
| 	void draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor); | ||||
|  | ||||
| 	const SDL_Palette * getPalette() const; | ||||
|  | ||||
| 	std::shared_ptr<ScalableImageInstance> createImageReference(); | ||||
|  | ||||
| 	void preparePlayerColoredImage(PlayerColor color); | ||||
| }; | ||||
|  | ||||
| class ScalableImageInstance final : public IImage | ||||
| { | ||||
| 	friend class ScalableImageShared; | ||||
|  | ||||
| 	std::shared_ptr<ScalableImageShared> image; | ||||
| 	std::shared_ptr<CanvasImage> scaledImage; | ||||
|  | ||||
| 	ScalableImageParameters parameters; | ||||
| 	EImageBlitMode blitMode; | ||||
|  | ||||
| public: | ||||
| 	ScalableImageInstance(const std::shared_ptr<ScalableImageShared> & image, EImageBlitMode blitMode); | ||||
|  | ||||
| 	void scaleTo(const Point & size, EScalingAlgorithm algorithm) override; | ||||
| 	void exportBitmap(const boost::filesystem::path & path) const override; | ||||
| 	bool isTransparent(const Point & coords) const override; | ||||
| 	Rect contentRect() const override; | ||||
| 	Point dimensions() const override; | ||||
| 	void setAlpha(uint8_t value) override; | ||||
| 	void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override; | ||||
| 	void setOverlayColor(const ColorRGBA & color) override; | ||||
| 	void playerColored(const PlayerColor & player) override; | ||||
| 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; | ||||
| 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; | ||||
|  | ||||
| 	void horizontalFlip(); | ||||
| 	void verticalFlip(); | ||||
| }; | ||||
|  | ||||
| @@ -11,13 +11,14 @@ | ||||
| #include "StdInc.h" | ||||
| #include "ScreenHandler.h" | ||||
|  | ||||
| #include "../eventsSDL/NotificationHandler.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../gui/WindowHandler.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "CMT.h" | ||||
|  | ||||
| #include "../../lib/CConfigHandler.h" | ||||
| #include "../../lib/constants/StringConstants.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../eventsSDL/NotificationHandler.h" | ||||
| #include "../gui/WindowHandler.h" | ||||
| #include "CMT.h" | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #ifdef VCMI_ANDROID | ||||
| #include "../lib/CAndroidVMHelper.h" | ||||
|   | ||||
| @@ -13,7 +13,6 @@ | ||||
| #include "MiscWidgets.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../render/AssetGenerator.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| @@ -53,8 +52,8 @@ CPicture::CPicture( const ImagePath & bmpname ) | ||||
| 	: CPicture(bmpname, Point(0,0)) | ||||
| {} | ||||
|  | ||||
| CPicture::CPicture( const ImagePath & bmpname, const Point & position ) | ||||
| 	: bg(GH.renderHandler().loadImage(bmpname, EImageBlitMode::COLORKEY)) | ||||
| CPicture::CPicture( const ImagePath & bmpname, const Point & position, EImageBlitMode mode ) | ||||
| 	: bg(GH.renderHandler().loadImage(bmpname, mode)) | ||||
| 	, needRefresh(false) | ||||
| { | ||||
| 	pos.x += position.x; | ||||
| @@ -74,6 +73,10 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position ) | ||||
| 	addUsedEvents(SHOW_POPUP); | ||||
| } | ||||
|  | ||||
| CPicture::CPicture( const ImagePath & bmpname, const Point & position ) | ||||
| 	:CPicture(bmpname, position, EImageBlitMode::COLORKEY) | ||||
| {} | ||||
|  | ||||
| CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y) | ||||
| 	: CPicture(bmpname, Point(x,y)) | ||||
| { | ||||
| @@ -118,7 +121,7 @@ void CPicture::setAlpha(uint8_t value) | ||||
|  | ||||
| void CPicture::scaleTo(Point size) | ||||
| { | ||||
| 	bg->scaleTo(size); | ||||
| 	bg->scaleTo(size, EScalingAlgorithm::BILINEAR); | ||||
|  | ||||
| 	pos.w = bg->width(); | ||||
| 	pos.h = bg->height(); | ||||
| @@ -160,7 +163,7 @@ CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position, Rect | ||||
|  | ||||
| void CFilledTexture::showAll(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), pos); | ||||
| 	CanvasClipRectGuard guard(to, pos); | ||||
|  | ||||
| 	for (int y=pos.top(); y < pos.bottom(); y+= imageArea.h) | ||||
| 	{ | ||||
| @@ -266,7 +269,7 @@ void CAnimImage::showAll(Canvas & to) | ||||
| 		if(auto img = anim->getImage(targetFrame, group)) | ||||
| 		{ | ||||
| 			if(isScaled()) | ||||
| 				img->scaleTo(scaledSize); | ||||
| 				img->scaleTo(scaledSize, EScalingAlgorithm::BILINEAR); | ||||
|  | ||||
| 			to.draw(img, pos.topLeft()); | ||||
| 		} | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class CAnimImage; | ||||
| class CLabel; | ||||
| class CAnimation; | ||||
| class IImage; | ||||
| enum class EImageBlitMode : uint8_t; | ||||
|  | ||||
| // Image class | ||||
| class CPicture : public CIntObject | ||||
| @@ -49,6 +50,7 @@ public: | ||||
|  | ||||
| 	/// Loads image from specified file name | ||||
| 	CPicture(const ImagePath & bmpname); | ||||
| 	CPicture(const ImagePath & bmpname, const Point & position, EImageBlitMode mode); | ||||
| 	CPicture(const ImagePath & bmpname, const Point & position); | ||||
| 	CPicture(const ImagePath & bmpname, int x, int y); | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,6 @@ | ||||
| #include "../windows/CMessage.h" | ||||
| #include "../windows/InfoWindows.h" | ||||
| #include "../adventureMap/CInGameConsole.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../render/IFont.h" | ||||
| @@ -298,7 +297,7 @@ void CMultiLineLabel::showAll(Canvas & to) | ||||
| 	Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * fontPtr->getLineHeight()); | ||||
| 	Point lineSize = Point(getTextLocation().w, fontPtr->getLineHeight()); | ||||
|  | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), getTextLocation()); // to properly trim text that is too big to fit | ||||
| 	CanvasClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit | ||||
|  | ||||
| 	for(int i = beginLine; i < std::min(totalLines, endLine); i++) | ||||
| 	{ | ||||
|   | ||||
| @@ -86,7 +86,7 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) | ||||
| void VideoWidgetBase::show(Canvas & to) | ||||
| { | ||||
| 	if(videoInstance) | ||||
| 		videoInstance->show(pos.topLeft(), to); | ||||
| 		to.draw(*videoInstance, pos.topLeft()); | ||||
| 	if(subTitle) | ||||
| 		subTitle->showAll(to); | ||||
| } | ||||
| @@ -162,7 +162,7 @@ void VideoWidgetBase::deactivate() | ||||
| void VideoWidgetBase::showAll(Canvas & to) | ||||
| { | ||||
| 	if(videoInstance) | ||||
| 		videoInstance->show(pos.topLeft(), to); | ||||
| 		to.draw(*videoInstance, pos.topLeft()); | ||||
| 	if(subTitle) | ||||
| 		subTitle->showAll(to); | ||||
| } | ||||
|   | ||||
| @@ -570,9 +570,8 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town): | ||||
| { | ||||
| 	OBJECT_CONSTRUCTION; | ||||
|  | ||||
| 	background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground); | ||||
| 	background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground, Point(0,0), EImageBlitMode::OPAQUE); | ||||
| 	background->needRefresh = true; | ||||
| 	background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); | ||||
| 	pos.w = background->pos.w; | ||||
| 	pos.h = background->pos.h; | ||||
|  | ||||
| @@ -974,7 +973,7 @@ void CCastleBuildings::enterCastleGate(BuildingID building) | ||||
| 			if(settings["general"]["enableUiEnhancements"].Bool()) | ||||
| 			{ | ||||
| 				auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); | ||||
| 				image->scaleTo(Point(35, 23)); | ||||
| 				image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST); | ||||
| 				images.push_back(image); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| #include "../widgets/TextControls.h" | ||||
| #include "../windows/GUIClasses.h" | ||||
| #include "../windows/InfoWindows.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/CanvasImage.h" | ||||
| #include "../render/IImage.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../render/Graphics.h" | ||||
| @@ -58,9 +58,10 @@ CMapOverview::CMapOverview(const std::string & mapName, const std::string & file | ||||
| 	fitToScreen(10); | ||||
| } | ||||
|  | ||||
| Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const | ||||
| std::shared_ptr<CanvasImage> CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const | ||||
| { | ||||
| 	Canvas canvas = Canvas(Point(map->width, map->height), CanvasScalingPolicy::IGNORE); | ||||
| 	auto canvasImage = GH.renderHandler().createImage(Point(map->width, map->height), CanvasScalingPolicy::IGNORE); | ||||
| 	auto canvas = canvasImage->getCanvas(); | ||||
|  | ||||
| 	for (int y = 0; y < map->height; ++y) | ||||
| 		for (int x = 0; x < map->width; ++x) | ||||
| @@ -91,12 +92,12 @@ Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, in | ||||
| 			canvas.drawPoint(Point(x, y), color); | ||||
| 		} | ||||
| 	 | ||||
| 	return canvas; | ||||
| 	return canvasImage; | ||||
| } | ||||
|  | ||||
| std::vector<Canvas> CMapOverviewWidget::createMinimaps(ResourcePath resource) const | ||||
| std::vector<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(ResourcePath resource) const | ||||
| { | ||||
| 	auto ret = std::vector<Canvas>(); | ||||
| 	std::vector<std::shared_ptr<CanvasImage>> ret; | ||||
|  | ||||
| 	CMapService mapService; | ||||
| 	std::unique_ptr<CMap> map; | ||||
| @@ -113,9 +114,9 @@ std::vector<Canvas> CMapOverviewWidget::createMinimaps(ResourcePath resource) co | ||||
| 	return createMinimaps(map); | ||||
| } | ||||
|  | ||||
| std::vector<Canvas> CMapOverviewWidget::createMinimaps(std::unique_ptr<CMap> & map) const | ||||
| std::vector<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(std::unique_ptr<CMap> & map) const | ||||
| { | ||||
| 	auto ret = std::vector<Canvas>(); | ||||
| 	std::vector<std::shared_ptr<CanvasImage>> ret; | ||||
|  | ||||
| 	for(int i = 0; i < (map->twoLevel ? 2 : 1); i++) | ||||
| 		ret.push_back(createMinimapForLayer(map, i)); | ||||
| @@ -133,17 +134,15 @@ std::shared_ptr<CPicture> CMapOverviewWidget::buildDrawMinimap(const JsonNode & | ||||
| 	if(id >= minimaps.size()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	Rect minimapRect = minimaps[id].getRenderArea(); | ||||
| 	double maxSideLengthSrc = std::max(minimapRect.w, minimapRect.h); | ||||
| 	Point minimapRect = minimaps[id]->dimensions(); | ||||
| 	double maxSideLengthSrc = std::max(minimapRect.x, minimapRect.y); | ||||
| 	double maxSideLengthDst = std::max(rect.w, rect.h); | ||||
| 	double resize = maxSideLengthSrc / maxSideLengthDst; | ||||
| 	Point newMinimapSize = Point(minimapRect.w / resize, minimapRect.h / resize); | ||||
| 	Point newMinimapSize = Point(minimapRect.x / resize, minimapRect.y / resize); | ||||
|  | ||||
| 	Canvas canvasScaled = Canvas(Point(rect.w, rect.h), CanvasScalingPolicy::AUTO); | ||||
| 	canvasScaled.drawScaled(minimaps[id], Point((rect.w - newMinimapSize.x) / 2, (rect.h - newMinimapSize.y) / 2), newMinimapSize); | ||||
| 	std::shared_ptr<IImage> img = GH.renderHandler().createImage(canvasScaled.getInternalSurface()); | ||||
| 	minimaps[id]->scaleTo(newMinimapSize, EScalingAlgorithm::NEAREST); // for sharp-looking minimap | ||||
|  | ||||
| 	return std::make_shared<CPicture>(img, Point(rect.x, rect.y)); | ||||
| 	return std::make_shared<CPicture>(minimaps[id], Point(rect.x, rect.y)); | ||||
| } | ||||
|  | ||||
| CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent): | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class CPicture; | ||||
| class CFilledTexture; | ||||
| class CTextBox; | ||||
| class IImage; | ||||
| class Canvas; | ||||
| class CanvasImage; | ||||
| class TransparentFilledRectangle; | ||||
| enum class ESelectionScreen : ui8; | ||||
|  | ||||
| @@ -33,11 +33,11 @@ class CMapOverviewWidget : public InterfaceObjectConfigurable | ||||
| 	CMapOverview& p; | ||||
|  | ||||
| 	bool drawPlayerElements; | ||||
| 	std::vector<Canvas> minimaps; | ||||
| 	std::vector<std::shared_ptr<CanvasImage>> minimaps; | ||||
|  | ||||
| 	Canvas createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const; | ||||
| 	std::vector<Canvas> createMinimaps(ResourcePath resource) const; | ||||
| 	std::vector<Canvas> createMinimaps(std::unique_ptr<CMap> & map) const; | ||||
| 	std::shared_ptr<CanvasImage> createMinimapForLayer(std::unique_ptr<CMap> & map, int layer) const; | ||||
| 	std::vector<std::shared_ptr<CanvasImage>> createMinimaps(ResourcePath resource) const; | ||||
| 	std::vector<std::shared_ptr<CanvasImage>> createMinimaps(std::unique_ptr<CMap> & map) const; | ||||
|  | ||||
| 	std::shared_ptr<CPicture> buildDrawMinimap(const JsonNode & config) const; | ||||
| public: | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
| #include "../adventureMap/AdventureMapInterface.h" | ||||
| #include "../adventureMap/CMinimap.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
|  | ||||
| #include "../../CCallback.h" | ||||
| #include "../../lib/CArtHandler.h" | ||||
| @@ -61,7 +60,7 @@ void CQuestIcon::clickPressed(const Point & cursorPosition) | ||||
|  | ||||
| void CQuestIcon::showAll(Canvas & to) | ||||
| { | ||||
| 	CSDL_Ext::CClipRectGuard guard(to.getInternalSurface(), parent->pos); | ||||
| 	CanvasClipRectGuard guard(to, parent->pos); | ||||
| 	CAnimImage::showAll(to); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../render/IRenderHandler.h" | ||||
| #include "../render/Canvas.h" | ||||
| #include "../render/CanvasImage.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| #include "../CPlayerInterface.h" | ||||
| @@ -87,8 +88,7 @@ std::shared_ptr<CPicture> CWindowObject::createBg(const ImagePath & imageName, b | ||||
| 	if(imageName.empty()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	auto image = std::make_shared<CPicture>(imageName); | ||||
| 	image->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); | ||||
| 	auto image = std::make_shared<CPicture>(imageName, Point(0,0), EImageBlitMode::OPAQUE); | ||||
| 	if(playerColored) | ||||
| 		image->setPlayerColor(LOCPLINT->playerID); | ||||
| 	return image; | ||||
| @@ -116,8 +116,7 @@ void CWindowObject::updateShadow() | ||||
| void CWindowObject::setShadow(bool on) | ||||
| { | ||||
| 	//size of shadow | ||||
| 	int sizeOriginal = 8; | ||||
| 	int size = sizeOriginal * GH.screenHandler().getScalingFactor(); | ||||
| 	int size = 8; | ||||
|  | ||||
| 	if(on == !shadowParts.empty()) | ||||
| 		return; | ||||
| @@ -130,61 +129,12 @@ void CWindowObject::setShadow(bool on) | ||||
|  | ||||
| 	if(on) | ||||
| 	{ | ||||
|  | ||||
| 		//helper to set last row | ||||
| 		auto blitAlphaRow = [](SDL_Surface *surf, size_t row) | ||||
| 		{ | ||||
| 			uint8_t * ptr = (uint8_t*)surf->pixels + surf->pitch * (row); | ||||
|  | ||||
| 			for (size_t i=0; i< surf->w; i++) | ||||
| 			{ | ||||
| 				Channels::px<4>::a.set(ptr, 128); | ||||
| 				ptr+=4; | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		// helper to set last column | ||||
| 		auto blitAlphaCol = [](SDL_Surface *surf, size_t col) | ||||
| 		{ | ||||
| 			uint8_t * ptr = (uint8_t*)surf->pixels + 4 * (col); | ||||
|  | ||||
| 			for (size_t i=0; i< surf->h; i++) | ||||
| 			{ | ||||
| 				Channels::px<4>::a.set(ptr, 128); | ||||
| 				ptr+= surf->pitch; | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		static SDL_Surface * shadowCornerTempl = nullptr; | ||||
| 		static SDL_Surface * shadowBottomTempl = nullptr; | ||||
| 		static SDL_Surface * shadowRightTempl = nullptr; | ||||
|  | ||||
| 		//one-time initialization | ||||
| 		if(!shadowCornerTempl) | ||||
| 		{ | ||||
| 			//create "template" surfaces | ||||
| 			shadowCornerTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, size); | ||||
| 			shadowBottomTempl = CSDL_Ext::createSurfaceWithBpp<4>(1, size); | ||||
| 			shadowRightTempl  = CSDL_Ext::createSurfaceWithBpp<4>(size, 1); | ||||
|  | ||||
| 			//fill with shadow body color | ||||
| 			CSDL_Ext::fillSurface(shadowCornerTempl, { 0, 0, 0, 192 } ); | ||||
| 			CSDL_Ext::fillSurface(shadowBottomTempl, { 0, 0, 0, 192 } ); | ||||
| 			CSDL_Ext::fillSurface(shadowRightTempl,  { 0, 0, 0, 192 } ); | ||||
|  | ||||
| 			//fill last row and column with more transparent color | ||||
| 			blitAlphaCol(shadowRightTempl , size-1); | ||||
| 			blitAlphaCol(shadowCornerTempl, size-1); | ||||
| 			blitAlphaRow(shadowBottomTempl, size-1); | ||||
| 			blitAlphaRow(shadowCornerTempl, size-1); | ||||
| 		} | ||||
|  | ||||
| 		//FIXME: do something with this points | ||||
| 		Point shadowStart; | ||||
| 		if (options & BORDERED) | ||||
| 			shadowStart = Point(sizeOriginal - 14, sizeOriginal - 14); | ||||
| 			shadowStart = Point(size - 14, size - 14); | ||||
| 		else | ||||
| 			shadowStart = Point(sizeOriginal, sizeOriginal); | ||||
| 			shadowStart = Point(size, size); | ||||
|  | ||||
| 		Point shadowPos; | ||||
| 		if (options & BORDERED) | ||||
| @@ -198,26 +148,36 @@ void CWindowObject::setShadow(bool on) | ||||
| 		else | ||||
| 			fullsize = Point(pos.w, pos.h); | ||||
|  | ||||
| 		//create base 8x8 piece of shadow | ||||
| 		SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); | ||||
| 		SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, (fullsize.x - sizeOriginal) * GH.screenHandler().getScalingFactor(), size); | ||||
| 		SDL_Surface * shadowRight  = CSDL_Ext::scaleSurface(shadowRightTempl,  size, (fullsize.y - sizeOriginal) * GH.screenHandler().getScalingFactor()); | ||||
| 		Point sizeCorner(size, size); | ||||
| 		Point sizeRight(fullsize.x - size, size); | ||||
| 		Point sizeBottom(size, fullsize.y - size); | ||||
|  | ||||
| 		blitAlphaCol(shadowBottom, 0); | ||||
| 		blitAlphaRow(shadowRight, 0); | ||||
| 		//create base 8x8 piece of shadow | ||||
| 		auto imageCorner = GH.renderHandler().createImage(sizeCorner, CanvasScalingPolicy::AUTO); | ||||
| 		auto imageRight  = GH.renderHandler().createImage(sizeRight,  CanvasScalingPolicy::AUTO); | ||||
| 		auto imageBottom = GH.renderHandler().createImage(sizeBottom, CanvasScalingPolicy::AUTO); | ||||
|  | ||||
| 		Canvas canvasCorner = imageCorner->getCanvas(); | ||||
| 		Canvas canvasRight = imageRight->getCanvas(); | ||||
| 		Canvas canvasBottom = imageBottom->getCanvas(); | ||||
|  | ||||
| 		canvasCorner.drawColor(Rect(Point(0,0), sizeCorner), { 0, 0, 0, 128 }); | ||||
| 		canvasRight.drawColor(Rect(Point(0,0), sizeRight), { 0, 0, 0, 128 }); | ||||
| 		canvasBottom.drawColor(Rect(Point(0,0), sizeBottom), { 0, 0, 0, 128 }); | ||||
|  | ||||
| 		canvasCorner.drawColor(Rect(Point(0,0), sizeCorner - Point(1,1)), { 0, 0, 0, 192 }); | ||||
| 		canvasRight.drawColor(Rect(Point(0,0),   sizeRight - Point(0,1)), { 0, 0, 0, 192 }); | ||||
| 		canvasBottom.drawColor(Rect(Point(0,0), sizeBottom - Point(1,0)), { 0, 0, 0, 192 }); | ||||
|  | ||||
| 		//generate "shadow" object with these 3 pieces in it | ||||
| 		{ | ||||
| 			OBJECT_CONSTRUCTION; | ||||
|  | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowCorner), Point(shadowPos.x,   shadowPos.y))); | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowRight ),  Point(shadowPos.x,   shadowStart.y))); | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( GH.renderHandler().createImage(shadowBottom), Point(shadowStart.x, shadowPos.y))); | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( imageCorner, Point(shadowPos.x,   shadowPos.y))); | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( imageRight, Point(shadowStart.x, shadowPos.y))); | ||||
| 			shadowParts.push_back(std::make_shared<CPicture>( imageBottom,  Point(shadowPos.x,   shadowStart.y))); | ||||
|  | ||||
| 		} | ||||
| 		SDL_FreeSurface(shadowCorner); | ||||
| 		SDL_FreeSurface(shadowBottom); | ||||
| 		SDL_FreeSurface(shadowRight); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -236,7 +236,7 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst) | ||||
| 	{ | ||||
| 		assert(artInst.getScrollSpellID().num >= 0); | ||||
| 		auto image = GH.renderHandler().loadImage(AnimationPath::builtin("spellscr"), artInst.getScrollSpellID().num, 0, EImageBlitMode::COLORKEY); | ||||
| 		image->scaleTo(Point(44,34)); | ||||
| 		image->scaleTo(Point(44,34), EScalingAlgorithm::BILINEAR); | ||||
|  | ||||
| 		CCS->curh->dragAndDropCursor(image); | ||||
| 	} | ||||
|   | ||||
| @@ -20,7 +20,7 @@ For upscaled images you have to use following folders (next to `sprites`, `data` | ||||
|  | ||||
| The sprites should have the same name and folder structure as in `sprites`, `data` and `video` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images. | ||||
|  | ||||
| ### Shadows / Overlays | ||||
| ### Shadows / Overlays / Player-colored images | ||||
|  | ||||
| It's also possible (but not necessary) to add high-definition shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`. | ||||
| In future, such shadows will likely become required to correctly exclude shadow from effects such as Clone spell. | ||||
| @@ -36,3 +36,11 @@ Currently needed for: | ||||
|  | ||||
| - Flaggable adventure map objects. Overlay must contain a transparent image with white flags on it and will be used to colorize flags to owning player | ||||
| - Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover | ||||
|  | ||||
| For images that are used for player-colored interface, it is possible to provide custom images for each player. For example `HeroScr4-red.png` will be used for hero window of red player. | ||||
|  | ||||
| - Currently needed for all UI elements that are player-colored in HoMM3. | ||||
| - Can NOT be used for player-owned adventure objects. Use `-overlay` images for such objects. | ||||
| - Possible suffixes are `red`, `blue`, `tan`, `green`, `orange`, `purple`, `teal`, `pink`, `neutral` (used only for turn order queue in combat) | ||||
|  | ||||
| It is possible to use such additional images for both upscaled (xbrz) graphics, as well as for original / 1x images. When using this feature for original / 1x image, make sure that your base image (without suffix) is rgb/rgba image, and not indexed / with palette | ||||
|   | ||||
| @@ -54,6 +54,11 @@ public: | ||||
| 		return Point(x*mul, y*mul); | ||||
| 	} | ||||
|  | ||||
| 	constexpr Point operator/(const Point &b) const | ||||
| 	{ | ||||
| 		return Point(x/b.x,y/b.y); | ||||
| 	} | ||||
|  | ||||
| 	constexpr Point operator*(const Point &b) const | ||||
| 	{ | ||||
| 		return Point(x*b.x,y*b.y); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user