main.cpp 36 KB

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