Kaynağa Gözat

Merge pull request #4874 from Laserlicht/load_prescaled

prescaled image support
Ivan Savenko 11 ay önce
ebeveyn
işleme
98a54b61b7

+ 1 - 0
client/render/ImageLocator.cpp

@@ -71,6 +71,7 @@ ImageLocator ImageLocator::copyFile() const
 {
 	ImageLocator result;
 	result.scalingFactor = 1;
+	result.preScaledFactor = preScaledFactor;
 	result.image = image;
 	result.defFile = defFile;
 	result.defFrame = defFrame;

+ 1 - 0
client/render/ImageLocator.h

@@ -33,6 +33,7 @@ struct ImageLocator
 	bool verticalFlip = false;
 	bool horizontalFlip = false;
 	int8_t scalingFactor = 0; // 0 = auto / use default scaling
+	int8_t preScaledFactor = 1;
 	EImageLayer layer = EImageLayer::ALL;
 
 	ImageLocator() = default;

+ 108 - 11
client/renderSDL/RenderHandler.cpp

@@ -55,6 +55,59 @@ std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath &
 	return result;
 }
 
+std::optional<ResourcePath> RenderHandler::getPathForScaleFactor(ResourcePath path, 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(ResourcePath p)
+{
+	auto path = p;
+	int scaleFactor = 1;
+	if(getScalingFactor() > 1)
+	{
+		std::vector<int> factorsToCheck = {getScalingFactor(), 4, 3, 2};
+		for(auto factorToCheck : factorsToCheck)
+		{
+			std::string name = boost::algorithm::to_upper_copy(p.getName());
+			boost::replace_all(name, "SPRITES/", std::string("SPRITES") + std::to_string(factorToCheck) + std::string("X/"));
+			boost::replace_all(name, "DATA/", std::string("DATA") + std::to_string(factorToCheck) + std::string("X/"));
+			ResourcePath scaledPath = ImagePath::builtin(name);
+			if(p.getType() != EResType::IMAGE)
+				scaledPath = AnimationPath::builtin(name);
+			auto tmpPath = getPathForScaleFactor(scaledPath, std::to_string(factorToCheck));
+			if(tmpPath)
+			{
+				path = *tmpPath;
+				scaleFactor = factorToCheck;
+				break;
+			}
+		}
+	}
+
+	return std::pair<ResourcePath, int>(path, scaleFactor);
+};
+
 void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config)
 {
 	std::string basepath;
@@ -96,7 +149,9 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c
 
 RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path)
 {
-	AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
+	auto tmp = getScalePath(path);
+	auto animPath = AnimationPath::builtin(tmp.first.getName());
+	AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/");
 
 	auto it = animationLayouts.find(actualPath);
 
@@ -123,11 +178,15 @@ 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(), path.getOriginalName());
+		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), animPath.getOriginalName());
 
 		initFromJson(result, config);
 	}
 
+	for(auto & g : result)
+		for(auto & i : g.second)
+			i.preScaledFactor = tmp.second;
+
 	animationLayouts[actualPath] = result;
 	return animationLayouts[actualPath];
 }
@@ -177,13 +236,23 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const Ima
 	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);
+		return std::make_shared<SDLImageShared>(*locator.image, locator.preScaledFactor);
 	}
 
 	if (locator.defFile)
 	{
 		auto defFile = getAnimationFile(*locator.defFile);
-		return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup);
+		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));
+		}
+		return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup, preScaledFactor);
 	}
 
 	throw std::runtime_error("Invalid image locator received!");
@@ -257,10 +326,24 @@ std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & loc
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
 {
-	if (locator.scalingFactor == 0 && getScalingFactor() != 1 )
+	ImageLocator adjustedLocator = locator;
+	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 = locator;
-		auto scaledLocator = locator;
+		auto unscaledLocator = adjustedLocator;
+		auto scaledLocator = adjustedLocator;
 
 		unscaledLocator.scalingFactor = 1;
 		scaledLocator.scalingFactor = getScalingFactor();
@@ -269,22 +352,36 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, E
 		return std::make_shared<ImageScaled>(scaledLocator, unscaledImage, mode);
 	}
 
-	if (locator.scalingFactor == 0)
+	if (adjustedLocator.scalingFactor == 0)
 	{
-		auto scaledLocator = locator;
+		auto scaledLocator = adjustedLocator;
 		scaledLocator.scalingFactor = getScalingFactor();
 
 		return loadImageImpl(scaledLocator)->createImageReference(mode);
 	}
 	else
 	{
-		return loadImageImpl(locator)->createImageReference(mode);
+		if(adjustedLocator.image)
+		{
+			std::string imgPath = (*adjustedLocator.image).getName();
+			if(adjustedLocator.layer == EImageLayer::OVERLAY)
+				imgPath += "-overlay";
+			if(adjustedLocator.layer == EImageLayer::SHADOW)
+				imgPath += "-shadow";
+
+			if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)))
+				adjustedLocator.image = ImagePath::builtin(imgPath);
+		}
+
+		return loadImageImpl(adjustedLocator)->createImageReference(mode);
 	}
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
 {
-	ImageLocator locator = getLocatorForAnimationFrame(path, frame, group);
+	auto tmp = getScalePath(path);
+	ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group);
+	locator.preScaledFactor = tmp.second;
 	return loadImage(locator, mode);
 }
 

+ 2 - 0
client/renderSDL/RenderHandler.h

@@ -29,6 +29,8 @@ class RenderHandler : public IRenderHandler
 	std::map<EFonts, std::shared_ptr<const IFont>> fonts;
 
 	std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
