main.cpp 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. /*
  2. * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.7 (2020-03-07) - standalone console program
  3. * --------------------------------------------------------------------------------------------
  4. * A utility by Viktor Chlumsky, (c) 2014 - 2020
  5. *
  6. */
  7. #ifdef MSDFGEN_STANDALONE
  8. #define _USE_MATH_DEFINES
  9. #define _CRT_SECURE_NO_WARNINGS
  10. #include <cstdio>
  11. #include <cmath>
  12. #include <cstring>
  13. #include "msdfgen.h"
  14. #include "msdfgen-ext.h"
  15. #include "core/ShapeDistanceFinder.h"
  16. #define SDF_ERROR_ESTIMATE_PRECISION 19
  17. #define DEFAULT_ANGLE_THRESHOLD 3.
  18. using namespace msdfgen;
  19. enum Format {
  20. AUTO,
  21. PNG,
  22. BMP,
  23. TIFF,
  24. TEXT,
  25. TEXT_FLOAT,
  26. BINARY,
  27. BINARY_FLOAT,
  28. BINARY_FLOAT_BE
  29. };
  30. static bool is8bitFormat(Format format) {
  31. return format == PNG || format == BMP || format == TEXT || format == BINARY;
  32. }
  33. static char toupper(char c) {
  34. return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
  35. }
  36. static bool parseUnsigned(unsigned &value, const char *arg) {
  37. char c;
  38. return sscanf(arg, "%u%c", &value, &c) == 1;
  39. }
  40. static bool parseUnsignedDecOrHex(unsigned &value, const char *arg) {
  41. if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X')) {
  42. char c;
  43. return sscanf(arg+2, "%x%c", &value, &c) == 1;
  44. }
  45. return parseUnsigned(value, arg);
  46. }
  47. static bool parseUnsignedLL(unsigned long long &value, const char *arg) {
  48. char c;
  49. return sscanf(arg, "%llu%c", &value, &c) == 1;
  50. }
  51. static bool parseDouble(double &value, const char *arg) {
  52. char c;
  53. return sscanf(arg, "%lf%c", &value, &c) == 1;
  54. }
  55. static bool parseUnicode(unicode_t &unicode, const char *arg) {
  56. unsigned uuc;
  57. if (parseUnsignedDecOrHex(uuc, arg)) {
  58. unicode = uuc;
  59. return true;
  60. }
  61. if (arg[0] == '\'' && arg[1] && arg[2] == '\'' && !arg[3]) {
  62. unicode = (unicode_t) (unsigned char) arg[1];
  63. return true;
  64. }
  65. return false;
  66. }
  67. static bool parseAngle(double &value, const char *arg) {
  68. char c1, c2;
  69. int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2);
  70. if (result == 1)
  71. return true;
  72. if (result == 2 && (c1 == 'd' || c1 == 'D')) {
  73. value *= M_PI/180;
  74. return true;
  75. }
  76. return false;
  77. }
  78. static void parseColoring(Shape &shape, const char *edgeAssignment) {
  79. unsigned c = 0, e = 0;
  80. if (shape.contours.size() < c) return;
  81. Contour *contour = &shape.contours[c];
  82. bool change = false;
  83. bool clear = true;
  84. for (const char *in = edgeAssignment; *in; ++in) {
  85. switch (*in) {
  86. case ',':
  87. if (change)
  88. ++e;
  89. if (clear)
  90. while (e < contour->edges.size()) {
  91. contour->edges[e]->color = WHITE;
  92. ++e;
  93. }
  94. ++c, e = 0;
  95. if (shape.contours.size() <= c) return;
  96. contour = &shape.contours[c];
  97. change = false;
  98. clear = true;
  99. break;
  100. case '?':
  101. clear = false;
  102. break;
  103. case 'C': case 'M': case 'W': case 'Y': case 'c': case 'm': case 'w': case 'y':
  104. if (change) {
  105. ++e;
  106. change = false;
  107. }
  108. if (e < contour->edges.size()) {
  109. contour->edges[e]->color = EdgeColor(
  110. (*in == 'C' || *in == 'c')*CYAN|
  111. (*in == 'M' || *in == 'm')*MAGENTA|
  112. (*in == 'Y' || *in == 'y')*YELLOW|
  113. (*in == 'W' || *in == 'w')*WHITE);
  114. change = true;
  115. }
  116. break;
  117. }
  118. }
  119. }
  120. template <int N>
  121. static void invertColor(const BitmapRef<float, N> &bitmap) {
  122. const float *end = bitmap.pixels+N*bitmap.width*bitmap.height;
  123. for (float *p = bitmap.pixels; p < end; ++p)
  124. *p = 1.f-*p;
  125. }
  126. static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows) {
  127. for (int row = 0; row < rows; ++row) {
  128. for (int col = 0; col < cols; ++col) {
  129. int v = clamp(int((*values++)*0x100), 0xff);
  130. fprintf(file, col ? " %02X" : "%02X", v);
  131. }
  132. fprintf(file, "\n");
  133. }
  134. return true;
  135. }
  136. static bool writeTextBitmapFloat(FILE *file, const float *values, int cols, int rows) {
  137. for (int row = 0; row < rows; ++row) {
  138. for (int col = 0; col < cols; ++col) {
  139. fprintf(file, col ? " %.9g" : "%.9g", *values++);
  140. }
  141. fprintf(file, "\n");
  142. }
  143. return true;
  144. }
  145. static bool writeBinBitmap(FILE *file, const float *values, int count) {
  146. for (int pos = 0; pos < count; ++pos) {
  147. unsigned char v = clamp(int((*values++)*0x100), 0xff);
  148. fwrite(&v, 1, 1, file);
  149. }
  150. return true;
  151. }
  152. #ifdef __BIG_ENDIAN__
  153. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  154. #else
  155. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  156. #endif
  157. {
  158. return (int) fwrite(values, sizeof(float), count, file) == count;
  159. }
  160. #ifdef __BIG_ENDIAN__
  161. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  162. #else
  163. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  164. #endif
  165. {
  166. for (int pos = 0; pos < count; ++pos) {
  167. const unsigned char *b = reinterpret_cast<const unsigned char *>(values++);
  168. for (int i = sizeof(float)-1; i >= 0; --i)
  169. fwrite(b+i, 1, 1, file);
  170. }
  171. return true;
  172. }
  173. static bool cmpExtension(const char *path, const char *ext) {
  174. for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
  175. if (a < path || toupper(*a) != toupper(*b))
  176. return false;
  177. return true;
  178. }
  179. template <int N>
  180. static const char * writeOutput(const BitmapConstRef<float, N> &bitmap, const char *filename, Format &format) {
  181. if (filename) {
  182. if (format == AUTO) {
  183. if (cmpExtension(filename, ".png")) format = PNG;
  184. else if (cmpExtension(filename, ".bmp")) format = BMP;
  185. else if (cmpExtension(filename, ".tif") || cmpExtension(filename, ".tiff")) format = TIFF;
  186. else if (cmpExtension(filename, ".txt")) format = TEXT;
  187. else if (cmpExtension(filename, ".bin")) format = BINARY;
  188. else
  189. return "Could not deduce format from output file name.";
  190. }
  191. switch (format) {
  192. case PNG: return savePng(bitmap, filename) ? NULL : "Failed to write output PNG image.";
  193. case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
  194. case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
  195. case TEXT: case TEXT_FLOAT: {
  196. FILE *file = fopen(filename, "w");
  197. if (!file) return "Failed to write output text file.";
  198. if (format == TEXT)
  199. writeTextBitmap(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  200. else if (format == TEXT_FLOAT)
  201. writeTextBitmapFloat(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  202. fclose(file);
  203. return NULL;
  204. }
  205. case BINARY: case BINARY_FLOAT: case BINARY_FLOAT_BE: {
  206. FILE *file = fopen(filename, "wb");
  207. if (!file) return "Failed to write output binary file.";
  208. if (format == BINARY)
  209. writeBinBitmap(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  210. else if (format == BINARY_FLOAT)
  211. writeBinBitmapFloat(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  212. else if (format == BINARY_FLOAT_BE)
  213. writeBinBitmapFloatBE(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  214. fclose(file);
  215. return NULL;
  216. }
  217. default:;
  218. }
  219. } else {
  220. if (format == AUTO || format == TEXT)
  221. writeTextBitmap(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  222. else if (format == TEXT_FLOAT)
  223. writeTextBitmapFloat(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  224. else
  225. return "Unsupported format for standard output.";
  226. }
  227. return NULL;
  228. }
  229. static const char *helpText =
  230. "\n"
  231. "Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION "\n"
  232. "---------------------------------------------------------------------\n"
  233. " Usage: msdfgen"
  234. #ifdef _WIN32
  235. ".exe"
  236. #endif
  237. " <mode> <input specification> <options>\n"
  238. "\n"
  239. "MODES\n"
  240. " sdf - Generate conventional monochrome (true) signed distance field.\n"
  241. " psdf - Generate monochrome signed pseudo-distance field.\n"
  242. " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
  243. " mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n"
  244. " metrics - Report shape metrics only.\n"
  245. "\n"
  246. "INPUT SPECIFICATION\n"
  247. " -defineshape <definition>\n"
  248. "\tDefines input shape using the ad-hoc text definition.\n"
  249. " -font <filename.ttf> <character code>\n"
  250. "\tLoads a single glyph from the specified font file. Format of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
  251. " -shapedesc <filename.txt>\n"
  252. "\tLoads text shape description from a file.\n"
  253. " -stdin\n"
  254. "\tReads text shape description from the standard input.\n"
  255. " -svg <filename.svg>\n"
  256. "\tLoads the last vector path found in the specified SVG file.\n"
  257. "\n"
  258. "OPTIONS\n"
  259. " -angle <angle>\n"
  260. "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
  261. " -ascale <x scale> <y scale>\n"
  262. "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
  263. " -autoframe\n"
  264. "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
  265. " -coloringstrategy <simple / inktrap>\n"
  266. "\tSelects the strategy of the edge coloring heuristic.\n"
  267. " -distanceshift <shift>\n"
  268. "\tShifts all normalized distances in the output distance field by this value.\n"
  269. " -edgecolors <sequence>\n"
  270. "\tOverrides automatic edge coloring with the specified color sequence.\n"
  271. " -errorcorrection <threshold>\n"
  272. "\tChanges the threshold used to detect and correct potential artifacts. 0 disables error correction.\n"
  273. " -estimateerror\n"
  274. "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
  275. " -exportshape <filename.txt>\n"
  276. "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
  277. " -fillrule <nonzero / evenodd / positive / negative>\n"
  278. "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
  279. " -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
  280. "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
  281. " -guessorder\n"
  282. "\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
  283. " -help\n"
  284. "\tDisplays this help.\n"
  285. " -legacy\n"
  286. "\tUses the original (legacy) distance field algorithms.\n"
  287. " -nooverlap\n"
  288. "\tDisables resolution of overlapping contours.\n"
  289. " -noscanline\n"
  290. "\tDisables the scanline pass, which corrects the distance field's signs according to the selected fill rule.\n"
  291. " -o <filename>\n"
  292. "\tSets the output file name. The default value is \"output.png\".\n"
  293. " -printmetrics\n"
  294. "\tPrints relevant metrics of the shape to the standard output.\n"
  295. " -pxrange <range>\n"
  296. "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
  297. " -range <range>\n"
  298. "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
  299. " -reverseorder\n"
  300. "\tGenerates the distance field as if shape vertices were in reverse order.\n"
  301. " -scale <scale>\n"
  302. "\tSets the scale used to convert shape units to pixels.\n"
  303. " -seed <n>\n"
  304. "\tSets the random seed for edge coloring heuristic.\n"
  305. " -size <width> <height>\n"
  306. "\tSets the dimensions of the output image.\n"
  307. " -stdout\n"
  308. "\tPrints the output instead of storing it in a file. Only text formats are supported.\n"
  309. " -testrender <filename.png> <width> <height>\n"
  310. "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
  311. " -testrendermulti <filename.png> <width> <height>\n"
  312. "\tRenders an image preview without flattening the color channels.\n"
  313. " -translate <x> <y>\n"
  314. "\tSets the translation of the shape in shape units.\n"
  315. " -yflip\n"
  316. "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
  317. "\n";
  318. int main(int argc, const char * const *argv) {
  319. #define ABORT(msg) { puts(msg); return 1; }
  320. // Parse command line arguments
  321. enum {
  322. NONE,
  323. SVG,
  324. FONT,
  325. DESCRIPTION_ARG,
  326. DESCRIPTION_STDIN,
  327. DESCRIPTION_FILE
  328. } inputType = NONE;
  329. enum {
  330. SINGLE,
  331. PSEUDO,
  332. MULTI,
  333. MULTI_AND_TRUE,
  334. METRICS
  335. } mode = MULTI;
  336. bool legacyMode = false;
  337. bool overlapSupport = true;
  338. bool scanlinePass = true;
  339. FillRule fillRule = FILL_NONZERO;
  340. Format format = AUTO;
  341. const char *input = NULL;
  342. const char *output = "output.png";
  343. const char *shapeExport = NULL;
  344. const char *testRender = NULL;
  345. const char *testRenderMulti = NULL;
  346. bool outputSpecified = false;
  347. GlyphIndex glyphIndex;
  348. unicode_t unicode = 0;
  349. int svgPathIndex = 0;
  350. int width = 64, height = 64;
  351. int testWidth = 0, testHeight = 0;
  352. int testWidthM = 0, testHeightM = 0;
  353. bool autoFrame = false;
  354. enum {
  355. RANGE_UNIT,
  356. RANGE_PX
  357. } rangeMode = RANGE_PX;
  358. double range = 1;
  359. double pxRange = 2;
  360. Vector2 translate;
  361. Vector2 scale = 1;
  362. bool scaleSpecified = false;
  363. double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
  364. double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
  365. float outputDistanceShift = 0.f;
  366. const char *edgeAssignment = NULL;
  367. bool yFlip = false;
  368. bool printMetrics = false;
  369. bool estimateError = false;
  370. bool skipColoring = false;
  371. enum {
  372. KEEP,
  373. REVERSE,
  374. GUESS
  375. } orientation = KEEP;
  376. unsigned long long coloringSeed = 0;
  377. void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
  378. int argPos = 1;
  379. bool suggestHelp = false;
  380. while (argPos < argc) {
  381. const char *arg = argv[argPos];
  382. #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
  383. #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; }
  384. #define SET_FORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false)
  385. ARG_MODE("sdf", SINGLE)
  386. ARG_MODE("psdf", PSEUDO)
  387. ARG_MODE("msdf", MULTI)
  388. ARG_MODE("mtsdf", MULTI_AND_TRUE)
  389. ARG_MODE("metrics", METRICS)
  390. ARG_CASE("-svg", 1) {
  391. inputType = SVG;
  392. input = argv[argPos+1];
  393. argPos += 2;
  394. continue;
  395. }
  396. ARG_CASE("-font", 2) {
  397. inputType = FONT;
  398. input = argv[argPos+1];
  399. const char *charArg = argv[argPos+2];
  400. unsigned gi;
  401. switch (charArg[0]) {
  402. case 'G': case 'g':
  403. if (parseUnsignedDecOrHex(gi, charArg+1))
  404. glyphIndex = GlyphIndex(gi);
  405. break;
  406. case 'U': case 'u':
  407. ++charArg;
  408. default:
  409. parseUnicode(unicode, charArg);
  410. }
  411. argPos += 3;
  412. continue;
  413. }
  414. ARG_CASE("-defineshape", 1) {
  415. inputType = DESCRIPTION_ARG;
  416. input = argv[argPos+1];
  417. argPos += 2;
  418. continue;
  419. }
  420. ARG_CASE("-stdin", 0) {
  421. inputType = DESCRIPTION_STDIN;
  422. input = "stdin";
  423. argPos += 1;
  424. continue;
  425. }
  426. ARG_CASE("-shapedesc", 1) {
  427. inputType = DESCRIPTION_FILE;
  428. input = argv[argPos+1];
  429. argPos += 2;
  430. continue;
  431. }
  432. ARG_CASE("-o", 1) {
  433. output = argv[argPos+1];
  434. outputSpecified = true;
  435. argPos += 2;
  436. continue;
  437. }
  438. ARG_CASE("-stdout", 0) {
  439. output = NULL;
  440. argPos += 1;
  441. continue;
  442. }
  443. ARG_CASE("-legacy", 0) {
  444. legacyMode = true;
  445. argPos += 1;
  446. continue;
  447. }
  448. ARG_CASE("-nooverlap", 0) {
  449. overlapSupport = false;
  450. argPos += 1;
  451. continue;
  452. }
  453. ARG_CASE("-noscanline", 0) {
  454. scanlinePass = false;
  455. argPos += 1;
  456. continue;
  457. }
  458. ARG_CASE("-scanline", 0) {
  459. scanlinePass = true;
  460. argPos += 1;
  461. continue;
  462. }
  463. ARG_CASE("-fillrule", 1) {
  464. if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO;
  465. else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD;
  466. else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE;
  467. else if (!strcmp(argv[argPos+1], "negative")) fillRule = FILL_NEGATIVE;
  468. else
  469. puts("Unknown fill rule specified.");
  470. argPos += 2;
  471. continue;
  472. }
  473. ARG_CASE("-format", 1) {
  474. if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
  475. else if (!strcmp(argv[argPos+1], "png")) SET_FORMAT(PNG, "png");
  476. else if (!strcmp(argv[argPos+1], "bmp")) SET_FORMAT(BMP, "bmp");
  477. else if (!strcmp(argv[argPos+1], "tiff")) SET_FORMAT(TIFF, "tif");
  478. else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SET_FORMAT(TEXT, "txt");
  479. else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
  480. else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SET_FORMAT(BINARY, "bin");
  481. else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin");
  482. else if (!strcmp(argv[argPos+1], "binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin");
  483. else
  484. puts("Unknown format specified.");
  485. argPos += 2;
  486. continue;
  487. }
  488. ARG_CASE("-size", 2) {
  489. unsigned w, h;
  490. if (!parseUnsigned(w, argv[argPos+1]) || !parseUnsigned(h, argv[argPos+2]) || !w || !h)
  491. ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
  492. width = w, height = h;
  493. argPos += 3;
  494. continue;
  495. }
  496. ARG_CASE("-autoframe", 0) {
  497. autoFrame = true;
  498. argPos += 1;
  499. continue;
  500. }
  501. ARG_CASE("-range", 1) {
  502. double r;
  503. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  504. ABORT("Invalid range argument. Use -range <range> with a positive real number.");
  505. rangeMode = RANGE_UNIT;
  506. range = r;
  507. argPos += 2;
  508. continue;
  509. }
  510. ARG_CASE("-pxrange", 1) {
  511. double r;
  512. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  513. ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
  514. rangeMode = RANGE_PX;
  515. pxRange = r;
  516. argPos += 2;
  517. continue;
  518. }
  519. ARG_CASE("-scale", 1) {
  520. double s;
  521. if (!parseDouble(s, argv[argPos+1]) || s <= 0)
  522. ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
  523. scale = s;
  524. scaleSpecified = true;
  525. argPos += 2;
  526. continue;
  527. }
  528. ARG_CASE("-ascale", 2) {
  529. double sx, sy;
  530. if (!parseDouble(sx, argv[argPos+1]) || !parseDouble(sy, argv[argPos+2]) || sx <= 0 || sy <= 0)
  531. ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
  532. scale.set(sx, sy);
  533. scaleSpecified = true;
  534. argPos += 3;
  535. continue;
  536. }
  537. ARG_CASE("-translate", 2) {
  538. double tx, ty;
  539. if (!parseDouble(tx, argv[argPos+1]) || !parseDouble(ty, argv[argPos+2]))
  540. ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
  541. translate.set(tx, ty);
  542. argPos += 3;
  543. continue;
  544. }
  545. ARG_CASE("-angle", 1) {
  546. double at;
  547. if (!parseAngle(at, argv[argPos+1]))
  548. ABORT("Invalid angle threshold. Use -angle <min angle> with a positive real number less than PI or a value in degrees followed by 'd' below 180d.");
  549. angleThreshold = at;
  550. argPos += 2;
  551. continue;
  552. }
  553. ARG_CASE("-errorcorrection", 1) {
  554. double ect;
  555. if (!parseDouble(ect, argv[argPos+1]) && (ect >= 1 || ect == 0))
  556. ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number greater than or equal to 1 or 0 to disable.");
  557. errorCorrectionThreshold = ect;
  558. argPos += 2;
  559. continue;
  560. }
  561. ARG_CASE("-coloringstrategy", 1) {
  562. if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple;
  563. else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap;
  564. else
  565. puts("Unknown coloring strategy specified.");
  566. argPos += 2;
  567. continue;
  568. }
  569. ARG_CASE("-edgecolors", 1) {
  570. static const char *allowed = " ?,cmwyCMWY";
  571. for (int i = 0; argv[argPos+1][i]; ++i) {
  572. for (int j = 0; allowed[j]; ++j)
  573. if (argv[argPos+1][i] == allowed[j])
  574. goto EDGE_COLOR_VERIFIED;
  575. ABORT("Invalid edge coloring sequence. Use -edgecolors <color sequence> with only the colors C, M, Y, and W. Separate contours by commas and use ? to keep the default assigment for a contour.");
  576. EDGE_COLOR_VERIFIED:;
  577. }
  578. edgeAssignment = argv[argPos+1];
  579. argPos += 2;
  580. continue;
  581. }
  582. ARG_CASE("-distanceshift", 1) {
  583. double ds;
  584. if (!parseDouble(ds, argv[argPos+1]))
  585. ABORT("Invalid distance shift. Use -distanceshift <shift> with a real value.");
  586. outputDistanceShift = (float) ds;
  587. argPos += 2;
  588. continue;
  589. }
  590. ARG_CASE("-exportshape", 1) {
  591. shapeExport = argv[argPos+1];
  592. argPos += 2;
  593. continue;
  594. }
  595. ARG_CASE("-testrender", 3) {
  596. unsigned w, h;
  597. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  598. ABORT("Invalid arguments for test render. Use -testrender <output.png> <width> <height>.");
  599. testRender = argv[argPos+1];
  600. testWidth = w, testHeight = h;
  601. argPos += 4;
  602. continue;
  603. }
  604. ARG_CASE("-testrendermulti", 3) {
  605. unsigned w, h;
  606. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  607. ABORT("Invalid arguments for test render. Use -testrendermulti <output.png> <width> <height>.");
  608. testRenderMulti = argv[argPos+1];
  609. testWidthM = w, testHeightM = h;
  610. argPos += 4;
  611. continue;
  612. }
  613. ARG_CASE("-yflip", 0) {
  614. yFlip = true;
  615. argPos += 1;
  616. continue;
  617. }
  618. ARG_CASE("-printmetrics", 0) {
  619. printMetrics = true;
  620. argPos += 1;
  621. continue;
  622. }
  623. ARG_CASE("-estimateerror", 0) {
  624. estimateError = true;
  625. argPos += 1;
  626. continue;
  627. }
  628. ARG_CASE("-keeporder", 0) {
  629. orientation = KEEP;
  630. argPos += 1;
  631. continue;
  632. }
  633. ARG_CASE("-reverseorder", 0) {
  634. orientation = REVERSE;
  635. argPos += 1;
  636. continue;
  637. }
  638. ARG_CASE("-guessorder", 0) {
  639. orientation = GUESS;
  640. argPos += 1;
  641. continue;
  642. }
  643. ARG_CASE("-seed", 1) {
  644. if (!parseUnsignedLL(coloringSeed, argv[argPos+1]))
  645. ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
  646. argPos += 2;
  647. continue;
  648. }
  649. ARG_CASE("-help", 0) {
  650. puts(helpText);
  651. return 0;
  652. }
  653. printf("Unknown setting or insufficient parameters: %s\n", arg);
  654. suggestHelp = true;
  655. ++argPos;
  656. }
  657. if (suggestHelp)
  658. printf("Use -help for more information.\n");
  659. // Load input
  660. Vector2 svgDims;
  661. double glyphAdvance = 0;
  662. if (!inputType || !input)
  663. ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
  664. if (mode == MULTI_AND_TRUE && (format == BMP || (format == AUTO && output && cmpExtension(output, ".bmp"))))
  665. ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode.");
  666. Shape shape;
  667. switch (inputType) {
  668. case SVG: {
  669. if (!loadSvgShape(shape, input, svgPathIndex, &svgDims))
  670. ABORT("Failed to load shape from SVG file.");
  671. break;
  672. }
  673. case FONT: {
  674. if (!glyphIndex && !unicode)
  675. ABORT("No character specified! Use -font <file.ttf/otf> <character code>. Character code can be a Unicode index (65, 0x41), a character in apostrophes ('A'), or a glyph index prefixed by g (g36, g0x24).");
  676. FreetypeHandle *ft = initializeFreetype();
  677. if (!ft) return -1;
  678. FontHandle *font = loadFont(ft, input);
  679. if (!font) {
  680. deinitializeFreetype(ft);
  681. ABORT("Failed to load font file.");
  682. }
  683. if (unicode)
  684. getGlyphIndex(glyphIndex, font, unicode);
  685. if (!loadGlyph(shape, font, glyphIndex, &glyphAdvance)) {
  686. destroyFont(font);
  687. deinitializeFreetype(ft);
  688. ABORT("Failed to load glyph from font file.");
  689. }
  690. destroyFont(font);
  691. deinitializeFreetype(ft);
  692. break;
  693. }
  694. case DESCRIPTION_ARG: {
  695. if (!readShapeDescription(input, shape, &skipColoring))
  696. ABORT("Parse error in shape description.");
  697. break;
  698. }
  699. case DESCRIPTION_STDIN: {
  700. if (!readShapeDescription(stdin, shape, &skipColoring))
  701. ABORT("Parse error in shape description.");
  702. break;
  703. }
  704. case DESCRIPTION_FILE: {
  705. FILE *file = fopen(input, "r");
  706. if (!file)
  707. ABORT("Failed to load shape description file.");
  708. if (!readShapeDescription(file, shape, &skipColoring))
  709. ABORT("Parse error in shape description.");
  710. fclose(file);
  711. break;
  712. }
  713. default:;
  714. }
  715. // Validate and normalize shape
  716. if (!shape.validate())
  717. ABORT("The geometry of the loaded shape is invalid.");
  718. shape.normalize();
  719. if (yFlip)
  720. shape.inverseYAxis = !shape.inverseYAxis;
  721. double avgScale = .5*(scale.x+scale.y);
  722. Shape::Bounds bounds = { };
  723. if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
  724. bounds = shape.getBounds();
  725. // Auto-frame
  726. if (autoFrame) {
  727. double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
  728. Vector2 frame(width, height);
  729. double m = .5+(double) outputDistanceShift;
  730. if (!scaleSpecified) {
  731. if (rangeMode == RANGE_UNIT)
  732. l -= m*range, b -= m*range, r += m*range, t += m*range;
  733. else
  734. frame -= 2*m*pxRange;
  735. }
  736. if (l >= r || b >= t)
  737. l = 0, b = 0, r = 1, t = 1;
  738. if (frame.x <= 0 || frame.y <= 0)
  739. ABORT("Cannot fit the specified pixel range.");
  740. Vector2 dims(r-l, t-b);
  741. if (scaleSpecified)
  742. translate = .5*(frame/scale-dims)-Vector2(l, b);
  743. else {
  744. if (dims.x*frame.y < dims.y*frame.x) {
  745. translate.set(.5*(frame.x/frame.y*dims.y-dims.x)-l, -b);
  746. scale = avgScale = frame.y/dims.y;
  747. } else {
  748. translate.set(-l, .5*(frame.y/frame.x*dims.x-dims.y)-b);
  749. scale = avgScale = frame.x/dims.x;
  750. }
  751. }
  752. if (rangeMode == RANGE_PX && !scaleSpecified)
  753. translate += m*pxRange/scale;
  754. }
  755. if (rangeMode == RANGE_PX)
  756. range = pxRange/min(scale.x, scale.y);
  757. // Print metrics
  758. if (mode == METRICS || printMetrics) {
  759. FILE *out = stdout;
  760. if (mode == METRICS && outputSpecified)
  761. out = fopen(output, "w");
  762. if (!out)
  763. ABORT("Failed to write output file.");
  764. if (shape.inverseYAxis)
  765. fprintf(out, "inverseY = true\n");
  766. if (bounds.r >= bounds.l && bounds.t >= bounds.b)
  767. fprintf(out, "bounds = %.12g, %.12g, %.12g, %.12g\n", bounds.l, bounds.b, bounds.r, bounds.t);
  768. if (svgDims.x != 0 && svgDims.y != 0)
  769. fprintf(out, "dimensions = %.12g, %.12g\n", svgDims.x, svgDims.y);
  770. if (glyphAdvance != 0)
  771. fprintf(out, "advance = %.12g\n", glyphAdvance);
  772. if (autoFrame) {
  773. if (!scaleSpecified)
  774. fprintf(out, "scale = %.12g\n", avgScale);
  775. fprintf(out, "translate = %.12g, %.12g\n", translate.x, translate.y);
  776. }
  777. if (rangeMode == RANGE_PX)
  778. fprintf(out, "range = %.12g\n", range);
  779. if (mode == METRICS && outputSpecified)
  780. fclose(out);
  781. }
  782. // Compute output
  783. Bitmap<float, 1> sdf;
  784. Bitmap<float, 3> msdf;
  785. Bitmap<float, 4> mtsdf;
  786. switch (mode) {
  787. case SINGLE: {
  788. sdf = Bitmap<float, 1>(width, height);
  789. if (legacyMode)
  790. generateSDF_legacy(sdf, shape, range, scale, translate);
  791. else
  792. generateSDF(sdf, shape, range, scale, translate, overlapSupport);
  793. break;
  794. }
  795. case PSEUDO: {
  796. sdf = Bitmap<float, 1>(width, height);
  797. if (legacyMode)
  798. generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
  799. else
  800. generatePseudoSDF(sdf, shape, range, scale, translate, overlapSupport);
  801. break;
  802. }
  803. case MULTI: {
  804. if (!skipColoring)
  805. edgeColoring(shape, angleThreshold, coloringSeed);
  806. if (edgeAssignment)
  807. parseColoring(shape, edgeAssignment);
  808. msdf = Bitmap<float, 3>(width, height);
  809. if (legacyMode)
  810. generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
  811. else
  812. generateMSDF(msdf, shape, range, scale, translate, errorCorrectionThreshold, overlapSupport);
  813. break;
  814. }
  815. case MULTI_AND_TRUE: {
  816. if (!skipColoring)
  817. edgeColoring(shape, angleThreshold, coloringSeed);
  818. if (edgeAssignment)
  819. parseColoring(shape, edgeAssignment);
  820. mtsdf = Bitmap<float, 4>(width, height);
  821. if (legacyMode)
  822. generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
  823. else
  824. generateMTSDF(mtsdf, shape, range, scale, translate, errorCorrectionThreshold, overlapSupport);
  825. break;
  826. }
  827. default:;
  828. }
  829. if (orientation == GUESS) {
  830. // Get sign of signed distance outside bounds
  831. Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
  832. double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
  833. orientation = distance <= 0 ? KEEP : REVERSE;
  834. }
  835. if (orientation == REVERSE) {
  836. switch (mode) {
  837. case SINGLE:
  838. case PSEUDO:
  839. invertColor<1>(sdf);
  840. break;
  841. case MULTI:
  842. invertColor<3>(msdf);
  843. break;
  844. case MULTI_AND_TRUE:
  845. invertColor<4>(mtsdf);
  846. break;
  847. default:;
  848. }
  849. }
  850. if (scanlinePass) {
  851. switch (mode) {
  852. case SINGLE:
  853. case PSEUDO:
  854. distanceSignCorrection(sdf, shape, scale, translate, fillRule);
  855. break;
  856. case MULTI:
  857. distanceSignCorrection(msdf, shape, scale, translate, fillRule);
  858. if (errorCorrectionThreshold > 0)
  859. msdfErrorCorrection(msdf, errorCorrectionThreshold/(scale*range));
  860. break;
  861. case MULTI_AND_TRUE:
  862. distanceSignCorrection(mtsdf, shape, scale, translate, fillRule);
  863. if (errorCorrectionThreshold > 0)
  864. msdfErrorCorrection(mtsdf, errorCorrectionThreshold/(scale*range));
  865. break;
  866. default:;
  867. }
  868. }
  869. if (outputDistanceShift) {
  870. float *pixel = NULL, *pixelsEnd = NULL;
  871. switch (mode) {
  872. case SINGLE:
  873. case PSEUDO:
  874. pixel = (float *) sdf;
  875. pixelsEnd = pixel+1*sdf.width()*sdf.height();
  876. break;
  877. case MULTI:
  878. pixel = (float *) msdf;
  879. pixelsEnd = pixel+3*msdf.width()*msdf.height();
  880. break;
  881. case MULTI_AND_TRUE:
  882. pixel = (float *) mtsdf;
  883. pixelsEnd = pixel+4*mtsdf.width()*mtsdf.height();
  884. break;
  885. default:;
  886. }
  887. while (pixel < pixelsEnd)
  888. *pixel++ += outputDistanceShift;
  889. }
  890. // Save output
  891. if (shapeExport) {
  892. FILE *file = fopen(shapeExport, "w");
  893. if (file) {
  894. writeShapeDescription(file, shape);
  895. fclose(file);
  896. } else
  897. puts("Failed to write shape export file.");
  898. }
  899. const char *error = NULL;
  900. switch (mode) {
  901. case SINGLE:
  902. case PSEUDO:
  903. error = writeOutput<1>(sdf, output, format);
  904. if (error)
  905. ABORT(error);
  906. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  907. simulate8bit(sdf);
  908. if (estimateError) {
  909. double sdfError = estimateSDFError(sdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  910. printf("SDF error ~ %e\n", sdfError);
  911. }
  912. if (testRenderMulti) {
  913. Bitmap<float, 3> render(testWidthM, testHeightM);
  914. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  915. if (!savePng(render, testRenderMulti))
  916. puts("Failed to write test render file.");
  917. }
  918. if (testRender) {
  919. Bitmap<float, 1> render(testWidth, testHeight);
  920. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  921. if (!savePng(render, testRender))
  922. puts("Failed to write test render file.");
  923. }
  924. break;
  925. case MULTI:
  926. error = writeOutput<3>(msdf, output, format);
  927. if (error)
  928. ABORT(error);
  929. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  930. simulate8bit(msdf);
  931. if (estimateError) {
  932. double sdfError = estimateSDFError(msdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  933. printf("SDF error ~ %e\n", sdfError);
  934. }
  935. if (testRenderMulti) {
  936. Bitmap<float, 3> render(testWidthM, testHeightM);
  937. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  938. if (!savePng(render, testRenderMulti))
  939. puts("Failed to write test render file.");
  940. }
  941. if (testRender) {
  942. Bitmap<float, 1> render(testWidth, testHeight);
  943. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  944. if (!savePng(render, testRender))
  945. ABORT("Failed to write test render file.");
  946. }
  947. break;
  948. case MULTI_AND_TRUE:
  949. error = writeOutput<4>(mtsdf, output, format);
  950. if (error)
  951. ABORT(error);
  952. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  953. simulate8bit(mtsdf);
  954. if (estimateError) {
  955. double sdfError = estimateSDFError(mtsdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  956. printf("SDF error ~ %e\n", sdfError);
  957. }
  958. if (testRenderMulti) {
  959. Bitmap<float, 4> render(testWidthM, testHeightM);
  960. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  961. if (!savePng(render, testRenderMulti))
  962. puts("Failed to write test render file.");
  963. }
  964. if (testRender) {
  965. Bitmap<float, 1> render(testWidth, testHeight);
  966. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  967. if (!savePng(render, testRender))
  968. ABORT("Failed to write test render file.");
  969. }
  970. break;
  971. default:;
  972. }
  973. return 0;
  974. }
  975. #endif