From ae441c998920bb56b8b40346f2b287dd4497e334 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Sun, 3 Mar 2024 20:43:56 +0100 Subject: [PATCH] Origin pixel alignment per axis, CLI setting --- README.md | 1 + msdf-atlas-gen/GlyphGeometry.cpp | 53 +++++++++++++---------------- msdf-atlas-gen/GlyphGeometry.h | 5 ++- msdf-atlas-gen/TightAtlasPacker.cpp | 11 +++++- msdf-atlas-gen/TightAtlasPacker.h | 4 +++ msdf-atlas-gen/main.cpp | 32 ++++++++++++++--- 6 files changed, 68 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 30d5949..97534c8 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Any non-empty subset of the following may be specified: - `-minsize ` – sets the minimum size. The largest possible size that fits the same atlas dimensions will be used - `-emrange ` – sets the distance field range in EM's - `-pxrange ` (default = 2) – sets the distance field range in output pixels +- `-pxalign ` (default = vertical) – enables or disables alignment of glyph's origin point with the pixel grid ### Distance field generator settings diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp index 13aee57..dfb4a17 100644 --- a/msdf-atlas-gen/GlyphGeometry.cpp +++ b/msdf-atlas-gen/GlyphGeometry.cpp @@ -51,32 +51,11 @@ void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned l fn(shape, angleThreshold, seed); } -void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) { - scale *= geometryScale; - range /= geometryScale; - box.range = range; - box.scale = scale; - if (bounds.l < bounds.r && bounds.b < bounds.t) { - double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; - l -= .5*range, b -= .5*range; - r += .5*range, t += .5*range; - if (miterLimit > 0) - shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); - int sl = (int) floor(scale*l-.5); - int sb = (int) floor(scale*b-.5); - int sr = (int) ceil(scale*r+.5); - int st = (int) ceil(scale*t+.5); - box.rect.w = sr-sl; - box.rect.h = st-sb; - box.translate.x = -sl/scale; - box.translate.y = -sb/scale; - } else { - box.rect.w = 0, box.rect.h = 0; - box.translate = msdfgen::Vector2(); - } +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool alignOrigin) { + wrapBox(scale, range, miterLimit, alignOrigin, alignOrigin); } -void GlyphGeometry::wrapBoxUnaligned(double scale, double range, double miterLimit) { +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool alignOriginX, bool alignOriginY) { scale *= geometryScale; range /= geometryScale; box.range = range; @@ -87,12 +66,26 @@ void GlyphGeometry::wrapBoxUnaligned(double scale, double range, double miterLim r += .5*range, t += .5*range; if (miterLimit > 0) shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); - double w = scale*(r-l); - double h = scale*(t-b); - box.rect.w = (int) ceil(w)+1; - box.rect.h = (int) ceil(h)+1; - box.translate.x = -l+.5*(box.rect.w-w)/scale; - box.translate.y = -b+.5*(box.rect.h-h)/scale; + if (alignOriginX) { + int sl = (int) floor(scale*l-.5); + int sr = (int) ceil(scale*r+.5); + box.rect.w = sr-sl; + box.translate.x = -sl/scale; + } else { + double w = scale*(r-l); + box.rect.w = (int) ceil(w)+1; + box.translate.x = -l+.5*(box.rect.w-w)/scale; + } + if (alignOriginY) { + int sb = (int) floor(scale*b-.5); + int st = (int) ceil(scale*t+.5); + box.rect.h = st-sb; + box.translate.y = -sb/scale; + } else { + double h = scale*(t-b); + box.rect.h = (int) ceil(h)+1; + box.translate.y = -b+.5*(box.rect.h-h)/scale; + } } else { box.rect.w = 0, box.rect.h = 0; box.translate = msdfgen::Vector2(); diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h index 10a791c..e3ffe20 100644 --- a/msdf-atlas-gen/GlyphGeometry.h +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -20,9 +20,8 @@ public: /// Applies edge coloring to glyph shape void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed); /// Computes the dimensions of the glyph's box as well as the transformation for the generator function - void wrapBox(double scale, double range, double miterLimit); - /// Computes the dimensions of the glyph's box and the transformation for the generator function, allowing origin to be located at a non-integer coordinate - void wrapBoxUnaligned(double scale, double range, double miterLimit); + void wrapBox(double scale, double range, double miterLimit, bool alignOrigin = false); + void wrapBox(double scale, double range, double miterLimit, bool alignOriginX, bool alignOriginY); /// Sets the glyph's box's position in the atlas void placeBox(int x, int y); /// Sets the glyph's box's rectangle in the atlas diff --git a/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp index 1396cdf..6c66678 100644 --- a/msdf-atlas-gen/TightAtlasPacker.cpp +++ b/msdf-atlas-gen/TightAtlasPacker.cpp @@ -17,6 +17,7 @@ TightAtlasPacker::TightAtlasPacker() : unitRange(0), pxRange(0), miterLimit(0), + alignOriginX(false), alignOriginY(false), scaleMaximizationTolerance(.001) { } @@ -30,7 +31,7 @@ int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstr for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { if (!glyph->isWhitespace()) { Rectangle rect = { }; - glyph->wrapBox(scale, range, miterLimit); + glyph->wrapBox(scale, range, miterLimit, alignOriginX, alignOriginY); glyph->getBoxSize(rect.w, rect.h); if (rect.w > 0 && rect.h > 0) { rectangles.push_back(rect); @@ -155,6 +156,14 @@ void TightAtlasPacker::setMiterLimit(double miterLimit) { this->miterLimit = miterLimit; } +void TightAtlasPacker::setOriginAlignment(bool align) { + alignOriginX = align, alignOriginY = align; +} + +void TightAtlasPacker::setOriginAlignment(bool alignX, bool alignY) { + alignOriginX = alignX, alignOriginY = alignY; +} + void TightAtlasPacker::getDimensions(int &width, int &height) const { width = this->width, height = this->height; } diff --git a/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h index 7c54feb..70849e6 100644 --- a/msdf-atlas-gen/TightAtlasPacker.h +++ b/msdf-atlas-gen/TightAtlasPacker.h @@ -44,6 +44,9 @@ public: void setPixelRange(double pxRange); /// Sets the miter limit for bounds computation void setMiterLimit(double miterLimit); + /// Sets whether each glyph's origin point should stay aligned with the pixel grid + void setOriginAlignment(bool align); + void setOriginAlignment(bool alignX, bool alignY); /// Outputs the atlas's final dimensions void getDimensions(int &width, int &height) const; @@ -61,6 +64,7 @@ private: double unitRange; double pxRange; double miterLimit; + bool alignOriginX, alignOriginY; double scaleMaximizationTolerance; int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const; diff --git a/msdf-atlas-gen/main.cpp b/msdf-atlas-gen/main.cpp index cac83bc..7453824 100644 --- a/msdf-atlas-gen/main.cpp +++ b/msdf-atlas-gen/main.cpp @@ -111,6 +111,8 @@ GLYPH CONFIGURATION Specifies the SDF distance range in EM's. -pxrange Specifies the SDF distance range in output pixels. The default value is 2. + -pxalign + Specifies whether each glyph's origin point should be aligned with the pixel grid. -nokerning Disables inclusion of kerning pair table in output files. @@ -250,6 +252,7 @@ struct Configuration { double pxRange; double angleThreshold; double miterLimit; + bool alignOriginX, alignOriginY; void (*edgeColoring)(msdfgen::Shape &, double, unsigned long long); bool expensiveColoring; unsigned long long coloringSeed; @@ -340,6 +343,7 @@ int main(int argc, const char * const *argv) { TightAtlasPacker::DimensionsConstraint atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE; config.angleThreshold = DEFAULT_ANGLE_THRESHOLD; config.miterLimit = DEFAULT_MITER_LIMIT; + config.alignOriginX = false, config.alignOriginY = true; config.threadCount = 0; // Parse command line @@ -561,6 +565,20 @@ int main(int argc, const char * const *argv) { ++argPos; continue; } + ARG_CASE("-pxalign", 1) { + if (!strcmp(argv[argPos+1], "off") || !memcmp(argv[argPos+1], "disable", 7) || !strcmp(argv[argPos+1], "0") || argv[argPos+1][0] == 'n') + config.alignOriginX = false, config.alignOriginY = false; + else if (!strcmp(argv[argPos+1], "on") || !memcmp(argv[argPos+1], "enable", 6) || !strcmp(argv[argPos+1], "1") || argv[argPos+1][0] == 'y') + config.alignOriginX = true, config.alignOriginY = true; + else if (argv[argPos+1][0] == 'h') + config.alignOriginX = true, config.alignOriginY = false; + else if (argv[argPos+1][0] == 'v' || !strcmp(argv[argPos+1], "baseline") || !strcmp(argv[argPos+1], "default")) + config.alignOriginX = false, config.alignOriginY = true; + else + ABORT("Unknown -pxalign setting. Use on, off, horizontal, or vertical."); + argPos += 2; + continue; + } ARG_CASE("-angle", 1) { double at; if (!parseAngle(at, argv[argPos+1])) @@ -571,7 +589,7 @@ int main(int argc, const char * const *argv) { } ARG_CASE("-errorcorrection", 1) { msdfgen::ErrorCorrectionConfig &ec = config.generatorAttributes.config.errorCorrection; - if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) { + if (!memcmp(argv[argPos+1], "disable", 7) || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) { ec.mode = msdfgen::ErrorCorrectionConfig::DISABLED; ec.distanceCheckMode = msdfgen::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")) { @@ -714,7 +732,8 @@ int main(int argc, const char * const *argv) { ".exe" #endif " -font -charset \n" - "Use -help for more information.\n", stderr + "Use -help for more information.\n", + stderr ); return 0; } @@ -789,8 +808,12 @@ int main(int argc, const char * const *argv) { config.imageFormat = ImageFormat::PNG; imageFormatName = "png"; // If image format is not specified and -imageout is the only image output, infer format from its extension - if (imageExtension != ImageFormat::UNSPECIFIED && !config.arteryFontFilename) - config.imageFormat = imageExtension; + if (!config.arteryFontFilename) { + if (imageExtension != ImageFormat::UNSPECIFIED) + config.imageFormat = imageExtension; + else + fputs("Warning: Could not infer image format from file extension, PNG will be used.\n", stderr); + } } if (config.imageType == ImageType::MTSDF && config.imageFormat == ImageFormat::BMP) ABORT("Atlas type not compatible with image format. MTSDF requires a format with alpha channel."); @@ -966,6 +989,7 @@ int main(int argc, const char * const *argv) { atlasPacker.setPixelRange(pxRange); atlasPacker.setUnitRange(unitRange); atlasPacker.setMiterLimit(config.miterLimit); + atlasPacker.setOriginAlignment(config.alignOriginX, config.alignOriginY); if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { if (remaining < 0) { ABORT("Failed to pack glyphs into atlas.");