main.cpp 48 KB

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