Last active
December 20, 2015 18:09
-
-
Save n-yoda/25ea623bffaebf233447 to your computer and use it in GitHub Desktop.
Render Font as Signed Distance Filed (WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdio> | |
#include <cstdlib> | |
#include <cstdint> | |
#include <cmath> | |
#include <memory> | |
#include <fstream> | |
#include <sstream> | |
extern "C" { | |
#include <ft2build.h> | |
#include FT_FREETYPE_H | |
#include FT_OUTLINE_H | |
const char* getErrorMessage(FT_Error err) { | |
#undef __FTERRORS_H__ | |
#define FT_ERRORDEF( e, v, s ) case e: return s; | |
#define FT_ERROR_START_LIST | |
#define FT_ERROR_END_LIST | |
switch (err) { | |
#include FT_ERRORS_H | |
} | |
return "(Unknown error)"; | |
} | |
} | |
void ExitOnError(FT_Error err) { | |
if (err) { | |
std::printf("%s\n", getErrorMessage(err)); | |
exit(1); | |
} | |
} | |
double Length(const FT_Vector &v) { | |
double x = v.x; | |
double y = v.y; | |
return std::sqrt(x * x + y * y); | |
} | |
double Neighbor(const FT_Vector &a, const FT_Vector &b) { | |
return std::abs(a.x - b.x) <= 1 && std::abs(a.y - b.y) <= 1; | |
} | |
uint8_t Clamp(long x) { | |
return x <= 0 ? 0 : (x >= 255 ? 255 : x); | |
} | |
struct Data { | |
Data(long w, long h) | |
: first(true), start(), prev(), | |
width(w), height(h), img(new uint8_t[w*h]), | |
scale(1), distUnit(1), bbox() { | |
std::fill(&img[0], &img[w * h], 0); | |
} | |
bool first; | |
FT_Vector start; | |
FT_Vector prev; | |
long width; | |
long height; | |
long scale; | |
uint8_t distUnit; | |
uint8_t distZero; | |
FT_BBox bbox; | |
std::unique_ptr<uint8_t[]> img; | |
FT_Vector PosToPixel(const FT_Vector& v) const { | |
return FT_Vector { | |
.x = (v.x - bbox.xMin) / scale, | |
.y = (v.y - bbox.yMin) / scale, | |
}; | |
} | |
bool ValidPixel(const FT_Vector& v) const { | |
return 0 <= v.x && v.x < width && 0 <= v.y && v.y < height; | |
} | |
void SetPixel(const FT_Vector& pixel, uint8_t value) { | |
img[pixel.y * width + pixel.x] = value; | |
} | |
uint8_t GetPixel(const FT_Vector& pixel) const { | |
return img[pixel.y * width + pixel.x]; | |
} | |
void SetDist(const FT_Vector& pixel, long sd) { | |
long src = (long)GetPixel(pixel) - distZero; | |
if (std::abs(sd) < std::abs(src)) { | |
SetPixel(pixel, Clamp(sd + (long)distZero)); | |
} | |
} | |
long CalcDist(const FT_Vector& a, const FT_Vector& b) const { | |
double len = Length({a.x - b.x, a.y - b.y}); | |
long d = len * distUnit / scale; | |
return d; | |
} | |
}; | |
struct SplitConic { | |
SplitConic(FT_Vector q0, FT_Vector c, FT_Vector q1) | |
: c0{(q0.x + c.x) / 2, (q0.y + c.y) / 2}, | |
c1{(q1.x + c.x) / 2, (q1.y + c.y) / 2}, | |
p1{(c0.x + c1.x) / 2, (c0.y + c1.y) / 2} {} | |
FT_Vector c0, c1, p1; | |
}; | |
int MoveTo(const FT_Vector* to, void* user) { | |
Data *data = static_cast<Data*>(user); | |
if (data->first) { | |
std::printf("Move (%ld, %ld)\n", to->x, to->y); | |
} else { | |
std::printf("\nMove (%ld, %ld) -> (%ld, %ld)\n", | |
data->prev.x, data->prev.y, to->x, to->y); | |
} | |
data->first = false; | |
data->start = *to; | |
data->prev = *to; | |
return 0; | |
} | |
void FillLineRec(const FT_Vector& p, const FT_Vector& a, | |
const FT_Vector& b, long sign, Data& data) { | |
FT_Vector ia = data.PosToPixel(a); | |
FT_Vector ib = data.PosToPixel(b); | |
bool aValid = data.ValidPixel(ia); | |
bool bValid = data.ValidPixel(ib); | |
if (!aValid && !bValid) { | |
return; | |
} else if (Neighbor(ia, ib)) { | |
if (aValid) { | |
data.SetDist(ia, sign * data.CalcDist(p, a)); | |
} | |
if (bValid) { | |
data.SetDist(ib, sign * data.CalcDist(p, b)); | |
} | |
} else { | |
FT_Vector c {.x = (a.x + b.x) / 2, .y = (a.y + b.y) / 2}; | |
FillLineRec(p, a, c, sign, data); | |
FillLineRec(p, c, b, sign, data); | |
} | |
} | |
void FillLine(const FT_Vector& p, const FT_Vector& n, long sign, Data& data) { | |
FT_Vector n2 = n; | |
while (Length(n2) <= (255 / data.distUnit) * data.scale) { | |
n2.x += n.x; | |
n2.y += n.y; | |
} | |
FT_Vector p2 {p.x + n2.x, p.y + n2.y}; | |
FillLineRec(p, p, p2, sign, data); | |
} | |
void RenderLine(const FT_Vector& p0, const FT_Vector& p1, | |
const FT_Vector& n, Data& data) { | |
FT_Vector i0 = data.PosToPixel(p0); | |
FT_Vector i1 = data.PosToPixel(p1); | |
if (std::abs(i0.x - i1.x) <= 1 && std::abs(i0.y - i1.y) <= 1) { | |
FillLine(p0, n, -1, data); | |
FillLine(p1, n, -1, data); | |
FillLine(p0, {-n.x, -n.y}, 1, data); | |
FillLine(p1, {-n.x, -n.y}, 1, data); | |
} else { | |
FT_Vector p01 {.x = (p0.x + p1.x) / 2, .y = (p0.y + p1.y) / 2}; | |
RenderLine(p0, p01, n, data); | |
RenderLine(p01, p1, n, data); | |
} | |
} | |
int LineTo(const FT_Vector* to, void* user) { | |
Data *data = static_cast<Data*>(user); | |
std::printf("Line (%ld, %ld) -> (%ld, %ld)\n", | |
data->prev.x, data->prev.y, to->x, to->y); | |
FT_Vector n{data->prev.y - to->y, to->x - data->prev.x}; | |
RenderLine(data->prev, *to, n, *data); | |
data->prev = *to; | |
return 0; | |
} | |
void RenderConic(const FT_Vector& p0, const FT_Vector& c0, | |
const FT_Vector& p1, Data& data) { | |
FT_Vector i0 = data.PosToPixel(p0); | |
FT_Vector i1 = data.PosToPixel(p1); | |
if (std::abs(i0.x - i1.x) <= 1 && std::abs(i0.y - i1.y) <= 1) { | |
FillLine(p0, {p0.y - c0.y, c0.x - p0.x}, -1, data); | |
FillLine(p1, {c0.y - p1.y, p1.x - c0.x}, -1, data); | |
FillLine(p0, {c0.y - p0.y, p0.x - c0.x}, 1, data); | |
FillLine(p1, {p1.y - c0.y, c0.x - p1.x}, 1, data); | |
} else { | |
SplitConic split(p0, c0, p1); | |
RenderConic(p0, split.c0, split.p1, data); | |
RenderConic(split.p1, split.c1, p1, data); | |
} | |
} | |
int ConicTo(const FT_Vector* control, const FT_Vector* to, void* user) { | |
Data *data = static_cast<Data*>(user); | |
std::printf("Conic (%ld, %ld) -> (%ld, %ld)\n", | |
data->prev.x, data->prev.y, to->x, to->y); | |
RenderConic(data->prev, *control, *to, *data); | |
data->prev = *to; | |
return 0; | |
} | |
int CubicTo(const FT_Vector* control1, const FT_Vector* control2, | |
const FT_Vector* to, void* user) { | |
Data *data = static_cast<Data*>(user); | |
std::printf("Cubic (%ld, %ld) -> (%ld, %ld)\n", | |
data->prev.x, data->prev.y, to->x, to->y); | |
data->prev = *to; | |
return 0; | |
} | |
int main(int argc, char** args) { | |
// Initialize library | |
FT_Library lib; | |
ExitOnError(FT_Init_FreeType(&lib)); | |
if (argc < 2) { | |
std::printf("Usage: ./glyph2dot font-path [index] [char]\n"); | |
return 1; | |
} | |
// Load font | |
FT_Face face; | |
int faceIndex = argc >= 3 ? std::atoi(args[2]) : 0; | |
ExitOnError(FT_New_Face(lib, args[1], faceIndex, &face)); | |
// Load glyph | |
char ch = argc >= 4 ? args[3][0] : 'A'; | |
ExitOnError(FT_Load_Char(face, ch, FT_LOAD_NO_SCALE)); | |
FT_Outline* outline = &face->glyph->outline; | |
FT_BBox bbox; | |
FT_Outline_Get_CBox(outline, &bbox); | |
long distUnit = 10; | |
int shift = 15; | |
long scale = 8; | |
long extent = (255 / distUnit) + ((255 % distUnit) ? 0 : 1); | |
bbox.xMin -= extent * scale; | |
bbox.xMax += extent * scale; | |
bbox.yMin -= extent * scale; | |
bbox.yMax += extent * scale; | |
std::printf("%ld, %ld %ld, %ld\n", bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); | |
long width = bbox.xMax - bbox.xMin; | |
long height = bbox.yMax - bbox.yMin; | |
Data data(width / scale, height / scale); | |
data.distUnit = distUnit; | |
data.distZero = 127; | |
data.scale = (1 << shift) * scale; | |
data.bbox = bbox; | |
data.bbox.xMin <<= shift; | |
data.bbox.xMax <<= shift; | |
data.bbox.yMin <<= shift; | |
data.bbox.yMax <<= shift; | |
FT_Outline_Funcs funcs {MoveTo, LineTo, ConicTo, CubicTo, shift, 0}; | |
// Render | |
FT_Outline_Decompose(outline, &funcs, &data); | |
std::printf("%ld, %ld\n", data.width, data.height); | |
// Output | |
std::stringstream name; | |
name << data.width << "." << data.height << ".bytes"; | |
std::ofstream ofs(name.str(), std::ios::binary); | |
ofs.write(reinterpret_cast<char*>(data.img.get()), data.width * data.height); | |
// Release | |
FT_Done_Face(face); | |
FT_Done_FreeType(lib); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment