Typography improvements
This commit is contained in:
@@ -13,9 +13,9 @@
|
||||
namespace
|
||||
{
|
||||
constexpr int kTextSdfSpread = 20;
|
||||
constexpr unsigned kTextSdfBlurPasses = 1;
|
||||
constexpr float kTextFontPixelSize = 144.0f;
|
||||
constexpr float kTextLayoutPadding = 48.0f;
|
||||
constexpr float kSdfInfinity = 1.0e20f;
|
||||
|
||||
class GdiplusSession
|
||||
{
|
||||
@@ -54,128 +54,115 @@ std::wstring Utf8ToWide(const std::string& text)
|
||||
return wide;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BuildLocalSdf(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
|
||||
void DistanceTransform1D(const std::vector<float>& input, std::vector<float>& output, unsigned count)
|
||||
{
|
||||
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
|
||||
std::vector<unsigned> locations(count, 0);
|
||||
std::vector<float> boundaries(static_cast<std::size_t>(count) + 1, 0.0f);
|
||||
|
||||
unsigned segment = 0;
|
||||
locations[0] = 0;
|
||||
boundaries[0] = -kSdfInfinity;
|
||||
boundaries[1] = kSdfInfinity;
|
||||
|
||||
for (unsigned q = 1; q < count; ++q)
|
||||
{
|
||||
float intersection = 0.0f;
|
||||
for (;;)
|
||||
{
|
||||
const unsigned location = locations[segment];
|
||||
intersection =
|
||||
((input[q] + static_cast<float>(q * q)) - (input[location] + static_cast<float>(location * location))) /
|
||||
(2.0f * static_cast<float>(q) - 2.0f * static_cast<float>(location));
|
||||
if (intersection > boundaries[segment] || segment == 0)
|
||||
break;
|
||||
--segment;
|
||||
}
|
||||
|
||||
++segment;
|
||||
locations[segment] = q;
|
||||
boundaries[segment] = intersection;
|
||||
boundaries[segment + 1] = kSdfInfinity;
|
||||
}
|
||||
|
||||
segment = 0;
|
||||
for (unsigned q = 0; q < count; ++q)
|
||||
{
|
||||
while (boundaries[segment + 1] < static_cast<float>(q))
|
||||
++segment;
|
||||
const unsigned location = locations[segment];
|
||||
const float delta = static_cast<float>(q) - static_cast<float>(location);
|
||||
output[q] = delta * delta + input[location];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> DistanceTransform2D(const std::vector<unsigned char>& targetMask, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<float> rowInput(width, 0.0f);
|
||||
std::vector<float> rowOutput(width, 0.0f);
|
||||
std::vector<float> columnInput(height, 0.0f);
|
||||
std::vector<float> columnOutput(height, 0.0f);
|
||||
std::vector<float> rowDistance(static_cast<std::size_t>(width) * height, 0.0f);
|
||||
std::vector<float> distance(static_cast<std::size_t>(width) * height, 0.0f);
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
const bool inside = alpha[static_cast<std::size_t>(y) * width + x] > 127;
|
||||
int bestDistanceSq = kTextSdfSpread * kTextSdfSpread;
|
||||
for (int oy = -kTextSdfSpread; oy <= kTextSdfSpread; ++oy)
|
||||
{
|
||||
const int sy = static_cast<int>(y) + oy;
|
||||
if (sy < 0 || sy >= static_cast<int>(height))
|
||||
continue;
|
||||
for (int ox = -kTextSdfSpread; ox <= kTextSdfSpread; ++ox)
|
||||
{
|
||||
const int sx = static_cast<int>(x) + ox;
|
||||
if (sx < 0 || sx >= static_cast<int>(width))
|
||||
continue;
|
||||
const bool sampleInside = alpha[static_cast<std::size_t>(sy) * width + sx] > 127;
|
||||
if (sampleInside == inside)
|
||||
continue;
|
||||
const int distanceSq = ox * ox + oy * oy;
|
||||
if (distanceSq < bestDistanceSq)
|
||||
bestDistanceSq = distanceSq;
|
||||
}
|
||||
}
|
||||
rowInput[x] = targetMask[static_cast<std::size_t>(y) * width + x] ? 0.0f : kSdfInfinity;
|
||||
DistanceTransform1D(rowInput, rowOutput, width);
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
rowDistance[static_cast<std::size_t>(y) * width + x] = rowOutput[x];
|
||||
}
|
||||
|
||||
const float distance = std::sqrt(static_cast<float>(bestDistanceSq));
|
||||
const float signedDistance = (inside ? 1.0f : -1.0f) * distance;
|
||||
float normalized = 0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2);
|
||||
const unsigned char sourceAlpha = alpha[static_cast<std::size_t>(y) * width + x];
|
||||
if (sourceAlpha > 0 && sourceAlpha < 255)
|
||||
normalized = static_cast<float>(sourceAlpha) / 255.0f;
|
||||
if (normalized < 0.0f)
|
||||
normalized = 0.0f;
|
||||
if (normalized > 1.0f)
|
||||
normalized = 1.0f;
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
columnInput[y] = rowDistance[static_cast<std::size_t>(y) * width + x];
|
||||
DistanceTransform1D(columnInput, columnOutput, height);
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
distance[static_cast<std::size_t>(y) * width + x] = columnOutput[y];
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BuildTextSdfTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<unsigned char> insideMask(static_cast<std::size_t>(width) * height, 0);
|
||||
std::vector<unsigned char> outsideMask(static_cast<std::size_t>(width) * height, 0);
|
||||
for (std::size_t index = 0; index < alpha.size(); ++index)
|
||||
{
|
||||
const bool inside = alpha[index] > 127;
|
||||
insideMask[index] = inside ? 1 : 0;
|
||||
outsideMask[index] = inside ? 0 : 1;
|
||||
}
|
||||
|
||||
const std::vector<float> distanceToInside = DistanceTransform2D(insideMask, width, height);
|
||||
const std::vector<float> distanceToOutside = DistanceTransform2D(outsideMask, width, height);
|
||||
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
const unsigned flippedY = height - 1 - y;
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
const std::size_t source = static_cast<std::size_t>(y) * width + x;
|
||||
const float signedDistance = std::sqrt(distanceToOutside[source]) - std::sqrt(distanceToInside[source]);
|
||||
const float normalized = std::clamp(
|
||||
0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2),
|
||||
0.0f,
|
||||
1.0f);
|
||||
const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
|
||||
const std::size_t out = (static_cast<std::size_t>(y) * width + x) * 4;
|
||||
const std::size_t out = (static_cast<std::size_t>(flippedY) * width + x) * 4;
|
||||
sdf[out + 0] = value;
|
||||
sdf[out + 1] = value;
|
||||
sdf[out + 2] = value;
|
||||
sdf[out + 3] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return sdf;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BuildTextCoverageTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<unsigned char> coverage(static_cast<std::size_t>(width) * height * 4, 0);
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
const unsigned char value = alpha[static_cast<std::size_t>(y) * width + x];
|
||||
const std::size_t out = (static_cast<std::size_t>(y) * width + x) * 4;
|
||||
coverage[out + 0] = value;
|
||||
coverage[out + 1] = value;
|
||||
coverage[out + 2] = value;
|
||||
coverage[out + 3] = value;
|
||||
}
|
||||
}
|
||||
return coverage;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> FlipTextTextureForShaderUv(const std::vector<unsigned char>& pixels, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<unsigned char> flipped(pixels.size(), 0);
|
||||
const std::size_t stride = static_cast<std::size_t>(width) * 4;
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
const std::size_t srcOffset = static_cast<std::size_t>(y) * stride;
|
||||
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - y) * stride;
|
||||
std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride);
|
||||
}
|
||||
return flipped;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BlurTextSdf(const std::vector<unsigned char>& pixels, unsigned width, unsigned height, unsigned passes)
|
||||
{
|
||||
std::vector<unsigned char> current = pixels;
|
||||
std::vector<unsigned char> next(pixels.size(), 0);
|
||||
for (unsigned pass = 0; pass < passes; ++pass)
|
||||
{
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
unsigned weightedTotal = 0;
|
||||
unsigned weightSum = 0;
|
||||
for (int oy = -1; oy <= 1; ++oy)
|
||||
{
|
||||
const int sy = static_cast<int>(y) + oy;
|
||||
if (sy < 0 || sy >= static_cast<int>(height))
|
||||
continue;
|
||||
for (int ox = -1; ox <= 1; ++ox)
|
||||
{
|
||||
const int sx = static_cast<int>(x) + ox;
|
||||
if (sx < 0 || sx >= static_cast<int>(width))
|
||||
continue;
|
||||
const unsigned weight = (ox == 0 && oy == 0) ? 4u : ((ox == 0 || oy == 0) ? 2u : 1u);
|
||||
const std::size_t sample = (static_cast<std::size_t>(sy) * width + sx) * 4;
|
||||
weightedTotal += static_cast<unsigned>(current[sample]) * weight;
|
||||
weightSum += weight;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned char value = static_cast<unsigned char>((weightedTotal + weightSum / 2) / weightSum);
|
||||
const std::size_t out = (static_cast<std::size_t>(y) * width + x) * 4;
|
||||
next[out + 0] = value;
|
||||
next[out + 1] = value;
|
||||
next[out + 2] = value;
|
||||
next[out + 3] = value;
|
||||
}
|
||||
}
|
||||
current.swap(next);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void WriteTextMaskDebugDump(const std::string& text, const std::vector<unsigned char>& alpha, const std::vector<unsigned char>& sdf, unsigned width, unsigned height)
|
||||
{
|
||||
try
|
||||
@@ -308,9 +295,7 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
|
||||
}
|
||||
}
|
||||
sdf = BuildTextCoverageTexture(alpha, kTextTextureWidth, kTextTextureHeight);
|
||||
sdf = BlurTextSdf(sdf, kTextTextureWidth, kTextTextureHeight, kTextSdfBlurPasses);
|
||||
sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight);
|
||||
sdf = BuildTextSdfTexture(alpha, kTextTextureWidth, kTextTextureHeight);
|
||||
WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
constexpr unsigned kTextTextureWidth = 2048;
|
||||
constexpr unsigned kTextTextureHeight = 256;
|
||||
constexpr unsigned kTextTextureWidth = 4096;
|
||||
constexpr unsigned kTextTextureHeight = 512;
|
||||
|
||||
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error);
|
||||
|
||||
Reference in New Issue
Block a user