From 0f48aaa7272032f53eb4eed3db7551917e37dac4 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Fri, 8 Mar 2024 02:07:35 +0100 Subject: [PATCH] Grid atlas origin pixel alignment --- LICENSE.txt | 2 +- README.md | 4 +- msdf-atlas-gen/Charset.h | 4 + msdf-atlas-gen/GlyphGeometry.cpp | 28 ++++-- msdf-atlas-gen/GlyphGeometry.h | 7 +- msdf-atlas-gen/GridAtlasPacker.cpp | 131 ++++++++++++++++++---------- msdf-atlas-gen/GridAtlasPacker.h | 4 + msdf-atlas-gen/TightAtlasPacker.cpp | 12 +-- msdf-atlas-gen/TightAtlasPacker.h | 6 +- msdf-atlas-gen/main.cpp | 66 +++++++------- msdf-atlas-gen/msdf-atlas-gen.h | 2 +- msdf-atlas-gen/size-selectors.cpp | 2 +- 12 files changed, 165 insertions(+), 103 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 3642b9f..d97dd49 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 - 2023 Viktor Chlumsky +Copyright (c) 2020 - 2024 Viktor Chlumsky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 892e60f..142913b 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ In that case, these additional options are available to customize the layout: - `-uniformcols ` – sets the number of columns - `-uniformcell ` – sets the dimensions of the grid's cells - `-uniformcellconstraint ` – sets constraint for cell dimensions (see explanation of options above) -- `-uniformorigin ` – sets whether the glyph's origin point should be fixed at the same position in each cell +- `-uniformorigin ` – sets whether the glyph's origin point should be fixed at the same position in each cell ### Outputs @@ -120,7 +120,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 +- `-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/Charset.h b/msdf-atlas-gen/Charset.h index 185f265..56e011a 100644 --- a/msdf-atlas-gen/Charset.h +++ b/msdf-atlas-gen/Charset.h @@ -5,6 +5,10 @@ #include #include "types.h" +#ifndef MSDF_ATLAS_PUBLIC +#define MSDF_ATLAS_PUBLIC +#endif + namespace msdf_atlas { /// Represents a set of Unicode codepoints (characters) diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp index 6ab1f34..e63e124 100644 --- a/msdf-atlas-gen/GlyphGeometry.cpp +++ b/msdf-atlas-gen/GlyphGeometry.cpp @@ -51,11 +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, bool alignOrigin) { - wrapBox(scale, range, miterLimit, alignOrigin, alignOrigin); +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) { + wrapBox(scale, range, miterLimit, pxAlignOrigin, pxAlignOrigin); } -void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool alignOriginX, bool alignOriginY) { +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) { scale *= geometryScale; range /= geometryScale; box.range = range; @@ -66,7 +66,7 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool r += .5*range, t += .5*range; if (miterLimit > 0) shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); - if (alignOriginX) { + if (pxAlignOriginX) { int sl = (int) floor(scale*l-.5); int sr = (int) ceil(scale*r+.5); box.rect.w = sr-sl; @@ -76,7 +76,7 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool box.rect.w = (int) ceil(w)+1; box.translate.x = -l+.5*(box.rect.w-w)/scale; } - if (alignOriginY) { + if (pxAlignOriginY) { int sb = (int) floor(scale*b-.5); int st = (int) ceil(scale*t+.5); box.rect.h = st-sb; @@ -92,7 +92,11 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool } } -void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY) { +void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) { + frameBox(scale, range, miterLimit, width, height, fixedX, fixedY, pxAlignOrigin, pxAlignOrigin); +} + +void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) { scale *= geometryScale; range /= geometryScale; box.range = range; @@ -110,13 +114,21 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); if (fixedX) box.translate.x = *fixedX/geometryScale; - else { + else if (pxAlignOriginX) { + int sl = (int) floor(scale*l-.5); + int sr = (int) ceil(scale*r+.5); + box.translate.x = (-sl+(box.rect.w-(sr-sl))/2)/scale; + } else { double w = scale*(r-l); box.translate.x = -l+.5*(box.rect.w-w)/scale; } if (fixedY) box.translate.y = *fixedY/geometryScale; - else { + else if (pxAlignOriginY) { + int sb = (int) floor(scale*b-.5); + int st = (int) ceil(scale*t+.5); + box.translate.y = (-sb+(box.rect.h-(st-sb))/2)/scale; + } else { double h = scale*(t-b); box.translate.y = -b+.5*(box.rect.h-h)/scale; } diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h index dc3f0da..b7fd3c8 100644 --- a/msdf-atlas-gen/GlyphGeometry.h +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -20,10 +20,11 @@ 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, bool alignOrigin = false); - void wrapBox(double scale, double range, double miterLimit, bool alignOriginX, bool alignOriginY); + void wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin = false); + void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY); /// Computes the glyph's transformation and alignment (unless specified) for given dimensions - void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY); + void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin = false); + void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY); /// 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/GridAtlasPacker.cpp b/msdf-atlas-gen/GridAtlasPacker.cpp index 29dd5b2..f94e78a 100644 --- a/msdf-atlas-gen/GridAtlasPacker.cpp +++ b/msdf-atlas-gen/GridAtlasPacker.cpp @@ -101,6 +101,7 @@ GridAtlasPacker::GridAtlasPacker() : unitRange(0), pxRange(0), miterLimit(0), + pxAlignOriginX(false), pxAlignOriginY(false), scaleMaximizationTolerance(.001), alignedColumnsBias(.125) { } @@ -131,10 +132,15 @@ msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &m } if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t) maxBounds = msdfgen::Shape::Bounds(); + // If origin is pixel-aligned but not fixed, one pixel has to be added to max dimensions to allow for aligning the origin by shifting by < 1 pixel if (hFixed) maxWidth = maxBounds.r-maxBounds.l; + else if (pxAlignOriginX) + maxWidth += 1; if (vFixed) maxHeight = maxBounds.t-maxBounds.b; + else if (pxAlignOriginY) + maxHeight += 1; return maxBounds; } @@ -144,6 +150,7 @@ double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWid cellWidth = BIG_VALUE; if (cellHeight <= 0) cellHeight = BIG_VALUE; + --cellWidth, --cellHeight; // Implicit half-pixel padding from each side to make sure that no representable values are beyond outermost pixel centers cellWidth -= padding, cellHeight -= padding; bool lastResult = false; #define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), unitRange+pxRange/(scale)), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight) @@ -169,23 +176,29 @@ double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWid return minScale; } -// Ultra spaghetti +// TODO tame spaghetti code int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { + if (!count) + return 0; bool cellHeightFinal = cellHeight > 0; bool explicitRows = rows > 0; - int cellCount = count; - if (!cellCount) - return 0; - + int cellCount = 0; if (columns > 0 && rows > 0) cellCount = columns*rows; - else if (columns > 0) - rows = (cellCount+columns-1)/columns; - else if (rows > 0) - columns = (cellCount+rows-1)/rows; - else if (width > 0 && cellWidth > 0) { - columns = (width+padding)/cellWidth; - rows = (cellCount+columns-1)/columns; + else { + // Count non-whitespace glyphs only + for (const GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { + if (!glyph->isWhitespace()) + ++cellCount; + } + if (columns > 0) + rows = (cellCount+columns-1)/columns; + else if (rows > 0) + columns = (cellCount+rows-1)/rows; + else if (width > 0 && cellWidth > 0) { + columns = (width+padding)/cellWidth; + rows = (cellCount+columns-1)/columns; + } } bool dimensionsChanged = false; @@ -212,14 +225,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { if (dimensionsChanged) lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); - if ((cellWidth > 0 && cellWidth-padding <= pxRange) || (cellHeight > 0 && cellHeight-padding <= pxRange)) // cells definitely too small + if ((cellWidth > 0 && cellWidth-padding-1 <= pxRange) || (cellHeight > 0 && cellHeight-padding-1 <= pxRange)) // cells definitely too small return -1; + msdfgen::Shape::Bounds maxBounds = { }; + double maxWidth = 0, maxHeight = 0; + if (scale <= 0) { + // If both pxRange and miterLimit is non-zero, miter bounds have to be computed for all potential scales if (pxRange && miterLimit > 0) { - double maxWidth = 0, maxHeight = 0; - msdfgen::Shape::Bounds maxBounds; if (cellWidth > 0 || cellHeight > 0) { scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); @@ -283,8 +298,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { else { maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, unitRange+pxRange/minScale); - cellWidth = (int) ceil(maxWidth)+padding; - cellHeight = (int) ceil(maxHeight)+padding; + cellWidth = (int) ceil(maxWidth)+padding+1; + cellHeight = (int) ceil(maxHeight)+padding+1; raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); if (scale < minScale) @@ -292,20 +307,26 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } if (!explicitRows && !cellHeightFinal) - cellHeight = (int) ceil(maxHeight)+padding; - fixedX = (-maxBounds.l+.5*(cellWidth-padding-maxWidth))/scale; - fixedY = (-maxBounds.b+.5*(cellHeight-padding-maxHeight))/scale; + cellHeight = (int) ceil(maxHeight)+padding+1; } else { - double maxWidth = 0, maxHeight = 0; - msdfgen::Shape::Bounds maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, unitRange); + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, unitRange); + int hSlack = 0, vSlack = 0; + if (pxAlignOriginX && !hFixed) { + maxWidth -= 1; // Added by getMaxBounds + hSlack = 1; + } + if (pxAlignOriginY && !vFixed) { + maxHeight -= 1; // Added by getMaxBounds + vSlack = 1; + } double hScale = 0, vScale = 0; if (cellWidth > 0) - hScale = (cellWidth-padding-pxRange)/maxWidth; + hScale = (cellWidth-hSlack-padding-1-pxRange)/maxWidth; if (cellHeight > 0) - vScale = (cellHeight-padding-pxRange)/maxHeight; + vScale = (cellHeight-vSlack-padding-1-pxRange)/maxHeight; if (hScale || vScale) { if (hScale && vScale) scale = std::min(hScale, vScale); @@ -318,7 +339,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { else if (width > 0 && height > 0) { double bestAlignedScale = 0; int bestCols = 0, bestAlignedCols = 0; - // TODO sqrtize + // TODO optimize to only test up to sqrt(cellCount) cols and rows like in the above branch (for (int q = (int) sqrt(cellCount)+1; ...) for (int cols = 1; cols < width; ++cols) { int rows = (cellCount+cols-1)/cols; int tWidth = (width+padding)/cols; @@ -326,8 +347,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { if (!(tWidth > 0 && tHeight > 0)) continue; lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint); - hScale = (tWidth-padding-pxRange)/maxWidth; - vScale = (tHeight-padding-pxRange)/maxHeight; + hScale = (tWidth-hSlack-padding-pxRange)/maxWidth; + vScale = (tHeight-vSlack-padding-pxRange)/maxHeight; double curScale = std::min(hScale, vScale); if (curScale > scale) { scale = curScale; @@ -354,32 +375,47 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } else { - cellWidth = (int) ceil(minScale*maxWidth+pxRange)+padding; - cellHeight = (int) ceil(minScale*maxHeight+pxRange)+padding; + cellWidth = (int) ceil(minScale*maxWidth+pxRange)+hSlack+padding+1; + cellHeight = (int) ceil(minScale*maxHeight+pxRange)+vSlack+padding+1; raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); - hScale = (cellWidth-padding-pxRange)/maxWidth; - vScale = (cellHeight-padding-pxRange)/maxHeight; + hScale = (cellWidth-hSlack-padding-1-pxRange)/maxWidth; + vScale = (cellHeight-vSlack-padding-1-pxRange)/maxHeight; scale = std::min(hScale, vScale); } if (!explicitRows && !cellHeightFinal) - cellHeight = (int) ceil(scale*maxHeight+pxRange)+padding; - fixedX = -maxBounds.l+.5*((cellWidth-padding)/scale-maxWidth); - fixedY = -maxBounds.b+.5*((cellHeight-padding)/scale-maxHeight); + cellHeight = (int) ceil(scale*maxHeight+pxRange)+vSlack+padding+1; + maxBounds.l *= scale, maxBounds.b *= scale; + maxBounds.r *= scale, maxBounds.t *= scale; + maxWidth *= scale, maxHeight *= scale; } - } else { - double maxWidth = 0, maxHeight = 0; - msdfgen::Shape::Bounds maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale); + } else { + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale); if (cellWidth < 0 || cellHeight < 0) { - cellWidth = (int) ceil(maxWidth)+padding; - cellHeight = (int) ceil(maxHeight)+padding; + cellWidth = (int) ceil(maxWidth)+padding+1; + cellHeight = (int) ceil(maxHeight)+padding+1; raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); } + } - fixedX = (-maxBounds.l+.5*(cellWidth-padding-maxWidth))/scale; - fixedY = (-maxBounds.b+.5*(cellHeight-padding-maxHeight))/scale; + // Compute fixed origin + if (hFixed) { + if (pxAlignOriginX) { + int sl = (int) floor(maxBounds.l-.5); + int sr = (int) ceil(maxBounds.r+.5); + fixedX = (-sl+(cellWidth-padding-(sr-sl))/2)/scale; + } else + fixedX = (-maxBounds.l+.5*(cellWidth-padding-maxWidth))/scale; + } + if (vFixed) { + if (pxAlignOriginY) { + int sb = (int) floor(maxBounds.b-.5); + int st = (int) ceil(maxBounds.t+.5); + fixedY = (-sb+(cellHeight-padding-(st-sb))/2)/scale; + } else + fixedY = (-maxBounds.b+.5*(cellHeight-padding-maxHeight))/scale; } if (width < 0 || height < 0) { @@ -420,7 +456,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { if (!glyph->isWhitespace()) { Rectangle rect = { }; - glyph->frameBox(scale, unitRange+pxRange/scale, miterLimit, cellWidth-padding, cellHeight-padding, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr); + glyph->frameBox(scale, unitRange+pxRange/scale, miterLimit, cellWidth-padding, cellHeight-padding, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr, pxAlignOriginX, pxAlignOriginY); glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight); if (++col >= columns) { if (++row >= rows) { @@ -431,9 +467,6 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } } - if (columns*rows < cellCount) { - // TODO return lower number - } return 0; } @@ -505,6 +538,14 @@ void GridAtlasPacker::setMiterLimit(double miterLimit) { this->miterLimit = miterLimit; } +void GridAtlasPacker::setOriginPixelAlignment(bool align) { + pxAlignOriginX = align, pxAlignOriginY = align; +} + +void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) { + pxAlignOriginX = alignX, pxAlignOriginY = alignY; +} + void GridAtlasPacker::getDimensions(int &width, int &height) const { width = this->width, height = this->height; } diff --git a/msdf-atlas-gen/GridAtlasPacker.h b/msdf-atlas-gen/GridAtlasPacker.h index 177464d..4455c2e 100644 --- a/msdf-atlas-gen/GridAtlasPacker.h +++ b/msdf-atlas-gen/GridAtlasPacker.h @@ -45,6 +45,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 setOriginPixelAlignment(bool align); + void setOriginPixelAlignment(bool alignX, bool alignY); /// Outputs the atlas's final dimensions void getDimensions(int &width, int &height) const; @@ -71,6 +74,7 @@ private: double unitRange; double pxRange; double miterLimit; + bool pxAlignOriginX, pxAlignOriginY; double scaleMaximizationTolerance; double alignedColumnsBias; diff --git a/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp index 87fe4ad..518cc9a 100644 --- a/msdf-atlas-gen/TightAtlasPacker.cpp +++ b/msdf-atlas-gen/TightAtlasPacker.cpp @@ -17,7 +17,7 @@ TightAtlasPacker::TightAtlasPacker() : unitRange(0), pxRange(0), miterLimit(0), - alignOriginX(false), alignOriginY(false), + pxAlignOriginX(false), pxAlignOriginY(false), scaleMaximizationTolerance(.001) { } @@ -31,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, alignOriginX, alignOriginY); + glyph->wrapBox(scale, range, miterLimit, pxAlignOriginX, pxAlignOriginY); glyph->getBoxSize(rect.w, rect.h); if (rect.w > 0 && rect.h > 0) { rectangles.push_back(rect); @@ -157,12 +157,12 @@ void TightAtlasPacker::setMiterLimit(double miterLimit) { this->miterLimit = miterLimit; } -void TightAtlasPacker::setOriginAlignment(bool align) { - alignOriginX = align, alignOriginY = align; +void TightAtlasPacker::setOriginPixelAlignment(bool align) { + pxAlignOriginX = align, pxAlignOriginY = align; } -void TightAtlasPacker::setOriginAlignment(bool alignX, bool alignY) { - alignOriginX = alignX, alignOriginY = alignY; +void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) { + pxAlignOriginX = alignX, pxAlignOriginY = alignY; } void TightAtlasPacker::getDimensions(int &width, int &height) const { diff --git a/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h index b89732a..2d3e4e1 100644 --- a/msdf-atlas-gen/TightAtlasPacker.h +++ b/msdf-atlas-gen/TightAtlasPacker.h @@ -37,8 +37,8 @@ public: /// 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); + void setOriginPixelAlignment(bool align); + void setOriginPixelAlignment(bool alignX, bool alignY); /// Outputs the atlas's final dimensions void getDimensions(int &width, int &height) const; @@ -56,7 +56,7 @@ private: double unitRange; double pxRange; double miterLimit; - bool alignOriginX, alignOriginY; + bool pxAlignOriginX, pxAlignOriginY; 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 ec237c8..b62f2b1 100644 --- a/msdf-atlas-gen/main.cpp +++ b/msdf-atlas-gen/main.cpp @@ -2,7 +2,7 @@ /* * MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR - standalone console program * -------------------------------------------------------------------------------- -* A utility by Viktor Chlumsky, (c) 2020 - 2023 +* A utility by Viktor Chlumsky, (c) 2020 - 2024 */ #ifdef MSDF_ATLAS_STANDALONE @@ -91,7 +91,7 @@ ATLAS CONFIGURATION Sets fixed dimensions of the grid's cells. -uniformcellconstraint Constrains cell dimensions to the given rule (see -pots / ... above) - -uniformorigin + -uniformorigin Sets whether the glyph's origin point should be fixed at the same position in each cell -yorigin Determines whether the Y-axis is oriented upwards (bottom origin, default) or downwards (top origin). @@ -121,7 +121,7 @@ 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 + -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. @@ -262,11 +262,11 @@ struct Configuration { double pxRange; double angleThreshold; double miterLimit; - bool alignOriginX, alignOriginY; + bool pxAlignOriginX, pxAlignOriginY; struct { int cellWidth, cellHeight; int cols, rows; - bool hFixed, vFixed; + bool fixedOriginX, fixedOriginY; } grid; void (*edgeColoring)(msdfgen::Shape &, double, unsigned long long); bool expensiveColoring; @@ -334,7 +334,7 @@ int main(int argc, const char * const *argv) { config.imageType = ImageType::MSDF; config.imageFormat = ImageFormat::UNSPECIFIED; config.yDirection = YDirection::BOTTOM_UP; - config.grid.vFixed = true; + config.grid.fixedOriginX = false, config.grid.fixedOriginY = true; config.edgeColoring = msdfgen::edgeColoringInkTrap; config.kerning = true; const char *imageFormatName = nullptr; @@ -362,7 +362,7 @@ int main(int argc, const char * const *argv) { DimensionsConstraint cellSizeConstraint = DimensionsConstraint::NONE; config.angleThreshold = DEFAULT_ANGLE_THRESHOLD; config.miterLimit = DEFAULT_MITER_LIMIT; - config.alignOriginX = false, config.alignOriginY = true; + config.pxAlignOriginX = false, config.pxAlignOriginY = true; config.threadCount = 0; // Parse command line @@ -585,16 +585,16 @@ int main(int argc, const char * const *argv) { 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; + if (!strcmp(argv[argPos+1], "off") || !memcmp(argv[argPos+1], "disable", 7) || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "false") || argv[argPos+1][0] == 'n') + config.pxAlignOriginX = false, config.pxAlignOriginY = false; + else if (!strcmp(argv[argPos+1], "on") || !memcmp(argv[argPos+1], "enable", 6) || !strcmp(argv[argPos+1], "1") || !strcmp(argv[argPos+1], "true") || !strcmp(argv[argPos+1], "hv") || argv[argPos+1][0] == 'y') + config.pxAlignOriginX = true, config.pxAlignOriginY = true; else if (argv[argPos+1][0] == 'h') - config.alignOriginX = true, config.alignOriginY = false; + config.pxAlignOriginX = true, config.pxAlignOriginY = false; else if (argv[argPos+1][0] == 'v' || !strcmp(argv[argPos+1], "baseline") || !strcmp(argv[argPos+1], "default")) - config.alignOriginX = false, config.alignOriginY = true; + config.pxAlignOriginX = false, config.pxAlignOriginY = true; else - ABORT("Unknown -pxalign setting. Use on, off, horizontal, or vertical."); + ABORT("Unknown -pxalign setting. Use one of: off, on, horizontal, vertical."); argPos += 2; continue; } @@ -650,16 +650,16 @@ int main(int argc, const char * const *argv) { } ARG_CASE("-uniformorigin", 1) { packingStyle = PackingStyle::GRID; - if (!strcmp(argv[argPos+1], "no") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none") || !strcmp(argv[argPos+1], "false") || !strcmp(argv[argPos+1], "disabled")) - config.grid.hFixed = false, config.grid.vFixed = false; - else if (!strcmp(argv[argPos+1], "yes") || !strcmp(argv[argPos+1], "1") || !strcmp(argv[argPos+1], "both") || !strcmp(argv[argPos+1], "true") || !strcmp(argv[argPos+1], "enabled")) - config.grid.hFixed = true, config.grid.vFixed = true; - else if (!strcmp(argv[argPos+1], "horizontal") || !strcmp(argv[argPos+1], "h")) - config.grid.hFixed = true, config.grid.vFixed = false; - else if (!strcmp(argv[argPos+1], "vertical") || !strcmp(argv[argPos+1], "v") || !strcmp(argv[argPos+1], "default")) - config.grid.hFixed = false, config.grid.vFixed = true; + if (!strcmp(argv[argPos+1], "off") || !memcmp(argv[argPos+1], "disable", 7) || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "false") || argv[argPos+1][0] == 'n') + config.grid.fixedOriginX = false, config.grid.fixedOriginY = false; + else if (!strcmp(argv[argPos+1], "on") || !memcmp(argv[argPos+1], "enable", 6) || !strcmp(argv[argPos+1], "1") || !strcmp(argv[argPos+1], "true") || !strcmp(argv[argPos+1], "hv") || argv[argPos+1][0] == 'y') + config.grid.fixedOriginX = true, config.grid.fixedOriginY = true; + else if (argv[argPos+1][0] == 'h') + config.grid.fixedOriginX = true, config.grid.fixedOriginY = false; + else if (argv[argPos+1][0] == 'v' || !strcmp(argv[argPos+1], "baseline") || !strcmp(argv[argPos+1], "default")) + config.grid.fixedOriginX = false, config.grid.fixedOriginY = true; else - ABORT("Unknown option for -uniformorigin. Use one of: yes, no, horizontal, vertical."); + ABORT("Unknown -uniformorigin setting. Use one of: off, on, horizontal, vertical."); argPos += 2; continue; } @@ -889,7 +889,7 @@ int main(int argc, const char * const *argv) { if (!config.arteryFontFilename) { if (imageExtension != ImageFormat::UNSPECIFIED) config.imageFormat = imageExtension; - else + else if (config.imageFilename) fputs("Warning: Could not infer image format from file extension, PNG will be used.\n", stderr); } } @@ -1072,7 +1072,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); + atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { if (remaining < 0) { ABORT("Failed to pack glyphs into atlas."); @@ -1095,7 +1095,7 @@ int main(int argc, const char * const *argv) { case PackingStyle::GRID: { GridAtlasPacker atlasPacker; - atlasPacker.setFixedOrigin(config.grid.hFixed, config.grid.vFixed); + atlasPacker.setFixedOrigin(config.grid.fixedOriginX, config.grid.fixedOriginY); if (fixedCellWidth >= 0 && fixedCellHeight >= 0) atlasPacker.setCellDimensions(fixedCellWidth, fixedCellHeight); else @@ -1114,7 +1114,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); + atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { if (remaining < 0) { ABORT("Failed to pack glyphs into atlas."); @@ -1133,14 +1133,14 @@ int main(int argc, const char * const *argv) { config.grid.rows = atlasPacker.getRows(); if (!fixedScale) printf("Glyph size: %.9g pixels/em\n", config.emSize); - if (config.grid.hFixed || config.grid.vFixed) { + if (config.grid.fixedOriginX || config.grid.fixedOriginY) { atlasPacker.getFixedOrigin(uniformOriginX, uniformOriginY); printf("Grid cell origin: "); - if (config.grid.hFixed) + if (config.grid.fixedOriginX) printf("X = %.9g", uniformOriginX); - if (config.grid.hFixed && config.grid.vFixed) + if (config.grid.fixedOriginX && config.grid.fixedOriginY) printf(", "); - if (config.grid.vFixed) { + if (config.grid.fixedOriginY) { switch (config.yDirection) { case YDirection::BOTTOM_UP: printf("Y = %.9g", uniformOriginY); @@ -1237,9 +1237,9 @@ int main(int argc, const char * const *argv) { if (packingStyle == PackingStyle::GRID) { gridMetrics.cellWidth = config.grid.cellWidth, gridMetrics.cellHeight = config.grid.cellHeight; gridMetrics.columns = config.grid.cols, gridMetrics.rows = config.grid.rows; - if (config.grid.hFixed) + if (config.grid.fixedOriginX) gridMetrics.originX = &uniformOriginX; - if (config.grid.vFixed) + if (config.grid.fixedOriginY) gridMetrics.originY = &uniformOriginY; gridMetrics.padding = padding; jsonMetrics.grid = &gridMetrics; diff --git a/msdf-atlas-gen/msdf-atlas-gen.h b/msdf-atlas-gen/msdf-atlas-gen.h index bf5f957..7f5b060 100644 --- a/msdf-atlas-gen/msdf-atlas-gen.h +++ b/msdf-atlas-gen/msdf-atlas-gen.h @@ -4,7 +4,7 @@ /* * MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR * --------------------------------------------------- - * A utility by Viktor Chlumsky, (c) 2020 - 2023 + * A utility by Viktor Chlumsky, (c) 2020 - 2024 * Generates compact bitmap font atlases using MSDFgen */ diff --git a/msdf-atlas-gen/size-selectors.cpp b/msdf-atlas-gen/size-selectors.cpp index ef2f21b..7c33c01 100644 --- a/msdf-atlas-gen/size-selectors.cpp +++ b/msdf-atlas-gen/size-selectors.cpp @@ -15,7 +15,7 @@ SquareSizeSelector::SquareSizeSelector(int minArea) : lowerBound(0), u template void SquareSizeSelector::updateCurrent() { if (upperBound < 0) - current = 5*lowerBound/4+16/MULTIPLE; + current = 5*lowerBound/4+16/MULTIPLE+1; else current = lowerBound+(upperBound-lowerBound)/2; }