├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Readme.md ├── msdf.c ├── msdf.h ├── sample ├── fonts │ ├── Roboto-Regular.ttf │ └── RobotoLicense.txt ├── sample.c └── svpng.h └── stb_truetype.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .DS_Store -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project(engine) 3 | 4 | add_definitions(-DSAMPLE_ROOT="${PROJECT_SOURCE_DIR}/sample") 5 | 6 | set(CMAKE_C_STANDARD 11) 7 | 8 | add_executable(msdf_sample sample/sample.c) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Peter Jakobs 4 | Copyright (c) 2018 exezin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # msdf_c 0.1 2 | A pure C99 multi-channel signed distance field generator. Handles MSDF bitmap 3 | Generation from given ttf/otf font (outlines). 4 | Single Header STB-style library 5 | 6 | --- 7 | 8 | [This library is my take on improving msdf-c with some fixes and improving its API](https://github.com/solenum/msdf-c) 9 | 10 | [Based on the C++ implementation by Viktor Chlumský.](https://github.com/Chlumsky/msdfgen) 11 | 12 | ~~~ 13 | // example fragment shader 14 | // scale is render_width/glyph_width 15 | // render_width being the width of each rendered glyph 16 | float median(float r, float g, float b) { 17 | return max(min(r, g), min(max(r, g), b)); 18 | } 19 | 20 | void main() 21 | { 22 | vec3 sample = texture(u_texture, uv).rgb; 23 | float dist = scale * (median(sample.r, sample.g, sample.b) - 0.5); 24 | float o = clamp(dist + 0.5, 0.0, 1.0); 25 | color = vec4(vec3(1.0), o); 26 | } 27 | ~~~ 28 | -------------------------------------------------------------------------------- /msdf.c: -------------------------------------------------------------------------------- 1 | #define STB_TRUETYPE_IMPLEMENTATION 2 | #include "stb_truetype.h" 3 | #define MSDF_IMPLEMENTATION 4 | #include "msdf.h" 5 | -------------------------------------------------------------------------------- /msdf.h: -------------------------------------------------------------------------------- 1 | /* msdf 2 | Handles multi-channel signed distance field bitmap 3 | generation from given ttf (stb_truetype.h) font. 4 | https://github.com/exezin/msdf-c 5 | Depends on stb_truetype.h to load the ttf file. 6 | This is in an unstable state, ymmv. 7 | Based on the C++ implementation by Viktor Chlumský. 8 | https://github.com/Chlumsky/msdfgen 9 | */ 10 | 11 | #ifndef MSDF_H 12 | #define MSDF_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | typedef struct { 24 | int glyphIdx; 25 | int left_bearing; 26 | int advance; 27 | int ix0, ix1; 28 | int iy0, iy1; 29 | } ex_metrics_t; 30 | 31 | typedef struct msdf_Result { 32 | int glyphIdx; 33 | int left_bearing; 34 | int advance; 35 | float* rgb; 36 | int width; 37 | int height; 38 | int yOffset; 39 | } msdf_Result; 40 | 41 | typedef struct msdf_AllocCtx { 42 | void* (*alloc)(size_t size, void* ctx); 43 | // free is optional and will not be called if it is null (useful for area allocators that free everything at once) 44 | void (*free)(void* ptr, void* ctx); 45 | void* ctx; 46 | } msdf_AllocCtx; 47 | 48 | /* 49 | Generates a bitmap from the specified glyph index of a stbtt font 50 | 51 | Returned result is 1 for success or 0 in case of an error 52 | */ 53 | int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc); 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | 59 | #ifdef MSDF_IMPLEMENTATION 60 | // pixel at (x, y) in bitmap (arr) 61 | #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]}) 62 | 63 | #define msdf_max(x, y) (((x) > (y)) ? (x) : (y)) 64 | #define msdf_min(x, y) (((x) < (y)) ? (x) : (y)) 65 | 66 | #define MSDF_INF -1e24 67 | #define MSDF_EDGE_THRESHOLD 0.02 68 | 69 | #ifndef MSDF_PI 70 | #define MSDF_PI 3.14159265358979323846 71 | #endif 72 | 73 | typedef float msdf_Vec2[2]; 74 | typedef float msdf_Vec3[3]; 75 | 76 | typedef struct 77 | { 78 | double dist; 79 | double d; 80 | } msdf_signedDistance; 81 | 82 | // the possible types: 83 | // STBTT_vmove = start of a contour 84 | // STBTT_vline = linear segment 85 | // STBTT_vcurve = quadratic segment 86 | // STBTT_vcubic = cubic segment 87 | typedef struct 88 | { 89 | int color; 90 | msdf_Vec2 p[4]; 91 | int type; 92 | } msdf_EdgeSegment; 93 | 94 | // defines what color channel an edge belongs to 95 | typedef enum 96 | { 97 | msdf_edgeColor_black = 0, 98 | msdf_edgeColor_red = 1, 99 | msdf_edgeColor_green = 2, 100 | msdf_edgeColor_yellow = 3, 101 | msdf_edgeColor_blue = 4, 102 | msdf_edgeColor_magenta = 5, 103 | msdf_edgeColor_cyan = 6, 104 | msdf_edgeColor_white = 7 105 | } msdf_edgeColor; 106 | 107 | static double msdf_median(double a, double b, double c) 108 | { 109 | return msdf_max(msdf_min(a, b), msdf_min(msdf_max(a, b), c)); 110 | } 111 | 112 | static int msdf_nonZeroSign(double n) 113 | { 114 | return 2 * (n > 0) - 1; 115 | } 116 | 117 | static double msdf_cross(msdf_Vec2 a, msdf_Vec2 b) 118 | { 119 | return a[0] * b[1] - a[1] * b[0]; 120 | } 121 | 122 | static void msdf_v2Scale(msdf_Vec2 r, msdf_Vec2 const v, float const s) 123 | { 124 | int i; 125 | for (i = 0; i < 2; ++i) 126 | r[i] = v[i] * s; 127 | } 128 | 129 | static float msdf_v2MulInner(msdf_Vec2 const a, msdf_Vec2 const b) 130 | { 131 | float p = 0.; 132 | int i; 133 | for (i = 0; i < 2; ++i) 134 | p += b[i] * a[i]; 135 | return p; 136 | } 137 | 138 | static float msdf_v2Leng(msdf_Vec2 const v) 139 | { 140 | return sqrtf(msdf_v2MulInner(v, v)); 141 | } 142 | 143 | static void msdf_v2Norm(msdf_Vec2 r, msdf_Vec2 const v) 144 | { 145 | float k = 1.0 / msdf_v2Leng(v); 146 | msdf_v2Scale(r, v, k); 147 | } 148 | 149 | static void msdf_v2Sub(msdf_Vec2 r, msdf_Vec2 const a, msdf_Vec2 const b) 150 | { 151 | int i; 152 | for (i = 0; i < 2; ++i) 153 | r[i] = a[i] - b[i]; 154 | } 155 | 156 | int msdf_solveQuadratic(double x[2], double a, double b, double c) 157 | { 158 | if (fabs(a) < 1e-14) 159 | { 160 | if (fabs(b) < 1e-14) 161 | { 162 | if (c == 0) 163 | return -1; 164 | return 0; 165 | } 166 | x[0] = -c / b; 167 | return 1; 168 | } 169 | 170 | double dscr = b * b - 4 * a * c; 171 | if (dscr > 0) 172 | { 173 | dscr = sqrt(dscr); 174 | x[0] = (-b + dscr) / (2 * a); 175 | x[1] = (-b - dscr) / (2 * a); 176 | return 2; 177 | } 178 | else if (dscr == 0) 179 | { 180 | x[0] = -b / (2 * a); 181 | return 1; 182 | } 183 | else 184 | { 185 | return 0; 186 | } 187 | } 188 | 189 | int msdf_solveCubicNormed(double *x, double a, double b, double c) 190 | { 191 | double a2 = a * a; 192 | double q = (a2 - 3 * b) / 9; 193 | double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54; 194 | double r2 = r * r; 195 | double q3 = q * q * q; 196 | double A, B; 197 | if (r2 < q3) 198 | { 199 | double t = r / sqrt(q3); 200 | if (t < -1) 201 | t = -1; 202 | if (t > 1) 203 | t = 1; 204 | t = acos(t); 205 | a /= 3; 206 | q = -2 * sqrt(q); 207 | x[0] = q * cos(t / 3) - a; 208 | x[1] = q * cos((t + 2 * MSDF_PI) / 3) - a; 209 | x[2] = q * cos((t - 2 * MSDF_PI) / 3) - a; 210 | return 3; 211 | } 212 | else 213 | { 214 | A = -pow(fabs(r) + sqrt(r2 - q3), 1 / 3.); 215 | if (r < 0) 216 | A = -A; 217 | B = A == 0 ? 0 : q / A; 218 | a /= 3; 219 | x[0] = (A + B) - a; 220 | x[1] = -0.5 * (A + B) - a; 221 | x[2] = 0.5 * sqrt(3.) * (A - B); 222 | if (fabs(x[2]) < 1e-14) 223 | return 2; 224 | return 1; 225 | } 226 | } 227 | 228 | int msdf_solveCubic(double x[3], double a, double b, double c, double d) 229 | { 230 | if (fabs(a) < 1e-14) 231 | return msdf_solveQuadratic(x, b, c, d); 232 | 233 | return msdf_solveCubicNormed(x, b / a, c / a, d / a); 234 | } 235 | 236 | void msdf_getOrtho(msdf_Vec2 r, msdf_Vec2 const v, int polarity, int allow_zero) 237 | { 238 | double len = msdf_v2Leng(v); 239 | 240 | if (len == 0) 241 | { 242 | if (polarity) 243 | { 244 | r[0] = 0; 245 | r[1] = !allow_zero; 246 | } 247 | else 248 | { 249 | r[0] = 0; 250 | r[1] = -!allow_zero; 251 | } 252 | return; 253 | } 254 | 255 | if (polarity) 256 | { 257 | r[0] = -v[1] / len; 258 | r[1] = v[0] / len; 259 | } 260 | else 261 | { 262 | r[0] = v[1] / len; 263 | r[1] = -v[0] / len; 264 | } 265 | } 266 | 267 | int msdf_pixelClash(const msdf_Vec3 a, const msdf_Vec3 b, double threshold) 268 | { 269 | int aIn = (a[0] > .5f) + (a[1] > .5f) + (a[2] > .5f) >= 2; 270 | int bIn = (b[0] > .5f) + (b[1] > .5f) + (b[2] > .5f) >= 2; 271 | if (aIn != bIn) 272 | return 0; 273 | 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)) 274 | return 0; 275 | float aa, ab, ba, bb, ac, bc; 276 | if ((a[0] > .5f) != (b[0] > .5f) && (a[0] < .5f) != (b[0] < .5f)) 277 | { 278 | aa = a[0], ba = b[0]; 279 | if ((a[1] > .5f) != (b[1] > .5f) && (a[1] < .5f) != (b[1] < .5f)) 280 | { 281 | ab = a[1], bb = b[1]; 282 | ac = a[2], bc = b[2]; 283 | } 284 | else if ((a[2] > .5f) != (b[2] > .5f) && (a[2] < .5f) != (b[2] < .5f)) 285 | { 286 | ab = a[2], bb = b[2]; 287 | ac = a[1], bc = b[1]; 288 | } 289 | else 290 | { 291 | return 0; 292 | } 293 | } 294 | 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)) 295 | { 296 | aa = a[1], ba = b[1]; 297 | ab = a[2], bb = b[2]; 298 | ac = a[0], bc = b[0]; 299 | } 300 | else 301 | { 302 | return 0; 303 | } 304 | return (fabsf(aa - ba) >= threshold) && (fabsf(ab - bb) >= threshold) && fabsf(ac - .5f) >= fabsf(bc - .5f); 305 | } 306 | 307 | void msdf_mix(msdf_Vec2 r, msdf_Vec2 a, msdf_Vec2 b, double weight) 308 | { 309 | r[0] = (1 - weight) * a[0] + weight * b[0]; 310 | r[1] = (1 - weight) * a[1] + weight * b[1]; 311 | } 312 | 313 | void msdf_linearDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 314 | { 315 | r[0] = e->p[1][0] - e->p[0][0]; 316 | r[1] = e->p[1][1] - e->p[0][1]; 317 | } 318 | 319 | void msdf_quadraticDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 320 | { 321 | msdf_Vec2 a, b; 322 | msdf_v2Sub(a, e->p[1], e->p[0]); 323 | msdf_v2Sub(b, e->p[2], e->p[1]); 324 | msdf_mix(r, a, b, param); 325 | } 326 | 327 | void msdf_cubicDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 328 | { 329 | msdf_Vec2 a, b, c, d, t; 330 | msdf_v2Sub(a, e->p[1], e->p[0]); 331 | msdf_v2Sub(b, e->p[2], e->p[1]); 332 | msdf_mix(c, a, b, param); 333 | msdf_v2Sub(a, e->p[3], e->p[2]); 334 | msdf_mix(d, b, a, param); 335 | msdf_mix(t, c, d, param); 336 | 337 | if (!t[0] && !t[1]) 338 | { 339 | if (param == 0) 340 | { 341 | r[0] = e->p[2][0] - e->p[0][0]; 342 | r[1] = e->p[2][1] - e->p[0][1]; 343 | return; 344 | } 345 | if (param == 1) 346 | { 347 | r[0] = e->p[3][0] - e->p[1][0]; 348 | r[1] = e->p[3][1] - e->p[1][1]; 349 | return; 350 | } 351 | } 352 | 353 | r[0] = t[0]; 354 | r[1] = t[1]; 355 | } 356 | 357 | void msdf_direction(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 358 | { 359 | switch (e->type) 360 | { 361 | case STBTT_vline: 362 | { 363 | msdf_linearDirection(r, e, param); 364 | break; 365 | } 366 | case STBTT_vcurve: 367 | { 368 | msdf_quadraticDirection(r, e, param); 369 | break; 370 | } 371 | case STBTT_vcubic: 372 | { 373 | msdf_cubicDirection(r, e, param); 374 | break; 375 | } 376 | } 377 | } 378 | 379 | void msdf_linearPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 380 | { 381 | msdf_mix(r, e->p[0], e->p[1], param); 382 | } 383 | 384 | void msdf_quadraticPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 385 | { 386 | msdf_Vec2 a, b; 387 | msdf_mix(a, e->p[0], e->p[1], param); 388 | msdf_mix(b, e->p[1], e->p[2], param); 389 | msdf_mix(r, a, b, param); 390 | } 391 | 392 | void msdf_cubicPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 393 | { 394 | msdf_Vec2 p12, a, b, c, d; 395 | msdf_mix(p12, e->p[1], e->p[2], param); 396 | 397 | msdf_mix(a, e->p[0], e->p[1], param); 398 | msdf_mix(b, a, p12, param); 399 | 400 | msdf_mix(c, e->p[2], e->p[3], param); 401 | msdf_mix(d, p12, c, param); 402 | 403 | msdf_mix(r, b, d, param); 404 | } 405 | 406 | void msdf_point(msdf_Vec2 r, msdf_EdgeSegment *e, double param) 407 | { 408 | switch (e->type) 409 | { 410 | case STBTT_vline: 411 | { 412 | msdf_linearPoint(r, e, param); 413 | break; 414 | } 415 | case STBTT_vcurve: 416 | { 417 | msdf_quadraticPoint(r, e, param); 418 | break; 419 | } 420 | case STBTT_vcubic: 421 | { 422 | msdf_cubicPoint(r, e, param); 423 | break; 424 | } 425 | } 426 | } 427 | 428 | // linear edge signed distance 429 | msdf_signedDistance msdf_linearDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) 430 | { 431 | msdf_Vec2 aq, ab, eq; 432 | msdf_v2Sub(aq, origin, e->p[0]); 433 | msdf_v2Sub(ab, e->p[1], e->p[0]); 434 | *param = msdf_v2MulInner(aq, ab) / msdf_v2MulInner(ab, ab); 435 | msdf_v2Sub(eq, e->p[*param > .5], origin); 436 | 437 | double endpoint_distance = msdf_v2Leng(eq); 438 | if (*param > 0 && *param < 1) 439 | { 440 | msdf_Vec2 ab_ortho; 441 | msdf_getOrtho(ab_ortho, ab, 0, 0); 442 | double ortho_dist = msdf_v2MulInner(ab_ortho, aq); 443 | if (fabs(ortho_dist) < endpoint_distance) 444 | return (msdf_signedDistance){ortho_dist, 0}; 445 | } 446 | 447 | msdf_v2Norm(ab, ab); 448 | msdf_v2Norm(eq, eq); 449 | double dist = msdf_nonZeroSign(msdf_cross(aq, ab)) * endpoint_distance; 450 | double d = fabs(msdf_v2MulInner(ab, eq)); 451 | return (msdf_signedDistance){dist, d}; 452 | } 453 | 454 | // quadratic edge signed distance 455 | msdf_signedDistance msdf_quadraticDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) 456 | { 457 | msdf_Vec2 qa, ab, br; 458 | msdf_v2Sub(qa, e->p[0], origin); 459 | msdf_v2Sub(ab, e->p[1], e->p[0]); 460 | br[0] = e->p[0][0] + e->p[2][0] - e->p[1][0] - e->p[1][0]; 461 | br[1] = e->p[0][1] + e->p[2][1] - e->p[1][1] - e->p[1][1]; 462 | 463 | double a = msdf_v2MulInner(br, br); 464 | double b = 3 * msdf_v2MulInner(ab, br); 465 | double c = 2 * msdf_v2MulInner(ab, ab) + msdf_v2MulInner(qa, br); 466 | double d = msdf_v2MulInner(qa, ab); 467 | double t[3]; 468 | int solutions = msdf_solveCubic(t, a, b, c, d); 469 | 470 | // distance from a 471 | double min_distance = msdf_nonZeroSign(msdf_cross(ab, qa)) * msdf_v2Leng(qa); 472 | *param = -msdf_v2MulInner(qa, ab) / msdf_v2MulInner(ab, ab); 473 | { 474 | msdf_Vec2 a, b; 475 | msdf_v2Sub(a, e->p[2], e->p[1]); 476 | msdf_v2Sub(b, e->p[2], origin); 477 | 478 | // distance from b 479 | double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b); 480 | if (fabs(distance) < fabs(min_distance)) 481 | { 482 | min_distance = distance; 483 | 484 | msdf_v2Sub(a, origin, e->p[1]); 485 | msdf_v2Sub(b, e->p[2], e->p[1]); 486 | *param = msdf_v2MulInner(a, b) / msdf_v2MulInner(b, b); 487 | } 488 | } 489 | 490 | for (int i = 0; i < solutions; ++i) 491 | { 492 | if (t[i] > 0 && t[i] < 1) 493 | { 494 | // end_point = p[0]+2*t[i]*ab+t[i]*t[i]*br; 495 | msdf_Vec2 end_point, a, b; 496 | end_point[0] = e->p[0][0] + 2 * t[i] * ab[0] + t[i] * t[i] * br[0]; 497 | end_point[1] = e->p[0][1] + 2 * t[i] * ab[1] + t[i] * t[i] * br[1]; 498 | 499 | msdf_v2Sub(a, e->p[2], e->p[0]); 500 | msdf_v2Sub(b, end_point, origin); 501 | double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b); 502 | if (fabs(distance) <= fabs(min_distance)) 503 | { 504 | min_distance = distance; 505 | *param = t[i]; 506 | } 507 | } 508 | } 509 | 510 | if (*param >= 0 && *param <= 1) 511 | return (msdf_signedDistance){min_distance, 0}; 512 | 513 | msdf_Vec2 aa, bb; 514 | msdf_v2Norm(ab, ab); 515 | msdf_v2Norm(qa, qa); 516 | msdf_v2Sub(aa, e->p[2], e->p[1]); 517 | msdf_v2Norm(aa, aa); 518 | msdf_v2Sub(bb, e->p[2], origin); 519 | msdf_v2Norm(bb, bb); 520 | 521 | if (*param < .5) 522 | return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(ab, qa))}; 523 | else 524 | return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(aa, bb))}; 525 | } 526 | 527 | // cubic edge signed distance 528 | msdf_signedDistance msdf_cubicDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) 529 | { 530 | msdf_Vec2 qa, ab, br, as; 531 | msdf_v2Sub(qa, e->p[0], origin); 532 | msdf_v2Sub(ab, e->p[1], e->p[0]); 533 | br[0] = e->p[2][0] - e->p[1][0] - ab[0]; 534 | br[1] = e->p[2][1] - e->p[1][1] - ab[1]; 535 | as[0] = (e->p[3][0] - e->p[2][0]) - (e->p[2][0] - e->p[1][0]) - br[0]; 536 | as[1] = (e->p[3][1] - e->p[2][1]) - (e->p[2][1] - e->p[1][1]) - br[1]; 537 | 538 | msdf_Vec2 ep_dir; 539 | msdf_direction(ep_dir, e, 0); 540 | 541 | // distance from a 542 | double min_distance = msdf_nonZeroSign(msdf_cross(ep_dir, qa)) * msdf_v2Leng(qa); 543 | *param = -msdf_v2MulInner(qa, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir); 544 | { 545 | msdf_Vec2 a; 546 | msdf_v2Sub(a, e->p[3], origin); 547 | 548 | msdf_direction(ep_dir, e, 1); 549 | // distance from b 550 | double distance = msdf_nonZeroSign(msdf_cross(ep_dir, a)) * msdf_v2Leng(a); 551 | if (fabs(distance) < fabs(min_distance)) 552 | { 553 | min_distance = distance; 554 | 555 | a[0] = origin[0] + ep_dir[0] - e->p[3][0]; 556 | a[1] = origin[1] + ep_dir[1] - e->p[3][1]; 557 | *param = msdf_v2MulInner(a, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir); 558 | } 559 | } 560 | 561 | const int search_starts = 4; 562 | for (int i = 0; i <= search_starts; ++i) 563 | { 564 | double t = (double)i / search_starts; 565 | for (int step = 0;; ++step) 566 | { 567 | msdf_Vec2 qpt; 568 | msdf_point(qpt, e, t); 569 | msdf_v2Sub(qpt, qpt, origin); 570 | msdf_Vec2 d; 571 | msdf_direction(d, e, t); 572 | double distance = msdf_nonZeroSign(msdf_cross(d, qpt)) * msdf_v2Leng(qpt); 573 | if (fabs(distance) < fabs(min_distance)) 574 | { 575 | min_distance = distance; 576 | *param = t; 577 | } 578 | if (step == search_starts) 579 | break; 580 | 581 | msdf_Vec2 d1, d2; 582 | d1[0] = 3 * as[0] * t * t + 6 * br[0] * t + 3 * ab[0]; 583 | d1[1] = 3 * as[1] * t * t + 6 * br[1] * t + 3 * ab[1]; 584 | d2[0] = 6 * as[0] * t + 6 * br[0]; 585 | d2[1] = 6 * as[1] * t + 6 * br[1]; 586 | 587 | t -= msdf_v2MulInner(qpt, d1) / (msdf_v2MulInner(d1, d1) + msdf_v2MulInner(qpt, d2)); 588 | if (t < 0 || t > 1) 589 | break; 590 | } 591 | } 592 | 593 | if (*param >= 0 && *param <= 1) 594 | return (msdf_signedDistance){min_distance, 0}; 595 | 596 | msdf_Vec2 d0, d1; 597 | msdf_direction(d0, e, 0); 598 | msdf_direction(d1, e, 1); 599 | msdf_v2Norm(d0, d0); 600 | msdf_v2Norm(d1, d1); 601 | msdf_v2Norm(qa, qa); 602 | msdf_Vec2 a; 603 | msdf_v2Sub(a, e->p[3], origin); 604 | msdf_v2Norm(a, a); 605 | 606 | if (*param < .5) 607 | return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d0, qa))}; 608 | else 609 | return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d1, a))}; 610 | } 611 | 612 | void msdf_distToPseudo(msdf_signedDistance *distance, msdf_Vec2 origin, double param, msdf_EdgeSegment *e) { 613 | if (param < 0) { 614 | msdf_Vec2 dir, p; 615 | msdf_direction(dir, e, 0); 616 | msdf_v2Norm(dir, dir); 617 | msdf_Vec2 aq = {origin[0], origin[1]}; 618 | msdf_point(p, e, 0); 619 | msdf_v2Sub(aq, origin, p); 620 | double ts = msdf_v2MulInner(aq, dir); 621 | if (ts < 0) { 622 | double pseudo_dist = msdf_cross(aq, dir); 623 | if (fabs(pseudo_dist) <= fabs(distance->dist)) { 624 | distance->dist = pseudo_dist; 625 | distance->d = 0; 626 | } 627 | } 628 | } else if (param > 1) { 629 | msdf_Vec2 dir, p; 630 | msdf_direction(dir, e, 1); 631 | msdf_v2Norm(dir, dir); 632 | msdf_Vec2 bq = {origin[0], origin[1]}; 633 | msdf_point(p, e, 1); 634 | msdf_v2Sub(bq, origin, p); 635 | double ts = msdf_v2MulInner(bq, dir); 636 | if (ts > 0) { 637 | double pseudo_dist = msdf_cross(bq, dir); 638 | if (fabs(pseudo_dist) <= fabs(distance->dist)) { 639 | distance->dist = pseudo_dist; 640 | distance->d = 0; 641 | } 642 | } 643 | } 644 | } 645 | 646 | int msdf_signedCompare(msdf_signedDistance a, msdf_signedDistance b) { 647 | return fabs(a.dist) < fabs(b.dist) || (fabs(a.dist) == fabs(b.dist) && a.d < b.d); 648 | } 649 | 650 | int msdf_isCorner(msdf_Vec2 a, msdf_Vec2 b, double threshold) { 651 | return msdf_v2MulInner(a, b) <= 0 || fabs(msdf_cross(a, b)) > threshold; 652 | } 653 | 654 | void msdf_switchColor(msdf_edgeColor *color, unsigned long long *seed, msdf_edgeColor banned) 655 | { 656 | msdf_edgeColor combined = *color & banned; 657 | if (combined == msdf_edgeColor_red || combined == msdf_edgeColor_green || combined == msdf_edgeColor_blue) { 658 | *color = (msdf_edgeColor)(combined ^ msdf_edgeColor_white); 659 | return; 660 | } 661 | 662 | if (*color == msdf_edgeColor_black || *color == msdf_edgeColor_white) { 663 | static const msdf_edgeColor start[3] = {msdf_edgeColor_cyan, msdf_edgeColor_magenta, msdf_edgeColor_yellow}; 664 | *color = start[*seed & 3]; 665 | *seed /= 3; 666 | return; 667 | } 668 | 669 | int shifted = *color << (1 + (*seed & 1)); 670 | *color = (msdf_edgeColor)((shifted | shifted >> 3) & msdf_edgeColor_white); 671 | *seed >>= 1; 672 | } 673 | 674 | void msdf_linearSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) 675 | { 676 | msdf_Vec2 p; 677 | 678 | msdf_point(p, e, 1 / 3.0); 679 | memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); 680 | memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); 681 | p1->color = e->color; 682 | 683 | msdf_point(p, e, 1 / 3.0); 684 | memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); 685 | msdf_point(p, e, 2 / 3.0); 686 | memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); 687 | p2->color = e->color; 688 | 689 | msdf_point(p, e, 2 / 3.0); 690 | memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); 691 | msdf_point(p, e, 2 / 3.0); 692 | memcpy(&p3->p[1], e->p[1], sizeof(msdf_Vec2)); 693 | p3->color = e->color; 694 | } 695 | 696 | void msdf_quadraticSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) 697 | { 698 | msdf_Vec2 p, a, b; 699 | 700 | memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); 701 | msdf_mix(p, e->p[0], e->p[1], 1 / 3.0); 702 | memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); 703 | msdf_point(p, e, 1 / 3.0); 704 | memcpy(&p1->p[2], p, sizeof(msdf_Vec2)); 705 | p1->color = e->color; 706 | 707 | msdf_point(p, e, 1 / 3.0); 708 | memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); 709 | msdf_mix(a, e->p[0], e->p[1], 5 / 9.0); 710 | msdf_mix(b, e->p[1], e->p[2], 4 / 9.0); 711 | msdf_mix(p, a, b, 0.5); 712 | memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); 713 | msdf_point(p, e, 2 / 3.0); 714 | memcpy(&p2->p[2], p, sizeof(msdf_Vec2)); 715 | p2->color = e->color; 716 | 717 | msdf_point(p, e, 2 / 3.0); 718 | memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); 719 | msdf_mix(p, e->p[1], e->p[2], 2 / 3.0); 720 | memcpy(&p3->p[1], p, sizeof(msdf_Vec2)); 721 | memcpy(&p3->p[2], e->p[2], sizeof(msdf_Vec2)); 722 | p3->color = e->color; 723 | } 724 | 725 | void msdf_cubicSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) 726 | { 727 | msdf_Vec2 p, a, b, c, d; 728 | 729 | memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); // p1 0 730 | if (e->p[0] == e->p[1]) { 731 | memcpy(&p1->p[1], e->p[0], sizeof(msdf_Vec2)); // ? p1 1 732 | } else { 733 | msdf_mix(p, e->p[0], e->p[1], 1 / 3.0); 734 | memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); // ? p1 1 735 | } 736 | msdf_mix(a, e->p[0], e->p[1], 1 / 3.0); 737 | msdf_mix(b, e->p[1], e->p[2], 1 / 3.0); 738 | msdf_mix(p, a, b, 1 / 3.0); 739 | memcpy(&p1->p[2], p, sizeof(msdf_Vec2)); // p1 2 740 | msdf_point(p, e, 1 / 3.0); 741 | memcpy(&p1->p[3], p, sizeof(msdf_Vec2)); // p1 3 742 | p1->color = e->color; 743 | 744 | msdf_point(p, e, 1 / 3.0); 745 | memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); // p2 0 746 | msdf_mix(a, e->p[0], e->p[1], 1 / 3.0); 747 | msdf_mix(b, e->p[1], e->p[2], 1 / 3.0); 748 | msdf_mix(c, a, b, 1 / 3.0); 749 | msdf_mix(a, e->p[1], e->p[2], 1 / 3.0); 750 | msdf_mix(b, e->p[2], e->p[3], 1 / 3.0); 751 | msdf_mix(d, a, b, 1 / 3.0); 752 | msdf_mix(p, c, d, 2 / 3.0); 753 | memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); // p2 1 754 | msdf_mix(a, e->p[0], e->p[1], 2 / 3.0); 755 | msdf_mix(b, e->p[1], e->p[2], 2 / 3.0); 756 | msdf_mix(c, a, b, 2 / 3.0); 757 | msdf_mix(a, e->p[1], e->p[2], 2 / 3.0); 758 | msdf_mix(b, e->p[2], e->p[3], 2 / 3.0); 759 | msdf_mix(d, a, b, 2 / 3.0); 760 | msdf_mix(p, c, d, 1 / 3.0); 761 | memcpy(&p2->p[2], p, sizeof(msdf_Vec2)); // p2 2 762 | msdf_point(p, e, 2 / 3.0); 763 | memcpy(&p2->p[3], p, sizeof(msdf_Vec2)); // p2 3 764 | p2->color = e->color; 765 | 766 | msdf_point(p, e, 2 / 3.0); 767 | memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); // p3 0 768 | 769 | msdf_mix(a, e->p[1], e->p[2], 2 / 3.0); 770 | msdf_mix(b, e->p[2], e->p[3], 2 / 3.0); 771 | msdf_mix(p, a, b, 2 / 3.0); 772 | memcpy(&p3->p[1], p, sizeof(msdf_Vec2)); // p3 1 773 | 774 | if (e->p[2] == e->p[3]) { 775 | memcpy(&p3->p[2], e->p[3], sizeof(msdf_Vec2)); // ? p3 2 776 | } else { 777 | msdf_mix(p, e->p[2], e->p[3], 2 / 3.0); 778 | memcpy(&p3->p[2], p, sizeof(msdf_Vec2)); // ? p3 2 779 | } 780 | 781 | memcpy(&p3->p[3], e->p[3], sizeof(msdf_Vec2)); // p3 3 782 | } 783 | 784 | void msdf_edgeSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) 785 | { 786 | switch (e->type) { 787 | case STBTT_vline: { 788 | msdf_linearSplit(e, p1, p2, p3); 789 | break; 790 | } 791 | case STBTT_vcurve: { 792 | msdf_quadraticSplit(e, p1, p2, p3); 793 | break; 794 | } 795 | case STBTT_vcubic: { 796 | msdf_cubicSplit(e, p1, p2, p3); 797 | break; 798 | } 799 | } 800 | } 801 | 802 | double msdf_shoelace(const msdf_Vec2 a, const msdf_Vec2 b) 803 | { 804 | return (b[0] - a[0]) * (a[1] + b[1]); 805 | } 806 | 807 | 808 | void* msdf__alloc(size_t size, void* ctx) { 809 | return malloc(size); 810 | } 811 | void msdf__free(void* ptr, void* ctx) { 812 | free(ptr); 813 | } 814 | 815 | int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc) { 816 | msdf_AllocCtx allocCtx; 817 | 818 | if (alloc) { 819 | allocCtx = *alloc; 820 | } else { 821 | allocCtx.alloc = msdf__alloc; 822 | allocCtx.free = msdf__free; 823 | allocCtx.ctx = NULL; 824 | } 825 | 826 | //char f = c; 827 | // Funit to pixel scale 828 | //float scale = stbtt_ScaleForMappingEmToPixels(font, h); 829 | int glyphIdx = stbttGlyphIndex; 830 | // get glyph bounding box (scaled later) 831 | int ix0, iy0, ix1, iy1; 832 | float xoff = .0, yoff = .0; 833 | stbtt_GetGlyphBox(font, glyphIdx, &ix0, &iy0, &ix1, &iy1); 834 | 835 | float glyphWidth = ix1 - ix0; 836 | float glyphHeight = iy1 - iy0; 837 | float borderWidthF32 = borderWidth; 838 | float wF32 = ceilf(glyphWidth * scale); 839 | float hF32 = ceilf(glyphHeight * scale); 840 | wF32 += 2.f * borderWidth; 841 | hF32 += 2.f * borderWidth; 842 | int w = wF32; 843 | int h = hF32; 844 | 845 | float* bitmap = (float*) allocCtx.alloc(w * h * 3 * sizeof(float), allocCtx.ctx); 846 | memset(bitmap, 0x0, w * h * 3 * sizeof(float)); 847 | 848 | // em scale 849 | //scale = stbtt_ScaleForMappingEmToPixels(font, h); 850 | 851 | //if (autofit) 852 | //{ 853 | 854 | // calculate new height 855 | //float newh = h + (h - (iy1 - iy0) * scale) - 4; 856 | 857 | // calculate new scale 858 | // see 'stbtt_ScaleForMappingEmToPixels' in stb_truetype.h 859 | //uint8_t *p = font->data + font->head + 18; 860 | //int unitsPerEm = p[0] * 256 + p[1]; 861 | //scale = ((float)h) / ((float)unitsPerEm); 862 | 863 | // make sure we are centered 864 | //xoff = .0; 865 | //yoff = .0; 866 | //} 867 | 868 | // get left offset and advance 869 | //int left_bearing, advance; 870 | //stbtt_GetGlyphHMetrics(font, glyphIdx, &advance, &left_bearing); 871 | //left_bearing *= scale; 872 | 873 | int32_t glyphOrgX = ix0 * scale; 874 | int32_t glyphOrgY = iy0 * scale; 875 | 876 | int32_t borderWidthX = borderWidth; 877 | int32_t borderWidthY = borderWidth; 878 | 879 | // org 8,8 880 | // - bord 4,4 881 | // erg: 4,4 882 | 883 | // calculate offset for centering glyph on bitmap 884 | 885 | //glyphOrgX >= 2 ? (glyphOrgX) : (); 886 | int32_t translateX = (glyphOrgX - borderWidth);//borderWidth + ((w / 2) - ((ix1 - ix0) * scale) / 2 - leftBearingScaled); 887 | int32_t translateY = (glyphOrgY - borderWidth);//borderWidth + ((h / 2) - ((iy1 - iy0) * scale) / 2 - ((float) iy0) * scale); 888 | //translateY = 8; 889 | // set the glyph metrics 890 | // (pre-scale them) 891 | 892 | #if 0 893 | if (metrics) 894 | { 895 | metrics->left_bearing = left_bearing; 896 | metrics->advance = advance * scale; 897 | metrics->ix0 = ix0 * scale; 898 | metrics->ix1 = ix1 * scale; 899 | metrics->iy0 = iy0 * scale; 900 | metrics->iy1 = iy1 * scale; 901 | metrics->glyphIdx = glyphIdx; 902 | } 903 | #endif 904 | 905 | stbtt_vertex *verts; 906 | int num_verts = stbtt_GetGlyphShape(font, glyphIdx, &verts); 907 | 908 | // figure out how many contours exist 909 | int contour_count = 0; 910 | for (int i = 0; i < num_verts; i++) { 911 | if (verts[i].type == STBTT_vmove) { 912 | contour_count++; 913 | } 914 | } 915 | 916 | if (contour_count == 0) { 917 | return 0; 918 | } 919 | 920 | // determin what vertices belong to what contours 921 | typedef struct { 922 | size_t start, end; 923 | } msdf_Indices; 924 | msdf_Indices *contours = allocCtx.alloc(sizeof(msdf_Indices) * contour_count, allocCtx.ctx); 925 | int j = 0; 926 | for (int i = 0; i <= num_verts; i++) { 927 | if (verts[i].type == STBTT_vmove) { 928 | if (i > 0) { 929 | contours[j].end = i; 930 | j++; 931 | } 932 | 933 | contours[j].start = i; 934 | } else if (i >= num_verts) { 935 | contours[j].end = i; 936 | } 937 | } 938 | 939 | typedef struct { 940 | msdf_signedDistance min_distance; 941 | msdf_EdgeSegment *near_edge; 942 | double near_param; 943 | } msdf_EdgePoint; 944 | 945 | typedef struct { 946 | msdf_EdgeSegment *edges; 947 | size_t edge_count; 948 | } msdf_Contour; 949 | 950 | // process verts into series of contour-specific edge lists 951 | msdf_Vec2 initial = {0, 0}; // fix this? 952 | msdf_Contour *contour_data = allocCtx.alloc(sizeof(msdf_Contour) * contour_count, allocCtx.ctx); 953 | double cscale = 64.0; 954 | for (int i = 0; i < contour_count; i++) { 955 | size_t count = contours[i].end - contours[i].start; 956 | contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * count, allocCtx.ctx); 957 | contour_data[i].edge_count = 0; 958 | 959 | size_t k = 0; 960 | for (int j = contours[i].start; j < contours[i].end; j++) { 961 | msdf_EdgeSegment *e = &contour_data[i].edges[k]; 962 | stbtt_vertex *v = &verts[j]; 963 | e->type = v->type; 964 | e->color = msdf_edgeColor_white; 965 | 966 | switch (v->type) { 967 | case STBTT_vmove: { 968 | msdf_Vec2 p = {v->x / cscale, v->y / cscale}; 969 | memcpy(&initial, p, sizeof(msdf_Vec2)); 970 | break; 971 | } 972 | 973 | case STBTT_vline: { 974 | msdf_Vec2 p = {v->x / cscale, v->y / cscale}; 975 | memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); 976 | memcpy(&e->p[1], p, sizeof(msdf_Vec2)); 977 | memcpy(&initial, p, sizeof(msdf_Vec2)); 978 | contour_data[i].edge_count++; 979 | k++; 980 | break; 981 | } 982 | 983 | case STBTT_vcurve: { 984 | msdf_Vec2 p = {v->x / cscale, v->y / cscale}; 985 | msdf_Vec2 c = {v->cx / cscale, v->cy / cscale}; 986 | memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); 987 | memcpy(&e->p[1], c, sizeof(msdf_Vec2)); 988 | memcpy(&e->p[2], p, sizeof(msdf_Vec2)); 989 | 990 | if ((e->p[0][0] == e->p[1][0] && e->p[0][1] == e->p[1][1]) || 991 | (e->p[1][0] == e->p[2][0] && e->p[1][1] == e->p[2][1])) 992 | { 993 | e->p[1][0] = 0.5 * (e->p[0][0] + e->p[2][0]); 994 | e->p[1][1] = 0.5 * (e->p[0][1] + e->p[2][1]); 995 | } 996 | 997 | memcpy(&initial, p, sizeof(msdf_Vec2)); 998 | contour_data[i].edge_count++; 999 | k++; 1000 | break; 1001 | } 1002 | 1003 | case STBTT_vcubic: { 1004 | msdf_Vec2 p = {v->x / cscale, v->y / cscale}; 1005 | msdf_Vec2 c = {v->cx / cscale, v->cy / cscale}; 1006 | msdf_Vec2 c1 = {v->cx1 / cscale, v->cy1 / cscale}; 1007 | memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); 1008 | memcpy(&e->p[1], c, sizeof(msdf_Vec2)); 1009 | memcpy(&e->p[2], c1, sizeof(msdf_Vec2)); 1010 | memcpy(&e->p[3], p, sizeof(msdf_Vec2)); 1011 | memcpy(&initial, p, sizeof(msdf_Vec2)); 1012 | contour_data[i].edge_count++; 1013 | k++; 1014 | break; 1015 | } 1016 | } 1017 | } 1018 | } 1019 | 1020 | // calculate edge-colors 1021 | uint64_t seed = 0; 1022 | double anglethreshold = 3.0; 1023 | double crossthreshold = sin(anglethreshold); 1024 | size_t corner_count = 0; 1025 | for (int i = 0; i < contour_count; ++i) { 1026 | for (int j = 0; j < contour_data[i].edge_count; ++j) { 1027 | corner_count++; 1028 | } 1029 | } 1030 | 1031 | int *corners = allocCtx.alloc(sizeof(int) * corner_count, allocCtx.ctx); 1032 | int cornerIndex = 0; 1033 | for (int i = 0; i < contour_count; ++i) { 1034 | 1035 | if (contour_data[i].edge_count > 0) { 1036 | msdf_Vec2 prev_dir, dir; 1037 | msdf_direction(prev_dir, &contour_data[i].edges[contour_data[i].edge_count - 1], 1); 1038 | 1039 | int index = 0; 1040 | for (int j = 0; j < contour_data[i].edge_count; ++j, ++index) { 1041 | msdf_EdgeSegment *e = &contour_data[i].edges[j]; 1042 | msdf_direction(dir, e, 0); 1043 | msdf_v2Norm(dir, dir); 1044 | msdf_v2Norm(prev_dir, prev_dir); 1045 | if (msdf_isCorner(prev_dir, dir, crossthreshold)) { 1046 | corners[cornerIndex++] = index; 1047 | } 1048 | msdf_direction(prev_dir, e, 1); 1049 | } 1050 | } 1051 | 1052 | if (cornerIndex == 0) { 1053 | for (int j = 0; j < contour_data[i].edge_count; ++j) { 1054 | contour_data[i].edges[j].color = msdf_edgeColor_white; 1055 | } 1056 | } else if (cornerIndex == 1) { 1057 | msdf_edgeColor colors[3] = {msdf_edgeColor_white, msdf_edgeColor_white}; 1058 | msdf_switchColor(&colors[0], &seed, msdf_edgeColor_black); 1059 | colors[2] = colors[0]; 1060 | msdf_switchColor(&colors[2], &seed, msdf_edgeColor_black); 1061 | 1062 | int corner = corners[0]; 1063 | if (contour_data[i].edge_count >= 3) { 1064 | int m = contour_data[i].edge_count; 1065 | for (int j = 0; j < m; ++j) { 1066 | contour_data[i].edges[(corner + j) % m].color = (colors + 1)[(int)(3 + 2.875 * i / (m - 1) - 1.4375 + .5) - 3]; 1067 | } 1068 | } else if (contour_data[i].edge_count >= 1) { 1069 | msdf_EdgeSegment *parts[7] = {NULL}; 1070 | msdf_edgeSplit(&contour_data[i].edges[0], parts[0 + 3 * corner], parts[1 + 3 * corner], parts[2 + 3 * corner]); 1071 | if (contour_data[i].edge_count >= 2) { 1072 | msdf_edgeSplit(&contour_data[i].edges[1], parts[3 - 3 * corner], parts[4 - 3 * corner], parts[5 - 3 * corner]); 1073 | parts[0]->color = parts[1]->color = colors[0]; 1074 | parts[2]->color = parts[3]->color = colors[1]; 1075 | parts[4]->color = parts[5]->color = colors[2]; 1076 | } else { 1077 | parts[0]->color = colors[0]; 1078 | parts[1]->color = colors[1]; 1079 | parts[2]->color = colors[2]; 1080 | } 1081 | if (allocCtx.free) { 1082 | allocCtx.free(contour_data[i].edges, allocCtx.ctx); 1083 | } 1084 | contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 7, allocCtx.ctx); 1085 | contour_data[i].edge_count = 0; 1086 | int index = 0; 1087 | for (int j = 0; parts[j]; ++j) { 1088 | memcpy(&contour_data[i].edges[index++], &parts[j], sizeof(msdf_EdgeSegment)); 1089 | contour_data[i].edge_count++; 1090 | } 1091 | } 1092 | } else { 1093 | int spline = 0; 1094 | int start = corners[0]; 1095 | int m = contour_data[i].edge_count; 1096 | msdf_edgeColor color = msdf_edgeColor_white; 1097 | msdf_switchColor(&color, &seed, msdf_edgeColor_black); 1098 | msdf_edgeColor initial_color = color; 1099 | for (int j = 0; j < m; ++j) { 1100 | int index = (start + j) % m; 1101 | if (spline + 1 < corner_count && corners[spline + 1] == index) { 1102 | ++spline; 1103 | 1104 | msdf_edgeColor s = (msdf_edgeColor)((spline == corner_count - 1) * initial_color); 1105 | msdf_switchColor(&color, &seed, s); 1106 | } 1107 | contour_data[i].edges[index].color = color; 1108 | } 1109 | } 1110 | } 1111 | 1112 | if (allocCtx.free) { 1113 | allocCtx.free(corners, allocCtx.ctx); 1114 | } 1115 | 1116 | // normalize shape 1117 | for (int i = 0; i < contour_count; i++) { 1118 | if (contour_data[i].edge_count == 1) { 1119 | msdf_EdgeSegment *parts[3] = {0}; 1120 | msdf_edgeSplit(&contour_data[i].edges[0], parts[0], parts[1], parts[2]); 1121 | if (allocCtx.free) { 1122 | allocCtx.free(contour_data[i].edges, allocCtx.ctx); 1123 | } 1124 | contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 3, allocCtx.ctx); 1125 | contour_data[i].edge_count = 3; 1126 | for (int j = 0; j < 3; j++) { 1127 | memcpy(&contour_data[i].edges[j], &parts[j], sizeof(msdf_EdgeSegment)); 1128 | } 1129 | } 1130 | } 1131 | 1132 | // calculate windings 1133 | int *windings = allocCtx.alloc(sizeof(int) * contour_count, allocCtx.ctx); 1134 | for (int i = 0; i < contour_count; i++) { 1135 | size_t edge_count = contour_data[i].edge_count; 1136 | if (edge_count == 0) { 1137 | windings[i] = 0; 1138 | continue; 1139 | } 1140 | 1141 | double total = 0; 1142 | 1143 | if (edge_count == 1) { 1144 | msdf_Vec2 a, b, c; 1145 | msdf_point(a, &contour_data[i].edges[0], 0); 1146 | msdf_point(b, &contour_data[i].edges[0], 1 / 3.0); 1147 | msdf_point(c, &contour_data[i].edges[0], 2 / 3.0); 1148 | total += msdf_shoelace(a, b); 1149 | total += msdf_shoelace(b, c); 1150 | total += msdf_shoelace(c, a); 1151 | } else if (edge_count == 2) { 1152 | msdf_Vec2 a, b, c, d; 1153 | msdf_point(a, &contour_data[i].edges[0], 0); 1154 | msdf_point(b, &contour_data[i].edges[0], 0.5); 1155 | msdf_point(c, &contour_data[i].edges[1], 0); 1156 | msdf_point(d, &contour_data[i].edges[1], 0.5); 1157 | total += msdf_shoelace(a, b); 1158 | total += msdf_shoelace(b, c); 1159 | total += msdf_shoelace(c, d); 1160 | total += msdf_shoelace(d, a); 1161 | } else { 1162 | msdf_Vec2 prev; 1163 | msdf_point(prev, &contour_data[i].edges[edge_count - 1], 0); 1164 | for (int j = 0; j < edge_count; j++) { 1165 | msdf_Vec2 cur; 1166 | msdf_point(cur, &contour_data[i].edges[j], 0); 1167 | total += msdf_shoelace(prev, cur); 1168 | memcpy(prev, cur, sizeof(msdf_Vec2)); 1169 | } 1170 | } 1171 | 1172 | windings[i] = ((0 < total) - (total < 0)); // sign 1173 | } 1174 | 1175 | typedef struct { 1176 | double r, g, b; 1177 | double med; 1178 | } msdf_MultiDistance; 1179 | 1180 | msdf_MultiDistance *contour_sd; 1181 | contour_sd = allocCtx.alloc(sizeof(msdf_MultiDistance) * contour_count, allocCtx.ctx); 1182 | 1183 | float invRange = 1.0 / range; 1184 | 1185 | for (int y = 0; y < h; ++y) { 1186 | int row = iy0 > iy1 ? y : h - y - 1; 1187 | for (int x = 0; x < w; ++x) { 1188 | float a64 = 64.0; 1189 | msdf_Vec2 p = {(translateX + x + xoff) / (scale * a64), (translateY + y + yoff) / (scale * a64)}; 1190 | //p[0] = ; 1191 | //p[1] = ; 1192 | msdf_EdgePoint sr, sg, sb; 1193 | sr.near_edge = sg.near_edge = sb.near_edge = NULL; 1194 | sr.near_param = sg.near_param = sb.near_param = 0; 1195 | sr.min_distance.dist = sg.min_distance.dist = sb.min_distance.dist = MSDF_INF; 1196 | sr.min_distance.d = sg.min_distance.d = sb.min_distance.d = 1; 1197 | double d = fabs(MSDF_INF); 1198 | double neg_dist = -MSDF_INF; 1199 | double pos_dist = MSDF_INF; 1200 | int winding = 0; 1201 | 1202 | // calculate distance to contours from current point (and if its inside or outside of the shape?) 1203 | for (int j = 0; j < contour_count; ++j) { 1204 | msdf_EdgePoint r, g, b; 1205 | r.near_edge = g.near_edge = b.near_edge = NULL; 1206 | r.near_param = g.near_param = b.near_param = 0; 1207 | r.min_distance.dist = g.min_distance.dist = b.min_distance.dist = MSDF_INF; 1208 | r.min_distance.d = g.min_distance.d = b.min_distance.d = 1; 1209 | 1210 | for (int k = 0; k < contour_data[j].edge_count; ++k) { 1211 | msdf_EdgeSegment *e = &contour_data[j].edges[k]; 1212 | double param; 1213 | msdf_signedDistance distance; 1214 | distance.dist = MSDF_INF; 1215 | distance.d = 1; 1216 | 1217 | // calculate signed distance 1218 | switch (e->type) { 1219 | case STBTT_vline: { 1220 | distance = msdf_linearDist(e, p, ¶m); 1221 | break; 1222 | } 1223 | case STBTT_vcurve: { 1224 | distance = msdf_quadraticDist(e, p, ¶m); 1225 | break; 1226 | } 1227 | case STBTT_vcubic: { 1228 | distance = msdf_cubicDist(e, p, ¶m); 1229 | break; 1230 | } 1231 | } 1232 | 1233 | if (e->color & msdf_edgeColor_red && msdf_signedCompare(distance, r.min_distance)) { 1234 | r.min_distance = distance; 1235 | r.near_edge = e; 1236 | r.near_param = param; 1237 | } 1238 | if (e->color & msdf_edgeColor_green && msdf_signedCompare(distance, g.min_distance)) { 1239 | g.min_distance = distance; 1240 | g.near_edge = e; 1241 | g.near_param = param; 1242 | } 1243 | if (e->color & msdf_edgeColor_blue && msdf_signedCompare(distance, b.min_distance)) { 1244 | b.min_distance = distance; 1245 | b.near_edge = e; 1246 | b.near_param = param; 1247 | } 1248 | } 1249 | 1250 | if (msdf_signedCompare(r.min_distance, sr.min_distance)) { 1251 | sr = r; 1252 | } 1253 | if (msdf_signedCompare(g.min_distance, sg.min_distance)) { 1254 | sg = g; 1255 | } 1256 | if (msdf_signedCompare(b.min_distance, sb.min_distance)) { 1257 | sb = b; 1258 | } 1259 | 1260 | double med_min_dist = fabs(msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist)); 1261 | 1262 | if (med_min_dist < d) { 1263 | d = med_min_dist; 1264 | winding = -windings[j]; 1265 | } 1266 | 1267 | if (r.near_edge) { 1268 | msdf_distToPseudo(&r.min_distance, p, r.near_param, r.near_edge); 1269 | } 1270 | if (g.near_edge) { 1271 | msdf_distToPseudo(&g.min_distance, p, g.near_param, g.near_edge); 1272 | } 1273 | if (b.near_edge) { 1274 | msdf_distToPseudo(&b.min_distance, p, b.near_param, b.near_edge); 1275 | } 1276 | 1277 | med_min_dist = msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist); 1278 | contour_sd[j].r = r.min_distance.dist; 1279 | contour_sd[j].g = g.min_distance.dist; 1280 | contour_sd[j].b = b.min_distance.dist; 1281 | contour_sd[j].med = med_min_dist; 1282 | 1283 | if (windings[j] > 0 && med_min_dist >= 0 && fabs(med_min_dist) < fabs(pos_dist)) { 1284 | pos_dist = med_min_dist; 1285 | } 1286 | if (windings[j] < 0 && med_min_dist <= 0 && fabs(med_min_dist) < fabs(neg_dist)) { 1287 | neg_dist = med_min_dist; 1288 | } 1289 | } 1290 | 1291 | if (sr.near_edge) { 1292 | msdf_distToPseudo(&sr.min_distance, p, sr.near_param, sr.near_edge); 1293 | } 1294 | if (sg.near_edge) { 1295 | msdf_distToPseudo(&sg.min_distance, p, sg.near_param, sg.near_edge); 1296 | } 1297 | if (sb.near_edge) { 1298 | msdf_distToPseudo(&sb.min_distance, p, sb.near_param, sb.near_edge); 1299 | } 1300 | 1301 | msdf_MultiDistance msd; 1302 | msd.r = msd.g = msd.b = msd.med = MSDF_INF; 1303 | if (pos_dist >= 0 && fabs(pos_dist) <= fabs(neg_dist)) { 1304 | msd.med = MSDF_INF; 1305 | winding = 1; 1306 | for (int i = 0; i < contour_count; ++i) { 1307 | if (windings[i] > 0 && contour_sd[i].med > msd.med && fabs(contour_sd[i].med) < fabs(neg_dist)) { 1308 | msd = contour_sd[i]; 1309 | } 1310 | } 1311 | } else if (neg_dist <= 0 && fabs(neg_dist) <= fabs(pos_dist)) { 1312 | msd.med = -MSDF_INF; 1313 | winding = -1; 1314 | for (int i = 0; i < contour_count; ++i) { 1315 | if (windings[i] < 0 && contour_sd[i].med < msd.med && fabs(contour_sd[i].med) < fabs(pos_dist)) { 1316 | msd = contour_sd[i]; 1317 | } 1318 | } 1319 | } 1320 | 1321 | for (int i = 0; i < contour_count; ++i) { 1322 | if (windings[i] != winding && fabs(contour_sd[i].med) < fabs(msd.med)) { 1323 | msd = contour_sd[i]; 1324 | } 1325 | } 1326 | 1327 | if (msdf_median(sr.min_distance.dist, sg.min_distance.dist, sb.min_distance.dist) == msd.med) { 1328 | msd.r = sr.min_distance.dist; 1329 | msd.g = sg.min_distance.dist; 1330 | msd.b = sb.min_distance.dist; 1331 | } 1332 | 1333 | size_t index = 3 * ((row * w) + x); 1334 | 1335 | float mr = ((float)msd.r) * invRange + 0.5f; 1336 | float mg = ((float)msd.g) * invRange + 0.5f; 1337 | float mb = ((float)msd.b) * invRange + 0.5f; 1338 | bitmap[index + 0] = mr; 1339 | bitmap[index + 1] = mg; 1340 | bitmap[index + 2] = mb; 1341 | 1342 | } 1343 | } 1344 | 1345 | if (allocCtx.free) { 1346 | for (int i = 0; i < contour_count; i++) { 1347 | allocCtx.free(contour_data[i].edges, allocCtx.ctx); 1348 | } 1349 | allocCtx.free(contour_data, allocCtx.ctx); 1350 | allocCtx.free(contour_sd, allocCtx.ctx); 1351 | allocCtx.free(contours, allocCtx.ctx); 1352 | allocCtx.free(windings, allocCtx.ctx); 1353 | allocCtx.free(verts, allocCtx.ctx); 1354 | } 1355 | 1356 | // msdf error correction 1357 | typedef struct { 1358 | int x, y; 1359 | } msdf_Clash; 1360 | msdf_Clash *clashes = allocCtx.alloc(sizeof(msdf_Clash) * w * h, allocCtx.ctx); 1361 | size_t cindex = 0; 1362 | 1363 | double tx = MSDF_EDGE_THRESHOLD / (scale * range); 1364 | double ty = MSDF_EDGE_THRESHOLD / (scale * range); 1365 | for (int y = 0; y < h; y++) { 1366 | for (int x = 0; x < w; x++) { 1367 | 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))) { 1368 | clashes[cindex].x = x; 1369 | clashes[cindex++].y = y; 1370 | } 1371 | } 1372 | } 1373 | 1374 | for (int i = 0; i < cindex; i++) { 1375 | size_t index = 3 * ((clashes[i].y * w) + clashes[i].x); 1376 | float med = msdf_median(bitmap[index], bitmap[index + 1], bitmap[index + 2]); 1377 | bitmap[index + 0] = med; 1378 | bitmap[index + 1] = med; 1379 | bitmap[index + 2] = med; 1380 | } 1381 | 1382 | if (allocCtx.free) { 1383 | allocCtx.free(clashes, allocCtx.ctx); 1384 | } 1385 | 1386 | result->glyphIdx = glyphIdx; 1387 | result->rgb = bitmap; 1388 | result->width = w; 1389 | result->height = h; 1390 | result->yOffset = translateY; 1391 | 1392 | return 1; 1393 | } 1394 | #endif 1395 | #endif // MSDF_H 1396 | -------------------------------------------------------------------------------- /sample/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjako/msdf_c/4cfa8df1924baee779bd2eb9590ca731d7d8ade3/sample/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /sample/fonts/RobotoLicense.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /sample/sample.c: -------------------------------------------------------------------------------- 1 | #include "svpng.h" 2 | #define STB_TRUETYPE_IMPLEMENTATION 3 | #include "../stb_truetype.h" 4 | #define MSDF_IMPLEMENTATION 5 | #include "../msdf.h" 6 | 7 | #include "stdio.h" 8 | 9 | 10 | #ifndef SAMPLE_ROOT 11 | #define SAMPLE_ROOT "" 12 | #endif 13 | 14 | typedef struct Range { 15 | void* content; 16 | size_t size; 17 | } Range; 18 | 19 | Range g_fileRead(const char* fileName) { 20 | Range range = {NULL, 0}; 21 | 22 | FILE* fileHandle = fopen(fileName, "rb"); 23 | if (fileHandle == NULL) { 24 | assert(!"File not fount"); 25 | return range; 26 | } 27 | 28 | fseek(fileHandle, 0, SEEK_END); // seek to end of file 29 | size_t size = ftell(fileHandle); // get current file pointer 30 | fseek(fileHandle, 0, SEEK_SET); // seek back to beginning of file 31 | 32 | if (size == 0) { 33 | assert(!"File size zero"); 34 | return range; 35 | } 36 | 37 | void* mem = malloc(size); 38 | size_t readSize = fread(mem, 1, size, fileHandle); 39 | if (readSize == size) { 40 | range.content = mem; 41 | range.size = size; 42 | } else { 43 | free(mem); 44 | } 45 | fclose(fileHandle); 46 | return range; 47 | } 48 | 49 | static void* g_alloc(size_t size, void* ctx) { 50 | return malloc(size); 51 | } 52 | 53 | static void g_free(void* ptr, void* ctx) { 54 | return free(ptr); 55 | } 56 | 57 | float g_min(float a, float b) { 58 | return a > b ? b : a; 59 | } 60 | 61 | float g_max(float a, float b) { 62 | return a > b ? a : b; 63 | } 64 | 65 | float g_clamp(float val, float min, float max) { 66 | return g_min(g_max(val, min), max); 67 | } 68 | 69 | float g_median(float r, float g, float b) { 70 | return g_max(g_min(r, g), g_min(g_max(r, g), b)); 71 | } 72 | 73 | float g_map(float min, float max, float v) { 74 | return (v - min) / (max - min); 75 | } 76 | 77 | int main() { 78 | stbtt_fontinfo stbttInfo; 79 | 80 | Range fontFile = g_fileRead(SAMPLE_ROOT "/fonts/Roboto-Regular.ttf"); 81 | 82 | if (fontFile.content == NULL) { 83 | assert(!"could not load font from disk"); 84 | return 1; 85 | } 86 | 87 | if (!stbtt_InitFont(&stbttInfo, fontFile.content, stbtt_GetFontOffsetForIndex(fontFile.content, 0))) { 88 | assert(!"Couldn't parse font"); 89 | return 1; 90 | } 91 | 92 | 93 | int ascent, descent; 94 | stbtt_GetFontVMetrics(&stbttInfo, &ascent, &descent, 0); 95 | 96 | int genSize = 128; 97 | float genScale = stbtt_ScaleForPixelHeight(&stbttInfo, genSize); 98 | 99 | int glyph = 'Y'; 100 | 101 | int glyphIdx = stbtt_FindGlyphIndex(&stbttInfo, glyph); 102 | 103 | msdf_AllocCtx allocCtx = {g_alloc, g_free, NULL}; 104 | 105 | int borderSize = 4; 106 | 107 | msdf_Result result; 108 | int success = msdf_genGlyph(&result, &stbttInfo, glyphIdx, borderSize, genScale, 2.0f / genSize, &allocCtx); 109 | 110 | if (success == 0) { 111 | assert(!"Failed to generate msdf glyph"); 112 | return 1; 113 | } 114 | 115 | FILE* fp = fopen(SAMPLE_ROOT "/sdf.png", "wb"); 116 | 117 | uint8_t* pixels = malloc(sizeof(uint8_t) * result.width * result.height * 3); 118 | float scale = genSize; 119 | float maxValue = 1.0 * (scale); 120 | float transistionWidth = ((((((float) genSize) * 0.7f) + scale) / (scale * 2.0f))); 121 | float transistionAbs = (transistionWidth - 0.5); 122 | float transistStart = 0.5 - transistionAbs / 2; 123 | float transistEnd = transistStart + transistionAbs; 124 | 125 | //float ff = expf(0.01); 126 | for (int y = 0; y < result.height; y++) { 127 | int yPos = result.width * 3 * y; 128 | uint8_t* pixelRow = pixels + (y * result.width * 3); 129 | for (int x = 0; x < result.width; x++) { 130 | int indexSdf = yPos + (x * 3); 131 | float r = result.rgb[indexSdf + 0]; 132 | float g = result.rgb[indexSdf + 1]; 133 | float b = result.rgb[indexSdf + 2]; 134 | 135 | 136 | r = ((((r) + scale) / (scale * 2.0f))); 137 | g = ((((g) + scale) / (scale * 2.0f))); 138 | b = ((((b) + scale) / (scale * 2.0f))); 139 | 140 | if (r > transistStart) { 141 | if (r > (transistEnd)) { 142 | r = 1.0f; 143 | } else { 144 | r = 0.0f + (r - transistStart) / (transistionAbs); 145 | } 146 | } else { 147 | r = 0.0f; 148 | } 149 | if (g > transistStart) { 150 | if (g > (transistEnd)) { 151 | g = 1.0f; 152 | } else { 153 | g = 0.0f + (g - transistStart) / (transistionAbs); 154 | } 155 | } else { 156 | g = 0.0f; 157 | } 158 | if (b > transistStart) { 159 | if (b > (transistEnd)) { 160 | b = 1.0f; 161 | } else { 162 | b = 0.0f + (b - transistStart) / (transistionAbs); 163 | } 164 | } else { 165 | b = 0.0f; 166 | } 167 | 168 | pixelRow[x * 3 + 0] = r * 255.0f; // (r > 0.5f) ? 255.0f : r * 255.0f; 169 | pixelRow[x * 3 + 1] = g * 255.0f; // (g > 0.5f) ? 255.0f : g * 255.0f; 170 | pixelRow[x * 3 + 2] = b * 255.0f; // (b > 0.5f) ? 255.0f : b * 255.0f; 171 | } 172 | } 173 | svpng(fp, result.width, result.height, pixels, 0); 174 | fclose(fp); 175 | 176 | // apply sdf 177 | 178 | FILE* finalImage = fopen(SAMPLE_ROOT "/final.png", "wb"); 179 | 180 | uint8_t* rgbaFinal = malloc(sizeof(uint8_t) * result.width * result.height * 4); 181 | 182 | for (int y = 0; y < result.height; y++) { 183 | uint8_t* pixelRow = pixels + (y * result.width * 3); 184 | uint8_t* rgbaFinalRow = rgbaFinal + (y * result.width * 4); 185 | for (int x = 0; x < result.width; x++) { 186 | float r = ((float)pixelRow[x * 3 + 0]) / 255.0f;// = r * 255.0f; // (r > 0.5f) ? 255.0f : r * 255.0f; 187 | float g = ((float)pixelRow[x * 3 + 1]) / 255.0f;// = g * 255.0f; // (g > 0.5f) ? 255.0f : g * 255.0f; 188 | float b = ((float)pixelRow[x * 3 + 2]) / 255.0f;// = b * 255.0f; // (b > 0.5f) ? 255.0f : b * 255.0f; 189 | float dist = g_median(r, g, b); 190 | float opacity = g_clamp(dist - 0.5f, 0, 1) * 2.0f; 191 | rgbaFinalRow[x * 4 + 0] = 0;//255.0f; 192 | rgbaFinalRow[x * 4 + 1] = 0;//255.0f; 193 | rgbaFinalRow[x * 4 + 2] = 0;//255.0f; 194 | rgbaFinalRow[x * 4 + 3] = opacity * 255.0f; 195 | 196 | } 197 | } 198 | svpng(finalImage, result.width, result.height, rgbaFinal, 1); 199 | fclose(finalImage); 200 | return 0; 201 | } -------------------------------------------------------------------------------- /sample/svpng.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2017 Milo Yip. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of pngout nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /*! \file 31 | \brief svpng() is a minimalistic C function for saving RGB/RGBA image into uncompressed PNG. 32 | \author Milo Yip 33 | \version 0.1.1 34 | \copyright MIT license 35 | \sa http://github.com/miloyip/svpng 36 | */ 37 | 38 | #ifndef SVPNG_INC_ 39 | #define SVPNG_INC_ 40 | 41 | /*! \def SVPNG_LINKAGE 42 | \brief User customizable linkage for svpng() function. 43 | By default this macro is empty. 44 | User may define this macro as static for static linkage, 45 | and/or inline in C99/C++, etc. 46 | */ 47 | #ifndef SVPNG_LINKAGE 48 | #define SVPNG_LINKAGE 49 | #endif 50 | 51 | /*! \def SVPNG_OUTPUT 52 | \brief User customizable output stream. 53 | By default, it uses C file descriptor and fputc() to output bytes. 54 | In C++, for example, user may use std::ostream or std::vector instead. 55 | */ 56 | #ifndef SVPNG_OUTPUT 57 | #include 58 | #define SVPNG_OUTPUT FILE* fp 59 | #endif 60 | 61 | /*! \def SVPNG_PUT 62 | \brief Write a byte 63 | */ 64 | #ifndef SVPNG_PUT 65 | #define SVPNG_PUT(u) fputc(u, fp) 66 | #endif 67 | 68 | 69 | /*! 70 | \brief Save a RGB/RGBA image in PNG format. 71 | \param SVPNG_OUTPUT Output stream (by default using file descriptor). 72 | \param w Width of the image. (<16383) 73 | \param h Height of the image. 74 | \param img Image pixel data in 24-bit RGB or 32-bit RGBA format. 75 | \param alpha Whether the image contains alpha channel. 76 | */ 77 | SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha) { 78 | static const unsigned t[] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 79 | /* CRC32 Table */ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; 80 | unsigned a = 1, b = 0, c, p = w * (alpha ? 4 : 3) + 1, x, y, i; /* ADLER-a, ADLER-b, CRC, pitch */ 81 | #define SVPNG_U8A(ua, l) for (i = 0; i < l; i++) SVPNG_PUT((ua)[i]); 82 | #define SVPNG_U32(u) do { SVPNG_PUT((u) >> 24); SVPNG_PUT(((u) >> 16) & 255); SVPNG_PUT(((u) >> 8) & 255); SVPNG_PUT((u) & 255); } while(0) 83 | #define SVPNG_U8C(u) do { SVPNG_PUT(u); c ^= (u); c = (c >> 4) ^ t[c & 15]; c = (c >> 4) ^ t[c & 15]; } while(0) 84 | #define SVPNG_U8AC(ua, l) for (i = 0; i < l; i++) SVPNG_U8C((ua)[i]) 85 | #define SVPNG_U16LC(u) do { SVPNG_U8C((u) & 255); SVPNG_U8C(((u) >> 8) & 255); } while(0) 86 | #define SVPNG_U32C(u) do { SVPNG_U8C((u) >> 24); SVPNG_U8C(((u) >> 16) & 255); SVPNG_U8C(((u) >> 8) & 255); SVPNG_U8C((u) & 255); } while(0) 87 | #define SVPNG_U8ADLER(u) do { SVPNG_U8C(u); a = (a + (u)) % 65521; b = (b + a) % 65521; } while(0) 88 | #define SVPNG_BEGIN(s, l) do { SVPNG_U32(l); c = ~0U; SVPNG_U8AC(s, 4); } while(0) 89 | #define SVPNG_END() SVPNG_U32(~c) 90 | SVPNG_U8A("\x89PNG\r\n\32\n", 8); /* Magic */ 91 | SVPNG_BEGIN("IHDR", 13); /* IHDR chunk { */ 92 | SVPNG_U32C(w); SVPNG_U32C(h); /* Width & Height (8 bytes) */ 93 | SVPNG_U8C(8); SVPNG_U8C(alpha ? 6 : 2); /* Depth=8, Color=True color with/without alpha (2 bytes) */ 94 | SVPNG_U8AC("\0\0\0", 3); /* Compression=Deflate, Filter=No, Interlace=No (3 bytes) */ 95 | SVPNG_END(); /* } */ 96 | SVPNG_BEGIN("IDAT", 2 + h * (5 + p) + 4); /* IDAT chunk { */ 97 | SVPNG_U8AC("\x78\1", 2); /* Deflate block begin (2 bytes) */ 98 | for (y = 0; y < h; y++) { /* Each horizontal line makes a block for simplicity */ 99 | SVPNG_U8C(y == h - 1); /* 1 for the last block, 0 for others (1 byte) */ 100 | SVPNG_U16LC(p); SVPNG_U16LC(~p); /* Size of block in little endian and its 1's complement (4 bytes) */ 101 | SVPNG_U8ADLER(0); /* No filter prefix (1 byte) */ 102 | for (x = 0; x < p - 1; x++, img++) 103 | SVPNG_U8ADLER(*img); /* Image pixel data */ 104 | } 105 | SVPNG_U32C((b << 16) | a); /* Deflate block end with adler (4 bytes) */ 106 | SVPNG_END(); /* } */ 107 | SVPNG_BEGIN("IEND", 0); SVPNG_END(); /* IEND chunk {} */ 108 | } 109 | 110 | #endif /* SVPNG_INC_ */ --------------------------------------------------------------------------------