Grid atlas origin pixel alignment

This commit is contained in:
Chlumsky 2024-03-08 02:07:35 +01:00
parent 37ffbd85ee
commit 0f48aaa727
12 changed files with 165 additions and 103 deletions

View File

@ -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

View File

@ -102,7 +102,7 @@ In that case, these additional options are available to customize the layout:
- `-uniformcols <N>` &ndash; sets the number of columns
- `-uniformcell <width> <height>` &ndash; sets the dimensions of the grid's cells
- `-uniformcellconstraint <none / pots / potr / square / square2 / square4>` &ndash; sets constraint for cell dimensions (see explanation of options above)
- `-uniformorigin <yes / no / horizontal / vertical>` &ndash; sets whether the glyph's origin point should be fixed at the same position in each cell
- `-uniformorigin <off / on / horizontal / vertical>` &ndash; 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 <em size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
- `-emrange <em range>` &ndash; sets the distance field range in em's
- `-pxrange <pixel range>` (default = 2) &ndash; sets the distance field range in output pixels
- `-pxalign <on / off / horizontal / vertical>` (default = vertical) &ndash; enables or disables alignment of glyph's origin point with the pixel grid
- `-pxalign <off / on / horizontal / vertical>` (default = vertical) &ndash; enables or disables alignment of glyph's origin point with the pixel grid
### Distance field generator settings

View File

@ -5,6 +5,10 @@
#include <set>
#include "types.h"
#ifndef MSDF_ATLAS_PUBLIC
#define MSDF_ATLAS_PUBLIC
#endif
namespace msdf_atlas {
/// Represents a set of Unicode codepoints (characters)

View File

@ -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;
}

View File

@ -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

View File

@ -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,17 +176,22 @@ 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)
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;
@ -187,6 +199,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
columns = (width+padding)/cellWidth;
rows = (cellCount+columns-1)/columns;
}
}
bool dimensionsChanged = false;
if (width < 0 && cellWidth > 0 && columns > 0) {
@ -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,31 +375,46 @@ 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);
}
}
// 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;
}
@ -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;
}

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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 <none / pots / potr / square / square2 / square4>
Constrains cell dimensions to the given rule (see -pots / ... above)
-uniformorigin <yes / no / horizontal / vertical>
-uniformorigin <off / on / horizontal / vertical>
Sets whether the glyph's origin point should be fixed at the same position in each cell
-yorigin <bottom / top>
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 <pixel range>
Specifies the SDF distance range in output pixels. The default value is 2.
-pxalign <on / off / horizontal / vertical>
-pxalign <off / on / horizontal / vertical>
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;

View File

@ -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
*/

View File

@ -15,7 +15,7 @@ SquareSizeSelector<MULTIPLE>::SquareSizeSelector(int minArea) : lowerBound(0), u
template <int MULTIPLE>
void SquareSizeSelector<MULTIPLE>::updateCurrent() {
if (upperBound < 0)
current = 5*lowerBound/4+16/MULTIPLE;
current = 5*lowerBound/4+16/MULTIPLE+1;
else
current = lowerBound+(upperBound-lowerBound)/2;
}