Browse Source

Merge pull request #4555 from IvanSavenko/xbrz_fonts

Improvements for fonts when xbrz is in use
Ivan Savenko 1 year ago
parent
commit
624607caae

+ 14 - 2
client/renderSDL/CBitmapFont.cpp

@@ -16,15 +16,17 @@
 #include "../render/Colors.h"
 #include "../render/IScreenHandler.h"
 
+#include "../../lib/CConfigHandler.h"
 #include "../../lib/Rect.h"
+#include "../../lib/VCMI_Lib.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/modding/CModHandler.h"
 #include "../../lib/texts/Languages.h"
 #include "../../lib/texts/TextOperations.h"
 #include "../../lib/vcmi_endian.h"
-#include "../../lib/VCMI_Lib.h"
 
 #include <SDL_surface.h>
+#include <SDL_image.h>
 
 struct AtlasLayout
 {
@@ -199,10 +201,20 @@ CBitmapFont::CBitmapFont(const std::string & filename):
 
 	if (GH.screenHandler().getScalingFactor() != 1)
 	{
-		auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor());
+		static const std::map<std::string, EScalingAlgorithm> filterNameToEnum = {
+			{ "nearest", EScalingAlgorithm::NEAREST},
+			{ "bilinear", EScalingAlgorithm::BILINEAR},
+			{ "xbrz", EScalingAlgorithm::XBRZ}
+		};
+
+		auto filterName = settings["video"]["fontUpscalingFilter"].String();
+		EScalingAlgorithm algorithm = filterNameToEnum.at(filterName);
+		auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm);
 		SDL_FreeSurface(atlasImage);
 		atlasImage = scaledSurface;
 	}
+
+	IMG_SavePNG(atlasImage, ("/home/ivan/font_" + filename).c_str());
 }
 
 CBitmapFont::~CBitmapFont()

+ 11 - 5
client/renderSDL/CTrueTypeFont.cpp

@@ -27,19 +27,25 @@ std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode &
 	return CResourceHandler::get()->load(ResourcePath(filename, EResType::TTF_FONT))->readAll();
 }
 
-TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
+int CTrueTypeFont::getPointSize(const JsonNode & config) const
 {
-	int pointSizeBase = static_cast<int>(config["size"].Float());
 	int scalingFactor = getScalingFactor();
-	int pointSize = pointSizeBase * scalingFactor;
 
+	if (config.isNumber())
+		return config.Integer() * scalingFactor;
+	else
+		return config[scalingFactor-1].Integer();
+}
+
+TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
+{
 	if(!TTF_WasInit() && TTF_Init()==-1)
 		throw std::runtime_error(std::string("Failed to initialize true type support: ") + TTF_GetError() + "\n");
 
-	return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), (int)data.second), 1, pointSize);
+	return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), data.second), 1, getPointSize(config["size"]));
 }
 
-int CTrueTypeFont::getFontStyle(const JsonNode &config)
+int CTrueTypeFont::getFontStyle(const JsonNode &config) const
 {
 	const JsonVector & names = config["style"].Vector();
 	int ret = 0;

+ 2 - 1
client/renderSDL/CTrueTypeFont.h

@@ -30,7 +30,8 @@ class CTrueTypeFont final : public IFont
 
 	std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
 	TTF_Font * loadFont(const JsonNode & config);
-	int getFontStyle(const JsonNode & config);
+	int getPointSize(const JsonNode & config) const;
+	int getFontStyle(const JsonNode & config) const;
 
 	void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
 public:

+ 3 - 23
client/renderSDL/SDLImage.cpp

@@ -270,30 +270,10 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
 	if (factor <= 0)
 		throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
 
-	if (palette && surf->format->palette)
+	if (palette && surf && surf->format->palette)
 		SDL_SetSurfacePalette(surf, palette);
 
-	/// Convert current surface to ARGB format suitable for xBRZ
-	/// TODO: skip its creation if this is format matches current surface (even if unlikely)
-	SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
-	SDL_Surface * scaled = CSDL_Ext::newSurface(Point(surf->w * factor, surf->h * factor), intermediate);
-
-	assert(intermediate->pitch == intermediate->w * 4);
-	assert(scaled->pitch == scaled->w * 4);
-
-	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
-	uint32_t * dstPixels = static_cast<uint32_t*>(scaled->pixels);
-
-	// avoid excessive granulation - xBRZ prefers at least 8-16 lines per task
-	// TODO: compare performance and size of images, recheck values for potentially better parameters
-	const int granulation = std::clamp(surf->h / 64 * 8, 8, 64);
-
-	tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
-	{
-		xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
-	});
-
-	SDL_FreeSurface(intermediate);
+	SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
 
 	auto ret = std::make_shared<SDLImageShared>(scaled);
 
@@ -307,7 +287,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
 	// erase our own reference
 	SDL_FreeSurface(scaled);
 
-	if (surf->format->palette)
+	if (surf && surf->format->palette)
 		SDL_SetSurfacePalette(surf, originalPalette);
 
 	return ret;

