KarmaEngine
Game Engine for practical learning and research purposes
Loading...
Searching...
No Matches
imstb_rectpack.h
Go to the documentation of this file.
1
10
11// [DEAR IMGUI]
12// This is a slightly modified version of stb_rect_pack.h 1.01.
13// Grep for [DEAR IMGUI] to find the changes.
14//
15// stb_rect_pack.h - v1.01 - public domain - rectangle packing
16// Sean Barrett 2014
17//
18// Useful for e.g. packing rectangular textures into an atlas.
19// Does not do rotation.
20//
21// Before #including,
22//
23// #define STB_RECT_PACK_IMPLEMENTATION
24//
25// in the file that you want to have the implementation.
26//
27// Not necessarily the awesomest packing method, but better than
28// the totally naive one in stb_truetype (which is primarily what
29// this is meant to replace).
30//
31// Has only had a few tests run, may have issues.
32//
33// More docs to come.
34//
35// No memory allocations; uses qsort() and assert() from stdlib.
36// Can override those by defining STBRP_SORT and STBRP_ASSERT.
37//
38// This library currently uses the Skyline Bottom-Left algorithm.
39//
40// Please note: better rectangle packers are welcome! Please
41// implement them to the same API, but with a different init
42// function.
43//
44// Credits
45//
46// Library
47// Sean Barrett
48// Minor features
49// Martins Mozeiko
50// github:IntellectualKitty
51//
52// Bugfixes / warning fixes
53// Jeremy Jaussaud
54// Fabian Giesen
55//
56// Version history:
57//
58// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
59// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
60// 0.99 (2019-02-07) warning fixes
61// 0.11 (2017-03-03) return packing success/fail result
62// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
63// 0.09 (2016-08-27) fix compiler warnings
64// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
65// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
66// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
67// 0.05: added STBRP_ASSERT to allow replacing assert
68// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
69// 0.01: initial release
70//
71// LICENSE
72//
73// See end of file for license information.
74
76//
77// INCLUDE SECTION
78//
79
80#ifndef STB_INCLUDE_STB_RECT_PACK_H
81#define STB_INCLUDE_STB_RECT_PACK_H
82
83#define STB_RECT_PACK_VERSION 1
84
85#ifdef STBRP_STATIC
86#define STBRP_DEF static
87#else
88#define STBRP_DEF extern
89#endif
90
91#ifdef __cplusplus
92extern "C" {
93#endif
94
95 typedef struct stbrp_context stbrp_context;
96 typedef struct stbrp_node stbrp_node;
97 typedef struct stbrp_rect stbrp_rect;
98
99 typedef int stbrp_coord;
100
101#define STBRP__MAXVAL 0x7fffffff
102 // Mostly for internal use, but this is the maximum supported coordinate value.
103
104 STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, int num_rects);
105 // Assign packed locations to rectangles. The rectangles are of type
106 // 'stbrp_rect' defined below, stored in the array 'rects', and there
107 // are 'num_rects' many of them.
108 //
109 // Rectangles which are successfully packed have the 'was_packed' flag
110 // set to a non-zero value and 'x' and 'y' store the minimum location
111 // on each axis (i.e. bottom-left in cartesian coordinates, top-left
112 // if you imagine y increasing downwards). Rectangles which do not fit
113 // have the 'was_packed' flag set to 0.
114 //
115 // You should not try to access the 'rects' array from another thread
116 // while this function is running, as the function temporarily reorders
117 // the array while it executes.
118 //
119 // To pack into another rectangle, you need to call stbrp_init_target
120 // again. To continue packing into the same rectangle, you can call
121 // this function again. Calling this multiple times with multiple rect
122 // arrays will probably produce worse packing results than calling it
123 // a single time with the full rectangle array, but the option is
124 // available.
125 //
126 // The function returns 1 if all of the rectangles were successfully
127 // packed and 0 otherwise.
128
130 {
131 // reserved for your use:
132 int id;
133
134 // input:
135 stbrp_coord w, h;
136
137 // output:
138 stbrp_coord x, y;
139 int was_packed; // non-zero if valid packing
140 }; // 16 bytes, nominally
141
142 STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, stbrp_node* nodes, int num_nodes);
143 // Initialize a rectangle packer to:
144 // pack a rectangle that is 'width' by 'height' in dimensions
145 // using temporary storage provided by the array 'nodes', which is 'num_nodes' long
146 //
147 // You must call this function every time you start packing into a new target.
148 //
149 // There is no "shutdown" function. The 'nodes' memory must stay valid for
150 // the following stbrp_pack_rects() call (or calls), but can be freed after
151 // the call (or calls) finish.
152 //
153 // Note: to guarantee best results, either:
154 // 1. make sure 'num_nodes' >= 'width'
155 // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
156 //
157 // If you don't do either of the above things, widths will be quantized to multiples
158 // of small integers to guarantee the algorithm doesn't run out of temporary storage.
159 //
160 // If you do #2, then the non-quantized algorithm will be used, but the algorithm
161 // may run out of temporary storage and be unable to pack some rectangles.
162
163 STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, int allow_out_of_mem);
164 // Optionally call this function after init but before doing any packing to
165 // change the handling of the out-of-temp-memory scenario, described above.
166 // If you call init again, this will be reset to the default (false).
167
168 STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic);
169 // Optionally select which packing heuristic the library should use. Different
170 // heuristics will produce better/worse results for different data sets.
171 // If you call init again, this will be reset to the default.
172
173 enum
174 {
175 STBRP_HEURISTIC_Skyline_default = 0,
176 STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
177 STBRP_HEURISTIC_Skyline_BF_sortHeight
178 };
179
181 //
182 // the details of the following structures don't matter to you, but they must
183 // be visible so you can handle the memory allocations for them
184
186 {
187 stbrp_coord x, y;
188 stbrp_node* next;
189 };
190
192 {
193 int width;
194 int height;
195 int align;
196 int init_mode;
197 int heuristic;
198 int num_nodes;
199 stbrp_node* active_head;
200 stbrp_node* free_head;
201 stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
202 };
203
204#ifdef __cplusplus
205}
206#endif
207
208#endif
209
211//
212// IMPLEMENTATION SECTION
213//
214#ifndef STBRP_SORT
215#include <stdlib.h>
216#define STBRP_SORT qsort
217#endif
218
219#ifndef STBRP_ASSERT
220#include <assert.h>
221#define STBRP_ASSERT assert
222#endif
223
224#ifdef _MSC_VER
225#define STBRP__NOTUSED(v) (void)(v)
226#define STBRP__CDECL __cdecl
227#else
228#define STBRP__NOTUSED(v) (void)sizeof(v)
229#define STBRP__CDECL
230#endif
231
232enum
233{
234 STBRP__INIT_skyline = 1
235};
236
237STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic)
238{
239 switch (context->init_mode) {
240 case STBRP__INIT_skyline:
241 STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
242 context->heuristic = heuristic;
243 break;
244 default:
245 STBRP_ASSERT(0);
246 }
247}
248
249STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, int allow_out_of_mem)
250{
251 if (allow_out_of_mem)
252 // if it's ok to run out of memory, then don't bother aligning them;
253 // this gives better packing, but may fail due to OOM (even though
254 // the rectangles easily fit). @TODO a smarter approach would be to only
255 // quantize once we've hit OOM, then we could get rid of this parameter.
256 context->align = 1;
257 else {
258 // if it's not ok to run out of memory, then quantize the widths
259 // so that num_nodes is always enough nodes.
260 //
261 // I.e. num_nodes * align >= width
262 // align >= width / num_nodes
263 // align = ceil(width/num_nodes)
264
265 context->align = (context->width + context->num_nodes - 1) / context->num_nodes;
266 }
267}
268
269STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, stbrp_node* nodes, int num_nodes)
270{
271 int i;
272
273 for (i = 0; i < num_nodes - 1; ++i)
274 nodes[i].next = &nodes[i + 1];
275 nodes[i].next = NULL;
276 context->init_mode = STBRP__INIT_skyline;
277 context->heuristic = STBRP_HEURISTIC_Skyline_default;
278 context->free_head = &nodes[0];
279 context->active_head = &context->extra[0];
280 context->width = width;
281 context->height = height;
282 context->num_nodes = num_nodes;
283 stbrp_setup_allow_out_of_mem(context, 0);
284
285 // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
286 context->extra[0].x = 0;
287 context->extra[0].y = 0;
288 context->extra[0].next = &context->extra[1];
289 context->extra[1].x = (stbrp_coord)width;
290 context->extra[1].y = (1 << 30);
291 context->extra[1].next = NULL;
292}
293
294// find minimum y position if it starts at x1
295static int stbrp__skyline_find_min_y(stbrp_context* c, stbrp_node* first, int x0, int width, int* pwaste)
296{
297 stbrp_node* node = first;
298 int x1 = x0 + width;
299 int min_y, visited_width, waste_area;
300
301 STBRP__NOTUSED(c);
302
303 STBRP_ASSERT(first->x <= x0);
304
305#if 0
306 // skip in case we're past the node
307 while (node->next->x <= x0)
308 ++node;
309#else
310 STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
311#endif
312
313 STBRP_ASSERT(node->x <= x0);
314
315 min_y = 0;
316 waste_area = 0;
317 visited_width = 0;
318 while (node->x < x1) {
319 if (node->y > min_y) {
320 // raise min_y higher.
321 // we've accounted for all waste up to min_y,
322 // but we'll now add more waste for everything we've visted
323 waste_area += visited_width * (node->y - min_y);
324 min_y = node->y;
325 // the first time through, visited_width might be reduced
326 if (node->x < x0)
327 visited_width += node->next->x - x0;
328 else
329 visited_width += node->next->x - node->x;
330 }
331 else {
332 // add waste area
333 int under_width = node->next->x - node->x;
334 if (under_width + visited_width > width)
335 under_width = width - visited_width;
336 waste_area += under_width * (min_y - node->y);
337 visited_width += under_width;
338 }
339 node = node->next;
340 }
341
342 *pwaste = waste_area;
343 return min_y;
344}
345
346typedef struct
347{
348 int x, y;
349 stbrp_node** prev_link;
351
352static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context* c, int width, int height)
353{
354 int best_waste = (1 << 30), best_x, best_y = (1 << 30);
356 stbrp_node** prev, * node, * tail, ** best = NULL;
357
358 // align to multiple of c->align
359 width = (width + c->align - 1);
360 width -= width % c->align;
361 STBRP_ASSERT(width % c->align == 0);
362
363 // if it can't possibly fit, bail immediately
364 if (width > c->width || height > c->height) {
365 fr.prev_link = NULL;
366 fr.x = fr.y = 0;
367 return fr;
368 }
369
370 node = c->active_head;
371 prev = &c->active_head;
372 while (node->x + width <= c->width) {
373 int y, waste;
374 y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
375 if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
376 // bottom left
377 if (y < best_y) {
378 best_y = y;
379 best = prev;
380 }
381 }
382 else {
383 // best-fit
384 if (y + height <= c->height) {
385 // can only use it if it first vertically
386 if (y < best_y || (y == best_y && waste < best_waste)) {
387 best_y = y;
388 best_waste = waste;
389 best = prev;
390 }
391 }
392 }
393 prev = &node->next;
394 node = node->next;
395 }
396
397 best_x = (best == NULL) ? 0 : (*best)->x;
398
399 // if doing best-fit (BF), we also have to try aligning right edge to each node position
400 //
401 // e.g, if fitting
402 //
403 // ____________________
404 // |____________________|
405 //
406 // into
407 //
408 // | |
409 // | ____________|
410 // |____________|
411 //
412 // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
413 //
414 // This makes BF take about 2x the time
415
416 if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
417 tail = c->active_head;
418 node = c->active_head;
419 prev = &c->active_head;
420 // find first node that's admissible
421 while (tail->x < width)
422 tail = tail->next;
423 while (tail) {
424 int xpos = tail->x - width;
425 int y, waste;
426 STBRP_ASSERT(xpos >= 0);
427 // find the left position that matches this
428 while (node->next->x <= xpos) {
429 prev = &node->next;
430 node = node->next;
431 }
432 STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
433 y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
434 if (y + height <= c->height) {
435 if (y <= best_y) {
436 if (y < best_y || waste < best_waste || (waste == best_waste && xpos < best_x)) {
437 best_x = xpos;
438 //STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
439 best_y = y;
440 best_waste = waste;
441 best = prev;
442 }
443 }
444 }
445 tail = tail->next;
446 }
447 }
448
449 fr.prev_link = best;
450 fr.x = best_x;
451 fr.y = best_y;
452 return fr;
453}
454
455static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context* context, int width, int height)
456{
457 // find best position according to heuristic
458 stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
459 stbrp_node* node, * cur;
460
461 // bail if:
462 // 1. it failed
463 // 2. the best node doesn't fit (we don't always check this)
464 // 3. we're out of memory
465 if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
466 res.prev_link = NULL;
467 return res;
468 }
469
470 // on success, create new node
471 node = context->free_head;
472 node->x = (stbrp_coord)res.x;
473 node->y = (stbrp_coord)(res.y + height);
474
475 context->free_head = node->next;
476
477 // insert the new node into the right starting point, and
478 // let 'cur' point to the remaining nodes needing to be
479 // stiched back in
480
481 cur = *res.prev_link;
482 if (cur->x < res.x) {
483 // preserve the existing one, so start testing with the next one
484 stbrp_node* next = cur->next;
485 cur->next = node;
486 cur = next;
487 }
488 else {
489 *res.prev_link = node;
490 }
491
492 // from here, traverse cur and free the nodes, until we get to one
493 // that shouldn't be freed
494 while (cur->next && cur->next->x <= res.x + width) {
495 stbrp_node* next = cur->next;
496 // move the current node to the free list
497 cur->next = context->free_head;
498 context->free_head = cur;
499 cur = next;
500 }
501
502 // stitch the list back in
503 node->next = cur;
504
505 if (cur->x < res.x + width)
506 cur->x = (stbrp_coord)(res.x + width);
507
508#ifdef _DEBUG
509 cur = context->active_head;
510 while (cur->x < context->width) {
511 STBRP_ASSERT(cur->x < cur->next->x);
512 cur = cur->next;
513 }
514 STBRP_ASSERT(cur->next == NULL);
515
516 {
517 int count = 0;
518 cur = context->active_head;
519 while (cur) {
520 cur = cur->next;
521 ++count;
522 }
523 cur = context->free_head;
524 while (cur) {
525 cur = cur->next;
526 ++count;
527 }
528 STBRP_ASSERT(count == context->num_nodes + 2);
529 }
530#endif
531
532 return res;
533}
534
535static int STBRP__CDECL rect_height_compare(const void* a, const void* b)
536{
537 const stbrp_rect* p = (const stbrp_rect*)a;
538 const stbrp_rect* q = (const stbrp_rect*)b;
539 if (p->h > q->h)
540 return -1;
541 if (p->h < q->h)
542 return 1;
543 return (p->w > q->w) ? -1 : (p->w < q->w);
544}
545
546static int STBRP__CDECL rect_original_order(const void* a, const void* b)
547{
548 const stbrp_rect* p = (const stbrp_rect*)a;
549 const stbrp_rect* q = (const stbrp_rect*)b;
550 return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
551}
552
553STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, int num_rects)
554{
555 int i, all_rects_packed = 1;
556
557 // we use the 'was_packed' field internally to allow sorting/unsorting
558 for (i = 0; i < num_rects; ++i) {
559 rects[i].was_packed = i;
560 }
561
562 // sort according to heuristic
563 STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
564
565 for (i = 0; i < num_rects; ++i) {
566 if (rects[i].w == 0 || rects[i].h == 0) {
567 rects[i].x = rects[i].y = 0; // empty rect needs no space
568 }
569 else {
570 stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
571 if (fr.prev_link) {
572 rects[i].x = (stbrp_coord)fr.x;
573 rects[i].y = (stbrp_coord)fr.y;
574 }
575 else {
576 rects[i].x = rects[i].y = STBRP__MAXVAL;
577 }
578 }
579 }
580
581 // unsort
582 STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
583
584 // set was_packed flags and all_rects_packed status
585 for (i = 0; i < num_rects; ++i) {
586 rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
587 if (!rects[i].was_packed)
588 all_rects_packed = 0;
589 }
590
591 // return the all_rects_packed status
592 return all_rects_packed;
593}
594
595/*
596------------------------------------------------------------------------------
597This software is available under 2 licenses -- choose whichever you prefer.
598------------------------------------------------------------------------------
599ALTERNATIVE A - MIT License
600Copyright (c) 2017 Sean Barrett
601Permission is hereby granted, free of charge, to any person obtaining a copy of
602this software and associated documentation files (the "Software"), to deal in
603the Software without restriction, including without limitation the rights to
604use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
605of the Software, and to permit persons to whom the Software is furnished to do
606so, subject to the following conditions:
607The above copyright notice and this permission notice shall be included in all
608copies or substantial portions of the Software.
609THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
610IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
611FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
612AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
613LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
614OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
615SOFTWARE.
616------------------------------------------------------------------------------
617ALTERNATIVE B - Public Domain (www.unlicense.org)
618This is free and unencumbered software released into the public domain.
619Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
620software, either in source code form or as a compiled binary, for any purpose,
621commercial or non-commercial, and by any means.
622In jurisdictions that recognize copyright laws, the author or authors of this
623software dedicate any and all copyright interest in the software to the public
624domain. We make this dedication for the benefit of the public at large and to
625the detriment of our heirs and successors. We intend this dedication to be an
626overt act of relinquishment in perpetuity of all present and future rights to
627this software under copyright law.
628THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
629IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
630FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
631AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
632ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
633WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
634------------------------------------------------------------------------------
635*/
Definition imstb_rectpack.h:347
Definition imstb_rectpack.h:192
Definition imstb_rectpack.h:186
Definition imstb_rectpack.h:130