diff --git a/README.md b/README.md index 470b070..49c93db 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The following comparison demonstrates the improvement in image quality. ![demo-sdf32](https://user-images.githubusercontent.com/18639794/106391906-e7aadc00-63ef-11eb-8f84-d402d0dd9174.png) - To learn more about this method, you can read my [Master's thesis](https://github.com/Chlumsky/msdfgen/files/3050967/thesis.pdf). -- Check out my [MSDF-Atlas-Gen](https://github.com/Chlumsky/msdf-atlas-gen) if you want to generate entire glyph atlases for text rendering. +- Check out my [MSDF-Atlas-Gen](https://github.com/Chlumsky/msdf-atlas-gen) if you want to generate entire font atlases for text rendering. - See what's new in the [changelog](CHANGELOG.md). ## Getting started @@ -68,7 +68,7 @@ The complete list of available options can be printed with **-help**. Some of the important ones are: - **-o \** – specifies the output file name. The desired format will be deduced from the extension (png, bmp, tif, txt, bin). Otherwise, use -format. - - **-size \ \** – specifies the dimensions of the output distance field (in pixels). + - **-dimensions \ \** – specifies the dimensions of the output distance field (in pixels). - **-range \**, **-pxrange \** – specifies the width of the range around the shape between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly. - **-scale \** – sets the scale used to convert shape units to distance field pixels. @@ -87,7 +87,7 @@ Some of the important ones are: For example, ``` -msdfgen.exe msdf -font C:\Windows\Fonts\arialbd.ttf 'M' -o msdf.png -size 32 32 -pxrange 4 -autoframe -testrender render.png 1024 1024 +msdfgen.exe msdf -font C:\Windows\Fonts\arialbd.ttf 'M' -o msdf.png -dimensions 32 32 -pxrange 4 -autoframe -testrender render.png 1024 1024 ``` will take the glyph capital M from the Arial Bold typeface, generate a 32×32 multi-channel distance field diff --git a/main.cpp b/main.cpp index 3569ff9..1a9058a 100644 --- a/main.cpp +++ b/main.cpp @@ -394,6 +394,8 @@ static const char *const helpText = "\tAutomatically scales (unless specified) and translates the shape to fit.\n" " -coloringstrategy \n" "\tSelects the strategy of the edge coloring heuristic.\n" + " -dimensions \n" + "\tSets the dimensions of the output image.\n" " -distanceshift \n" "\tShifts all normalized distances in the output distance field by this value.\n" " -edgecolors \n" @@ -453,8 +455,6 @@ static const char *const helpText = #endif " -seed \n" "\tSets the random seed for edge coloring heuristic.\n" - " -size \n" - "\tSets the dimensions of the output image.\n" " -stdout\n" "\tPrints the output instead of storing it in a file. Only text formats are supported.\n" " -testrender \n" @@ -580,8 +580,10 @@ int main(int argc, const char *const *argv) { bool suggestHelp = false; while (argPos < argc) { const char *arg = argv[argPos]; - #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc) + #define ARG_CASE(s, p) if ((!strcmp(arg, s)) && argPos+(p) < argc && (++argPos, true)) + #define ARG_CASE_OR ) || !strcmp(arg, #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; } + #define ARG_IS(s) (!strcmp(argv[argPos], s)) #define SET_FORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false) // Accept arguments prefixed with -- instead of - @@ -597,8 +599,7 @@ int main(int argc, const char *const *argv) { #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_SVG) ARG_CASE("-svg", 1) { inputType = SVG; - input = argv[argPos+1]; - argPos += 2; + input = argv[argPos++]; continue; } #endif @@ -609,9 +610,9 @@ int main(int argc, const char *const *argv) { #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS || (!strcmp(arg, "-varfont") && (inputType = VAR_FONT, true)) #endif - )) { - input = argv[argPos+1]; - const char *charArg = argv[argPos+2]; + ) && (++argPos, true)) { + input = argv[argPos++]; + const char *charArg = argv[argPos++]; unsigned gi; switch (charArg[0]) { case 'G': case 'g': @@ -626,7 +627,6 @@ int main(int argc, const char *const *argv) { default: parseUnicode(unicode, charArg); } - argPos += 3; continue; } #else @@ -642,309 +642,271 @@ int main(int argc, const char *const *argv) { #endif ARG_CASE("-defineshape", 1) { inputType = DESCRIPTION_ARG; - input = argv[argPos+1]; - argPos += 2; + input = argv[argPos++]; continue; } ARG_CASE("-stdin", 0) { inputType = DESCRIPTION_STDIN; input = "stdin"; - argPos += 1; continue; } ARG_CASE("-shapedesc", 1) { inputType = DESCRIPTION_FILE; - input = argv[argPos+1]; - argPos += 2; + input = argv[argPos++]; continue; } - ARG_CASE("-o", 1) { - output = argv[argPos+1]; + ARG_CASE("-o" ARG_CASE_OR "-out" ARG_CASE_OR "-output" ARG_CASE_OR "-imageout", 1) { + output = argv[argPos++]; outputSpecified = true; - argPos += 2; continue; } ARG_CASE("-stdout", 0) { output = NULL; - argPos += 1; continue; } ARG_CASE("-legacy", 0) { legacyMode = true; - argPos += 1; continue; } ARG_CASE("-nopreprocess", 0) { geometryPreproc = NO_PREPROCESS; - argPos += 1; continue; } ARG_CASE("-windingpreprocess", 0) { geometryPreproc = WINDING_PREPROCESS; - argPos += 1; continue; } ARG_CASE("-preprocess", 0) { geometryPreproc = FULL_PREPROCESS; - argPos += 1; continue; } ARG_CASE("-nooverlap", 0) { generatorConfig.overlapSupport = false; - argPos += 1; continue; } ARG_CASE("-overlap", 0) { generatorConfig.overlapSupport = true; - argPos += 1; continue; } ARG_CASE("-noscanline", 0) { scanlinePass = false; - argPos += 1; continue; } ARG_CASE("-scanline", 0) { scanlinePass = true; - argPos += 1; continue; } ARG_CASE("-fillrule", 1) { scanlinePass = true; - if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO; - else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD; - else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE; - else if (!strcmp(argv[argPos+1], "negative")) fillRule = FILL_NEGATIVE; + if (ARG_IS("nonzero")) fillRule = FILL_NONZERO; + else if (ARG_IS("evenodd") || ARG_IS("odd")) fillRule = FILL_ODD; + else if (ARG_IS("positive")) fillRule = FILL_POSITIVE; + else if (ARG_IS("negative")) fillRule = FILL_NEGATIVE; else fputs("Unknown fill rule specified.\n", stderr); - argPos += 2; + ++argPos; continue; } ARG_CASE("-format", 1) { - if (!strcmp(argv[argPos+1], "auto")) format = AUTO; + if (ARG_IS("auto")) format = AUTO; #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG) - else if (!strcmp(argv[argPos+1], "png")) SET_FORMAT(PNG, "png"); + else if (ARG_IS("png")) SET_FORMAT(PNG, "png"); #else - else if (!strcmp(argv[argPos+1], "png")) + else if (ARG_IS("png")) fputs("PNG format is not available in core-only version.\n", stderr); #endif - else if (!strcmp(argv[argPos+1], "bmp")) SET_FORMAT(BMP, "bmp"); - else if (!strcmp(argv[argPos+1], "tiff") || !strcmp(argv[argPos+1], "tif")) SET_FORMAT(TIFF, "tif"); - else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SET_FORMAT(TEXT, "txt"); - else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt"); - else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SET_FORMAT(BINARY, "bin"); - else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin"); - else if (!strcmp(argv[argPos+1], "binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin"); + else if (ARG_IS("bmp")) SET_FORMAT(BMP, "bmp"); + else if (ARG_IS("tiff") || ARG_IS("tif")) SET_FORMAT(TIFF, "tif"); + else if (ARG_IS("text") || ARG_IS("txt")) SET_FORMAT(TEXT, "txt"); + else if (ARG_IS("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt"); + else if (ARG_IS("bin") || ARG_IS("binary")) SET_FORMAT(BINARY, "bin"); + else if (ARG_IS("binfloat") || ARG_IS("binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin"); + else if (ARG_IS("binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin"); else fputs("Unknown format specified.\n", stderr); - argPos += 2; + ++argPos; continue; } - ARG_CASE("-size", 2) { + ARG_CASE("-dimensions" ARG_CASE_OR "-size", 2) { unsigned w, h; - if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h)) - ABORT("Invalid size arguments. Use -size with two positive integers."); + if (!(parseUnsigned(w, argv[argPos++]) && parseUnsigned(h, argv[argPos++]) && w && h)) + ABORT("Invalid dimensions. Use -dimensions with two positive integers."); width = w, height = h; - argPos += 3; continue; } ARG_CASE("-autoframe", 0) { autoFrame = true; - argPos += 1; continue; } - ARG_CASE("-range", 1) { + ARG_CASE("-range" ARG_CASE_OR "-unitrange", 1) { double r; - if (!(parseDouble(r, argv[argPos+1]) && r > 0)) + if (!(parseDouble(r, argv[argPos++]) && r > 0)) ABORT("Invalid range argument. Use -range with a positive real number."); rangeMode = RANGE_UNIT; range = r; - argPos += 2; continue; } ARG_CASE("-pxrange", 1) { double r; - if (!(parseDouble(r, argv[argPos+1]) && r > 0)) + if (!(parseDouble(r, argv[argPos++]) && r > 0)) ABORT("Invalid range argument. Use -pxrange with a positive real number."); rangeMode = RANGE_PX; pxRange = r; - argPos += 2; continue; } ARG_CASE("-scale", 1) { double s; - if (!(parseDouble(s, argv[argPos+1]) && s > 0)) + if (!(parseDouble(s, argv[argPos++]) && s > 0)) ABORT("Invalid scale argument. Use -scale with a positive real number."); scale = s; scaleSpecified = true; - argPos += 2; continue; } ARG_CASE("-ascale", 2) { double sx, sy; - if (!(parseDouble(sx, argv[argPos+1]) && parseDouble(sy, argv[argPos+2]) && sx > 0 && sy > 0)) + if (!(parseDouble(sx, argv[argPos++]) && parseDouble(sy, argv[argPos++]) && sx > 0 && sy > 0)) ABORT("Invalid scale arguments. Use -ascale with two positive real numbers."); scale.set(sx, sy); scaleSpecified = true; - argPos += 3; continue; } ARG_CASE("-translate", 2) { double tx, ty; - if (!(parseDouble(tx, argv[argPos+1]) && parseDouble(ty, argv[argPos+2]))) + if (!(parseDouble(tx, argv[argPos++]) && parseDouble(ty, argv[argPos++]))) ABORT("Invalid translate arguments. Use -translate with two real numbers."); translate.set(tx, ty); - argPos += 3; continue; } ARG_CASE("-angle", 1) { double at; - if (!parseAngle(at, argv[argPos+1])) + if (!parseAngle(at, argv[argPos++])) ABORT("Invalid angle threshold. Use -angle with a positive real number less than PI or a value in degrees followed by 'd' below 180d."); angleThreshold = at; - argPos += 2; continue; } ARG_CASE("-errorcorrection", 1) { - if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) { + if (ARG_IS("disable") || ARG_IS("disabled") || ARG_IS("0") || ARG_IS("none") || ARG_IS("false")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) { + } else if (ARG_IS("default") || ARG_IS("auto") || ARG_IS("auto-mixed") || ARG_IS("mixed")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE; - } else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) { + } else if (ARG_IS("auto-fast") || ARG_IS("fast")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) { + } else if (ARG_IS("auto-full") || ARG_IS("full")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) { + } else if (ARG_IS("distance") || ARG_IS("distance-fast") || ARG_IS("indiscriminate") || ARG_IS("indiscriminate-fast")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) { + } else if (ARG_IS("distance-full") || ARG_IS("indiscriminate-full")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "edge-fast")) { + } else if (ARG_IS("edge-fast")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) { + } else if (ARG_IS("edge") || ARG_IS("edge-full")) { generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY; generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; - } else if (!strcmp(argv[argPos+1], "help")) { + } else if (ARG_IS("help")) { puts(errorCorrectionHelpText); return 0; } else fputs("Unknown error correction mode. Use -errorcorrection help for more information.\n", stderr); + ++argPos; explicitErrorCorrectionMode = true; - argPos += 2; continue; } ARG_CASE("-errordeviationratio", 1) { double edr; - if (!(parseDouble(edr, argv[argPos+1]) && edr > 0)) + if (!(parseDouble(edr, argv[argPos++]) && edr > 0)) ABORT("Invalid error deviation ratio. Use -errordeviationratio with a positive real number."); generatorConfig.errorCorrection.minDeviationRatio = edr; - argPos += 2; continue; } ARG_CASE("-errorimproveratio", 1) { double eir; - if (!(parseDouble(eir, argv[argPos+1]) && eir > 0)) + if (!(parseDouble(eir, argv[argPos++]) && eir > 0)) ABORT("Invalid error improvement ratio. Use -errorimproveratio with a positive real number."); generatorConfig.errorCorrection.minImproveRatio = eir; - argPos += 2; continue; } - ARG_CASE("-coloringstrategy", 1) { - if (!strcmp(argv[argPos+1], "simple")) - edgeColoring = &edgeColoringSimple; - else if (!strcmp(argv[argPos+1], "inktrap")) - edgeColoring = &edgeColoringInkTrap; - else if (!strcmp(argv[argPos+1], "distance")) - edgeColoring = &edgeColoringByDistance; + ARG_CASE("-coloringstrategy" ARG_CASE_OR "-edgecoloring", 1) { + if (ARG_IS("simple")) edgeColoring = &edgeColoringSimple; + else if (ARG_IS("inktrap")) edgeColoring = &edgeColoringInkTrap; + else if (ARG_IS("distance")) edgeColoring = &edgeColoringByDistance; else fputs("Unknown coloring strategy specified.\n", stderr); - argPos += 2; + ++argPos; continue; } ARG_CASE("-edgecolors", 1) { static const char *allowed = " ?,cmwyCMWY"; - for (int i = 0; argv[argPos+1][i]; ++i) { + for (int i = 0; argv[argPos][i]; ++i) { for (int j = 0; allowed[j]; ++j) - if (argv[argPos+1][i] == allowed[j]) + if (argv[argPos][i] == allowed[j]) goto EDGE_COLOR_VERIFIED; ABORT("Invalid edge coloring sequence. Use -edgecolors with only the colors C, M, Y, and W. Separate contours by commas and use ? to keep the default assigment for a contour."); EDGE_COLOR_VERIFIED:; } - edgeAssignment = argv[argPos+1]; - argPos += 2; + edgeAssignment = argv[argPos++]; continue; } ARG_CASE("-distanceshift", 1) { double ds; - if (!parseDouble(ds, argv[argPos+1])) + if (!parseDouble(ds, argv[argPos++])) ABORT("Invalid distance shift. Use -distanceshift with a real value."); outputDistanceShift = (float) ds; - argPos += 2; continue; } ARG_CASE("-exportshape", 1) { - shapeExport = argv[argPos+1]; - argPos += 2; + shapeExport = argv[argPos++]; continue; } ARG_CASE("-testrender", 3) { unsigned w, h; - if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h) + testRender = argv[argPos++]; + if (!(parseUnsigned(w, argv[argPos++]) && parseUnsigned(h, argv[argPos++]) && (int) w > 0 && (int) h > 0)) ABORT("Invalid arguments for test render. Use -testrender ."); - testRender = argv[argPos+1]; testWidth = w, testHeight = h; - argPos += 4; continue; } ARG_CASE("-testrendermulti", 3) { unsigned w, h; - if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h) + testRenderMulti = argv[argPos++]; + if (!(parseUnsigned(w, argv[argPos++]) && parseUnsigned(h, argv[argPos++]) && (int) w > 0 && (int) h > 0)) ABORT("Invalid arguments for test render. Use -testrendermulti ."); - testRenderMulti = argv[argPos+1]; testWidthM = w, testHeightM = h; - argPos += 4; continue; } ARG_CASE("-yflip", 0) { yFlip = true; - argPos += 1; continue; } ARG_CASE("-printmetrics", 0) { printMetrics = true; - argPos += 1; continue; } ARG_CASE("-estimateerror", 0) { estimateError = true; - argPos += 1; continue; } ARG_CASE("-keeporder", 0) { orientation = KEEP; - argPos += 1; continue; } ARG_CASE("-reverseorder", 0) { orientation = REVERSE; - argPos += 1; continue; } ARG_CASE("-guessorder", 0) { orientation = GUESS; - argPos += 1; continue; } ARG_CASE("-seed", 1) { - if (!parseUnsignedLL(coloringSeed, argv[argPos+1])) + if (!parseUnsignedLL(coloringSeed, argv[argPos++])) ABORT("Invalid seed. Use -seed with N being a non-negative integer."); - argPos += 2; continue; } ARG_CASE("-version", 0) { @@ -955,9 +917,8 @@ int main(int argc, const char *const *argv) { puts(helpText); return 0; } - fprintf(stderr, "Unknown setting or insufficient parameters: %s\n", argv[argPos]); + fprintf(stderr, "Unknown setting or insufficient parameters: %s\n", argv[argPos++]); suggestHelp = true; - ++argPos; } if (suggestHelp) fprintf(stderr, "Use -help for more information.\n");