+	std::optional<ResourcePath> getPathForScaleFactor(ResourcePath path, std::string factor);
+	std::pair<ResourcePath, int> getScalePath(ResourcePath p);
 	AnimationLayoutMap & getAnimationLayout(const AnimationPath & path);
 	void initFromJson(AnimationLayoutMap & layout, const JsonNode & config);
 

+ 26 - 14
client/renderSDL/SDLImage.cpp

@@ -89,11 +89,12 @@ int IImage::height() const
 	return dimensions().y;
 }
 
-SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group)
+SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int preScaleFactor)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
-	originalPalette(nullptr)
+	originalPalette(nullptr),
+	preScaleFactor(preScaleFactor)
 {
 	SDLImageLoader loader(this);
 	data->loadFrame(frame, group, loader);
@@ -101,11 +102,12 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group
 	savePalette();
 }
 
-SDLImageShared::SDLImageShared(SDL_Surface * from)
+SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
-	originalPalette(nullptr)
+	originalPalette(nullptr),
+	preScaleFactor(preScaleFactor)
 {
 	surf = from;
 	if (surf == nullptr)
@@ -118,11 +120,12 @@ SDLImageShared::SDLImageShared(SDL_Surface * from)
 	fullSize.y = surf->h;
 }
 
-SDLImageShared::SDLImageShared(const ImagePath & filename)
+SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
-	originalPalette(nullptr)
+	originalPalette(nullptr),
+	preScaleFactor(preScaleFactor)
 {
 	surf = BitmapHandler::loadBitmap(filename);
 
@@ -274,9 +277,18 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
 	if (palette && surf && surf->format->palette)
 		SDL_SetSurfacePalette(surf, palette);
 
-	SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
+	SDL_Surface * scaled = nullptr;
+	if(preScaleFactor == factor)
+	{
+		scaled = CSDL_Ext::newSurface(Point(surf->w, surf->h), surf);
+		SDL_BlitSurface(surf, nullptr, scaled, nullptr);
+	}
+	else if(preScaleFactor == 1)
+		scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
+	else
+		scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor);
 
-	auto ret = std::make_shared<SDLImageShared>(scaled);
+	auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor);
 
 	ret->fullSize.x = fullSize.x * factor;
 	ret->fullSize.y = fullSize.y * factor;
@@ -296,8 +308,8 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
 
 std::shared_ptr<ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
 {
-	float scaleX = float(size.x) / dimensions().x;
-	float scaleY = float(size.y) / dimensions().y;
+	float scaleX = float(size.x) / fullSize.x;
+	float scaleY = float(size.y) / fullSize.y;
 
 	if (palette && surf->format->palette)
 		SDL_SetSurfacePalette(surf, palette);
@@ -311,7 +323,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Pa
 	else
 		CSDL_Ext::setDefaultColorKey(scaled);//just in case
 
-	auto ret = std::make_shared<SDLImageShared>(scaled);
+	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);
@@ -355,7 +367,7 @@ bool SDLImageShared::isTransparent(const Point & coords) const
 
 Point SDLImageShared::dimensions() const
 {
-	return fullSize;
+	return fullSize / preScaleFactor;
 }
 
 std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode)
@@ -369,7 +381,7 @@ std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode
 std::shared_ptr<ISharedImage> SDLImageShared::horizontalFlip() const
 {
 	SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
-	auto ret = std::make_shared<SDLImageShared>(flipped);
+	auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
 	ret->fullSize = fullSize;
 	ret->margins.x = margins.x;
 	ret->margins.y = fullSize.y - surf->h - margins.y;
@@ -381,7 +393,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::horizontalFlip() const
 std::shared_ptr<ISharedImage> SDLImageShared::verticalFlip() const
 {
 	SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
-	auto ret = std::make_shared<SDLImageShared>(flipped);
+	auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
 	ret->fullSize = fullSize;
 	ret->margins.x = fullSize.x - surf->w - margins.x;
 	ret->margins.y = margins.y;

+ 6 - 3
client/renderSDL/SDLImage.h

@@ -35,6 +35,9 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
 	//total size including borders
 	Point fullSize;
 
+	//pre scaled image
+	int preScaleFactor;
+
 	// Keep the original palette, in order to do color switching operation
 	void savePalette();
 
@@ -42,11 +45,11 @@ 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);
+	SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int preScaleFactor=1);
 	//Load from bitmap file
-	SDLImageShared(const ImagePath & filename);
+	SDLImageShared(const ImagePath & filename, int preScaleFactor=1);
 	//Create using existing surface, extraRef will increase refcount on SDL_Surface
-	SDLImageShared(SDL_Surface * from);
+	SDLImageShared(SDL_Surface * from, int preScaleFactor=1);
 	~SDLImageShared();
 
 	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override;

+ 29 - 0
docs/modders/HD_Graphics.md

@@ -0,0 +1,29 @@
+# HD Graphics
+
+It's possible to provide alternative HD-Graphics within mods. They will be used if any upscaling filter is activated.
+
+## Preconditions
+
+It's still necessary to add 1x graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled.
+
+Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images.
+
+If user for example selects 3x resolution and only 2x exists in mod then the 2x images are upscaled to 3x (same for other combinations > 1x).
+
+## Mod
+
+For upscaled images you have to use following folders (next to `sprites` and `data` folders):
+- `sprites2x`, `sprites3x`, `sprites4x` for sprites
+- `data2x`, `data3x`, `data4x` for images
+
+The sprites should have the same name and folder structure as in `sprites` and `data` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images.
+
+### Shadows / Overlays
+
+It's also possible (but not necessary) to add HD shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`.
+
+Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI.
+
+Currently needed for:
+- flaggable adventure map objects (needs a transparent image with white flags on it)
+- creature battle animations (needs a transparent image with white outline of creature for highlighting on mouse hover)