You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ugui/text_rendering/msdf_c/msdf.h

1396 lines
44 KiB

2 years ago
/* msdf
Handles multi-channel signed distance field bitmap
generation from given ttf (stb_truetype.h) font.
https://github.com/exezin/msdf-c
Depends on stb_truetype.h to load the ttf file.
This is in an unstable state, ymmv.
Based on the C++ implementation by Viktor Chlumský.
https://github.com/Chlumsky/msdfgen
*/
#ifndef MSDF_H
#define MSDF_H
#include <inttypes.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int glyphIdx;
int left_bearing;
int advance;
int ix0, ix1;
int iy0, iy1;
} ex_metrics_t;
typedef struct msdf_Result {
int glyphIdx;
int left_bearing;
int advance;
float* rgb;
int width;
int height;
int yOffset;
} msdf_Result;
typedef struct msdf_AllocCtx {
void* (*alloc)(size_t size, void* ctx);
// free is optional and will not be called if it is null (useful for area allocators that free everything at once)
void (*free)(void* ptr, void* ctx);
void* ctx;
} msdf_AllocCtx;
/*
Generates a bitmap from the specified glyph index of a stbtt font
Returned result is 1 for success or 0 in case of an error
*/
int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc);
#ifdef __cplusplus
}
#endif
#ifdef MSDF_IMPLEMENTATION
// pixel at (x, y) in bitmap (arr)
#define msdf_pixelAt(x, y, w, arr) ((msdf_Vec3){arr[(3 * (((y)*w) + x))], arr[(3 * (((y)*w) + x)) + 1], arr[(3 * (((y)*w) + x)) + 2]})
#define msdf_max(x, y) (((x) > (y)) ? (x) : (y))
#define msdf_min(x, y) (((x) < (y)) ? (x) : (y))
#define MSDF_INF -1e24
#define MSDF_EDGE_THRESHOLD 0.02
#ifndef MSDF_PI
#define MSDF_PI 3.14159265358979323846
#endif
typedef float msdf_Vec2[2];
typedef float msdf_Vec3[3];
typedef struct
{
double dist;
double d;
} msdf_signedDistance;
// the possible types:
// STBTT_vmove = start of a contour
// STBTT_vline = linear segment
// STBTT_vcurve = quadratic segment
// STBTT_vcubic = cubic segment
typedef struct
{
int color;
msdf_Vec2 p[4];
int type;
} msdf_EdgeSegment;
// defines what color channel an edge belongs to
typedef enum
{
msdf_edgeColor_black = 0,
msdf_edgeColor_red = 1,
msdf_edgeColor_green = 2,
msdf_edgeColor_yellow = 3,
msdf_edgeColor_blue = 4,
msdf_edgeColor_magenta = 5,
msdf_edgeColor_cyan = 6,
msdf_edgeColor_white = 7
} msdf_edgeColor;
static double msdf_median(double a, double b, double c)
{
return msdf_max(msdf_min(a, b), msdf_min(msdf_max(a, b), c));
}
static int msdf_nonZeroSign(double n)
{
return 2 * (n > 0) - 1;
}
static double msdf_cross(msdf_Vec2 a, msdf_Vec2 b)
{
return a[0] * b[1] - a[1] * b[0];
}
static void msdf_v2Scale(msdf_Vec2 r, msdf_Vec2 const v, float const s)
{
int i;
for (i = 0; i < 2; ++i)
r[i] = v[i] * s;
}
static float msdf_v2MulInner(msdf_Vec2 const a, msdf_Vec2 const b)
{
float p = 0.;
int i;
for (i = 0; i < 2; ++i)
p += b[i] * a[i];
return p;
}
static float msdf_v2Leng(msdf_Vec2 const v)
{
return sqrtf(msdf_v2MulInner(v, v));
}
static void msdf_v2Norm(msdf_Vec2 r, msdf_Vec2 const v)
{
float k = 1.0 / msdf_v2Leng(v);
msdf_v2Scale(r, v, k);
}
static void msdf_v2Sub(msdf_Vec2 r, msdf_Vec2 const a, msdf_Vec2 const b)
{
int i;
for (i = 0; i < 2; ++i)
r[i] = a[i] - b[i];
}
int msdf_solveQuadratic(double x[2], double a, double b, double c)
{
if (fabs(a) < 1e-14)
{
if (fabs(b) < 1e-14)
{
if (c == 0)
return -1;
return 0;
}
x[0] = -c / b;
return 1;
}
double dscr = b * b - 4 * a * c;
if (dscr > 0)
{
dscr = sqrt(dscr);
x[0] = (-b + dscr) / (2 * a);
x[1] = (-b - dscr) / (2 * a);
return 2;
}
else if (dscr == 0)
{
x[0] = -b / (2 * a);
return 1;
}
else
{
return 0;
}
}
int msdf_solveCubicNormed(double *x, double a, double b, double c)
{
double a2 = a * a;
double q = (a2 - 3 * b) / 9;
double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54;
double r2 = r * r;
double q3 = q * q * q;
double A, B;
if (r2 < q3)
{
double t = r / sqrt(q3);
if (t < -1)
t = -1;
if (t > 1)
t = 1;
t = acos(t);
a /= 3;
q = -2 * sqrt(q);
x[0] = q * cos(t / 3) - a;
x[1] = q * cos((t + 2 * MSDF_PI) / 3) - a;
x[2] = q * cos((t - 2 * MSDF_PI) / 3) - a;
return 3;
}
else
{
A = -pow(fabs(r) + sqrt(r2 - q3), 1 / 3.);
if (r < 0)
A = -A;
B = A == 0 ? 0 : q / A;
a /= 3;
x[0] = (A + B) - a;
x[1] = -0.5 * (A + B) - a;
x[2] = 0.5 * sqrt(3.) * (A - B);
if (fabs(x[2]) < 1e-14)
return 2;
return 1;
}
}
int msdf_solveCubic(double x[3], double a, double b, double c, double d)
{
if (fabs(a) < 1e-14)
return msdf_solveQuadratic(x, b, c, d);
return msdf_solveCubicNormed(x, b / a, c / a, d / a);
}
void msdf_getOrtho(msdf_Vec2 r, msdf_Vec2 const v, int polarity, int allow_zero)
{
double len = msdf_v2Leng(v);
if (len == 0)
{
if (polarity)
{
r[0] = 0;
r[1] = !allow_zero;
}
else
{
r[0] = 0;
r[1] = -!allow_zero;
}
return;
}
if (polarity)
{
r[0] = -v[1] / len;
r[1] = v[0] / len;
}
else
{
r[0] = v[1] / len;
r[1] = -v[0] / len;
}
}
int msdf_pixelClash(const msdf_Vec3 a, const msdf_Vec3 b, double threshold)
{
int aIn = (a[0] > .5f) + (a[1] > .5f) + (a[2] > .5f) >= 2;
int bIn = (b[0] > .5f) + (b[1] > .5f) + (b[2] > .5f) >= 2;
if (aIn != bIn)
return 0;
if ((a[0] > .5f && a[1] > .5f && a[2] > .5f) || (a[0] < .5f && a[1] < .5f && a[2] < .5f) || (b[0] > .5f && b[1] > .5f && b[2] > .5f) || (b[0] < .5f && b[1] < .5f && b[2] < .5f))
return 0;
float aa, ab, ba, bb, ac, bc;
if ((a[0] > .5f) != (b[0] > .5f) && (a[0] < .5f) != (b[0] < .5f))
{
aa = a[0], ba = b[0];
if ((a[1] > .5f) != (b[1] > .5f) && (a[1] < .5f) != (b[1] < .5f))
{
ab = a[1], bb = b[1];
ac = a[2], bc = b[2];
}
else if ((a[2] > .5f) != (b[2] > .5f) && (a[2] < .5f) != (b[2] < .5f))
{
ab = a[2], bb = b[2];
ac = a[1], bc = b[1];
}
else
{
return 0;
}
}
else if ((a[1] > .5f) != (b[1] > .5f) && (a[1] < .5f) != (b[1] < .5f) && (a[2] > .5f) != (b[2] > .5f) && (a[2] < .5f) != (b[2] < .5f))
{
aa = a[1], ba = b[1];
ab = a[2], bb = b[2];
ac = a[0], bc = b[0];
}
else
{
return 0;
}
return (fabsf(aa - ba) >= threshold) && (fabsf(ab - bb) >= threshold) && fabsf(ac - .5f) >= fabsf(bc - .5f);
}
void msdf_mix(msdf_Vec2 r, msdf_Vec2 a, msdf_Vec2 b, double weight)
{
r[0] = (1 - weight) * a[0] + weight * b[0];
r[1] = (1 - weight) * a[1] + weight * b[1];
}
void msdf_linearDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
r[0] = e->p[1][0] - e->p[0][0];
r[1] = e->p[1][1] - e->p[0][1];
}
void msdf_quadraticDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
msdf_Vec2 a, b;
msdf_v2Sub(a, e->p[1], e->p[0]);
msdf_v2Sub(b, e->p[2], e->p[1]);
msdf_mix(r, a, b, param);
}
void msdf_cubicDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
msdf_Vec2 a, b, c, d, t;
msdf_v2Sub(a, e->p[1], e->p[0]);
msdf_v2Sub(b, e->p[2], e->p[1]);
msdf_mix(c, a, b, param);
msdf_v2Sub(a, e->p[3], e->p[2]);
msdf_mix(d, b, a, param);
msdf_mix(t, c, d, param);
if (!t[0] && !t[1])
{
if (param == 0)
{
r[0] = e->p[2][0] - e->p[0][0];
r[1] = e->p[2][1] - e->p[0][1];
return;
}
if (param == 1)
{
r[0] = e->p[3][0] - e->p[1][0];
r[1] = e->p[3][1] - e->p[1][1];
return;
}
}
r[0] = t[0];
r[1] = t[1];
}
void msdf_direction(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
switch (e->type)
{
case STBTT_vline:
{
msdf_linearDirection(r, e, param);
break;
}
case STBTT_vcurve:
{
msdf_quadraticDirection(r, e, param);
break;
}
case STBTT_vcubic:
{
msdf_cubicDirection(r, e, param);
break;
}
}
}
void msdf_linearPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
msdf_mix(r, e->p[0], e->p[1], param);
}
void msdf_quadraticPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
msdf_Vec2 a, b;
msdf_mix(a, e->p[0], e->p[1], param);
msdf_mix(b, e->p[1], e->p[2], param);
msdf_mix(r, a, b, param);
}
void msdf_cubicPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
msdf_Vec2 p12, a, b, c, d;
msdf_mix(p12, e->p[1], e->p[2], param);
msdf_mix(a, e->p[0], e->p[1], param);
msdf_mix(b, a, p12, param);
msdf_mix(c, e->p[2], e->p[3], param);
msdf_mix(d, p12, c, param);
msdf_mix(r, b, d, param);
}
void msdf_point(msdf_Vec2 r, msdf_EdgeSegment *e, double param)
{
switch (e->type)
{
case STBTT_vline:
{
msdf_linearPoint(r, e, param);
break;
}
case STBTT_vcurve:
{
msdf_quadraticPoint(r, e, param);
break;
}
case STBTT_vcubic:
{
msdf_cubicPoint(r, e, param);
break;
}
}
}
// linear edge signed distance
msdf_signedDistance msdf_linearDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param)
{
msdf_Vec2 aq, ab, eq;
msdf_v2Sub(aq, origin, e->p[0]);
msdf_v2Sub(ab, e->p[1], e->p[0]);
*param = msdf_v2MulInner(aq, ab) / msdf_v2MulInner(ab, ab);
msdf_v2Sub(eq, e->p[*param > .5], origin);
double endpoint_distance = msdf_v2Leng(eq);
if (*param > 0 && *param < 1)
{
msdf_Vec2 ab_ortho;
msdf_getOrtho(ab_ortho, ab, 0, 0);
double ortho_dist = msdf_v2MulInner(ab_ortho, aq);
if (fabs(ortho_dist) < endpoint_distance)
return (msdf_signedDistance){ortho_dist, 0};
}
msdf_v2Norm(ab, ab);
msdf_v2Norm(eq, eq);
double dist = msdf_nonZeroSign(msdf_cross(aq, ab)) * endpoint_distance;
double d = fabs(msdf_v2MulInner(ab, eq));
return (msdf_signedDistance){dist, d};
}
// quadratic edge signed distance
msdf_signedDistance msdf_quadraticDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param)
{
msdf_Vec2 qa, ab, br;
msdf_v2Sub(qa, e->p[0], origin);
msdf_v2Sub(ab, e->p[1], e->p[0]);
br[0] = e->p[0][0] + e->p[2][0] - e->p[1][0] - e->p[1][0];
br[1] = e->p[0][1] + e->p[2][1] - e->p[1][1] - e->p[1][1];
double a = msdf_v2MulInner(br, br);
double b = 3 * msdf_v2MulInner(ab, br);
double c = 2 * msdf_v2MulInner(ab, ab) + msdf_v2MulInner(qa, br);
double d = msdf_v2MulInner(qa, ab);
double t[3];
int solutions = msdf_solveCubic(t, a, b, c, d);
// distance from a
double min_distance = msdf_nonZeroSign(msdf_cross(ab, qa)) * msdf_v2Leng(qa);
*param = -msdf_v2MulInner(qa, ab) / msdf_v2MulInner(ab, ab);
{
msdf_Vec2 a, b;
msdf_v2Sub(a, e->p[2], e->p[1]);
msdf_v2Sub(b, e->p[2], origin);
// distance from b
double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b);
if (fabs(distance) < fabs(min_distance))
{
min_distance = distance;
msdf_v2Sub(a, origin, e->p[1]);
msdf_v2Sub(b, e->p[2], e->p[1]);
*param = msdf_v2MulInner(a, b) / msdf_v2MulInner(b, b);
}
}
for (int i = 0; i < solutions; ++i)
{
if (t[i] > 0 && t[i] < 1)
{
// end_point = p[0]+2*t[i]*ab+t[i]*t[i]*br;
msdf_Vec2 end_point, a, b;
end_point[0] = e->p[0][0] + 2 * t[i] * ab[0] + t[i] * t[i] * br[0];
end_point[1] = e->p[0][1] + 2 * t[i] * ab[1] + t[i] * t[i] * br[1];
msdf_v2Sub(a, e->p[2], e->p[0]);
msdf_v2Sub(b, end_point, origin);
double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b);
if (fabs(distance) <= fabs(min_distance))
{
min_distance = distance;
*param = t[i];
}
}
}
if (*param >= 0 && *param <= 1)
return (msdf_signedDistance){min_distance, 0};
msdf_Vec2 aa, bb;
msdf_v2Norm(ab, ab);
msdf_v2Norm(qa, qa);
msdf_v2Sub(aa, e->p[2], e->p[1]);
msdf_v2Norm(aa, aa);
msdf_v2Sub(bb, e->p[2], origin);
msdf_v2Norm(bb, bb);
if (*param < .5)
return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(ab, qa))};
else
return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(aa, bb))};
}
// cubic edge signed distance
msdf_signedDistance msdf_cubicDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param)
{
msdf_Vec2 qa, ab, br, as;
msdf_v2Sub(qa, e->p[0], origin);
msdf_v2Sub(ab, e->p[1], e->p[0]);
br[0] = e->p[2][0] - e->p[1][0] - ab[0];
br[1] = e->p[2][1] - e->p[1][1] - ab[1];
as[0] = (e->p[3][0] - e->p[2][0]) - (e->p[2][0] - e->p[1][0]) - br[0];
as[1] = (e->p[3][1] - e->p[2][1]) - (e->p[2][1] - e->p[1][1]) - br[1];
msdf_Vec2 ep_dir;
msdf_direction(ep_dir, e, 0);
// distance from a
double min_distance = msdf_nonZeroSign(msdf_cross(ep_dir, qa)) * msdf_v2Leng(qa);
*param = -msdf_v2MulInner(qa, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir);
{
msdf_Vec2 a;
msdf_v2Sub(a, e->p[3], origin);
msdf_direction(ep_dir, e, 1);
// distance from b
double distance = msdf_nonZeroSign(msdf_cross(ep_dir, a)) * msdf_v2Leng(a);
if (fabs(distance) < fabs(min_distance))
{
min_distance = distance;
a[0] = origin[0] + ep_dir[0] - e->p[3][0];
a[1] = origin[1] + ep_dir[1] - e->p[3][1];
*param = msdf_v2MulInner(a, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir);
}
}
const int search_starts = 4;
for (int i = 0; i <= search_starts; ++i)
{
double t = (double)i / search_starts;
for (int step = 0;; ++step)
{
msdf_Vec2 qpt;
msdf_point(qpt, e, t);
msdf_v2Sub(qpt, qpt, origin);
msdf_Vec2 d;
msdf_direction(d, e, t);
double distance = msdf_nonZeroSign(msdf_cross(d, qpt)) * msdf_v2Leng(qpt);
if (fabs(distance) < fabs(min_distance))
{
min_distance = distance;
*param = t;
}
if (step == search_starts)
break;
msdf_Vec2 d1, d2;
d1[0] = 3 * as[0] * t * t + 6 * br[0] * t + 3 * ab[0];
d1[1] = 3 * as[1] * t * t + 6 * br[1] * t + 3 * ab[1];
d2[0] = 6 * as[0] * t + 6 * br[0];
d2[1] = 6 * as[1] * t + 6 * br[1];
t -= msdf_v2MulInner(qpt, d1) / (msdf_v2MulInner(d1, d1) + msdf_v2MulInner(qpt, d2));
if (t < 0 || t > 1)
break;
}
}
if (*param >= 0 && *param <= 1)
return (msdf_signedDistance){min_distance, 0};
msdf_Vec2 d0, d1;
msdf_direction(d0, e, 0);
msdf_direction(d1, e, 1);
msdf_v2Norm(d0, d0);
msdf_v2Norm(d1, d1);
msdf_v2Norm(qa, qa);
msdf_Vec2 a;
msdf_v2Sub(a, e->p[3], origin);
msdf_v2Norm(a, a);
if (*param < .5)
return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d0, qa))};
else
return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d1, a))};
}
void msdf_distToPseudo(msdf_signedDistance *distance, msdf_Vec2 origin, double param, msdf_EdgeSegment *e) {
if (param < 0) {
msdf_Vec2 dir, p;
msdf_direction(dir, e, 0);
msdf_v2Norm(dir, dir);
msdf_Vec2 aq = {origin[0], origin[1]};
msdf_point(p, e, 0);
msdf_v2Sub(aq, origin, p);
double ts = msdf_v2MulInner(aq, dir);
if (ts < 0) {
double pseudo_dist = msdf_cross(aq, dir);
if (fabs(pseudo_dist) <= fabs(distance->dist)) {
distance->dist = pseudo_dist;
distance->d = 0;
}
}
} else if (param > 1) {
msdf_Vec2 dir, p;
msdf_direction(dir, e, 1);
msdf_v2Norm(dir, dir);
msdf_Vec2 bq = {origin[0], origin[1]};
msdf_point(p, e, 1);
msdf_v2Sub(bq, origin, p);
double ts = msdf_v2MulInner(bq, dir);
if (ts > 0) {
double pseudo_dist = msdf_cross(bq, dir);
if (fabs(pseudo_dist) <= fabs(distance->dist)) {
distance->dist = pseudo_dist;
distance->d = 0;
}
}
}
}
int msdf_signedCompare(msdf_signedDistance a, msdf_signedDistance b) {
return fabs(a.dist) < fabs(b.dist) || (fabs(a.dist) == fabs(b.dist) && a.d < b.d);
}
int msdf_isCorner(msdf_Vec2 a, msdf_Vec2 b, double threshold) {
return msdf_v2MulInner(a, b) <= 0 || fabs(msdf_cross(a, b)) > threshold;
}
void msdf_switchColor(msdf_edgeColor *color, unsigned long long *seed, msdf_edgeColor banned)
{
msdf_edgeColor combined = *color & banned;
if (combined == msdf_edgeColor_red || combined == msdf_edgeColor_green || combined == msdf_edgeColor_blue) {
*color = (msdf_edgeColor)(combined ^ msdf_edgeColor_white);
return;
}
if (*color == msdf_edgeColor_black || *color == msdf_edgeColor_white) {
static const msdf_edgeColor start[3] = {msdf_edgeColor_cyan, msdf_edgeColor_magenta, msdf_edgeColor_yellow};
*color = start[*seed & 3];
*seed /= 3;
return;
}
int shifted = *color << (1 + (*seed & 1));
*color = (msdf_edgeColor)((shifted | shifted >> 3) & msdf_edgeColor_white);
*seed >>= 1;
}
void msdf_linearSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3)
{
msdf_Vec2 p;
msdf_point(p, e, 1 / 3.0);
memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2));
memcpy(&p1->p[1], p, sizeof(msdf_Vec2));
p1->color = e->color;
msdf_point(p, e, 1 / 3.0);
memcpy(&p2->p[0], p, sizeof(msdf_Vec2));
msdf_point(p, e, 2 / 3.0);
memcpy(&p2->p[1], p, sizeof(msdf_Vec2));
p2->color = e->color;
msdf_point(p, e, 2 / 3.0);
memcpy(&p3->p[0], p, sizeof(msdf_Vec2));
msdf_point(p, e, 2 / 3.0);
memcpy(&p3->p[1], e->p[1], sizeof(msdf_Vec2));
p3->color = e->color;
}
void msdf_quadraticSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3)
{
msdf_Vec2 p, a, b;
memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2));
msdf_mix(p, e->p[0], e->p[1], 1 / 3.0);
memcpy(&p1->p[1], p, sizeof(msdf_Vec2));
msdf_point(p, e, 1 / 3.0);
memcpy(&p1->p[2], p, sizeof(msdf_Vec2));
p1->color = e->color;
msdf_point(p, e, 1 / 3.0);
memcpy(&p2->p[0], p, sizeof(msdf_Vec2));
msdf_mix(a, e->p[0], e->p[1], 5 / 9.0);
msdf_mix(b, e->p[1], e->p[2], 4 / 9.0);
msdf_mix(p, a, b, 0.5);
memcpy(&p2->p[1], p, sizeof(msdf_Vec2));
msdf_point(p, e, 2 / 3.0);
memcpy(&p2->p[2], p, sizeof(msdf_Vec2));
p2->color = e->color;
msdf_point(p, e, 2 / 3.0);
memcpy(&p3->p[0], p, sizeof(msdf_Vec2));
msdf_mix(p, e->p[1], e->p[2], 2 / 3.0);
memcpy(&p3->p[1], p, sizeof(msdf_Vec2));
memcpy(&p3->p[2], e->p[2], sizeof(msdf_Vec2));
p3->color = e->color;
}
void msdf_cubicSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3)
{
msdf_Vec2 p, a, b, c, d;
memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); // p1 0
if (e->p[0] == e->p[1]) {
memcpy(&p1->p[1], e->p[0], sizeof(msdf_Vec2)); // ? p1 1
} else {
msdf_mix(p, e->p[0], e->p[1], 1 / 3.0);
memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); // ? p1 1
}
msdf_mix(a, e->p[0], e->p[1], 1 / 3.0);
msdf_mix(b, e->p[1], e->p[2], 1 / 3.0);
msdf_mix(p, a, b, 1 / 3.0);
memcpy(&p1->p[2], p, sizeof(msdf_Vec2)); // p1 2
msdf_point(p, e, 1 / 3.0);
memcpy(&p1->p[3], p, sizeof(msdf_Vec2)); // p1 3
p1->color = e->color;
msdf_point(p, e, 1 / 3.0);
memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); // p2 0
msdf_mix(a, e->p[0], e->p[1], 1 / 3.0);
msdf_mix(b, e->p[1], e->p[2], 1 / 3.0);
msdf_mix(c, a, b, 1 / 3.0);
msdf_mix(a, e->p[1], e->p[2], 1 / 3.0);
msdf_mix(b, e->p[2], e->p[3], 1 / 3.0);
msdf_mix(d, a, b, 1 / 3.0);
msdf_mix(p, c, d, 2 / 3.0);
memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); // p2 1
msdf_mix(a, e->p[0], e->p[1], 2 / 3.0);
msdf_mix(b, e->p[1], e->p[2], 2 / 3.0);
msdf_mix(c, a, b, 2 / 3.0);
msdf_mix(a, e->p[1], e->p[2], 2 / 3.0);
msdf_mix(b, e->p[2], e->p[3], 2 / 3.0);
msdf_mix(d, a, b, 2 / 3.0);
msdf_mix(p, c, d, 1 / 3.0);
memcpy(&p2->p[2], p, sizeof(msdf_Vec2)); // p2 2
msdf_point(p, e, 2 / 3.0);
memcpy(&p2->p[3], p, sizeof(msdf_Vec2)); // p2 3
p2->color = e->color;
msdf_point(p, e, 2 / 3.0);
memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); // p3 0
msdf_mix(a, e->p[1], e->p[2], 2 / 3.0);
msdf_mix(b, e->p[2], e->p[3], 2 / 3.0);
msdf_mix(p, a, b, 2 / 3.0);
memcpy(&p3->p[1], p, sizeof(msdf_Vec2)); // p3 1
if (e->p[2] == e->p[3]) {
memcpy(&p3->p[2], e->p[3], sizeof(msdf_Vec2)); // ? p3 2
} else {
msdf_mix(p, e->p[2], e->p[3], 2 / 3.0);
memcpy(&p3->p[2], p, sizeof(msdf_Vec2)); // ? p3 2
}
memcpy(&p3->p[3], e->p[3], sizeof(msdf_Vec2)); // p3 3
}
void msdf_edgeSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3)
{
switch (e->type) {
case STBTT_vline: {
msdf_linearSplit(e, p1, p2, p3);
break;
}
case STBTT_vcurve: {
msdf_quadraticSplit(e, p1, p2, p3);
break;
}
case STBTT_vcubic: {
msdf_cubicSplit(e, p1, p2, p3);
break;
}
}
}
double msdf_shoelace(const msdf_Vec2 a, const msdf_Vec2 b)
{
return (b[0] - a[0]) * (a[1] + b[1]);
}
void* msdf__alloc(size_t size, void* ctx) {
return malloc(size);
}
void msdf__free(void* ptr, void* ctx) {
free(ptr);
}
int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc) {
msdf_AllocCtx allocCtx;
if (alloc) {
allocCtx = *alloc;
} else {
allocCtx.alloc = msdf__alloc;
allocCtx.free = msdf__free;
allocCtx.ctx = NULL;
}
//char f = c;
// Funit to pixel scale
//float scale = stbtt_ScaleForMappingEmToPixels(font, h);
int glyphIdx = stbttGlyphIndex;
// get glyph bounding box (scaled later)
int ix0, iy0, ix1, iy1;
float xoff = .0, yoff = .0;
stbtt_GetGlyphBox(font, glyphIdx, &ix0, &iy0, &ix1, &iy1);
float glyphWidth = ix1 - ix0;
float glyphHeight = iy1 - iy0;
float borderWidthF32 = borderWidth;
float wF32 = ceilf(glyphWidth * scale);
float hF32 = ceilf(glyphHeight * scale);
wF32 += 2.f * borderWidth;
hF32 += 2.f * borderWidth;
int w = wF32;
int h = hF32;
float* bitmap = (float*) allocCtx.alloc(w * h * 3 * sizeof(float), allocCtx.ctx);
memset(bitmap, 0x0, w * h * 3 * sizeof(float));
// em scale
//scale = stbtt_ScaleForMappingEmToPixels(font, h);
//if (autofit)
//{
// calculate new height
//float newh = h + (h - (iy1 - iy0) * scale) - 4;
// calculate new scale
// see 'stbtt_ScaleForMappingEmToPixels' in stb_truetype.h
//uint8_t *p = font->data + font->head + 18;
//int unitsPerEm = p[0] * 256 + p[1];
//scale = ((float)h) / ((float)unitsPerEm);
// make sure we are centered
//xoff = .0;
//yoff = .0;
//}
// get left offset and advance
//int left_bearing, advance;
//stbtt_GetGlyphHMetrics(font, glyphIdx, &advance, &left_bearing);
//left_bearing *= scale;
int32_t glyphOrgX = ix0 * scale;
int32_t glyphOrgY = iy0 * scale;
int32_t borderWidthX = borderWidth;
int32_t borderWidthY = borderWidth;
// org 8,8
// - bord 4,4
// erg: 4,4
// calculate offset for centering glyph on bitmap
//glyphOrgX >= 2 ? (glyphOrgX) : ();
int32_t translateX = (glyphOrgX - borderWidth);//borderWidth + ((w / 2) - ((ix1 - ix0) * scale) / 2 - leftBearingScaled);
int32_t translateY = (glyphOrgY - borderWidth);//borderWidth + ((h / 2) - ((iy1 - iy0) * scale) / 2 - ((float) iy0) * scale);
//translateY = 8;
// set the glyph metrics
// (pre-scale them)
#if 0
if (metrics)
{
metrics->left_bearing = left_bearing;
metrics->advance = advance * scale;
metrics->ix0 = ix0 * scale;
metrics->ix1 = ix1 * scale;
metrics->iy0 = iy0 * scale;
metrics->iy1 = iy1 * scale;
metrics->glyphIdx = glyphIdx;
}
#endif
stbtt_vertex *verts;
int num_verts = stbtt_GetGlyphShape(font, glyphIdx, &verts);
// figure out how many contours exist
int contour_count = 0;
for (int i = 0; i < num_verts; i++) {
if (verts[i].type == STBTT_vmove) {
contour_count++;
}
}
if (contour_count == 0) {
return 0;
}
// determin what vertices belong to what contours
typedef struct {
size_t start, end;
} msdf_Indices;
msdf_Indices *contours = allocCtx.alloc(sizeof(msdf_Indices) * contour_count, allocCtx.ctx);
int j = 0;
for (int i = 0; i <= num_verts; i++) {
if (verts[i].type == STBTT_vmove) {
if (i > 0) {
contours[j].end = i;
j++;
}
contours[j].start = i;
} else if (i >= num_verts) {
contours[j].end = i;
}
}
typedef struct {
msdf_signedDistance min_distance;
msdf_EdgeSegment *near_edge;
double near_param;
} msdf_EdgePoint;
typedef struct {
msdf_EdgeSegment *edges;
size_t edge_count;
} msdf_Contour;
// process verts into series of contour-specific edge lists
msdf_Vec2 initial = {0, 0}; // fix this?
msdf_Contour *contour_data = allocCtx.alloc(sizeof(msdf_Contour) * contour_count, allocCtx.ctx);
double cscale = 64.0;
for (int i = 0; i < contour_count; i++) {
size_t count = contours[i].end - contours[i].start;
contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * count, allocCtx.ctx);
contour_data[i].edge_count = 0;
size_t k = 0;
for (int j = contours[i].start; j < contours[i].end; j++) {
msdf_EdgeSegment *e = &contour_data[i].edges[k];
stbtt_vertex *v = &verts[j];
e->type = v->type;
e->color = msdf_edgeColor_white;
switch (v->type) {
case STBTT_vmove: {
msdf_Vec2 p = {v->x / cscale, v->y / cscale};
memcpy(&initial, p, sizeof(msdf_Vec2));
break;
}
case STBTT_vline: {
msdf_Vec2 p = {v->x / cscale, v->y / cscale};
memcpy(&e->p[0], initial, sizeof(msdf_Vec2));
memcpy(&e->p[1], p, sizeof(msdf_Vec2));
memcpy(&initial, p, sizeof(msdf_Vec2));
contour_data[i].edge_count++;
k++;
break;
}
case STBTT_vcurve: {
msdf_Vec2 p = {v->x / cscale, v->y / cscale};
msdf_Vec2 c = {v->cx / cscale, v->cy / cscale};
memcpy(&e->p[0], initial, sizeof(msdf_Vec2));
memcpy(&e->p[1], c, sizeof(msdf_Vec2));
memcpy(&e->p[2], p, sizeof(msdf_Vec2));
if ((e->p[0][0] == e->p[1][0] && e->p[0][1] == e->p[1][1]) ||
(e->p[1][0] == e->p[2][0] && e->p[1][1] == e->p[2][1]))
{
e->p[1][0] = 0.5 * (e->p[0][0] + e->p[2][0]);
e->p[1][1] = 0.5 * (e->p[0][1] + e->p[2][1]);
}
memcpy(&initial, p, sizeof(msdf_Vec2));
contour_data[i].edge_count++;
k++;
break;
}
case STBTT_vcubic: {
msdf_Vec2 p = {v->x / cscale, v->y / cscale};
msdf_Vec2 c = {v->cx / cscale, v->cy / cscale};
msdf_Vec2 c1 = {v->cx1 / cscale, v->cy1 / cscale};
memcpy(&e->p[0], initial, sizeof(msdf_Vec2));
memcpy(&e->p[1], c, sizeof(msdf_Vec2));
memcpy(&e->p[2], c1, sizeof(msdf_Vec2));
memcpy(&e->p[3], p, sizeof(msdf_Vec2));
memcpy(&initial, p, sizeof(msdf_Vec2));
contour_data[i].edge_count++;
k++;
break;
}
}
}
}
// calculate edge-colors
uint64_t seed = 0;
double anglethreshold = 3.0;
double crossthreshold = sin(anglethreshold);
size_t corner_count = 0;
for (int i = 0; i < contour_count; ++i) {
for (int j = 0; j < contour_data[i].edge_count; ++j) {
corner_count++;
}
}
int *corners = allocCtx.alloc(sizeof(int) * corner_count, allocCtx.ctx);
int cornerIndex = 0;
for (int i = 0; i < contour_count; ++i) {
if (contour_data[i].edge_count > 0) {
msdf_Vec2 prev_dir, dir;
msdf_direction(prev_dir, &contour_data[i].edges[contour_data[i].edge_count - 1], 1);
int index = 0;
for (int j = 0; j < contour_data[i].edge_count; ++j, ++index) {
msdf_EdgeSegment *e = &contour_data[i].edges[j];
msdf_direction(dir, e, 0);
msdf_v2Norm(dir, dir);
msdf_v2Norm(prev_dir, prev_dir);
if (msdf_isCorner(prev_dir, dir, crossthreshold)) {
corners[cornerIndex++] = index;
}
msdf_direction(prev_dir, e, 1);
}
}
if (cornerIndex == 0) {
for (int j = 0; j < contour_data[i].edge_count; ++j) {
contour_data[i].edges[j].color = msdf_edgeColor_white;
}
} else if (cornerIndex == 1) {
msdf_edgeColor colors[3] = {msdf_edgeColor_white, msdf_edgeColor_white};
msdf_switchColor(&colors[0], &seed, msdf_edgeColor_black);
colors[2] = colors[0];
msdf_switchColor(&colors[2], &seed, msdf_edgeColor_black);
int corner = corners[0];
if (contour_data[i].edge_count >= 3) {
int m = contour_data[i].edge_count;
for (int j = 0; j < m; ++j) {
contour_data[i].edges[(corner + j) % m].color = (colors + 1)[(int)(3 + 2.875 * i / (m - 1) - 1.4375 + .5) - 3];
}
} else if (contour_data[i].edge_count >= 1) {
msdf_EdgeSegment *parts[7] = {NULL};
msdf_edgeSplit(&contour_data[i].edges[0], parts[0 + 3 * corner], parts[1 + 3 * corner], parts[2 + 3 * corner]);
if (contour_data[i].edge_count >= 2) {
msdf_edgeSplit(&contour_data[i].edges[1], parts[3 - 3 * corner], parts[4 - 3 * corner], parts[5 - 3 * corner]);
parts[0]->color = parts[1]->color = colors[0];
parts[2]->color = parts[3]->color = colors[1];
parts[4]->color = parts[5]->color = colors[2];
} else {
parts[0]->color = colors[0];
parts[1]->color = colors[1];
parts[2]->color = colors[2];
}
if (allocCtx.free) {
allocCtx.free(contour_data[i].edges, allocCtx.ctx);
}
contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 7, allocCtx.ctx);
contour_data[i].edge_count = 0;
int index = 0;
for (int j = 0; parts[j]; ++j) {
memcpy(&contour_data[i].edges[index++], &parts[j], sizeof(msdf_EdgeSegment));
contour_data[i].edge_count++;
}
}
} else {
int spline = 0;
int start = corners[0];
int m = contour_data[i].edge_count;
msdf_edgeColor color = msdf_edgeColor_white;
msdf_switchColor(&color, &seed, msdf_edgeColor_black);
msdf_edgeColor initial_color = color;
for (int j = 0; j < m; ++j) {
int index = (start + j) % m;
if (spline + 1 < corner_count && corners[spline + 1] == index) {
++spline;
msdf_edgeColor s = (msdf_edgeColor)((spline == corner_count - 1) * initial_color);
msdf_switchColor(&color, &seed, s);
}
contour_data[i].edges[index].color = color;
}
}
}
if (allocCtx.free) {
allocCtx.free(corners, allocCtx.ctx);
}
// normalize shape
for (int i = 0; i < contour_count; i++) {
if (contour_data[i].edge_count == 1) {
msdf_EdgeSegment *parts[3] = {0};
msdf_edgeSplit(&contour_data[i].edges[0], parts[0], parts[1], parts[2]);
if (allocCtx.free) {
allocCtx.free(contour_data[i].edges, allocCtx.ctx);
}
contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 3, allocCtx.ctx);
contour_data[i].edge_count = 3;
for (int j = 0; j < 3; j++) {
memcpy(&contour_data[i].edges[j], parts[j], sizeof(msdf_EdgeSegment));
}
}
}
// calculate windings
int *windings = allocCtx.alloc(sizeof(int) * contour_count, allocCtx.ctx);
for (int i = 0; i < contour_count; i++) {
size_t edge_count = contour_data[i].edge_count;
if (edge_count == 0) {
windings[i] = 0;
continue;
}
double total = 0;
if (edge_count == 1) {
msdf_Vec2 a, b, c;
msdf_point(a, &contour_data[i].edges[0], 0);
msdf_point(b, &contour_data[i].edges[0], 1 / 3.0);
msdf_point(c, &contour_data[i].edges[0], 2 / 3.0);
total += msdf_shoelace(a, b);
total += msdf_shoelace(b, c);
total += msdf_shoelace(c, a);
} else if (edge_count == 2) {
msdf_Vec2 a, b, c, d;
msdf_point(a, &contour_data[i].edges[0], 0);
msdf_point(b, &contour_data[i].edges[0], 0.5);
msdf_point(c, &contour_data[i].edges[1], 0);
msdf_point(d, &contour_data[i].edges[1], 0.5);
total += msdf_shoelace(a, b);
total += msdf_shoelace(b, c);
total += msdf_shoelace(c, d);
total += msdf_shoelace(d, a);
} else {
msdf_Vec2 prev;
msdf_point(prev, &contour_data[i].edges[edge_count - 1], 0);
for (int j = 0; j < edge_count; j++) {
msdf_Vec2 cur;
msdf_point(cur, &contour_data[i].edges[j], 0);
total += msdf_shoelace(prev, cur);
memcpy(prev, cur, sizeof(msdf_Vec2));
}
}
windings[i] = ((0 < total) - (total < 0)); // sign
}
typedef struct {
double r, g, b;
double med;
} msdf_MultiDistance;
msdf_MultiDistance *contour_sd;
contour_sd = allocCtx.alloc(sizeof(msdf_MultiDistance) * contour_count, allocCtx.ctx);
float invRange = 1.0 / range;
for (int y = 0; y < h; ++y) {
int row = iy0 > iy1 ? y : h - y - 1;
for (int x = 0; x < w; ++x) {
float a64 = 64.0;
msdf_Vec2 p = {(translateX + x + xoff) / (scale * a64), (translateY + y + yoff) / (scale * a64)};
//p[0] = ;
//p[1] = ;
msdf_EdgePoint sr, sg, sb;
sr.near_edge = sg.near_edge = sb.near_edge = NULL;
sr.near_param = sg.near_param = sb.near_param = 0;
sr.min_distance.dist = sg.min_distance.dist = sb.min_distance.dist = MSDF_INF;
sr.min_distance.d = sg.min_distance.d = sb.min_distance.d = 1;
double d = fabs(MSDF_INF);
double neg_dist = -MSDF_INF;
double pos_dist = MSDF_INF;
int winding = 0;
// calculate distance to contours from current point (and if its inside or outside of the shape?)
for (int j = 0; j < contour_count; ++j) {
msdf_EdgePoint r, g, b;
r.near_edge = g.near_edge = b.near_edge = NULL;
r.near_param = g.near_param = b.near_param = 0;
r.min_distance.dist = g.min_distance.dist = b.min_distance.dist = MSDF_INF;
r.min_distance.d = g.min_distance.d = b.min_distance.d = 1;
for (int k = 0; k < contour_data[j].edge_count; ++k) {
msdf_EdgeSegment *e = &contour_data[j].edges[k];
double param;
msdf_signedDistance distance;
distance.dist = MSDF_INF;
distance.d = 1;
// calculate signed distance
switch (e->type) {
case STBTT_vline: {
distance = msdf_linearDist(e, p, &param);
break;
}
case STBTT_vcurve: {
distance = msdf_quadraticDist(e, p, &param);
break;
}
case STBTT_vcubic: {
distance = msdf_cubicDist(e, p, &param);
break;
}
}
if (e->color & msdf_edgeColor_red && msdf_signedCompare(distance, r.min_distance)) {
r.min_distance = distance;
r.near_edge = e;
r.near_param = param;
}
if (e->color & msdf_edgeColor_green && msdf_signedCompare(distance, g.min_distance)) {
g.min_distance = distance;
g.near_edge = e;
g.near_param = param;
}
if (e->color & msdf_edgeColor_blue && msdf_signedCompare(distance, b.min_distance)) {
b.min_distance = distance;
b.near_edge = e;
b.near_param = param;
}
}
if (msdf_signedCompare(r.min_distance, sr.min_distance)) {
sr = r;
}
if (msdf_signedCompare(g.min_distance, sg.min_distance)) {
sg = g;
}
if (msdf_signedCompare(b.min_distance, sb.min_distance)) {
sb = b;
}
double med_min_dist = fabs(msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist));
if (med_min_dist < d) {
d = med_min_dist;
winding = -windings[j];
}
if (r.near_edge) {
msdf_distToPseudo(&r.min_distance, p, r.near_param, r.near_edge);
}
if (g.near_edge) {
msdf_distToPseudo(&g.min_distance, p, g.near_param, g.near_edge);
}
if (b.near_edge) {
msdf_distToPseudo(&b.min_distance, p, b.near_param, b.near_edge);
}
med_min_dist = msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist);
contour_sd[j].r = r.min_distance.dist;
contour_sd[j].g = g.min_distance.dist;
contour_sd[j].b = b.min_distance.dist;
contour_sd[j].med = med_min_dist;
if (windings[j] > 0 && med_min_dist >= 0 && fabs(med_min_dist) < fabs(pos_dist)) {
pos_dist = med_min_dist;
}
if (windings[j] < 0 && med_min_dist <= 0 && fabs(med_min_dist) < fabs(neg_dist)) {
neg_dist = med_min_dist;
}
}
if (sr.near_edge) {
msdf_distToPseudo(&sr.min_distance, p, sr.near_param, sr.near_edge);
}
if (sg.near_edge) {
msdf_distToPseudo(&sg.min_distance, p, sg.near_param, sg.near_edge);
}
if (sb.near_edge) {
msdf_distToPseudo(&sb.min_distance, p, sb.near_param, sb.near_edge);
}
msdf_MultiDistance msd;
msd.r = msd.g = msd.b = msd.med = MSDF_INF;
if (pos_dist >= 0 && fabs(pos_dist) <= fabs(neg_dist)) {
msd.med = MSDF_INF;
winding = 1;
for (int i = 0; i < contour_count; ++i) {
if (windings[i] > 0 && contour_sd[i].med > msd.med && fabs(contour_sd[i].med) < fabs(neg_dist)) {
msd = contour_sd[i];
}
}
} else if (neg_dist <= 0 && fabs(neg_dist) <= fabs(pos_dist)) {
msd.med = -MSDF_INF;
winding = -1;
for (int i = 0; i < contour_count; ++i) {
if (windings[i] < 0 && contour_sd[i].med < msd.med && fabs(contour_sd[i].med) < fabs(pos_dist)) {
msd = contour_sd[i];
}
}
}
for (int i = 0; i < contour_count; ++i) {
if (windings[i] != winding && fabs(contour_sd[i].med) < fabs(msd.med)) {
msd = contour_sd[i];
}
}
if (msdf_median(sr.min_distance.dist, sg.min_distance.dist, sb.min_distance.dist) == msd.med) {
msd.r = sr.min_distance.dist;
msd.g = sg.min_distance.dist;
msd.b = sb.min_distance.dist;
}
size_t index = 3 * ((row * w) + x);
float mr = ((float)msd.r) * invRange + 0.5f;
float mg = ((float)msd.g) * invRange + 0.5f;
float mb = ((float)msd.b) * invRange + 0.5f;
bitmap[index + 0] = mr;
bitmap[index + 1] = mg;
bitmap[index + 2] = mb;
}
}
if (allocCtx.free) {
for (int i = 0; i < contour_count; i++) {
allocCtx.free(contour_data[i].edges, allocCtx.ctx);
}
allocCtx.free(contour_data, allocCtx.ctx);
allocCtx.free(contour_sd, allocCtx.ctx);
allocCtx.free(contours, allocCtx.ctx);
allocCtx.free(windings, allocCtx.ctx);
allocCtx.free(verts, allocCtx.ctx);
}
// msdf error correction
typedef struct {
int x, y;
} msdf_Clash;
msdf_Clash *clashes = allocCtx.alloc(sizeof(msdf_Clash) * w * h, allocCtx.ctx);
size_t cindex = 0;
double tx = MSDF_EDGE_THRESHOLD / (scale * range);
double ty = MSDF_EDGE_THRESHOLD / (scale * range);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if ((x > 0 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(msdf_max(x - 1, 0), y, w, bitmap), tx)) || (x < w - 1 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(msdf_min(x + 1, w - 1), y, w, bitmap), tx)) || (y > 0 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(x, msdf_max(y - 1, 0), w, bitmap), ty)) || (y < h - 1 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(x, msdf_min(y + 1, h - 1), w, bitmap), ty))) {
clashes[cindex].x = x;
clashes[cindex++].y = y;
}
}
}
for (int i = 0; i < cindex; i++) {
size_t index = 3 * ((clashes[i].y * w) + clashes[i].x);
float med = msdf_median(bitmap[index], bitmap[index + 1], bitmap[index + 2]);
bitmap[index + 0] = med;
bitmap[index + 1] = med;
bitmap[index + 2] = med;
}
if (allocCtx.free) {
allocCtx.free(clashes, allocCtx.ctx);
}
result->glyphIdx = glyphIdx;
result->rgb = bitmap;
result->width = w;
result->height = h;
result->yOffset = translateY;
return 1;
}
#endif
#endif // MSDF_H