Uniform grid atlas completed
This commit is contained in:
parent
0f48aaa727
commit
37d185878d
|
|
@ -7,14 +7,14 @@ namespace msdf_atlas {
|
|||
|
||||
static int floorPOT(int x) {
|
||||
int y = 1;
|
||||
while (x >= y)
|
||||
while (x >= y && y<<1)
|
||||
y <<= 1;
|
||||
return y>>1;
|
||||
}
|
||||
|
||||
static int ceilPOT(int x) {
|
||||
int y = 1;
|
||||
while (x > y)
|
||||
while (x > y && y<<1)
|
||||
y <<= 1;
|
||||
return y;
|
||||
}
|
||||
|
|
@ -103,7 +103,8 @@ GridAtlasPacker::GridAtlasPacker() :
|
|||
miterLimit(0),
|
||||
pxAlignOriginX(false), pxAlignOriginY(false),
|
||||
scaleMaximizationTolerance(.001),
|
||||
alignedColumnsBias(.125)
|
||||
alignedColumnsBias(.125),
|
||||
cutoff(false)
|
||||
{ }
|
||||
|
||||
msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double range) const {
|
||||
|
|
@ -176,12 +177,12 @@ double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWid
|
|||
return minScale;
|
||||
}
|
||||
|
||||
// TODO tame spaghetti code
|
||||
// Can this spaghetti code be simplified?
|
||||
// Idea: Maybe it could be rewritten into a while (not all properties deduced) cycle, and compute one value in each iteration
|
||||
int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
||||
if (!count)
|
||||
return 0;
|
||||
bool cellHeightFinal = cellHeight > 0;
|
||||
bool explicitRows = rows > 0;
|
||||
GridAtlasPacker initial(*this);
|
||||
int cellCount = 0;
|
||||
if (columns > 0 && rows > 0)
|
||||
cellCount = columns*rows;
|
||||
|
|
@ -201,29 +202,23 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
}
|
||||
}
|
||||
|
||||
bool dimensionsChanged = false;
|
||||
if (width < 0 && cellWidth > 0 && columns > 0) {
|
||||
width = columns*cellWidth-padding;
|
||||
dimensionsChanged = true;
|
||||
}
|
||||
if (height < 0 && cellHeight > 0 && rows > 0) {
|
||||
height = rows*cellHeight-padding;
|
||||
dimensionsChanged = true;
|
||||
}
|
||||
if (dimensionsChanged)
|
||||
if (width < 0 && cellWidth > 0 && columns > 0)
|
||||
width = columns*cellWidth;
|
||||
if (height < 0 && cellHeight > 0 && rows > 0)
|
||||
height = rows*cellHeight;
|
||||
if (width != initial.width || height != initial.height)
|
||||
raiseToConstraint(width, height, dimensionsConstraint);
|
||||
|
||||
dimensionsChanged = false;
|
||||
if (cellWidth < 0 && width > 0 && columns > 0) {
|
||||
if (cellWidth < 0 && width > 0 && columns > 0)
|
||||
cellWidth = (width+padding)/columns;
|
||||
dimensionsChanged = true;
|
||||
}
|
||||
if (cellHeight < 0 && height > 0 && rows > 0) {
|
||||
if (cellHeight < 0 && height > 0 && rows > 0)
|
||||
cellHeight = (height+padding)/rows;
|
||||
dimensionsChanged = true;
|
||||
}
|
||||
if (dimensionsChanged)
|
||||
if (cellWidth != initial.cellWidth || cellHeight != initial.cellHeight) {
|
||||
bool positiveCellWidth = cellWidth > 0, positiveCellHeight = cellHeight > 0;
|
||||
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
|
||||
if ((cellWidth == 0 && positiveCellWidth) || (cellHeight == 0 && positiveCellHeight))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((cellWidth > 0 && cellWidth-padding-1 <= pxRange) || (cellHeight > 0 && cellHeight-padding-1 <= pxRange)) // cells definitely too small
|
||||
return -1;
|
||||
|
|
@ -238,8 +233,11 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
|
||||
if (cellWidth > 0 || cellHeight > 0) {
|
||||
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
|
||||
if (scale < minScale)
|
||||
return -1;
|
||||
if (scale < minScale) {
|
||||
scale = minScale;
|
||||
cutoff = true;
|
||||
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale);
|
||||
}
|
||||
}
|
||||
|
||||
else if (width > 0 && height > 0) {
|
||||
|
|
@ -250,9 +248,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
int rows = (cellCount+cols-1)/cols;
|
||||
int tWidth = (width+padding)/cols;
|
||||
int tHeight = (height+padding)/rows;
|
||||
if (!(tWidth > 0 && tHeight > 0))
|
||||
continue;
|
||||
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
|
||||
if (tWidth > 0 && tHeight > 0) {
|
||||
double curScale = scaleToFit(glyphs, count, tWidth, tHeight, maxBounds, maxWidth, maxHeight);
|
||||
if (curScale > scale) {
|
||||
scale = curScale;
|
||||
|
|
@ -262,15 +259,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
bestAlignedScale = curScale;
|
||||
bestAlignedCols = cols;
|
||||
}
|
||||
|
||||
}
|
||||
cols = (cellCount+q-1)/q;
|
||||
rows = (cellCount+cols-1)/cols;
|
||||
tWidth = (width+padding)/cols;
|
||||
tHeight = (height+padding)/rows;
|
||||
if (!(tWidth > 0 && tHeight > 0))
|
||||
continue;
|
||||
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
|
||||
curScale = scaleToFit(glyphs, count, tWidth, tHeight, maxBounds, maxWidth, maxHeight);
|
||||
if (tWidth > 0 && tHeight > 0) {
|
||||
double curScale = scaleToFit(glyphs, count, tWidth, tHeight, maxBounds, maxWidth, maxHeight);
|
||||
if (curScale > scale) {
|
||||
scale = curScale;
|
||||
bestCols = cols;
|
||||
|
|
@ -280,6 +276,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
bestAlignedCols = cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bestCols)
|
||||
return -1;
|
||||
// If columns can be aligned with total width at a slight cost to glyph scale, use that number of columns instead
|
||||
|
|
@ -294,9 +291,11 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
cellHeight = (height+padding)/rows;
|
||||
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
|
||||
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
|
||||
if (scale < minScale)
|
||||
scale = -1;
|
||||
}
|
||||
|
||||
else {
|
||||
if (scale <= 0) {
|
||||
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, unitRange+pxRange/minScale);
|
||||
cellWidth = (int) ceil(maxWidth)+padding+1;
|
||||
cellHeight = (int) ceil(maxHeight)+padding+1;
|
||||
|
|
@ -306,8 +305,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, unitRange+pxRange/minScale);
|
||||
}
|
||||
|
||||
if (!explicitRows && !cellHeightFinal)
|
||||
cellHeight = (int) ceil(maxHeight)+padding+1;
|
||||
if (initial.rows < 0 && initial.cellHeight < 0) {
|
||||
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(maxHeight)+padding+1;
|
||||
raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
|
||||
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
|
||||
cellWidth = optimalCellWidth;
|
||||
cellHeight = optimalCellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
|
@ -332,8 +337,10 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
scale = std::min(hScale, vScale);
|
||||
else
|
||||
scale = hScale+vScale;
|
||||
if (scale < minScale)
|
||||
return -1;
|
||||
if (scale < minScale) {
|
||||
scale = minScale;
|
||||
cutoff = true;
|
||||
}
|
||||
}
|
||||
|
||||
else if (width > 0 && height > 0) {
|
||||
|
|
@ -344,11 +351,10 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
int rows = (cellCount+cols-1)/cols;
|
||||
int tWidth = (width+padding)/cols;
|
||||
int tHeight = (height+padding)/rows;
|
||||
if (!(tWidth > 0 && tHeight > 0))
|
||||
continue;
|
||||
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
|
||||
hScale = (tWidth-hSlack-padding-pxRange)/maxWidth;
|
||||
vScale = (tHeight-vSlack-padding-pxRange)/maxHeight;
|
||||
if (tWidth > 0 && tHeight > 0) {
|
||||
hScale = (tWidth-hSlack-padding-1-pxRange)/maxWidth;
|
||||
vScale = (tHeight-vSlack-padding-1-pxRange)/maxHeight;
|
||||
double curScale = std::min(hScale, vScale);
|
||||
if (curScale > scale) {
|
||||
scale = curScale;
|
||||
|
|
@ -359,6 +365,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
bestAlignedCols = cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bestCols)
|
||||
return -1;
|
||||
// If columns can be aligned with total width at a slight cost to glyph scale, use that number of columns instead
|
||||
|
|
@ -372,9 +379,11 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
cellWidth = (width+padding)/columns;
|
||||
cellHeight = (height+padding)/rows;
|
||||
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
|
||||
if (scale < minScale)
|
||||
scale = -1;
|
||||
}
|
||||
|
||||
else {
|
||||
if (scale <= 0) {
|
||||
cellWidth = (int) ceil(minScale*maxWidth+pxRange)+hSlack+padding+1;
|
||||
cellHeight = (int) ceil(minScale*maxHeight+pxRange)+vSlack+padding+1;
|
||||
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
|
||||
|
|
@ -383,8 +392,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
scale = std::min(hScale, vScale);
|
||||
}
|
||||
|
||||
if (!explicitRows && !cellHeightFinal)
|
||||
cellHeight = (int) ceil(scale*maxHeight+pxRange)+vSlack+padding+1;
|
||||
if (initial.rows < 0 && initial.cellHeight < 0) {
|
||||
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+pxRange)+vSlack+padding+1;
|
||||
raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
|
||||
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
|
||||
cellWidth = optimalCellWidth;
|
||||
cellHeight = optimalCellHeight;
|
||||
}
|
||||
}
|
||||
maxBounds.l *= scale, maxBounds.b *= scale;
|
||||
maxBounds.r *= scale, maxBounds.t *= scale;
|
||||
maxWidth *= scale, maxHeight *= scale;
|
||||
|
|
@ -393,11 +408,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
|
||||
} else {
|
||||
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale);
|
||||
int optimalCellWidth = (int) ceil(maxWidth)+padding+1;
|
||||
int optimalCellHeight = (int) ceil(maxHeight)+padding+1;
|
||||
if (cellWidth < 0 || cellHeight < 0) {
|
||||
cellWidth = (int) ceil(maxWidth)+padding+1;
|
||||
cellHeight = (int) ceil(maxHeight)+padding+1;
|
||||
cellWidth = optimalCellWidth;
|
||||
cellHeight = optimalCellHeight;
|
||||
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
|
||||
}
|
||||
} else if (cellWidth < optimalCellWidth || cellHeight < optimalCellHeight)
|
||||
cutoff = true;
|
||||
}
|
||||
|
||||
// Compute fixed origin
|
||||
|
|
@ -445,17 +463,27 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
}
|
||||
width = columns*cellWidth, height = rows*cellHeight;
|
||||
raiseToConstraint(width, height, dimensionsConstraint);
|
||||
// raiseToConstraint may have increased dimensions significantly, rerun if cell dimensions can be optimized.
|
||||
if (dimensionsConstraint != DimensionsConstraint::NONE && initial.cellWidth < 0 && initial.cellHeight < 0) {
|
||||
cellWidth = initial.cellWidth;
|
||||
cellHeight = initial.cellHeight;
|
||||
columns = initial.columns;
|
||||
rows = initial.rows;
|
||||
scale = initial.scale;
|
||||
return pack(glyphs, count);
|
||||
}
|
||||
}
|
||||
|
||||
if (columns < 0) {
|
||||
columns = (width+padding)/cellWidth;
|
||||
rows = (cellCount+columns-1)/columns;
|
||||
}
|
||||
if (rows*cellHeight > height)
|
||||
rows = height/cellHeight;
|
||||
|
||||
int col = 0, row = 0;
|
||||
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, pxAlignOriginX, pxAlignOriginY);
|
||||
glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight);
|
||||
if (++col >= columns) {
|
||||
|
|
@ -567,7 +595,7 @@ double GridAtlasPacker::getScale() const {
|
|||
}
|
||||
|
||||
double GridAtlasPacker::getPixelRange() const {
|
||||
return pxRange;
|
||||
return pxRange+scale*unitRange;
|
||||
}
|
||||
|
||||
void GridAtlasPacker::getFixedOrigin(double &x, double &y) {
|
||||
|
|
@ -575,4 +603,8 @@ void GridAtlasPacker::getFixedOrigin(double &x, double &y) {
|
|||
y = fixedY-.5/scale;
|
||||
}
|
||||
|
||||
bool GridAtlasPacker::hasCutoff() const {
|
||||
return cutoff;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,14 +51,20 @@ public:
|
|||
|
||||
/// Outputs the atlas's final dimensions
|
||||
void getDimensions(int &width, int &height) const;
|
||||
/// Outputs the horizontal and vertical difference between the bottom left corners of consecutive grid cells
|
||||
void getCellDimensions(int &width, int &height) const;
|
||||
/// Returns the final number of grid columns
|
||||
int getColumns() const;
|
||||
/// Returns the final number of grid rows
|
||||
int getRows() const;
|
||||
/// Returns the final glyph scale
|
||||
double getScale() const;
|
||||
/// Returns the final combined pixel range (including converted unit range)
|
||||
double getPixelRange() const;
|
||||
/// Outputs the position of the origin within each cell, each value is only valid if the origin is fixed in the respective dimension
|
||||
void getFixedOrigin(double &x, double &y);
|
||||
/// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully
|
||||
bool hasCutoff() const;
|
||||
|
||||
private:
|
||||
int columns, rows;
|
||||
|
|
@ -77,6 +83,7 @@ private:
|
|||
bool pxAlignOriginX, pxAlignOriginY;
|
||||
double scaleMaximizationTolerance;
|
||||
double alignedColumnsBias;
|
||||
bool cutoff;
|
||||
|
||||
static void lowerToConstraint(int &width, int &height, DimensionsConstraint constraint);
|
||||
static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint);
|
||||
|
|
|
|||
|
|
@ -116,8 +116,6 @@ int TightAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
|
|||
scale = packAndScale(glyphs, count);
|
||||
if (scale <= 0)
|
||||
return -1;
|
||||
pxRange += scale*unitRange;
|
||||
unitRange = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +172,7 @@ double TightAtlasPacker::getScale() const {
|
|||
}
|
||||
|
||||
double TightAtlasPacker::getPixelRange() const {
|
||||
return pxRange;
|
||||
return pxRange+scale*unitRange;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1123,6 +1123,8 @@ int main(int argc, const char * const *argv) {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
if (atlasPacker.hasCutoff())
|
||||
fputs("Warning: Grid cell too constrained to fully fit all glyphs, some may be cut off!\n", stderr);
|
||||
atlasPacker.getDimensions(config.width, config.height);
|
||||
if (!(config.width > 0 && config.height > 0))
|
||||
ABORT("Unable to determine atlas size.");
|
||||
|
|
|
|||
Loading…
Reference in New Issue