dp_rect_pack  1.1.3
Rectangle packing library for C++
dp_rect_pack.h
Go to the documentation of this file.
1 /*
2  * Rectangle packing library.
3  * v1.1.3
4  *
5  * Copyright (c) 2017-2021 Daniel Plakhotich
6  *
7  * This software is provided 'as-is', without any express or implied
8  * warranty. In no event will the authors be held liable for any damages
9  * arising from the use of this software.
10  *
11  * Permission is granted to anyone to use this software for any purpose,
12  * including commercial applications, and to alter it and redistribute it
13  * freely, subject to the following restrictions:
14  *
15  * 1. The origin of this software must not be misrepresented; you must not
16  * claim that you wrote the original software. If you use this software
17  * in a product, an acknowledgement in the product documentation would be
18  * appreciated but is not required.
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  * 3. This notice may not be removed or altered from any source distribution.
22  */
23 
24 
25 #ifndef DP_RECT_PACK_H
26 #define DP_RECT_PACK_H
27 
28 #include <cassert>
29 #include <cstddef>
30 #include <vector>
31 
32 
33 #define DP_RECT_PACK_VERSION_MAJOR 1
34 #define DP_RECT_PACK_VERSION_MINOR 1
35 #define DP_RECT_PACK_VERSION_PATCH 3
36 
37 
38 namespace dp {
39 namespace rect_pack {
40 
41 
48 struct InsertStatus {
49  enum Type {
50  ok,
53 
64  };
65 };
66 
67 
68 // A note on the implementation.
69 // The current algorithm is absolutely the same as in version 1.0.0,
70 // except that we only keep the leaf nodes of the binary tree. This
71 // dramatically improves performance and reduces memory usage, but
72 // growDown() and growRight() methods are harder to understand
73 // because the leafs insertion order depends on several layers of
74 // parent branches that don't physically exist. I added comments to
75 // help you visualize what happens, but it will probably be easier
76 // to just look at the code of the version 1.0.0.
77 
78 
93 template<typename GeomT = int>
94 class RectPacker {
95 public:
96  struct Spacing {
97  GeomT x;
98  GeomT y;
99 
103  explicit Spacing(GeomT spacing)
104  : x(spacing)
105  , y(spacing)
106  {}
107 
108  Spacing(GeomT x, GeomT y)
109  : x(x)
110  , y(y)
111  {}
112  };
113 
114  struct Padding {
115  GeomT top;
116  GeomT bottom;
117  GeomT left;
118  GeomT right;
119 
123  explicit Padding(GeomT padding)
124  : top(padding)
125  , bottom(padding)
126  , left(padding)
127  , right(padding)
128  {}
129 
130  Padding(GeomT top, GeomT bottom, GeomT left, GeomT right)
131  : top(top)
132  , bottom(bottom)
133  , left(left)
134  , right(right)
135  {}
136  };
137 
138  struct Position {
139  GeomT x;
140  GeomT y;
141 
143  : x()
144  , y()
145  {}
146 
147  Position(GeomT x, GeomT y)
148  : x(x)
149  , y(y)
150  {}
151  };
152 
156  struct InsertResult {
164 
169 
175  std::size_t pageIndex;
176  };
177 
212  GeomT maxPageWidth, GeomT maxPageHeight,
213  const Spacing& rectsSpacing = Spacing(0),
214  const Padding& pagePadding = Padding(0))
215  : ctx(maxPageWidth, maxPageHeight, rectsSpacing, pagePadding)
216  , pages(1)
217  {}
218 
224  std::size_t getNumPages() const
225  {
226  return pages.size();
227  }
228 
238  void getPageSize(std::size_t pageIndex, GeomT& width, GeomT& height) const
239  {
240  const Size size = pages[pageIndex].getSize(ctx);
241  width = size.w;
242  height = size.h;
243  }
244 
265  InsertResult insert(GeomT width, GeomT height);
266 private:
267  struct Size {
268  GeomT w;
269  GeomT h;
270 
271  Size(GeomT w, GeomT h)
272  : w(w)
273  , h(h)
274  {}
275  };
276 
277  struct Context;
278  class Page {
279  public:
280  Page()
281  : nodes()
282  , rootSize(0, 0)
283  , growDownRootBottomIdx(0)
284  {}
285 
286  Size getSize(const Context& ctx) const
287  {
288  return Size(
289  ctx.padding.left + rootSize.w + ctx.padding.right,
290  ctx.padding.top + rootSize.h + ctx.padding.bottom);
291  }
292 
293  bool insert(Context& ctx, const Size& rect, Position& pos);
294  private:
295  struct Node {
296  Position pos;
297  Size size;
298 
299  Node(GeomT x, GeomT y, GeomT w, GeomT h)
300  : pos(x, y)
301  , size(w, h)
302  {}
303  };
304 
305  // Leaf nodes of the binary tree in depth-first order
306  std::vector<Node> nodes;
307  Size rootSize;
308  // The index of the first leaf bottom node of the new root
309  // created in growDown(). See the method for more details.
310  std::size_t growDownRootBottomIdx;
311 
312  bool tryInsert(Context& ctx, const Size& rect, Position& pos);
313  bool findNode(
314  const Size& rect,
315  std::size_t& nodeIdx, Position& pos) const;
316  void subdivideNode(
317  Context& ctx, std::size_t nodeIdx, const Size& rect);
318  bool tryGrow(Context& ctx, const Size& rect, Position& pos);
319  void growDown(Context& ctx, const Size& rect, Position& pos);
320  void growRight(Context& ctx, const Size& rect, Position& pos);
321  };
322 
323  struct Context {
324  Size maxSize;
325  Spacing spacing;
326  Padding padding;
327 
328  Context(
329  GeomT maxPageWidth, GeomT maxPageHeight,
330  const Spacing& rectsSpacing, const Padding& pagePadding);
331 
332  static void subtractPadding(GeomT& padding, GeomT& size);
333  };
334 
335  Context ctx;
336  std::vector<Page> pages;
337 };
338 
339 
340 template<typename GeomT>
342 RectPacker<GeomT>::insert(GeomT width, GeomT height)
343 {
344  InsertResult result;
345 
346  if (width < 0 || height < 0) {
348  return result;
349  }
350 
351  if (width == 0 || height == 0) {
353  return result;
354  }
355 
356  if (width > ctx.maxSize.w || height > ctx.maxSize.h) {
358  return result;
359  }
360 
361  const Size rect(width, height);
362 
363  for (std::size_t i = 0; i < pages.size(); ++i)
364  if (pages[i].insert(ctx, rect, result.pos)) {
365  result.status = InsertStatus::ok;
366  result.pageIndex = i;
367  return result;
368  }
369 
370  pages.push_back(Page());
371  Page& page = pages.back();
372  page.insert(ctx, rect, result.pos);
373  result.status = InsertStatus::ok;
374  result.pageIndex = pages.size() - 1;
375 
376  return result;
377 }
378 
379 
380 template<typename GeomT>
382  Context& ctx, const Size& rect, Position& pos)
383 {
384  assert(rect.w > 0);
385  assert(rect.w <= ctx.maxSize.w);
386  assert(rect.h > 0);
387  assert(rect.h <= ctx.maxSize.h);
388 
389  // The first insertion should be handled especially since
390  // growRight() and growDown() add spacing between the root
391  // and the inserted rectangle.
392  if (rootSize.w == 0) {
393  rootSize = rect;
394  pos.x = ctx.padding.left;
395  pos.y = ctx.padding.top;
396 
397  return true;
398  }
399 
400  return tryInsert(ctx, rect, pos) || tryGrow(ctx, rect, pos);
401 }
402 
403 
404 template<typename GeomT>
406  Context& ctx, const Size& rect, Position& pos)
407 {
408  std::size_t nodeIdx;
409  if (findNode(rect, nodeIdx, pos)) {
410  subdivideNode(ctx, nodeIdx, rect);
411  return true;
412  }
413 
414  return false;
415 }
416 
417 
418 template<typename GeomT>
420  const Size& rect, std::size_t& nodeIdx, Position& pos) const
421 {
422  for (nodeIdx = 0; nodeIdx < nodes.size(); ++nodeIdx) {
423  const Node& node = nodes[nodeIdx];
424  if (rect.w <= node.size.w && rect.h <= node.size.h) {
425  pos = node.pos;
426  return true;
427  }
428  }
429 
430  return false;
431 }
432 
433 
448 template<typename GeomT>
450  Context& ctx, std::size_t nodeIdx, const Size& rect)
451 {
452  assert(nodeIdx < nodes.size());
453 
454  Node& node = nodes[nodeIdx];
455 
456  assert(node.size.w >= rect.w);
457  const GeomT rightW = node.size.w - rect.w;
458  const bool hasSpaceRight = rightW > ctx.spacing.x;
459 
460  assert(node.size.h >= rect.h);
461  const GeomT bottomH = node.size.h - rect.h;
462  const bool hasSpaceBelow = bottomH > ctx.spacing.y;
463 
464  if (hasSpaceRight) {
465  // Right node replaces the current
466 
467  const GeomT bottomX = node.pos.x;
468  const GeomT bottomW = node.size.w;
469 
470  node.pos.x += rect.w + ctx.spacing.x;
471  node.size.w = rightW - ctx.spacing.x;
472  node.size.h = rect.h;
473 
474  if (hasSpaceBelow) {
475  nodes.insert(
476  nodes.begin() + nodeIdx + 1,
477  Node(
478  bottomX,
479  node.pos.y + rect.h + ctx.spacing.y,
480  bottomW,
481  bottomH - ctx.spacing.y));
482 
483  if (nodeIdx <= growDownRootBottomIdx)
484  ++growDownRootBottomIdx;
485  }
486  } else if (hasSpaceBelow) {
487  // Bottom node replaces the current
488  node.pos.y += rect.h + ctx.spacing.y;
489  node.size.h = bottomH - ctx.spacing.y;
490  } else {
491  nodes.erase(nodes.begin() + nodeIdx);
492  if (nodeIdx < growDownRootBottomIdx)
493  --growDownRootBottomIdx;
494  }
495 }
496 
497 
498 template<typename GeomT>
500  Context& ctx, const Size& rect, Position& pos)
501 {
502  assert(ctx.maxSize.w >= rootSize.w);
503  const GeomT freeW = ctx.maxSize.w - rootSize.w;
504  assert(ctx.maxSize.h >= rootSize.h);
505  const GeomT freeH = ctx.maxSize.h - rootSize.h;
506 
507  const bool canGrowDown = (
508  freeH >= rect.h && freeH - rect.h >= ctx.spacing.y);
509  const bool mustGrowDown = (
510  canGrowDown
511  && freeW >= ctx.spacing.x
512  && (rootSize.w + ctx.spacing.x
513  >= rootSize.h + rect.h + ctx.spacing.y));
514  if (mustGrowDown) {
515  growDown(ctx, rect, pos);
516  return true;
517  }
518 
519  const bool canGrowRight = (
520  freeW >= rect.w && freeW - rect.w >= ctx.spacing.x);
521  if (canGrowRight) {
522  growRight(ctx, rect, pos);
523  return true;
524  }
525 
526  if (canGrowDown) {
527  growDown(ctx, rect, pos);
528  return true;
529  }
530 
531  return false;
532 }
533 
534 
535 template<typename GeomT>
537  Context& ctx, const Size& rect, Position& pos)
538 {
539  assert(ctx.maxSize.h > rootSize.h);
540  assert(ctx.maxSize.h - rootSize.h >= rect.h);
541  assert(ctx.maxSize.h - rootSize.h - rect.h >= ctx.spacing.y);
542 
543  pos.x = ctx.padding.left;
544  pos.y = ctx.padding.top + rootSize.h + ctx.spacing.y;
545 
546  if (rootSize.w < rect.w) {
547  if (rect.w - rootSize.w > ctx.spacing.x) {
548  // The auxiliary node becomes the right child of the new
549  // root. It contains the current root (bottom child) and
550  // free space at the current root's right (right child).
551  nodes.insert(
552  nodes.begin(),
553  Node(
554  ctx.padding.left + rootSize.w + ctx.spacing.x,
555  ctx.padding.top,
556  rect.w - rootSize.w - ctx.spacing.x,
557  rootSize.h));
558  ++growDownRootBottomIdx;
559  }
560 
561  rootSize.w = rect.w;
562  } else if (rootSize.w - rect.w > ctx.spacing.x) {
563  // Free space at the right of the inserted rect becomes the
564  // right child of the rect's node, which in turn is the
565  // bottom child of the new root.
566  nodes.insert(
567  nodes.begin() + growDownRootBottomIdx,
568  Node(
569  pos.x + rect.w + ctx.spacing.x,
570  pos.y,
571  rootSize.w - rect.w - ctx.spacing.x,
572  rect.h));
573 
574  // The inserted node is visited before the node from the next
575  // growDown() since the current new root will be the right
576  // child of the next root.
577  ++growDownRootBottomIdx;
578  }
579 
580  rootSize.h += ctx.spacing.y + rect.h;
581 }
582 
583 
584 template<typename GeomT>
586  Context& ctx, const Size& rect, Position& pos)
587 {
588  assert(ctx.maxSize.w > rootSize.w);
589  assert(ctx.maxSize.w - rootSize.w >= rect.w);
590  assert(ctx.maxSize.w - rootSize.w - rect.w >= ctx.spacing.x);
591 
592  pos.x = ctx.padding.left + rootSize.w + ctx.spacing.x;
593  pos.y = ctx.padding.top;
594 
595  if (rootSize.h < rect.h) {
596  if (rect.h - rootSize.h > ctx.spacing.y)
597  // The auxiliary node becomes the bottom child of the
598  // new root. It contains the current root (right child)
599  // and free space at the current root's bottom, if any
600  // (bottom child).
601  nodes.insert(
602  nodes.end(),
603  Node(
604  ctx.padding.left,
605  ctx.padding.top + rootSize.h + ctx.spacing.y,
606  rootSize.w,
607  rect.h - rootSize.h - ctx.spacing.y));
608 
609  rootSize.h = rect.h;
610  } else if (rootSize.h - rect.h > ctx.spacing.y) {
611  // Free space at the bottom of the inserted rect becomes the
612  // bottom child of the rect's node, which in turn is the
613  // right child of the new root node.
614  nodes.insert(
615  nodes.begin(),
616  Node(
617  pos.x,
618  pos.y + rect.h + ctx.spacing.y,
619  rect.w,
620  rootSize.h - rect.h - ctx.spacing.y));
621  ++growDownRootBottomIdx;
622  }
623 
624  rootSize.w += ctx.spacing.x + rect.w;
625 }
626 
627 
628 template<typename GeomT>
630  GeomT maxPageWidth, GeomT maxPageHeight,
631  const Spacing& rectsSpacing, const Padding& pagePadding)
632  : maxSize(maxPageWidth, maxPageHeight)
633  , spacing(rectsSpacing)
634  , padding(pagePadding)
635 {
636  if (maxSize.w < 0)
637  maxSize.w = 0;
638  if (maxSize.h < 0)
639  maxSize.h = 0;
640 
641  if (spacing.x < 0)
642  spacing.x = 0;
643  if (spacing.y < 0)
644  spacing.y = 0;
645 
646  subtractPadding(padding.top, maxSize.h);
647  subtractPadding(padding.bottom, maxSize.h);
648  subtractPadding(padding.left, maxSize.w);
649  subtractPadding(padding.right, maxSize.w);
650 }
651 
652 
653 template<typename GeomT>
655  GeomT& padding, GeomT& size)
656 {
657  if (padding < 0)
658  padding = 0;
659  else if (padding < size)
660  size -= padding;
661  else {
662  padding = size;
663  size = 0;
664  }
665 }
666 
667 
668 } // namespace rect_pack
669 } // namespace dp
670 
671 
672 #endif // DP_RECT_PACK_H
GeomT top
Definition: dp_rect_pack.h:115
Spacing(GeomT spacing)
Construct Spacing with the same spacing for both dimensions.
Definition: dp_rect_pack.h:103
InsertStatus::Type status
Status of the insertion.
Definition: dp_rect_pack.h:163
Definition: dp_rect_pack.h:138
Definition: dp_rect_pack.h:96
std::size_t pageIndex
Index of the page in which the rectangle was inserted.
Definition: dp_rect_pack.h:175
Successful insertion.
Definition: dp_rect_pack.h:50
Position pos
Position of the inserted rectangle within the page.
Definition: dp_rect_pack.h:168
Rectangle is too big to fit in a single page.
Definition: dp_rect_pack.h:63
Type
Definition: dp_rect_pack.h:49
Width and/or height is zero.
Definition: dp_rect_pack.h:52
GeomT y
Vertical spacing.
Definition: dp_rect_pack.h:98
Padding(GeomT top, GeomT bottom, GeomT left, GeomT right)
Definition: dp_rect_pack.h:130
RectPacker(GeomT maxPageWidth, GeomT maxPageHeight, const Spacing &rectsSpacing=Spacing(0), const Padding &pagePadding=Padding(0))
RectPacker constructor.
Definition: dp_rect_pack.h:211
Spacing(GeomT x, GeomT y)
Definition: dp_rect_pack.h:108
Width and/or height is negative.
Definition: dp_rect_pack.h:51
GeomT bottom
Definition: dp_rect_pack.h:116
InsertResult insert(GeomT width, GeomT height)
Insert a rectangle.
Definition: dp_rect_pack.h:342
std::size_t getNumPages() const
Return the current number of pages.
Definition: dp_rect_pack.h:224
GeomT y
Definition: dp_rect_pack.h:140
GeomT left
Definition: dp_rect_pack.h:117
GeomT x
Definition: dp_rect_pack.h:139
void getPageSize(std::size_t pageIndex, GeomT &width, GeomT &height) const
Return the current size of the page.
Definition: dp_rect_pack.h:238
Result returned by RectPacker::insert().
Definition: dp_rect_pack.h:156
GeomT right
Definition: dp_rect_pack.h:118
Position(GeomT x, GeomT y)
Definition: dp_rect_pack.h:147
GeomT x
Horizontal spacing.
Definition: dp_rect_pack.h:97
Definition: dp_rect_pack.h:114
Padding(GeomT padding)
Construct Padding with the same padding for all sides.
Definition: dp_rect_pack.h:123
Definition: dp_rect_pack.h:38
Rectangle packer.
Definition: dp_rect_pack.h:94
Status of the RectPacker::InsertResult.
Definition: dp_rect_pack.h:48
Position()
Definition: dp_rect_pack.h:142