/// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned /// struct will have the width and height values swapped. /// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure /// tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up /// some extra time. /// @param rectChoice The free rectangle choice heuristic rule to use. /// @param splitMethod The free rectangle split heuristic rule to use. public BinRect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) { // Find where to put the new rectangle. int freeNodeIndex = 0; BinRect newRect = FindPositionForNewNode(width, height, rectChoice, ref freeNodeIndex); // Abort if we didn't have enough space in the bin. if (newRect.height == 0) { return(newRect); } // Remove the space that was just consumed by the new rectangle. { var item = freeRectangles[freeNodeIndex]; SplitFreeRectByHeuristic(ref item, ref newRect, splitMethod); freeRectangles[freeNodeIndex] = item; freeRectangles.RemoveAt(freeNodeIndex); } // Perform a Rectangle Merge step if desired. if (merge) { MergeFreeList(); } // Remember the new used rectangle. usedRectangles.Add(newRect); return(newRect); }
/// Computes the placement score for placing the given rectangle with the given method. /// @param score1 [out] The primary placement score will be outputted here. /// @param score2 [out] The secondary placement score will be outputted here. This isu sed to break ties. /// @return This struct identifies where the rectangle would be placed if it were placed. private BinRect ScoreRect(int width, int height, bool rot, FreeRectChoiceHeuristic method, ref int score1, ref int score2) { BinRect newNode = new BinRect(); score1 = int.MaxValue; score2 = int.MinValue; switch (method) { case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(rot, width, height, ref score1, ref score2); break; case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(rot, width, height, ref score1, ref score2); break; case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(rot, width, height, ref score1); score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better. break; case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(rot, width, height, ref score2, ref score1); break; case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(rot, width, height, ref score1, ref score2); break; } // Cannot fit the current rectangle. if (newNode.height == 0) { score1 = int.MaxValue; score2 = int.MaxValue; } return(newNode); }
static int ScoreBestLongSideFit(int width, int height, ref BinRect freeRect) { int leftoverHoriz = Math.Abs(freeRect.width - width); int leftoverVert = Math.Abs(freeRect.height - height); int leftover = Math.Max(leftoverHoriz, leftoverVert); return(leftover); }
/// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it. /// Determines the split axis by using the given heuristic. public void SplitFreeRectByHeuristic(ref BinRect freeRect, ref BinRect placedRect, GuillotineSplitHeuristic method) { // Compute the lengths of the leftover area. int w = freeRect.width - placedRect.width; int h = freeRect.height - placedRect.height; // Placing placedRect into freeRect results in an L-shaped free area, which must be split into // two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line. // We have two choices: horizontal or vertical. // Use the given heuristic to decide which choice to make. bool splitHorizontal; switch (method) { case GuillotineSplitHeuristic.SplitShorterLeftoverAxis: // Split along the shorter leftover axis. splitHorizontal = (w <= h); break; case GuillotineSplitHeuristic.SplitLongerLeftoverAxis: // Split along the longer leftover axis. splitHorizontal = (w > h); break; case GuillotineSplitHeuristic.SplitMinimizeArea: // Maximize the larger area == minimize the smaller area. // Tries to make the single bigger rectangle. splitHorizontal = (placedRect.width * h > w * placedRect.height); break; case GuillotineSplitHeuristic.SplitMaximizeArea: // Maximize the smaller area == minimize the larger area. // Tries to make the rectangles more even-sized. splitHorizontal = (placedRect.width * h <= w * placedRect.height); break; case GuillotineSplitHeuristic.SplitShorterAxis: // Split along the shorter total axis. splitHorizontal = (freeRect.width <= freeRect.height); break; case GuillotineSplitHeuristic.SplitLongerAxis: // Split along the longer total axis. splitHorizontal = (freeRect.width > freeRect.height); break; default: splitHorizontal = true; break; } // Perform the actual split. SplitFreeRectAlongAxis(ref freeRect, ref placedRect, splitHorizontal); }
private BinRect FindPositionForNewNodeBestAreaFit(bool rot, int width, int height, ref int bestAreaFit, ref int bestShortSideFit) { BinRect bestNode = new BinRect(); bestAreaFit = int.MaxValue; bestShortSideFit = int.MaxValue; for (int i = 0; i < freeRectangles.Count; ++i) { int areaFit = freeRectangles[i].width * freeRectangles[i].height - width * height; // Try to place the rectangle in upright (non-flipped) orientation. if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { int leftoverHoriz = Math.Abs(freeRectangles[i].width - width); int leftoverVert = Math.Abs(freeRectangles[i].height - height); int shortSideFit = Math.Min(leftoverHoriz, leftoverVert); if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = width; bestNode.height = height; bestShortSideFit = shortSideFit; bestAreaFit = areaFit; } } if (rot) { if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { int leftoverHoriz = Math.Abs(freeRectangles[i].width - height); int leftoverVert = Math.Abs(freeRectangles[i].height - width); int shortSideFit = Math.Min(leftoverHoriz, leftoverVert); if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = height; bestNode.height = width; bestShortSideFit = shortSideFit; bestAreaFit = areaFit; } } } } return(bestNode); }
/// @return True if the free node was split. private bool SplitFreeNode(BinRect freeNode, ref BinRect usedNode) { // Test with SAT if the rectangles even intersect. if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x || usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) { return(false); } if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) { // New node at the top side of the used node. if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) { BinRect newNode = freeNode; newNode.height = usedNode.y - newNode.y; freeRectangles.Add(newNode); } // New node at the bottom side of the used node. if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) { BinRect newNode = freeNode; newNode.y = usedNode.y + usedNode.height; newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height); freeRectangles.Add(newNode); } } if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) { // New node at the left side of the used node. if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) { BinRect newNode = freeNode; newNode.width = usedNode.x - newNode.x; freeRectangles.Add(newNode); } // New node at the right side of the used node. if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) { BinRect newNode = freeNode; newNode.x = usedNode.x + usedNode.width; newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width); freeRectangles.Add(newNode); } } return(true); }
/// (Re)initializes the packer to an empty bin of width x height units. Call whenever /// you need to restart with a new bin. public void Init(int width, int height) { binWidth = width; binHeight = height; usedRectangles = new List <BinRect>(); freeRectangles = new List <BinRect>(); BinRect n = new BinRect(); n.x = 0; n.y = 0; n.width = width; n.height = height; usedRectangles.Clear(); freeRectangles.Clear(); freeRectangles.Add(n); }
/// Places the given rectangle into the bin. private void PlaceRect(ref BinRect node) { int numRectanglesToProcess = freeRectangles.Count; for (int i = 0; i < numRectanglesToProcess; ++i) { if (SplitFreeNode(freeRectangles[i], ref node)) { freeRectangles.RemoveAt(i); --i; --numRectanglesToProcess; } } PruneFreeList(); usedRectangles.Add(node); }
static int ScoreByHeuristic(int width, int height, ref BinRect freeRect, FreeRectChoiceHeuristic rectChoice) { switch (rectChoice) { case FreeRectChoiceHeuristic.RectBestAreaFit: return(ScoreBestAreaFit(width, height, ref freeRect)); case FreeRectChoiceHeuristic.RectBestShortSideFit: return(ScoreBestShortSideFit(width, height, ref freeRect)); case FreeRectChoiceHeuristic.RectBestLongSideFit: return(ScoreBestLongSideFit(width, height, ref freeRect)); case FreeRectChoiceHeuristic.RectWorstAreaFit: return(ScoreWorstAreaFit(width, height, ref freeRect)); case FreeRectChoiceHeuristic.RectWorstShortSideFit: return(ScoreWorstShortSideFit(width, height, ref freeRect)); case FreeRectChoiceHeuristic.RectWorstLongSideFit: return(ScoreWorstLongSideFit(width, height, ref freeRect)); default: return(int.MaxValue); } }
private BinRect FindPositionForNewNodeBottomLeft(bool rot, int width, int height, ref int bestY, ref int bestX) { BinRect bestNode = new BinRect(); bestY = int.MaxValue; bestX = int.MaxValue; for (int i = 0; i < freeRectangles.Count; ++i) { // Try to place the rectangle in upright (non-flipped) orientation. if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { int topSideY = freeRectangles[i].y + height; if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = width; bestNode.height = height; bestY = topSideY; bestX = freeRectangles[i].x; } } if (rot) { if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { int topSideY = freeRectangles[i].y + width; if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = height; bestNode.height = width; bestY = topSideY; bestX = freeRectangles[i].x; } } } } return(bestNode); }
/// Inserts a single rectangle into the bin, possibly rotated. public BinRect Insert(int width, int height, bool rot, FreeRectChoiceHeuristic method) { BinRect newNode = new BinRect(); // Unused in this function. We don't need to know the score after finding the position. int score1 = int.MaxValue; int score2 = int.MaxValue; switch (method) { case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(rot, width, height, ref score1, ref score2); break; case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(rot, width, height, ref score1, ref score2); break; case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(rot, width, height, ref score1); break; case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(rot, width, height, ref score2, ref score1); break; case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(rot, width, height, ref score1, ref score2); break; } if (newNode.height == 0) { return(newNode); } int numRectanglesToProcess = freeRectangles.Count; for (int i = 0; i < numRectanglesToProcess; ++i) { if (SplitFreeNode(freeRectangles[i], ref newNode)) { freeRectangles.RemoveAt(i); --i; --numRectanglesToProcess; } } PruneFreeList(); usedRectangles.Add(newNode); return(newNode); }
private BinRect FindPositionForNewNodeContactPoint(bool rot, int width, int height, ref int bestContactScore) { BinRect bestNode = new BinRect(); bestContactScore = -1; for (int i = 0; i < freeRectangles.Count; ++i) { // Try to place the rectangle in upright (non-flipped) orientation. if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, width, height); if (score > bestContactScore) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = width; bestNode.height = height; bestContactScore = score; } } if (rot) { if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, height, width); if (score > bestContactScore) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = height; bestNode.height = width; bestContactScore = score; } } } } return(bestNode); }
/// Inserts the given list of rectangles in an offline/batch mode, possibly rotated. /// @param rects The list of rectangles to insert. This vector will be destroyed in the process. /// @param dst [out] This list will contain the packed rectangles. The indices will not correspond to that of rects. /// @param method The rectangle placement rule to use when packing. public void Insert(List <RectSize> rects, List <BinRect> dst, bool rot, FreeRectChoiceHeuristic method) { dst.Clear(); while (rects.Count > 0) { int bestScore1 = int.MaxValue; int bestScore2 = int.MaxValue; int bestRectIndex = -1; BinRect bestNode = new BinRect(); for (int i = 0; i < rects.Count; ++i) { int score1 = 0; int score2 = 0; BinRect newNode = ScoreRect(rects[i].width, rects[i].height, rot, method, ref score1, ref score2); if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { bestScore1 = score1; bestScore2 = score2; bestNode = newNode; bestRectIndex = i; } } if (bestRectIndex == -1) { return; } PlaceRect(ref bestNode); dst.Add(bestNode); rects.RemoveAt(bestRectIndex); } }
/// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis. public void SplitFreeRectAlongAxis(ref BinRect freeRect, ref BinRect placedRect, bool splitHorizontal) { // Form the two new rectangles. BinRect bottom = new BinRect(); bottom.x = freeRect.x; bottom.y = freeRect.y + placedRect.height; bottom.height = freeRect.height - placedRect.height; BinRect right = new BinRect(); right.x = freeRect.x + placedRect.width; right.y = freeRect.y; right.width = freeRect.width - placedRect.width; if (splitHorizontal) { bottom.width = freeRect.width; right.height = placedRect.height; } else // Split vertically { bottom.width = placedRect.width; right.height = freeRect.height; } // Add the new rectangles into the free rectangle pool if they weren't degenerate. if (bottom.width > 0 && bottom.height > 0) { freeRectangles.Add(bottom); } if (right.width > 0 && right.height > 0) { freeRectangles.Add(right); } }
public bool IsContainedIn(BinRect a, BinRect b) { return(a.x >= b.x && a.y >= b.y && a.x + a.width <= b.x + b.width && a.y + a.height <= b.y + b.height); }
// The following functions compute (penalty) score values if a rect of the given size was placed into the // given free rectangle. In these score values, smaller is better. static int ScoreBestAreaFit(int width, int height, ref BinRect freeRect) { return(freeRect.width * freeRect.height - width * height); }
static int ScoreWorstLongSideFit(int width, int height, ref BinRect freeRect) { return(-ScoreBestLongSideFit(width, height, ref freeRect)); }
public void Pack(List <PackerBitmap> bitmaps, bool verbose, bool unique, bool rotate) { var packer = new MaxRectsBinPack(Width, Height); int ww = 0; int hh = 0; while (bitmaps.Count > 0) { var bitmap = bitmaps[bitmaps.Count - 1]; if (verbose) { Console.WriteLine($" { bitmaps.Count - 1 }: {bitmap.Name}"); } //Check to see if this is a duplicate of an already packed bitmap if (unique) { if (DupLookup.TryGetValue(bitmap.GetHashCode(), out var di)) { Point p = Points[di]; p.dupID = di; Points.Add(p); Bitmaps.Add(bitmap); bitmaps.RemoveAt(bitmaps.Count - 1); continue; } } //If it's not a duplicate, pack it into the atlas { BinRect rect = packer.Insert(bitmap.Width + Pad, bitmap.Height + Pad, rotate, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit); if (rect.width == 0 || rect.height == 0) { break; } if (unique) { DupLookup.Add(bitmap.GetHashCode(), Points.Count); } //Check if we rotated it Point p; p.x = rect.x; p.y = rect.y; p.dupID = -1; p.rot = rotate && (bitmap.Width != (rect.width - Pad)); Points.Add(p); Bitmaps.Add(bitmap); bitmaps.RemoveAt(bitmaps.Count - 1); ww = Math.Max(rect.x + rect.width, ww); hh = Math.Max(rect.y + rect.height, hh); } } while (Width / 2 >= ww) { Width /= 2; } while (Height / 2 >= hh) { Height /= 2; } }
/// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into. /// Running time is Theta(|freeRectangles|). /// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new /// rect was placed. /// @return A Rect structure that represents the placement of the new rect into the best free rectangle. public BinRect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, ref int nodeIndex) { BinRect bestNode = new BinRect(); int bestScore = int.MaxValue; /// Try each free rectangle to find the best one for placement. for (int i = 0; i < freeRectangles.Count; ++i) { // If this is a perfect fit upright, choose it immediately. if (width == freeRectangles[i].width && height == freeRectangles[i].height) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = width; bestNode.height = height; bestScore = int.MinValue; break; } // If this is a perfect fit sideways, choose it. else if (height == freeRectangles[i].width && width == freeRectangles[i].height) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = height; bestNode.height = width; bestScore = int.MinValue; break; } // Does the rectangle fit upright? else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height) { var item = freeRectangles[i]; int score = ScoreByHeuristic(width, height, ref item, rectChoice); freeRectangles[i] = item; if (score < bestScore) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = width; bestNode.height = height; bestScore = score; } } // Does the rectangle fit sideways? else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height) { var item = freeRectangles[i]; int score = ScoreByHeuristic(width, height, ref item, rectChoice); freeRectangles[i] = item; if (score < bestScore) { bestNode.x = freeRectangles[i].x; bestNode.y = freeRectangles[i].y; bestNode.width = height; bestNode.height = width; bestScore = score; } } } return(bestNode); }