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