Files
FlaxEngine/Source/Engine/Render2D/MSDFGenerator.h
2026-02-28 10:13:03 +08:00

143 lines
5.1 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include <ThirdParty/freetype/ftoutln.h>
#include <ThirdParty/msdfgen/msdfgen.h>
#include <ThirdParty/msdfgen/msdfgen/core/ShapeDistanceFinder.h>
class MSDFGenerator
{
using Point2 = msdfgen::Point2;
using Shape = msdfgen::Shape;
using Contour = msdfgen::Contour;
using EdgeHolder = msdfgen::EdgeHolder;
static Point2 ftPoint2(const FT_Vector& vector, double scale)
{
return Point2(scale * vector.x, scale * vector.y);
}
struct FtContext
{
double scale;
Point2 position;
Shape* shape;
Contour* contour;
};
static int ftMoveTo(const FT_Vector* to, void* user)
{
FtContext* context = reinterpret_cast<FtContext*>(user);
if (!(context->contour && context->contour->edges.empty()))
context->contour = &context->shape->addContour();
context->position = ftPoint2(*to, context->scale);
return 0;
}
static int ftLineTo(const FT_Vector* to, void* user)
{
FtContext* context = reinterpret_cast<FtContext*>(user);
Point2 endpoint = ftPoint2(*to, context->scale);
if (endpoint != context->position)
{
context->contour->addEdge(EdgeHolder(context->position, endpoint));
context->position = endpoint;
}
return 0;
}
static int ftConicTo(const FT_Vector* control, const FT_Vector* to, void* user)
{
FtContext* context = reinterpret_cast<FtContext*>(user);
Point2 endpoint = ftPoint2(*to, context->scale);
if (endpoint != context->position)
{
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control, context->scale), endpoint));
context->position = endpoint;
}
return 0;
}
static int ftCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user)
{
FtContext* context = reinterpret_cast<FtContext*>(user);
Point2 endpoint = ftPoint2(*to, context->scale);
if (endpoint != context->position || msdfgen::crossProduct(ftPoint2(*control1, context->scale) - endpoint, ftPoint2(*control2, context->scale) - endpoint))
{
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1, context->scale), ftPoint2(*control2, context->scale), endpoint));
context->position = endpoint;
}
return 0;
}
static void correctWinding(Shape& shape, Shape::Bounds& bounds)
{
Point2 p(bounds.l - (bounds.r - bounds.l) - 1, bounds.b - (bounds.t - bounds.b) - 1);
double distance = msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
if (distance > 0)
{
for (auto& contour : shape.contours)
contour.reverse();
}
}
public:
static void GenerateMSDF(FT_GlyphSlot glyph, Array<byte>& output, int32& outputWidth, int32& outputHeight, int16& top, int16& left)
{
Shape shape;
shape.contours.clear();
shape.inverseYAxis = true;
FtContext context = { };
context.scale = 1.0 / 64.0;
context.shape = &shape;
FT_Outline_Funcs ftFunctions;
ftFunctions.move_to = &ftMoveTo;
ftFunctions.line_to = &ftLineTo;
ftFunctions.conic_to = &ftConicTo;
ftFunctions.cubic_to = &ftCubicTo;
ftFunctions.shift = 0;
ftFunctions.delta = 0;
FT_Outline_Decompose(&glyph->outline, &ftFunctions, &context);
shape.normalize();
edgeColoringSimple(shape, 3.0);
// Todo: make this configurable
// Also hard-coded in material: MSDFFontMaterial
const double pxRange = 4.0;
Shape::Bounds bounds = shape.getBounds();
int32 width = static_cast<int32>(Math::CeilToInt(bounds.r - bounds.l + pxRange));
int32 height = static_cast<int32>(Math::CeilToInt(bounds.t - bounds.b + pxRange));
msdfgen::Bitmap<float, 4> msdf(width, height);
auto transform = msdfgen::Vector2(Math::Ceil(-bounds.l + pxRange / 2.0), Math::Ceil(-bounds.b + pxRange / 2.0));
msdfgen::SDFTransformation t(msdfgen::Projection(1.0, transform), msdfgen::Range(pxRange));
correctWinding(shape, bounds);
generateMTSDF(msdf, shape, t);
output.Resize(width * height * 4);
const msdfgen::BitmapConstRef<float, 4>& bitmap = msdf;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
output[(y * width + x) * 4] = msdfgen::pixelFloatToByte(bitmap(x, y)[0]);
output[(y * width + x) * 4 + 1] = msdfgen::pixelFloatToByte(bitmap(x, y)[1]);
output[(y * width + x) * 4 + 2] = msdfgen::pixelFloatToByte(bitmap(x, y)[2]);
output[(y * width + x) * 4 + 3] = msdfgen::pixelFloatToByte(bitmap(x, y)[3]);
}
}
outputWidth = width;
outputHeight = height;
top = height - static_cast<int16>(transform.y);
left = -static_cast<int16>(transform.x);
}
};