main.cpp 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /*
  2. * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.9 (2021-05-28) - standalone console program
  3. * --------------------------------------------------------------------------------------------
  4. * A utility by Viktor Chlumsky, (c) 2014 - 2021
  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. #if defined(MSDFGEN_USE_SKIA) && defined(MSDFGEN_USE_OPENMP)
  230. #define TITLE_SUFFIX " with Skia & OpenMP"
  231. #define EXTRA_UNDERLINE "-------------------"
  232. #elif defined(MSDFGEN_USE_SKIA)
  233. #define TITLE_SUFFIX " with Skia"
  234. #define EXTRA_UNDERLINE "----------"
  235. #elif defined(MSDFGEN_USE_OPENMP)
  236. #define TITLE_SUFFIX " with OpenMP"
  237. #define EXTRA_UNDERLINE "------------"
  238. #else
  239. #define TITLE_SUFFIX
  240. #define EXTRA_UNDERLINE
  241. #endif
  242. static const char *helpText =
  243. "\n"
  244. "Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION TITLE_SUFFIX "\n"
  245. "---------------------------------------------------------------------" EXTRA_UNDERLINE "\n"
  246. " Usage: msdfgen"
  247. #ifdef _WIN32
  248. ".exe"
  249. #endif
  250. " <mode> <input specification> <options>\n"
  251. "\n"
  252. "MODES\n"
  253. " sdf - Generate conventional monochrome (true) signed distance field.\n"
  254. " psdf - Generate monochrome signed pseudo-distance field.\n"
  255. " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
  256. " mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n"
  257. " metrics - Report shape metrics only.\n"
  258. "\n"
  259. "INPUT SPECIFICATION\n"
  260. " -defineshape <definition>\n"
  261. "\tDefines input shape using the ad-hoc text definition.\n"
  262. " -font <filename.ttf> <character code>\n"
  263. "\tLoads a single glyph from the specified font file.\n"
  264. "\tFormat of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
  265. " -shapedesc <filename.txt>\n"
  266. "\tLoads text shape description from a file.\n"
  267. " -stdin\n"
  268. "\tReads text shape description from the standard input.\n"
  269. " -svg <filename.svg>\n"
  270. "\tLoads the last vector path found in the specified SVG file.\n"
  271. "\n"
  272. // Keep alphabetical order!
  273. "OPTIONS\n"
  274. " -angle <angle>\n"
  275. "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
  276. " -ascale <x scale> <y scale>\n"
  277. "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
  278. " -autoframe\n"
  279. "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
  280. " -coloringstrategy <simple / inktrap / distance>\n"
  281. "\tSelects the strategy of the edge coloring heuristic.\n"
  282. " -distanceshift <shift>\n"
  283. "\tShifts all normalized distances in the output distance field by this value.\n"
  284. " -edgecolors <sequence>\n"
  285. "\tOverrides automatic edge coloring with the specified color sequence.\n"
  286. " -errorcorrection <mode>\n"
  287. "\tChanges the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.\n"
  288. " -errordeviationratio <ratio>\n"
  289. "\tSets the minimum ratio between the actual and maximum expected distance delta to be considered an error.\n"
  290. " -errorimproveratio <ratio>\n"
  291. "\tSets the minimum ratio between the pre-correction distance error and the post-correction distance error.\n"
  292. " -estimateerror\n"
  293. "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
  294. " -exportshape <filename.txt>\n"
  295. "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
  296. " -fillrule <nonzero / evenodd / positive / negative>\n"
  297. "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
  298. " -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
  299. "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
  300. " -guessorder\n"
  301. "\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
  302. " -help\n"
  303. "\tDisplays this help.\n"
  304. " -legacy\n"
  305. "\tUses the original (legacy) distance field algorithms.\n"
  306. #ifdef MSDFGEN_USE_SKIA
  307. " -nopreprocess\n"
  308. "\tDisables path preprocessing which resolves self-intersections and overlapping contours.\n"
  309. #else
  310. " -nooverlap\n"
  311. "\tDisables resolution of overlapping contours.\n"
  312. " -noscanline\n"
  313. "\tDisables the scanline pass, which corrects the distance field's signs according to the selected fill rule.\n"
  314. #endif
  315. " -o <filename>\n"
  316. "\tSets the output file name. The default value is \"output.png\".\n"
  317. #ifdef MSDFGEN_USE_SKIA
  318. " -overlap\n"
  319. "\tSwitches to distance field generator with support for overlapping contours.\n"
  320. #endif
  321. " -printmetrics\n"
  322. "\tPrints relevant metrics of the shape to the standard output.\n"
  323. " -pxrange <range>\n"
  324. "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
  325. " -range <range>\n"
  326. "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
  327. " -reverseorder\n"
  328. "\tGenerates the distance field as if the shape's vertices were in reverse order.\n"
  329. " -scale <scale>\n"
  330. "\tSets the scale used to convert shape units to pixels.\n"
  331. #ifdef MSDFGEN_USE_SKIA
  332. " -scanline\n"
  333. "\tPerforms an additional scanline pass to fix the signs of the distances.\n"
  334. #endif
  335. " -seed <n>\n"
  336. "\tSets the random seed for edge coloring heuristic.\n"
  337. " -size <width> <height>\n"
  338. "\tSets the dimensions of the output image.\n"
  339. " -stdout\n"
  340. "\tPrints the output instead of storing it in a file. Only text formats are supported.\n"
  341. " -testrender <filename.png> <width> <height>\n"
  342. "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
  343. " -testrendermulti <filename.png> <width> <height>\n"
  344. "\tRenders an image preview without flattening the color channels.\n"
  345. " -translate <x> <y>\n"
  346. "\tSets the translation of the shape in shape units.\n"
  347. " -windingpreprocess\n"
  348. "\tAttempts to fix only the contour windings assuming no self-intersections and even-odd fill rule.\n"
  349. " -yflip\n"
  350. "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
  351. "\n";
  352. static const char *errorCorrectionHelpText =
  353. "\n"
  354. "ERROR CORRECTION MODES\n"
  355. " auto-fast\n"
  356. "\tDetects inversion artifacts and distance errors that do not affect edges by range testing.\n"
  357. " auto-full\n"
  358. "\tDetects inversion artifacts and distance errors that do not affect edges by exact distance evaluation.\n"
  359. " auto-mixed (default)\n"
  360. "\tDetects inversions by distance evaluation and distance errors that do not affect edges by range testing.\n"
  361. " disabled\n"
  362. "\tDisables error correction.\n"
  363. " distance-fast\n"
  364. "\tDetects distance errors by range testing. Does not care if edges and corners are affected.\n"
  365. " distance-full\n"
  366. "\tDetects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow.\n"
  367. " edge-fast\n"
  368. "\tDetects inversion artifacts only by range testing.\n"
  369. " edge-full\n"
  370. "\tDetects inversion artifacts only by exact distance evaluation.\n"
  371. " help\n"
  372. "\tDisplays this help.\n"
  373. "\n";
  374. int main(int argc, const char * const *argv) {
  375. #define ABORT(msg) { puts(msg); return 1; }
  376. // Parse command line arguments
  377. enum {
  378. NONE,
  379. SVG,
  380. FONT,
  381. DESCRIPTION_ARG,
  382. DESCRIPTION_STDIN,
  383. DESCRIPTION_FILE
  384. } inputType = NONE;
  385. enum {
  386. SINGLE,
  387. PSEUDO,
  388. MULTI,
  389. MULTI_AND_TRUE,
  390. METRICS
  391. } mode = MULTI;
  392. enum {
  393. NO_PREPROCESS,
  394. WINDING_PREPROCESS,
  395. FULL_PREPROCESS
  396. } geometryPreproc = (
  397. #ifdef MSDFGEN_USE_SKIA
  398. FULL_PREPROCESS
  399. #else
  400. NO_PREPROCESS
  401. #endif
  402. );
  403. bool legacyMode = false;
  404. MSDFGeneratorConfig generatorConfig;
  405. generatorConfig.overlapSupport = geometryPreproc == NO_PREPROCESS;
  406. bool scanlinePass = geometryPreproc == NO_PREPROCESS;
  407. FillRule fillRule = FILL_NONZERO;
  408. Format format = AUTO;
  409. const char *input = NULL;
  410. const char *output = "output.png";
  411. const char *shapeExport = NULL;
  412. const char *testRender = NULL;
  413. const char *testRenderMulti = NULL;
  414. bool outputSpecified = false;
  415. bool glyphIndexSpecified = false;
  416. GlyphIndex glyphIndex;
  417. unicode_t unicode = 0;
  418. int svgPathIndex = 0;
  419. int width = 64, height = 64;
  420. int testWidth = 0, testHeight = 0;
  421. int testWidthM = 0, testHeightM = 0;
  422. bool autoFrame = false;
  423. enum {
  424. RANGE_UNIT,
  425. RANGE_PX
  426. } rangeMode = RANGE_PX;
  427. double range = 1;
  428. double pxRange = 2;
  429. Vector2 translate;
  430. Vector2 scale = 1;
  431. bool scaleSpecified = false;
  432. double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
  433. float outputDistanceShift = 0.f;
  434. const char *edgeAssignment = NULL;
  435. bool yFlip = false;
  436. bool printMetrics = false;
  437. bool estimateError = false;
  438. bool skipColoring = false;
  439. enum {
  440. KEEP,
  441. REVERSE,
  442. GUESS
  443. } orientation = KEEP;
  444. unsigned long long coloringSeed = 0;
  445. void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
  446. bool explicitErrorCorrectionMode = false;
  447. int argPos = 1;
  448. bool suggestHelp = false;
  449. while (argPos < argc) {
  450. const char *arg = argv[argPos];
  451. #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
  452. #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; }
  453. #define SET_FORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false)
  454. ARG_MODE("sdf", SINGLE)
  455. ARG_MODE("psdf", PSEUDO)
  456. ARG_MODE("msdf", MULTI)
  457. ARG_MODE("mtsdf", MULTI_AND_TRUE)
  458. ARG_MODE("metrics", METRICS)
  459. ARG_CASE("-svg", 1) {
  460. inputType = SVG;
  461. input = argv[argPos+1];
  462. argPos += 2;
  463. continue;
  464. }
  465. ARG_CASE("-font", 2) {
  466. inputType = FONT;
  467. input = argv[argPos+1];
  468. const char *charArg = argv[argPos+2];
  469. unsigned gi;
  470. switch (charArg[0]) {
  471. case 'G': case 'g':
  472. if (parseUnsignedDecOrHex(gi, charArg+1)) {
  473. glyphIndex = GlyphIndex(gi);
  474. glyphIndexSpecified = true;
  475. }
  476. break;
  477. case 'U': case 'u':
  478. ++charArg;
  479. default:
  480. parseUnicode(unicode, charArg);
  481. }
  482. argPos += 3;
  483. continue;
  484. }
  485. ARG_CASE("-defineshape", 1) {
  486. inputType = DESCRIPTION_ARG;
  487. input = argv[argPos+1];
  488. argPos += 2;
  489. continue;
  490. }
  491. ARG_CASE("-stdin", 0) {
  492. inputType = DESCRIPTION_STDIN;
  493. input = "stdin";
  494. argPos += 1;
  495. continue;
  496. }
  497. ARG_CASE("-shapedesc", 1) {
  498. inputType = DESCRIPTION_FILE;
  499. input = argv[argPos+1];
  500. argPos += 2;
  501. continue;
  502. }
  503. ARG_CASE("-o", 1) {
  504. output = argv[argPos+1];
  505. outputSpecified = true;
  506. argPos += 2;
  507. continue;
  508. }
  509. ARG_CASE("-stdout", 0) {
  510. output = NULL;
  511. argPos += 1;
  512. continue;
  513. }
  514. ARG_CASE("-legacy", 0) {
  515. legacyMode = true;
  516. argPos += 1;
  517. continue;
  518. }
  519. ARG_CASE("-nopreprocess", 0) {
  520. geometryPreproc = NO_PREPROCESS;
  521. argPos += 1;
  522. continue;
  523. }
  524. ARG_CASE("-windingpreprocess", 0) {
  525. geometryPreproc = WINDING_PREPROCESS;
  526. argPos += 1;
  527. continue;
  528. }
  529. ARG_CASE("-preprocess", 0) {
  530. geometryPreproc = FULL_PREPROCESS;
  531. argPos += 1;
  532. continue;
  533. }
  534. ARG_CASE("-nooverlap", 0) {
  535. generatorConfig.overlapSupport = false;
  536. argPos += 1;
  537. continue;
  538. }
  539. ARG_CASE("-overlap", 0) {
  540. generatorConfig.overlapSupport = true;
  541. argPos += 1;
  542. continue;
  543. }
  544. ARG_CASE("-noscanline", 0) {
  545. scanlinePass = false;
  546. argPos += 1;
  547. continue;
  548. }
  549. ARG_CASE("-scanline", 0) {
  550. scanlinePass = true;
  551. argPos += 1;
  552. continue;
  553. }
  554. ARG_CASE("-fillrule", 1) {
  555. scanlinePass = true;
  556. if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO;
  557. else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD;
  558. else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE;
  559. else if (!strcmp(argv[argPos+1], "negative")) fillRule = FILL_NEGATIVE;
  560. else
  561. puts("Unknown fill rule specified.");
  562. argPos += 2;
  563. continue;
  564. }
  565. ARG_CASE("-format", 1) {
  566. if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
  567. else if (!strcmp(argv[argPos+1], "png")) SET_FORMAT(PNG, "png");
  568. else if (!strcmp(argv[argPos+1], "bmp")) SET_FORMAT(BMP, "bmp");
  569. else if (!strcmp(argv[argPos+1], "tiff")) SET_FORMAT(TIFF, "tif");
  570. else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SET_FORMAT(TEXT, "txt");
  571. else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
  572. else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SET_FORMAT(BINARY, "bin");
  573. else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin");
  574. else if (!strcmp(argv[argPos+1], "binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin");
  575. else
  576. puts("Unknown format specified.");
  577. argPos += 2;
  578. continue;
  579. }
  580. ARG_CASE("-size", 2) {
  581. unsigned w, h;
  582. if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h))
  583. ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
  584. width = w, height = h;
  585. argPos += 3;
  586. continue;
  587. }
  588. ARG_CASE("-autoframe", 0) {
  589. autoFrame = true;
  590. argPos += 1;
  591. continue;
  592. }
  593. ARG_CASE("-range", 1) {
  594. double r;
  595. if (!(parseDouble(r, argv[argPos+1]) && r > 0))
  596. ABORT("Invalid range argument. Use -range <range> with a positive real number.");
  597. rangeMode = RANGE_UNIT;
  598. range = r;
  599. argPos += 2;
  600. continue;
  601. }
  602. ARG_CASE("-pxrange", 1) {
  603. double r;
  604. if (!(parseDouble(r, argv[argPos+1]) && r > 0))
  605. ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
  606. rangeMode = RANGE_PX;
  607. pxRange = r;
  608. argPos += 2;
  609. continue;
  610. }
  611. ARG_CASE("-scale", 1) {
  612. double s;
  613. if (!(parseDouble(s, argv[argPos+1]) && s > 0))
  614. ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
  615. scale = s;
  616. scaleSpecified = true;
  617. argPos += 2;
  618. continue;
  619. }
  620. ARG_CASE("-ascale", 2) {
  621. double sx, sy;
  622. if (!(parseDouble(sx, argv[argPos+1]) && parseDouble(sy, argv[argPos+2]) && sx > 0 && sy > 0))
  623. ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
  624. scale.set(sx, sy);
  625. scaleSpecified = true;
  626. argPos += 3;
  627. continue;
  628. }
  629. ARG_CASE("-translate", 2) {
  630. double tx, ty;
  631. if (!(parseDouble(tx, argv[argPos+1]) && parseDouble(ty, argv[argPos+2])))
  632. ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
  633. translate.set(tx, ty);
  634. argPos += 3;
  635. continue;
  636. }
  637. ARG_CASE("-angle", 1) {
  638. double at;
  639. if (!parseAngle(at, argv[argPos+1]))
  640. 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.");
  641. angleThreshold = at;
  642. argPos += 2;
  643. continue;
  644. }
  645. ARG_CASE("-errorcorrection", 1) {
  646. if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) {
  647. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
  648. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  649. } else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) {
  650. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  651. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE;
  652. } else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) {
  653. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  654. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  655. } else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) {
  656. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  657. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  658. } else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) {
  659. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
  660. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  661. } else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) {
  662. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
  663. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  664. } else if (!strcmp(argv[argPos+1], "edge-fast")) {
  665. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
  666. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  667. } else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) {
  668. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
  669. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  670. } else if (!strcmp(argv[argPos+1], "help")) {
  671. puts(errorCorrectionHelpText);
  672. return 0;
  673. } else
  674. puts("Unknown error correction mode. Use -errorcorrection help for more information.");
  675. explicitErrorCorrectionMode = true;
  676. argPos += 2;
  677. continue;
  678. }
  679. ARG_CASE("-errordeviationratio", 1) {
  680. double edr;
  681. if (!(parseDouble(edr, argv[argPos+1]) && edr > 0))
  682. ABORT("Invalid error deviation ratio. Use -errordeviationratio <ratio> with a positive real number.");
  683. generatorConfig.errorCorrection.minDeviationRatio = edr;
  684. argPos += 2;
  685. continue;
  686. }
  687. ARG_CASE("-errorimproveratio", 1) {
  688. double eir;
  689. if (!(parseDouble(eir, argv[argPos+1]) && eir > 0))
  690. ABORT("Invalid error improvement ratio. Use -errorimproveratio <ratio> with a positive real number.");
  691. generatorConfig.errorCorrection.minImproveRatio = eir;
  692. argPos += 2;
  693. continue;
  694. }
  695. ARG_CASE("-coloringstrategy", 1) {
  696. if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple;
  697. else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap;
  698. else if (!strcmp(argv[argPos+1], "distance")) edgeColoring = edgeColoringByDistance;
  699. else
  700. puts("Unknown coloring strategy specified.");
  701. argPos += 2;
  702. continue;
  703. }
  704. ARG_CASE("-edgecolors", 1) {
  705. static const char *allowed = " ?,cmwyCMWY";
  706. for (int i = 0; argv[argPos+1][i]; ++i) {
  707. for (int j = 0; allowed[j]; ++j)
  708. if (argv[argPos+1][i] == allowed[j])
  709. goto EDGE_COLOR_VERIFIED;
  710. 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.");
  711. EDGE_COLOR_VERIFIED:;
  712. }
  713. edgeAssignment = argv[argPos+1];
  714. argPos += 2;
  715. continue;
  716. }
  717. ARG_CASE("-distanceshift", 1) {
  718. double ds;
  719. if (!parseDouble(ds, argv[argPos+1]))
  720. ABORT("Invalid distance shift. Use -distanceshift <shift> with a real value.");
  721. outputDistanceShift = (float) ds;
  722. argPos += 2;
  723. continue;
  724. }
  725. ARG_CASE("-exportshape", 1) {
  726. shapeExport = argv[argPos+1];
  727. argPos += 2;
  728. continue;
  729. }
  730. ARG_CASE("-testrender", 3) {
  731. unsigned w, h;
  732. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  733. ABORT("Invalid arguments for test render. Use -testrender <output.png> <width> <height>.");
  734. testRender = argv[argPos+1];
  735. testWidth = w, testHeight = h;
  736. argPos += 4;
  737. continue;
  738. }
  739. ARG_CASE("-testrendermulti", 3) {
  740. unsigned w, h;
  741. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  742. ABORT("Invalid arguments for test render. Use -testrendermulti <output.png> <width> <height>.");
  743. testRenderMulti = argv[argPos+1];
  744. testWidthM = w, testHeightM = h;
  745. argPos += 4;
  746. continue;
  747. }
  748. ARG_CASE("-yflip", 0) {
  749. yFlip = true;
  750. argPos += 1;
  751. continue;
  752. }
  753. ARG_CASE("-printmetrics", 0) {
  754. printMetrics = true;
  755. argPos += 1;
  756. continue;
  757. }
  758. ARG_CASE("-estimateerror", 0) {
  759. estimateError = true;
  760. argPos += 1;
  761. continue;
  762. }
  763. ARG_CASE("-keeporder", 0) {
  764. orientation = KEEP;
  765. argPos += 1;
  766. continue;
  767. }
  768. ARG_CASE("-reverseorder", 0) {
  769. orientation = REVERSE;
  770. argPos += 1;
  771. continue;
  772. }
  773. ARG_CASE("-guessorder", 0) {
  774. orientation = GUESS;
  775. argPos += 1;
  776. continue;
  777. }
  778. ARG_CASE("-seed", 1) {
  779. if (!parseUnsignedLL(coloringSeed, argv[argPos+1]))
  780. ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
  781. argPos += 2;
  782. continue;
  783. }
  784. ARG_CASE("-help", 0) {
  785. puts(helpText);
  786. return 0;
  787. }
  788. printf("Unknown setting or insufficient parameters: %s\n", arg);
  789. suggestHelp = true;
  790. ++argPos;
  791. }
  792. if (suggestHelp)
  793. printf("Use -help for more information.\n");
  794. // Load input
  795. Vector2 svgDims;
  796. double glyphAdvance = 0;
  797. if (!inputType || !input)
  798. ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
  799. if (mode == MULTI_AND_TRUE && (format == BMP || (format == AUTO && output && cmpExtension(output, ".bmp"))))
  800. ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode.");
  801. Shape shape;
  802. switch (inputType) {
  803. case SVG: {
  804. if (!loadSvgShape(shape, input, svgPathIndex, &svgDims))
  805. ABORT("Failed to load shape from SVG file.");
  806. break;
  807. }
  808. case FONT: {
  809. if (!glyphIndexSpecified && !unicode)
  810. 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).");
  811. FreetypeHandle *ft = initializeFreetype();
  812. if (!ft) return -1;
  813. FontHandle *font = loadFont(ft, input);
  814. if (!font) {
  815. deinitializeFreetype(ft);
  816. ABORT("Failed to load font file.");
  817. }
  818. if (unicode)
  819. getGlyphIndex(glyphIndex, font, unicode);
  820. if (!loadGlyph(shape, font, glyphIndex, &glyphAdvance)) {
  821. destroyFont(font);
  822. deinitializeFreetype(ft);
  823. ABORT("Failed to load glyph from font file.");
  824. }
  825. destroyFont(font);
  826. deinitializeFreetype(ft);
  827. break;
  828. }
  829. case DESCRIPTION_ARG: {
  830. if (!readShapeDescription(input, shape, &skipColoring))
  831. ABORT("Parse error in shape description.");
  832. break;
  833. }
  834. case DESCRIPTION_STDIN: {
  835. if (!readShapeDescription(stdin, shape, &skipColoring))
  836. ABORT("Parse error in shape description.");
  837. break;
  838. }
  839. case DESCRIPTION_FILE: {
  840. FILE *file = fopen(input, "r");
  841. if (!file)
  842. ABORT("Failed to load shape description file.");
  843. if (!readShapeDescription(file, shape, &skipColoring))
  844. ABORT("Parse error in shape description.");
  845. fclose(file);
  846. break;
  847. }
  848. default:;
  849. }
  850. // Validate and normalize shape
  851. if (!shape.validate())
  852. ABORT("The geometry of the loaded shape is invalid.");
  853. switch (geometryPreproc) {
  854. case NO_PREPROCESS:
  855. break;
  856. case WINDING_PREPROCESS:
  857. shape.orientContours();
  858. break;
  859. case FULL_PREPROCESS:
  860. #ifdef MSDFGEN_USE_SKIA
  861. if (!resolveShapeGeometry(shape))
  862. puts("Shape geometry preprocessing failed, skipping.");
  863. else if (skipColoring) {
  864. skipColoring = false;
  865. puts("Note: Input shape coloring won't be preserved due to geometry preprocessing");
  866. }
  867. #else
  868. ABORT("Shape geometry preprocessing (-preprocess) is not available in this version because the Skia library is not present.");
  869. #endif
  870. break;
  871. }
  872. shape.normalize();
  873. if (yFlip)
  874. shape.inverseYAxis = !shape.inverseYAxis;
  875. double avgScale = .5*(scale.x+scale.y);
  876. Shape::Bounds bounds = { };
  877. if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
  878. bounds = shape.getBounds();
  879. // Auto-frame
  880. if (autoFrame) {
  881. double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
  882. Vector2 frame(width, height);
  883. double m = .5+(double) outputDistanceShift;
  884. if (!scaleSpecified) {
  885. if (rangeMode == RANGE_UNIT)
  886. l -= m*range, b -= m*range, r += m*range, t += m*range;
  887. else
  888. frame -= 2*m*pxRange;
  889. }
  890. if (l >= r || b >= t)
  891. l = 0, b = 0, r = 1, t = 1;
  892. if (frame.x <= 0 || frame.y <= 0)
  893. ABORT("Cannot fit the specified pixel range.");
  894. Vector2 dims(r-l, t-b);
  895. if (scaleSpecified)
  896. translate = .5*(frame/scale-dims)-Vector2(l, b);
  897. else {
  898. if (dims.x*frame.y < dims.y*frame.x) {
  899. translate.set(.5*(frame.x/frame.y*dims.y-dims.x)-l, -b);
  900. scale = avgScale = frame.y/dims.y;
  901. } else {
  902. translate.set(-l, .5*(frame.y/frame.x*dims.x-dims.y)-b);
  903. scale = avgScale = frame.x/dims.x;
  904. }
  905. }
  906. if (rangeMode == RANGE_PX && !scaleSpecified)
  907. translate += m*pxRange/scale;
  908. }
  909. if (rangeMode == RANGE_PX)
  910. range = pxRange/min(scale.x, scale.y);
  911. // Print metrics
  912. if (mode == METRICS || printMetrics) {
  913. FILE *out = stdout;
  914. if (mode == METRICS && outputSpecified)
  915. out = fopen(output, "w");
  916. if (!out)
  917. ABORT("Failed to write output file.");
  918. if (shape.inverseYAxis)
  919. fprintf(out, "inverseY = true\n");
  920. if (bounds.r >= bounds.l && bounds.t >= bounds.b)
  921. fprintf(out, "bounds = %.12g, %.12g, %.12g, %.12g\n", bounds.l, bounds.b, bounds.r, bounds.t);
  922. if (svgDims.x != 0 && svgDims.y != 0)
  923. fprintf(out, "dimensions = %.12g, %.12g\n", svgDims.x, svgDims.y);
  924. if (glyphAdvance != 0)
  925. fprintf(out, "advance = %.12g\n", glyphAdvance);
  926. if (autoFrame) {
  927. if (!scaleSpecified)
  928. fprintf(out, "scale = %.12g\n", avgScale);
  929. fprintf(out, "translate = %.12g, %.12g\n", translate.x, translate.y);
  930. }
  931. if (rangeMode == RANGE_PX)
  932. fprintf(out, "range = %.12g\n", range);
  933. if (mode == METRICS && outputSpecified)
  934. fclose(out);
  935. }
  936. // Compute output
  937. Projection projection(scale, translate);
  938. Bitmap<float, 1> sdf;
  939. Bitmap<float, 3> msdf;
  940. Bitmap<float, 4> mtsdf;
  941. MSDFGeneratorConfig postErrorCorrectionConfig(generatorConfig);
  942. if (scanlinePass) {
  943. if (explicitErrorCorrectionMode && generatorConfig.errorCorrection.distanceCheckMode != ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) {
  944. const char *fallbackModeName = "unknown";
  945. switch (generatorConfig.errorCorrection.mode) {
  946. case ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break;
  947. case ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break;
  948. case ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break;
  949. case ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break;
  950. }
  951. printf("Selected error correction mode not compatible with scanline pass, falling back to %s.\n", fallbackModeName);
  952. }
  953. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
  954. postErrorCorrectionConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  955. }
  956. switch (mode) {
  957. case SINGLE: {
  958. sdf = Bitmap<float, 1>(width, height);
  959. if (legacyMode)
  960. generateSDF_legacy(sdf, shape, range, scale, translate);
  961. else
  962. generateSDF(sdf, shape, projection, range, generatorConfig);
  963. break;
  964. }
  965. case PSEUDO: {
  966. sdf = Bitmap<float, 1>(width, height);
  967. if (legacyMode)
  968. generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
  969. else
  970. generatePseudoSDF(sdf, shape, projection, range, generatorConfig);
  971. break;
  972. }
  973. case MULTI: {
  974. if (!skipColoring)
  975. edgeColoring(shape, angleThreshold, coloringSeed);
  976. if (edgeAssignment)
  977. parseColoring(shape, edgeAssignment);
  978. msdf = Bitmap<float, 3>(width, height);
  979. if (legacyMode)
  980. generateMSDF_legacy(msdf, shape, range, scale, translate, generatorConfig.errorCorrection);
  981. else
  982. generateMSDF(msdf, shape, projection, range, generatorConfig);
  983. break;
  984. }
  985. case MULTI_AND_TRUE: {
  986. if (!skipColoring)
  987. edgeColoring(shape, angleThreshold, coloringSeed);
  988. if (edgeAssignment)
  989. parseColoring(shape, edgeAssignment);
  990. mtsdf = Bitmap<float, 4>(width, height);
  991. if (legacyMode)
  992. generateMTSDF_legacy(mtsdf, shape, range, scale, translate, generatorConfig.errorCorrection);
  993. else
  994. generateMTSDF(mtsdf, shape, projection, range, generatorConfig);
  995. break;
  996. }
  997. default:;
  998. }
  999. if (orientation == GUESS) {
  1000. // Get sign of signed distance outside bounds
  1001. Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
  1002. double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
  1003. orientation = distance <= 0 ? KEEP : REVERSE;
  1004. }
  1005. if (orientation == REVERSE) {
  1006. switch (mode) {
  1007. case SINGLE:
  1008. case PSEUDO:
  1009. invertColor<1>(sdf);
  1010. break;
  1011. case MULTI:
  1012. invertColor<3>(msdf);
  1013. break;
  1014. case MULTI_AND_TRUE:
  1015. invertColor<4>(mtsdf);
  1016. break;
  1017. default:;
  1018. }
  1019. }
  1020. if (scanlinePass) {
  1021. switch (mode) {
  1022. case SINGLE:
  1023. case PSEUDO:
  1024. distanceSignCorrection(sdf, shape, projection, fillRule);
  1025. break;
  1026. case MULTI:
  1027. distanceSignCorrection(msdf, shape, projection, fillRule);
  1028. msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
  1029. break;
  1030. case MULTI_AND_TRUE:
  1031. distanceSignCorrection(mtsdf, shape, projection, fillRule);
  1032. msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
  1033. break;
  1034. default:;
  1035. }
  1036. }
  1037. if (outputDistanceShift) {
  1038. float *pixel = NULL, *pixelsEnd = NULL;
  1039. switch (mode) {
  1040. case SINGLE:
  1041. case PSEUDO:
  1042. pixel = (float *) sdf;
  1043. pixelsEnd = pixel+1*sdf.width()*sdf.height();
  1044. break;
  1045. case MULTI:
  1046. pixel = (float *) msdf;
  1047. pixelsEnd = pixel+3*msdf.width()*msdf.height();
  1048. break;
  1049. case MULTI_AND_TRUE:
  1050. pixel = (float *) mtsdf;
  1051. pixelsEnd = pixel+4*mtsdf.width()*mtsdf.height();
  1052. break;
  1053. default:;
  1054. }
  1055. while (pixel < pixelsEnd)
  1056. *pixel++ += outputDistanceShift;
  1057. }
  1058. // Save output
  1059. if (shapeExport) {
  1060. FILE *file = fopen(shapeExport, "w");
  1061. if (file) {
  1062. writeShapeDescription(file, shape);
  1063. fclose(file);
  1064. } else
  1065. puts("Failed to write shape export file.");
  1066. }
  1067. const char *error = NULL;
  1068. switch (mode) {
  1069. case SINGLE:
  1070. case PSEUDO:
  1071. error = writeOutput<1>(sdf, output, format);
  1072. if (error)
  1073. ABORT(error);
  1074. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1075. simulate8bit(sdf);
  1076. if (estimateError) {
  1077. double sdfError = estimateSDFError(sdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1078. printf("SDF error ~ %e\n", sdfError);
  1079. }
  1080. if (testRenderMulti) {
  1081. Bitmap<float, 3> render(testWidthM, testHeightM);
  1082. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  1083. if (!savePng(render, testRenderMulti))
  1084. puts("Failed to write test render file.");
  1085. }
  1086. if (testRender) {
  1087. Bitmap<float, 1> render(testWidth, testHeight);
  1088. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  1089. if (!savePng(render, testRender))
  1090. puts("Failed to write test render file.");
  1091. }
  1092. break;
  1093. case MULTI:
  1094. error = writeOutput<3>(msdf, output, format);
  1095. if (error)
  1096. ABORT(error);
  1097. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1098. simulate8bit(msdf);
  1099. if (estimateError) {
  1100. double sdfError = estimateSDFError(msdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1101. printf("SDF error ~ %e\n", sdfError);
  1102. }
  1103. if (testRenderMulti) {
  1104. Bitmap<float, 3> render(testWidthM, testHeightM);
  1105. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1106. if (!savePng(render, testRenderMulti))
  1107. puts("Failed to write test render file.");
  1108. }
  1109. if (testRender) {
  1110. Bitmap<float, 1> render(testWidth, testHeight);
  1111. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1112. if (!savePng(render, testRender))
  1113. ABORT("Failed to write test render file.");
  1114. }
  1115. break;
  1116. case MULTI_AND_TRUE:
  1117. error = writeOutput<4>(mtsdf, output, format);
  1118. if (error)
  1119. ABORT(error);
  1120. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1121. simulate8bit(mtsdf);
  1122. if (estimateError) {
  1123. double sdfError = estimateSDFError(mtsdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1124. printf("SDF error ~ %e\n", sdfError);
  1125. }
  1126. if (testRenderMulti) {
  1127. Bitmap<float, 4> render(testWidthM, testHeightM);
  1128. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1129. if (!savePng(render, testRenderMulti))
  1130. puts("Failed to write test render file.");
  1131. }
  1132. if (testRender) {
  1133. Bitmap<float, 1> render(testWidth, testHeight);
  1134. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1135. if (!savePng(render, testRender))
  1136. ABORT("Failed to write test render file.");
  1137. }
  1138. break;
  1139. default:;
  1140. }
  1141. return 0;
  1142. }
  1143. #endif