tweaked rasterized packer

This commit is contained in:
Andrea Maggiordomo 2019-12-10 10:13:38 +01:00
parent acf2331c70
commit 6c02f53fb1
2 changed files with 92 additions and 140 deletions

View File

@ -241,6 +241,10 @@ public:
//the width (in pixels) of the gutter added around the outline //the width (in pixels) of the gutter added around the outline
int gutterWidth; int gutterWidth;
// if false, then do not combine the costs when doubeHorizon is used. This
// can help to keep the packing area in a rectangular region
bool minmax;
///default constructor ///default constructor
Parameters() Parameters()
{ {
@ -250,6 +254,7 @@ public:
permutations=false; permutations=false;
rotationNum = 16; rotationNum = 16;
gutterWidth = 0; gutterWidth = 0;
minmax = false;
} }
}; };
@ -267,11 +272,7 @@ public:
//the bottomHorizon stores the height of the i-th column in the current solution //the bottomHorizon stores the height of the i-th column in the current solution
std::vector<int> mBottomHorizon; std::vector<int> mBottomHorizon;
// secondary horizons, these keep track of the space between the bottom of the // inner horizons base and extent (number of free cells)
// grid and the already placed polygons
std::vector<int> mSecLeftHorizon;
std::vector<int> mSecBottomHorizon;
std::vector<int> mInnerBottomHorizon; std::vector<int> mInnerBottomHorizon;
std::vector<int> mInnerBottomExtent; std::vector<int> mInnerBottomExtent;
@ -290,9 +291,6 @@ public:
mBottomHorizon.resize(size.X(), 0); mBottomHorizon.resize(size.X(), 0);
mLeftHorizon.resize(size.Y(), 0); mLeftHorizon.resize(size.Y(), 0);
mSecBottomHorizon.resize(size.X(), size.Y() - 1);
mSecLeftHorizon.resize(size.Y(), size.X() - 1);
mInnerBottomHorizon.resize(size.X(), 0); mInnerBottomHorizon.resize(size.X(), 0);
mInnerBottomExtent.resize(size.X(), 0); mInnerBottomExtent.resize(size.X(), 0);
@ -351,35 +349,22 @@ public:
for (size_t i = 0; i < bottom.size(); ++i) { for (size_t i = 0; i < bottom.size(); ++i) {
int y = mInnerBottomHorizon[col + i] - bottom[i]; int y = mInnerBottomHorizon[col + i] - bottom[i];
if (y > y_max) { if (y > y_max) {
if (y + poly.gridHeight(rast_i) >= mSize.Y()) if (y + poly.gridHeight(rast_i) >= mSize.Y()) {
return INVALID_POSITION; return INVALID_POSITION;
}
y_max = y; y_max = y;
} }
} }
// check if the placement is feasible // check if the placement is feasible
for (size_t i = 0; i < bottom.size(); ++i) { for (size_t i = 0; i < bottom.size(); ++i) {
if (y_max + bottom[i] < mBottomHorizon[col + i] if (y_max + bottom[i] < mBottomHorizon[col + i]
&& y_max + bottom[i] + deltaY[i] > mInnerBottomHorizon[col + i] + mInnerBottomExtent[col + i]) && y_max + bottom[i] + deltaY[i] > mInnerBottomHorizon[col + i] + mInnerBottomExtent[col + i]) {
return INVALID_POSITION; return INVALID_POSITION;
}
} }
return y_max; return y_max;
} }
int pushY(RasterizedOutline2& poly, int col, int rast_i) {
std::vector<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& deltaY = poly.getDeltaY(rast_i);
int y_min = INT_MAX;
for (size_t i = 0; i < bottom.size(); ++i) {
int y = mSecBottomHorizon[col + i] - (bottom[i] + deltaY[i]);
if (y < y_min) {
if (y < 0)
return INVALID_POSITION;
y_min = y;
}
}
return y_min;
}
//given a poly and the row at which it is placed, //given a poly and the row at which it is placed,
//this returns the X at which the wasted space is minimum //this returns the X at which the wasted space is minimum
//i.e. the X at which the polygon touches the left horizon //i.e. the X at which the polygon touches the left horizon
@ -418,21 +403,6 @@ public:
return x_max; return x_max;
} }
int pushX(RasterizedOutline2& poly, int row, int rast_i) {
std::vector<int>& left = poly.getLeft(rast_i);
std::vector<int>& deltaX = poly.getDeltaX(rast_i);
int x_min = INT_MAX;
for (size_t i = 0; i < left.size(); ++i) {
int x = mSecLeftHorizon[row + i] - (left[i] + deltaX[i]);
if (x < x_min) {
if (x < 0)
return INVALID_POSITION;
x_min = x;
}
}
return x_min;
}
int costYWithPenaltyOnX(RasterizedOutline2& poly, Point2i pos, int rast_i) { int costYWithPenaltyOnX(RasterizedOutline2& poly, Point2i pos, int rast_i) {
std::vector<int>& left = poly.getLeft(rast_i); std::vector<int>& left = poly.getLeft(rast_i);
std::vector<int>& deltaX = poly.getDeltaX(rast_i); std::vector<int>& deltaX = poly.getDeltaX(rast_i);
@ -508,20 +478,20 @@ public:
{ {
//return pos.Y() + poly.gridHeight(rast_i); //return pos.Y() + poly.gridHeight(rast_i);
int cost = -INT_MAX; int maxY = -INT_MAX;
std::vector<int>& bottom = poly.getBottom(rast_i); std::vector<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& deltaY = poly.getDeltaY(rast_i); std::vector<int>& deltaY = poly.getDeltaY(rast_i);
for (unsigned i = 0; i < bottom.size(); ++i) { for (unsigned i = 0; i < bottom.size(); ++i) {
int currentCost; int yi = 0;
if (pos.Y() + bottom[i] + deltaY[i] < mBottomHorizon[pos.X() + i]) { if (pos.Y() + bottom[i] + deltaY[i] < mBottomHorizon[pos.X() + i]) {
currentCost = -(pos.Y() + bottom[i]); yi = -(pos.Y() + bottom[i]);
} else { } else {
currentCost = pos.Y() + bottom[i] + deltaY[i]; yi = pos.Y() + bottom[i] + deltaY[i];
} }
if (currentCost > cost) if (yi > maxY)
cost = currentCost; maxY = yi;
} }
return cost; return maxY;
} }
@ -529,20 +499,20 @@ public:
{ {
//return pos.X() + poly.gridWidth(rast_i); //return pos.X() + poly.gridWidth(rast_i);
int cost = -INT_MAX; int maxX = -INT_MAX;
std::vector<int>& left = poly.getLeft(rast_i); std::vector<int>& left = poly.getLeft(rast_i);
std::vector<int>& deltaX = poly.getDeltaX(rast_i); std::vector<int>& deltaX = poly.getDeltaX(rast_i);
for (unsigned i = 0; i < left.size(); ++i) { for (unsigned i = 0; i < left.size(); ++i) {
int currentCost = 0; int xi = 0;
if (pos.X() + left[i] + deltaX[i] < mLeftHorizon[pos.Y() + i]) { if (pos.X() + left[i] + deltaX[i] < mLeftHorizon[pos.Y() + i]) {
currentCost = -(pos.X() + left[i]); xi = -(pos.X() + left[i]);
} else { } else {
currentCost = pos.X() + left[i] + deltaX[i]; xi = pos.X() + left[i] + deltaX[i];
} }
if (currentCost > cost) if (xi > maxX)
currentCost = cost; maxX = xi;
} }
return cost; return maxX;
} }
/* Returns the number of empty cells between the poly's left side and the /* Returns the number of empty cells between the poly's left side and the
@ -614,16 +584,8 @@ public:
} }
} }
int secBtmHor = pos.Y() + bottom[i];
if (secBtmHor < mSecBottomHorizon[pos.X() + i])
mSecBottomHorizon[pos.X() + i] = secBtmHor;
} }
/*
if (params.costFunction != Parameters::MixedCost
&& !params.doubleHorizon) return;
*/
//update left horizon //update left horizon
for (int i = 0; i < poly.gridHeight(rast_i); i++) { for (int i = 0; i < poly.gridHeight(rast_i); i++) {
int tmpHor = pos.X() + left[i] + deltaX[i]; int tmpHor = pos.X() + left[i] + deltaX[i];
@ -656,10 +618,6 @@ public:
mInnerLeftExtent[pos.Y() + i] = 0; mInnerLeftExtent[pos.Y() + i] = 0;
} }
} }
int secLeftHor = pos.X() + left[i];
if (secLeftHor < mSecLeftHorizon[pos.Y() + i])
mSecLeftHorizon[pos.Y() + i] = secLeftHor;
} }
} }
}; };
@ -811,51 +769,36 @@ public:
return trials; return trials;
} }
static bool PackAtFixedScale(std::vector<std::vector<Point2x>> &polyPointsVec, /*
const std::vector<Point2i> &containerSizes, * Pack charts using a best effort policy. The idea is that this function
std::vector<Similarity2x> &trVec, * packs what it can in the given space without scaling the outlines.
std::vector<int> &polyToContainer, *
const Parameters &packingPar, * Returns the number of charts actually packed.
float scale) *
{ * Function parameters:
//create the vector of polys, starting for the poly points we received as parameter
std::vector<RasterizedOutline2> polyVec(polyPointsVec.size());
for(size_t i=0;i<polyVec.size();i++) {
polyVec[i].setPoints(polyPointsVec[i]);
}
std::vector<std::vector<int>> trials = InitializePermutationVectors(polyPointsVec, packingPar);
for (std::size_t i = 0; i < trials.size(); ++i) {
std::vector<Similarity2x> trVecIter;
std::vector<int> polyToContainerIter;
if (PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, scale, polyVec, trials[i])) {
trVec = trVecIter;
polyToContainer = polyToContainerIter;
return true;
}
}
return false;
}
/* Function parameters:
* outline2Vec (IN) vector of outlines to pack * outline2Vec (IN) vector of outlines to pack
* containerSizes (IN) vector of container (grid) sizes * containerSizes (IN) vector of container (grid) sizes
* trVec (OUT) vector of transformations that must be applied to the objects * trVec (OUT) vector of transformations that must be applied to the objects
* polyToContainer (OUT) vector of outline-to-container mappings. If polyToContainer[i] == -1 * polyToContainer (OUT) vector of outline-to-container mappings. If polyToContainer[i] == -1
* then outline i did not fit in the packing grids, and the transformation trVec[i] is meaningless * then outline i did not fit in the packing grids, and the transformation trVec[i] is meaningless
* * */
* Returns true if it packed at least one polygon into the container static int
*
* The idea is that this function packs what it can in the given space without transforming the
* outlines, and returns enough information to the caller in order to decide what to do */
static bool
PackBestEffort(std::vector<std::vector<Point2x>> &outline2Vec, PackBestEffort(std::vector<std::vector<Point2x>> &outline2Vec,
const std::vector<Point2i> &containerSizes, const std::vector<Point2i> &containerSizes,
std::vector<Similarity2x> &trVec, std::vector<Similarity2x> &trVec,
std::vector<int> &polyToContainer, std::vector<int> &polyToContainer,
const Parameters &packingPar) const Parameters &packingPar)
{
return PackBestEffortAtScale(outline2Vec, containerSizes, trVec, polyToContainer, packingPar, 1.0f);
}
/* Same as PackBestEffort() but allows to specify the outlines scaling factor */
static int
PackBestEffortAtScale(std::vector<std::vector<Point2x>> &outline2Vec,
const std::vector<Point2i> &containerSizes,
std::vector<Similarity2x> &trVec,
std::vector<int> &polyToContainer,
const Parameters &packingPar, float scaleFactor)
{ {
std::vector<RasterizedOutline2> polyVec(outline2Vec.size()); std::vector<RasterizedOutline2> polyVec(outline2Vec.size());
for(size_t i=0;i<polyVec.size();i++) { for(size_t i=0;i<polyVec.size();i++) {
@ -867,18 +810,18 @@ public:
std::vector<std::vector<int>> trials = InitializePermutationVectors(outline2Vec, packingPar); std::vector<std::vector<int>> trials = InitializePermutationVectors(outline2Vec, packingPar);
int bestNumPlaced = 0; int bestNumPlaced = 0;
for (std::size_t i = 0; i < trials.size(); ++i) { for (std::size_t i = 0; i < trials.size(); ++i) {
// TODO this should probably attempt at maximizing the occupancy and/or the number of placed polygons
std::vector<Similarity2x> trVecIter; std::vector<Similarity2x> trVecIter;
std::vector<int> polyToContainerIter; std::vector<int> polyToContainerIter;
PolyPacking(outline2Vec, containerSizes, trVecIter, polyToContainerIter, packingPar, 1.0, polyVec, trials[i], true); PolyPacking(outline2Vec, containerSizes, trVecIter, polyToContainerIter, packingPar, scaleFactor, polyVec, trials[i], true);
int numPlaced = outline2Vec.size() - std::count(polyToContainerIter.begin(), polyToContainerIter.end(), -1); int numPlaced = outline2Vec.size() - std::count(polyToContainerIter.begin(), polyToContainerIter.end(), -1);
if (numPlaced > bestNumPlaced) { if (numPlaced > bestNumPlaced) {
trVec = trVecIter; trVec = trVecIter;
polyToContainer = polyToContainerIter; polyToContainer = polyToContainerIter;
bestNumPlaced = numPlaced;
} }
} }
return bestNumPlaced > 0; return bestNumPlaced;
} }
//tries to pack polygons using the given gridSize and scaleFactor //tries to pack polygons using the given gridSize and scaleFactor
@ -942,27 +885,31 @@ public:
//look for the best position, dropping from top //look for the best position, dropping from top
for (int col = 0; col < maxCol; col++) { for (int col = 0; col < maxCol; col++) {
int currPolyY; int currPolyY;
currPolyY = packingFields[grid_i].dropY(polyVec[i],col, rast_i); if (!placedUsingSecondaryHorizon) {
if (currPolyY != INVALID_POSITION) { currPolyY = packingFields[grid_i].dropY(polyVec[i],col, rast_i);
assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop"); if (currPolyY != INVALID_POSITION) {
int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) + assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop");
packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i); int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i);
if (currCost < bestCost) { if (packingPar.doubleHorizon && (packingPar.minmax == true))
bestContainer = grid_i; currCost += packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i);
bestCost = currCost; if (currCost < bestCost) {
bestRastIndex = rast_i; bestContainer = grid_i;
bestPolyX = col; bestCost = currCost;
bestPolyY = currPolyY; bestRastIndex = rast_i;
placedUsingSecondaryHorizon = false; bestPolyX = col;
bestPolyY = currPolyY;
placedUsingSecondaryHorizon = false;
}
} }
} }
if (packingPar.innerHorizon) { if (packingPar.innerHorizon) {
currPolyY = packingFields[grid_i].dropYInner(polyVec[i],col, rast_i); currPolyY = packingFields[grid_i].dropYInner(polyVec[i],col, rast_i);
if (currPolyY != INVALID_POSITION) { if (currPolyY != INVALID_POSITION) {
assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop_inner"); assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop_inner");
int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) + int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i);
packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i); if (packingPar.doubleHorizon && (packingPar.minmax == true))
if (currCost < bestCost) { currCost += packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i);
if (!placedUsingSecondaryHorizon || currCost < bestCost) {
bestContainer = grid_i; bestContainer = grid_i;
bestCost = currCost; bestCost = currCost;
bestRastIndex = rast_i; bestRastIndex = rast_i;
@ -979,28 +926,31 @@ public:
for (int row = 0; row < maxRow; row++) { for (int row = 0; row < maxRow; row++) {
int currPolyX; int currPolyX;
currPolyX = packingFields[grid_i].dropX(polyVec[i],row, rast_i); if (!placedUsingSecondaryHorizon) {
if (currPolyX != INVALID_POSITION) { currPolyX = packingFields[grid_i].dropX(polyVec[i],row, rast_i);
assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop"); if (currPolyX != INVALID_POSITION) {
int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) + assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop");
packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i); int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i);
if (currCost < bestCost) { if (packingPar.doubleHorizon && (packingPar.minmax == true))
bestContainer = grid_i; currCost += packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i);
bestCost = currCost; if (currCost < bestCost) {
bestRastIndex = rast_i; bestContainer = grid_i;
bestPolyX = currPolyX; bestCost = currCost;
bestPolyY = row; bestRastIndex = rast_i;
placedUsingSecondaryHorizon = false; bestPolyX = currPolyX;
bestPolyY = row;
placedUsingSecondaryHorizon = false;
}
} }
} }
if (packingPar.innerHorizon) { if (packingPar.innerHorizon) {
currPolyX = packingFields[grid_i].dropXInner(polyVec[i],row, rast_i); currPolyX = packingFields[grid_i].dropXInner(polyVec[i],row, rast_i);
if (currPolyX != INVALID_POSITION) { if (currPolyX != INVALID_POSITION) {
assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop_inner"); assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop_inner");
int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) + int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i);
packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i); if (packingPar.doubleHorizon && (packingPar.minmax == true))
if (currCost < bestCost) { currCost += packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i);
if (!placedUsingSecondaryHorizon || currCost < bestCost) {
bestContainer = grid_i; bestContainer = grid_i;
bestCost = currCost; bestCost = currCost;
bestRastIndex = rast_i; bestRastIndex = rast_i;

View File

@ -39,8 +39,9 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly,
// and adding the gutter width. // and adding the gutter width.
int sizeX = (int)ceil(bb.DimX()*scale); int sizeX = (int)ceil(bb.DimX()*scale);
int sizeY = (int)ceil(bb.DimY()*scale); int sizeY = (int)ceil(bb.DimY()*scale);
sizeX += gutterWidth; int safetyBuffer = 2;
sizeY += gutterWidth; sizeX += (gutterWidth + safetyBuffer);
sizeY += (gutterWidth + safetyBuffer);
QImage img(sizeX,sizeY,QImage::Format_RGB32); QImage img(sizeX,sizeY,QImage::Format_RGB32);
QColor backgroundColor(Qt::transparent); QColor backgroundColor(Qt::transparent);
@ -66,7 +67,7 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly,
painter.setPen(qp); painter.setPen(qp);
painter.resetTransform(); painter.resetTransform();
painter.translate(QPointF(-(bb.min.X()*scale) + gutterWidth/2.0f, -(bb.min.Y()*scale) + gutterWidth/2.0f)); painter.translate(QPointF(-(bb.min.X()*scale) + (gutterWidth + safetyBuffer)/2.0f, -(bb.min.Y()*scale) + (gutterWidth + safetyBuffer)/2.0f));
painter.rotate(math::ToDeg(rotRad)); painter.rotate(math::ToDeg(rotRad));
painter.scale(scale,scale); painter.scale(scale,scale);
@ -102,7 +103,7 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly,
painter.setPen(qp); painter.setPen(qp);
painter.resetTransform(); painter.resetTransform();
painter.translate(QPointF(-(bb.min.X()*scale) + gutterWidth/2.0f, -(bb.min.Y()*scale) + gutterWidth/2.0f)); painter.translate(QPointF(-(bb.min.X()*scale) + (gutterWidth + safetyBuffer)/2.0f, -(bb.min.Y()*scale) + (gutterWidth + safetyBuffer)/2.0f));
painter.rotate(math::ToDeg(rotRad)); painter.rotate(math::ToDeg(rotRad));
painter.scale(scale,scale); painter.scale(scale,scale);
@ -195,8 +196,9 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly,
for (int y = 0; y < img.height(); y++) { for (int y = 0; y < img.height(); y++) {
const uchar* line = img.scanLine(y); const uchar* line = img.scanLine(y);
for(int x = 0; x < img.width(); ++x) { for(int x = 0; x < img.width(); ++x) {
if (((QRgb*)line)[x] == yellow) if (((QRgb*)line)[x] == yellow) {
tetrisGrid[y][x] = 1; tetrisGrid[y][x] = 1;
}
} }
} }