main.cpp 37 KB

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