1 #ifndef SHELF_PACK_HPP 2 #define SHELF_PACK_HPP 3 4 #include <algorithm> 5 #include <cstdint> 6 #include <deque> 7 #include <limits> 8 #include <map> 9 #include <vector> 10 11 namespace mapbox { 12 13 const char * const SHELF_PACK_VERSION = "2.1.1"; 14 15 16 17 class Bin { 18 friend class ShelfPack; 19 20 public: 21 /** 22 * Create a new Bin. 23 * 24 * @class Bin 25 * @param {int32_t} id Unique bin identifier 26 * @param {int32_t} [w1=-1] Width of the new Bin 27 * @param {int32_t} [h1=-1] Height of the new Bin 28 * @param {int32_t} [maxw1=-1] Maximum Width of the new Bin 29 * @param {int32_t} [maxh1=-1] Maximum Height of the new Bin 30 * @param {int32_t} [x1=-1] X location of the Bin 31 * @param {int32_t} [y1=-1] Y location of the Bin 32 * 33 * @example 34 * Bin b(-1, 12, 16); 35 */ Bin(int32_t id1=-1,int32_t w1=-1,int32_t h1=-1,int32_t maxw1=-1,int32_t maxh1=-1,int32_t x1=-1,int32_t y1=-1)36 explicit Bin( 37 int32_t id1 = -1, 38 int32_t w1 = -1, 39 int32_t h1 = -1, 40 int32_t maxw1 = -1, 41 int32_t maxh1 = -1, 42 int32_t x1 = -1, 43 int32_t y1 = -1 44 ) : id(id1), w(w1), h(h1), maxw(maxw1), maxh(maxh1), x(x1), y(y1), refcount_(0) { 45 46 if (maxw == -1) { 47 maxw = w; 48 } 49 if (maxh == -1) { 50 maxh = h; 51 } 52 } 53 54 int32_t id; 55 int32_t w; 56 int32_t h; 57 int32_t maxw; 58 int32_t maxh; 59 int32_t x; 60 int32_t y; 61 refcount() const62 int32_t refcount() const { return refcount_; } 63 64 private: 65 66 int32_t refcount_; 67 }; 68 69 70 class Shelf { 71 public: 72 /** 73 * Create a new Shelf. 74 * 75 * @class Shelf 76 * @param {int32_t} y1 Top coordinate of the new shelf 77 * @param {int32_t} w1 Width of the new shelf 78 * @param {int32_t} h1 Height of the new shelf 79 * 80 * @example 81 * Shelf shelf(64, 512, 24); 82 */ Shelf(int32_t y1,int32_t w1,int32_t h1)83 explicit Shelf(int32_t y1, int32_t w1, int32_t h1) : 84 x_(0), y_(y1), w_(w1), h_(h1), wfree_(w1) { } 85 86 87 /** 88 * Allocate a single bin into the shelf. 89 * Bin is stored in a `bins_` container. 90 * Returned pointer is stable until the shelf is destroyed. 91 * 92 * @param {int32_t} id Unique bin identifier, pass -1 to generate a new one 93 * @param {int32_t} w1 Width of the bin to allocate 94 * @param {int32_t} h1 Height of the bin to allocate 95 * @returns {Bin*} `Bin` pointer with `id`, `x`, `y`, `w`, `h` members 96 * 97 * @example 98 * Bin* result = shelf.alloc(-1, 12, 16); 99 */ alloc(int32_t id,int32_t w1,int32_t h1)100 Bin* alloc(int32_t id, int32_t w1, int32_t h1) { 101 if (w1 > wfree_ || h1 > h_) { 102 return nullptr; 103 } 104 int32_t x1 = x_; 105 x_ += w1; 106 wfree_ -= w1; 107 bins_.emplace_back(id, w1, h1, w1, h_, x1, y_); 108 return &bins_.back(); 109 } 110 111 112 /** 113 * Resize the shelf. 114 * 115 * @param {int32_t} w1 Requested new width of the shelf 116 * @returns {bool} `true` if resize succeeded, `false` if failed 117 * 118 * @example 119 * shelf.resize(512); 120 */ resize(int32_t w1)121 bool resize(int32_t w1) { 122 wfree_ += (w1 - w_); 123 w_ = w1; 124 return true; 125 } 126 x() const127 int32_t x() const { return x_; } y() const128 int32_t y() const { return y_; } w() const129 int32_t w() const { return w_; } h() const130 int32_t h() const { return h_; } wfree() const131 int32_t wfree() const { return wfree_; } 132 133 private: 134 int32_t x_; 135 int32_t y_; 136 int32_t w_; 137 int32_t h_; 138 int32_t wfree_; 139 140 std::deque<Bin> bins_; 141 }; 142 143 144 145 class ShelfPack { 146 public: 147 148 struct ShelfPackOptions { ShelfPackOptionsmapbox::ShelfPack::ShelfPackOptions149 inline ShelfPackOptions() : autoResize(false) { }; 150 bool autoResize; 151 }; 152 153 struct PackOptions { PackOptionsmapbox::ShelfPack::PackOptions154 inline PackOptions() : inPlace(false) { }; 155 bool inPlace; 156 }; 157 158 159 /** 160 * Create a new ShelfPack bin allocator. 161 * 162 * Uses the Shelf Best Height Fit algorithm from 163 * http://clb.demon.fi/files/RectangleBinPack.pdf 164 * 165 * @class ShelfPack 166 * @param {int32_t} [w=64] Initial width of the sprite 167 * @param {int32_t} [h=64] Initial width of the sprite 168 * @param {ShelfPackOptions} [options] 169 * @param {bool} [options.autoResize=false] If `true`, the sprite will automatically grow 170 * 171 * @example 172 * ShelfPack::ShelfPackOptions options; 173 * options.autoResize = false; 174 * ShelfPack sprite = new ShelfPack(64, 64, options); 175 */ ShelfPack(int32_t w=0,int32_t h=0,const ShelfPackOptions & options=ShelfPackOptions{})176 explicit ShelfPack(int32_t w = 0, int32_t h = 0, const ShelfPackOptions &options = ShelfPackOptions{}) { 177 width_ = w > 0 ? w : 64; 178 height_ = h > 0 ? h : 64; 179 autoResize_ = options.autoResize; 180 maxId_ = 0; 181 } 182 183 184 /** 185 * Batch pack multiple bins into the sprite. 186 * 187 * @param {vector<Bin>} bins Array of requested bins - each object should have `w`, `h` values 188 * @param {PackOptions} [options] 189 * @param {bool} [options.inPlace=false] If `true`, the supplied bin objects will be updated inplace with `x` and `y` values 190 * @returns {vector<Bin*>} Array of Bin pointers - each bin is a struct with `x`, `y`, `w`, `h` values 191 * 192 * @example 193 * std::vector<Bin> moreBins; 194 * moreBins.emplace_back(-1, 12, 24); 195 * moreBins.emplace_back(-1, 12, 12); 196 * moreBins.emplace_back(-1, 10, 10); 197 * 198 * ShelfPack::PackOptions options; 199 * options.inPlace = true; 200 * std::vector<Bin*> results = sprite.pack(moreBins, options); 201 */ pack(std::vector<Bin> & bins,const PackOptions & options=PackOptions{})202 std::vector<Bin*> pack(std::vector<Bin> &bins, const PackOptions &options = PackOptions{}) { 203 std::vector<Bin*> results; 204 205 for (auto& bin : bins) { 206 if (bin.w > 0 && bin.h > 0) { 207 Bin* allocation = packOne(bin.id, bin.w, bin.h); 208 if (!allocation) { 209 continue; 210 } 211 if (options.inPlace) { 212 bin.id = allocation->id; 213 bin.x = allocation->x; 214 bin.y = allocation->y; 215 } 216 results.push_back(allocation); 217 } 218 } 219 220 shrink(); 221 222 return results; 223 } 224 225 226 /** 227 * Pack a single bin into the sprite. 228 * 229 * @param {int32_t} id Unique bin identifier, pass -1 to generate a new one 230 * @param {int32_t} w Width of the bin to allocate 231 * @param {int32_t} h Height of the bin to allocate 232 * @returns {Bin*} Pointer to a packed Bin with `id`, `x`, `y`, `w`, `h` members 233 * 234 * @example 235 * Bin* result = sprite.packOne(-1, 12, 16); 236 */ packOne(int32_t id,int32_t w,int32_t h)237 Bin* packOne(int32_t id, int32_t w, int32_t h) { 238 int32_t y = 0; 239 int32_t waste = 0; 240 struct { 241 Shelf* pshelf = nullptr; 242 Bin* pfreebin = nullptr; 243 int32_t waste = std::numeric_limits<std::int32_t>::max(); 244 } best; 245 246 // if id was supplied, attempt a lookup.. 247 if (id != -1) { 248 Bin* pbin = getBin(id); 249 if (pbin) { // we packed this bin already 250 ref(*pbin); 251 return pbin; 252 } 253 maxId_ = std::max(id, maxId_); 254 } else { 255 id = ++maxId_; 256 } 257 258 // First try to reuse a free bin.. 259 for (auto& freebin : freebins_) { 260 // exactly the right height and width, use it.. 261 if (h == freebin->maxh && w == freebin->maxw) { 262 return allocFreebin(freebin, id, w, h); 263 } 264 // not enough height or width, skip it.. 265 if (h > freebin->maxh || w > freebin->maxw) { 266 continue; 267 } 268 // extra height or width, minimize wasted area.. 269 if (h <= freebin->maxh && w <= freebin->maxw) { 270 waste = (freebin->maxw * freebin->maxh) - (w * h); 271 if (waste < best.waste) { 272 best.waste = waste; 273 best.pfreebin = freebin; 274 } 275 } 276 } 277 278 // Next find the best shelf 279 for (auto& shelf : shelves_) { 280 y += shelf.h(); 281 282 // not enough width on this shelf, skip it.. 283 if (w > shelf.wfree()) { 284 continue; 285 } 286 // exactly the right height, pack it.. 287 if (h == shelf.h()) { 288 return allocShelf(shelf, id, w, h); 289 } 290 // not enough height, skip it.. 291 if (h > shelf.h()) { 292 continue; 293 } 294 // extra height, minimize wasted area.. 295 if (h < shelf.h()) { 296 waste = (shelf.h() - h) * w; 297 if (waste < best.waste) { 298 best.waste = waste; 299 best.pshelf = &shelf; 300 } 301 } 302 } 303 304 if (best.pfreebin) { 305 return allocFreebin(best.pfreebin, id, w, h); 306 } 307 308 if (best.pshelf) { 309 return allocShelf(*best.pshelf, id, w, h); 310 } 311 312 // No free bins or shelves.. add shelf.. 313 if (h <= (height_ - y) && w <= width_) { 314 shelves_.emplace_back(y, width_, h); 315 return allocShelf(shelves_.back(), id, w, h); 316 } 317 318 // No room for more shelves.. 319 // If `autoResize` option is set, grow the sprite as follows: 320 // * double whichever sprite dimension is smaller (`w1` or `h1`) 321 // * if sprite dimensions are equal, grow width before height 322 // * accomodate very large bin requests (big `w` or `h`) 323 if (autoResize_) { 324 int32_t h1, h2, w1, w2; 325 326 h1 = h2 = height_; 327 w1 = w2 = width_; 328 329 if (w1 <= h1 || w > w1) { // grow width.. 330 w2 = std::max(w, w1) * 2; 331 } 332 if (h1 < w1 || h > h1) { // grow height.. 333 h2 = std::max(h, h1) * 2; 334 } 335 336 resize(w2, h2); 337 return packOne(id, w, h); // retry 338 } 339 340 return nullptr; 341 } 342 343 344 /** 345 * 346 * Shrink the width/height of the sprite to the bare minimum. 347 * Since shelf-pack doubles first width, then height when running out of shelf space 348 * this can result in fairly large unused space both in width and height if that happens 349 * towards the end of bin packing. 350 */ shrink()351 void shrink() { 352 if (shelves_.size()) { 353 int32_t w2 = 0; 354 int32_t h2 = 0; 355 356 for (auto& shelf : shelves_) { 357 h2 += shelf.h(); 358 w2 = std::max(shelf.w() - shelf.wfree(), w2); 359 } 360 361 resize(w2, h2); 362 } 363 } 364 365 366 /** 367 * Return a packed bin given its id, or nullptr if the id is not found 368 * 369 * @param {int32_t} id Unique identifier for this bin, 370 * @returns {Bin*} Pointer to a packed Bin with `id`, `x`, `y`, `w`, `h` members 371 * 372 * @example 373 * Bin* result = sprite.getBin(5); 374 */ getBin(int32_t id)375 Bin* getBin(int32_t id) { 376 std::map<int32_t, Bin*>::iterator it = usedbins_.find(id); 377 return (it == usedbins_.end()) ? nullptr : it->second; 378 } 379 380 381 /** 382 * Increment the ref count of a bin and update statistics. 383 * 384 * @param {Bin&} bin Bin reference 385 * @returns {int32_t} New refcount of the bin 386 * 387 * @example 388 * Bin* bin = sprite.getBin(5); 389 * if (bin) { 390 * sprite.ref(*bin); 391 * } 392 */ ref(Bin & bin)393 int32_t ref(Bin& bin) { 394 if (++bin.refcount_ == 1) { // a new Bin.. record height in stats historgram.. 395 int32_t h = bin.h; 396 stats_[h] = (stats_[h] | 0) + 1; 397 } 398 399 return bin.refcount_; 400 }; 401 402 403 /** 404 * Decrement the ref count of a bin and update statistics. 405 * The bin will be automatically marked as free space once the refcount reaches 0. 406 * Memory for the bin is not freed, as unreferenced bins may be reused later. 407 * 408 * @param {Bin&} bin Bin reference 409 * @returns {int32_t} New refcount of the bin 410 * 411 * @example 412 * Bin* bin = sprite.getBin(5); 413 * if (bin) { 414 * sprite.unref(*bin); 415 * } 416 */ unref(Bin & bin)417 int32_t unref(Bin& bin) { 418 if (bin.refcount_ == 0) { 419 return 0; 420 } 421 422 if (--bin.refcount_ == 0) { 423 stats_[bin.h]--; 424 usedbins_.erase(bin.id); 425 freebins_.push_back(&bin); 426 } 427 428 return bin.refcount_; 429 } 430 431 432 /** 433 * Clear the sprite and reset statistics. 434 * 435 * @example 436 * sprite.clear(); 437 */ clear()438 void clear() { 439 shelves_.clear(); 440 freebins_.clear(); 441 usedbins_.clear(); 442 stats_.clear(); 443 maxId_ = 0; 444 } 445 446 447 /** 448 * Resize the sprite. 449 * 450 * @param {int32_t} w Requested new sprite width 451 * @param {int32_t} h Requested new sprite height 452 * @returns {bool} `true` if resize succeeded, `false` if failed 453 * 454 * @example 455 * sprite.resize(256, 256); 456 */ resize(int32_t w,int32_t h)457 bool resize(int32_t w, int32_t h) { 458 width_ = w; 459 height_ = h; 460 for (auto& shelf : shelves_) { 461 shelf.resize(width_); 462 } 463 return true; 464 } 465 width() const466 int32_t width() const { return width_; } height() const467 int32_t height() const { return height_; } 468 469 470 private: 471 472 /** 473 * Called by packOne() to allocate a bin by reusing an existing freebin 474 * 475 * @private 476 * @param {Bin*} bin Pointer to a freebin to reuse 477 * @param {int32_t} w Width of the bin to allocate 478 * @param {int32_t} h Height of the bin to allocate 479 * @param {int32_t} id Unique identifier for this bin 480 * @returns {Bin*} Pointer to a Bin with `id`, `x`, `y`, `w`, `h` properties 481 * 482 * @example 483 * Bin* bin = sprite.allocFreebin(pfreebin, 12, 16, 5); 484 */ allocFreebin(Bin * bin,int32_t id,int32_t w,int32_t h)485 Bin* allocFreebin(Bin* bin, int32_t id, int32_t w, int32_t h) { 486 freebins_.erase(std::remove(freebins_.begin(), freebins_.end(), bin), freebins_.end()); 487 bin->id = id; 488 bin->w = w; 489 bin->h = h; 490 bin->refcount_ = 0; 491 usedbins_[id] = bin; 492 ref(*bin); 493 return bin; 494 } 495 496 497 /** 498 * Called by `packOne() to allocate bin on an existing shelf 499 * Memory for the bin is allocated on the heap by `shelf.alloc()` 500 * 501 * @private 502 * @param {Shelf&} shelf Reference to the shelf to allocate the bin on 503 * @param {int32_t} w Width of the bin to allocate 504 * @param {int32_t} h Height of the bin to allocate 505 * @param {int32_t} id Unique identifier for this bin 506 * @returns {Bin*} Pointer to a Bin with `id`, `x`, `y`, `w`, `h` properties 507 * 508 * @example 509 * Bin* bin = sprite.allocShelf(shelf, 12, 16, 5); 510 */ allocShelf(Shelf & shelf,int32_t id,int32_t w,int32_t h)511 Bin* allocShelf(Shelf& shelf, int32_t id, int32_t w, int32_t h) { 512 Bin* pbin = shelf.alloc(id, w, h); 513 if (pbin) { 514 usedbins_[id] = pbin; 515 ref(*pbin); 516 } 517 return pbin; 518 } 519 520 521 int32_t width_; 522 int32_t height_; 523 int32_t maxId_; 524 bool autoResize_; 525 526 std::deque<Shelf> shelves_; 527 std::map<int32_t, Bin*> usedbins_; 528 std::vector<Bin*> freebins_; 529 std::map<int32_t, int32_t> stats_; 530 }; 531 532 533 } // namespace mapbox 534 535 #endif 536