1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge pull request #4555 from IvanSavenko/xbrz_fonts

Improvements for fonts when xbrz is in use
This commit is contained in:
Ivan Savenko 2024-09-05 15:06:25 +03:00 committed by GitHub
commit 624607caae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 81 additions and 46 deletions

View File

@ -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()

View File

@ -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();
}
int CTrueTypeFont::getPointSize(const JsonNode & config) const
{
int scalingFactor = getScalingFactor();
if (config.isNumber())
return config.Integer() * scalingFactor;
else
return config[scalingFactor-1].Integer();
}
TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
{
int pointSizeBase = static_cast<int>(config["size"].Float());
int scalingFactor = getScalingFactor();
int pointSize = pointSizeBase * scalingFactor;
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;

View File

@ -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:

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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":

View File

@ -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" ],