+ 20 - 7
client/renderSDL/SDL_Extensions.cpp

@@ -638,8 +638,8 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
 	if(!surf || !width || !height)
 		return nullptr;
 
-	if (surf->w * 2 == width && surf->h * 2 == height)
-		return scaleSurfaceIntegerFactor(surf, 2);
+	// 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);
@@ -654,7 +654,7 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
 	return ret;
 }
 
-SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor)
+SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm)
 {
 	if(surf == nullptr || factor == 0)
 		return nullptr;
@@ -662,7 +662,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
 	int newWidth = surf->w * factor;
 	int newHight = surf->h * factor;
 
-	SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
+	SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
 	SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate);
 
 	assert(intermediate->pitch == intermediate->w * 4);
@@ -675,10 +675,23 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
 	// TODO: compare performance and size of images, recheck values for potentially better parameters
 	const int granulation = std::clamp(surf->h / 64 * 8, 8, 64);
 
-	tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
+	switch (algorithm)
 	{
-		xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
-	});
+		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:
+			tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
+			{
+				xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
+			});
+			break;
+		default:
+			throw std::runtime_error("invalid scaling algorithm!");
+	}
 
 	SDL_FreeSurface(intermediate);
 

+ 8 - 1
client/renderSDL/SDL_Extensions.h

@@ -27,6 +27,13 @@ class Point;
 
 VCMI_LIB_NAMESPACE_END
 
+enum class EScalingAlgorithm : int8_t
+{
+	NEAREST,
+	BILINEAR,
+	XBRZ
+};
+
 namespace CSDL_Ext
 {
 
@@ -92,7 +99,7 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 
 	// bilinear filtering. Always returns rgba surface
 	SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height);
-	SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor);
+	SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler);
 
 	template<int bpp>
 	void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);

+ 11 - 5
client/windows/CMessage.cpp

@@ -73,20 +73,19 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
 	// each iteration generates one output line
 	while(text.length())
 	{
-		ui32 lineWidth = 0; //in characters or given char metric
 		ui32 wordBreak = -1; //last position for line break (last space character)
 		ui32 currPos = 0; //current position in text
 		bool opened = false; //set to true when opening brace is found
 		std::string color; //color found
 
 		size_t symbolSize = 0; // width of character, in bytes
-		size_t glyphWidth = 0; // width of printable glyph, pixels
+
+		std::string printableString;
 
 		// loops till line is full or end of text reached
-		while(currPos < text.length() && text[currPos] != 0x0a && lineWidth < maxLineWidth)
+		while(currPos < text.length() && text[currPos] != 0x0a)
 		{
 			symbolSize = TextOperations::getUnicodeCharacterSize(text[currPos]);
-			glyphWidth = graphics->fonts[font]->getGlyphWidth(text.data() + currPos);
 
 			// candidate for line break
 			if(ui8(text[currPos]) <= ui8(' '))
@@ -116,7 +115,14 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
 				color = "";
 			}
 			else
-				lineWidth += glyphWidth;
+			{
+				std::string newPrintableString = printableString;
+				newPrintableString.append(text.data() + currPos, symbolSize);
+				if (graphics->fonts[font]->getStringWidth(newPrintableString) < maxLineWidth)
+					printableString.append(text.data() + currPos, symbolSize);
+				else
+					break;
+			}
 			currPos += symbolSize;
 		}
 

+ 5 - 1
config/fonts.json

@@ -19,7 +19,11 @@
 	// Should be in format:
 	// <replaced bitmap font name, case-sensetive> : <true type font description>
 	// "file" - file to load font from, must be in data/ directory
-	// "size" - point size of font
+	// "size" - point size of font. Can be defined in two forms:
+	// a) single number, e.g. 10. In this case, game will automatically multiply font size by upscaling factor when xBRZ is in use, 
+	//    so xbrz 2x will use 20px, xbrz 3x will use 30px, etc
+	// b) list of scaling factors for each scaling mode, e.g. [ 10, 16, 22, 26]. In this case game will select point size according to xBRZ scaling factor
+	//    so unscaled mode will use 10px, xbrz2 will use 16px, and xbrz3 will use 22
 	// "style" - italic and\or bold, indicates font style
 	// "blend" - if set to true, font will be antialiased
 	"trueType":

+ 6 - 0
config/schemas/settings.json

@@ -167,6 +167,7 @@
 				"targetfps",
 				"vsync",
 				"upscalingFilter",
+				"fontUpscalingFilter",
 				"downscalingFilter"
 			],
 			"properties" : {
@@ -231,6 +232,11 @@
 					"type" : "boolean",
 					"default" : true
 				},
+				"fontUpscalingFilter" : {
+					"type" : "string",
+					"enum" : [ "nearest", "bilinear", "xbrz" ],
+					"default" : "nearest"
+				},
 				"upscalingFilter" : {
 					"type" : "string",
 					"enum" : [ "auto", "none", "xbrz2", "xbrz3", "xbrz4" ],