1
2#include "stb_rect_pack.h"
3#define STB_RECT_PACK_IMPLEMENTATION
4//////////////////////////////////////////////////////////////////////////////
5//
6// IMPLEMENTATION SECTION
7//
8
9#ifdef STB_RECT_PACK_IMPLEMENTATION
10#ifndef STBRP_SORT
11#include <stdlib.h>
12#define STBRP_SORT qsort
13#endif
14
15#ifndef STBRP_ASSERT
16#include <assert.h>
17#define STBRP_ASSERT assert
18#endif
19
20#ifdef _MSC_VER
21#define STBRP__NOTUSED(v) (void)(v)
22#else
23#define STBRP__NOTUSED(v) (void)sizeof(v)
24#endif
25
26enum
27{
28 STBRP__INIT_skyline = 1
29};
30
31STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
32{
33 switch (context->init_mode) {
34 case STBRP__INIT_skyline:
35 STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
36 context->heuristic = heuristic;
37 break;
38 default:
39 STBRP_ASSERT(0);
40 }
41}
42
43STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
44{
45 if (allow_out_of_mem)
46 // if it's ok to run out of memory, then don't bother aligning them;
47 // this gives better packing, but may fail due to OOM (even though
48 // the rectangles easily fit). @TODO a smarter approach would be to only
49 // quantize once we've hit OOM, then we could get rid of this parameter.
50 context->align = 1;
51 else {
52 // if it's not ok to run out of memory, then quantize the widths
53 // so that num_nodes is always enough nodes.
54 //
55 // I.e. num_nodes * align >= width
56 // align >= width / num_nodes
57 // align = ceil(width/num_nodes)
58
59 context->align = (context->width + context->num_nodes-1) / context->num_nodes;
60 }
61}
62
63STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
64{
65 int i;
66#ifndef STBRP_LARGE_RECTS
67 STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
68#endif
69
70 for (i=0; i < num_nodes-1; ++i)
71 nodes[i].next = &nodes[i+1];
72 nodes[i].next = NULL;
73 context->init_mode = STBRP__INIT_skyline;
74 context->heuristic = STBRP_HEURISTIC_Skyline_default;
75 context->free_head = &nodes[0];
76 context->active_head = &context->extra[0];
77 context->width = width;
78 context->height = height;
79 context->num_nodes = num_nodes;
80 stbrp_setup_allow_out_of_mem(context, allow_out_of_mem: 0);
81
82 // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
83 context->extra[0].x = 0;
84 context->extra[0].y = 0;
85 context->extra[0].next = &context->extra[1];
86 context->extra[1].x = (stbrp_coord) width;
87#ifdef STBRP_LARGE_RECTS
88 context->extra[1].y = (1<<30);
89#else
90 context->extra[1].y = 65535;
91#endif
92 context->extra[1].next = NULL;
93}
94
95// find minimum y position if it starts at x1
96static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
97{
98 stbrp_node *node = first;
99 int x1 = x0 + width;
100 int min_y, visited_width, waste_area;
101
102 STBRP__NOTUSED(c);
103
104 STBRP_ASSERT(first->x <= x0);
105
106 #if 0
107 // skip in case we're past the node
108 while (node->next->x <= x0)
109 ++node;
110 #else
111 STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
112 #endif
113
114 STBRP_ASSERT(node->x <= x0);
115
116 min_y = 0;
117 waste_area = 0;
118 visited_width = 0;
119 while (node->x < x1) {
120 if (node->y > min_y) {
121 // raise min_y higher.
122 // we've accounted for all waste up to min_y,
123 // but we'll now add more waste for everything we've visited
124 waste_area += visited_width * (node->y - min_y);
125 min_y = node->y;
126 // the first time through, visited_width might be reduced
127 if (node->x < x0)
128 visited_width += node->next->x - x0;
129 else
130 visited_width += node->next->x - node->x;
131 } else {
132 // add waste area
133 int under_width = node->next->x - node->x;
134 if (under_width + visited_width > width)
135 under_width = width - visited_width;
136 waste_area += under_width * (min_y - node->y);
137 visited_width += under_width;
138 }
139 node = node->next;
140 }
141
142 *pwaste = waste_area;
143 return min_y;
144}
145
146typedef struct
147{
148 int x,y;
149 stbrp_node **prev_link;
150} stbrp__findresult;
151
152static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
153{
154 int best_waste = (1<<30), best_x, best_y = (1 << 30);
155 stbrp__findresult fr;
156 stbrp_node **prev, *node, *tail, **best = NULL;
157
158 // align to multiple of c->align
159 width = (width + c->align - 1);
160 width -= width % c->align;
161 STBRP_ASSERT(width % c->align == 0);
162
163 node = c->active_head;
164 prev = &c->active_head;
165 while (node->x + width <= c->width) {
166 int y,waste;
167 y = stbrp__skyline_find_min_y(c, first: node, x0: node->x, width, pwaste: &waste);
168 if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
169 // bottom left
170 if (y < best_y) {
171 best_y = y;
172 best = prev;
173 }
174 } else {
175 // best-fit
176 if (y + height <= c->height) {
177 // can only use it if it first vertically
178 if (y < best_y || (y == best_y && waste < best_waste)) {
179 best_y = y;
180 best_waste = waste;
181 best = prev;
182 }
183 }
184 }
185 prev = &node->next;
186 node = node->next;
187 }
188
189 best_x = (best == NULL) ? 0 : (*best)->x;
190
191 // if doing best-fit (BF), we also have to try aligning right edge to each node position
192 //
193 // e.g, if fitting
194 //
195 // ____________________
196 // |____________________|
197 //
198 // into
199 //
200 // | |
201 // | ____________|
202 // |____________|
203 //
204 // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
205 //
206 // This makes BF take about 2x the time
207
208 if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
209 tail = c->active_head;
210 node = c->active_head;
211 prev = &c->active_head;
212 // find first node that's admissible
213 while (tail->x < width)
214 tail = tail->next;
215 while (tail) {
216 int xpos = tail->x - width;
217 int y,waste;
218 STBRP_ASSERT(xpos >= 0);
219 // find the left position that matches this
220 while (node->next->x <= xpos) {
221 prev = &node->next;
222 node = node->next;
223 }
224 STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
225 y = stbrp__skyline_find_min_y(c, first: node, x0: xpos, width, pwaste: &waste);
226 if (y + height < c->height) {
227 if (y <= best_y) {
228 if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
229 best_x = xpos;
230 STBRP_ASSERT(y <= best_y);
231 best_y = y;
232 best_waste = waste;
233 best = prev;
234 }
235 }
236 }
237 tail = tail->next;
238 }
239 }
240
241 fr.prev_link = best;
242 fr.x = best_x;
243 fr.y = best_y;
244 return fr;
245}
246
247static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
248{
249 // find best position according to heuristic
250 stbrp__findresult res = stbrp__skyline_find_best_pos(c: context, width, height);
251 stbrp_node *node, *cur;
252
253 // bail if:
254 // 1. it failed
255 // 2. the best node doesn't fit (we don't always check this)
256 // 3. we're out of memory
257 if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
258 res.prev_link = NULL;
259 return res;
260 }
261
262 // on success, create new node
263 node = context->free_head;
264 node->x = (stbrp_coord) res.x;
265 node->y = (stbrp_coord) (res.y + height);
266
267 context->free_head = node->next;
268
269 // insert the new node into the right starting point, and
270 // let 'cur' point to the remaining nodes needing to be
271 // stiched back in
272
273 cur = *res.prev_link;
274 if (cur->x < res.x) {
275 // preserve the existing one, so start testing with the next one
276 stbrp_node *next = cur->next;
277 cur->next = node;
278 cur = next;
279 } else {
280 *res.prev_link = node;
281 }
282
283 // from here, traverse cur and free the nodes, until we get to one
284 // that shouldn't be freed
285 while (cur->next && cur->next->x <= res.x + width) {
286 stbrp_node *next = cur->next;
287 // move the current node to the free list
288 cur->next = context->free_head;
289 context->free_head = cur;
290 cur = next;
291 }
292
293 // stitch the list back in
294 node->next = cur;
295
296 if (cur->x < res.x + width)
297 cur->x = (stbrp_coord) (res.x + width);
298
299#ifdef _DEBUG
300 cur = context->active_head;
301 while (cur->x < context->width) {
302 STBRP_ASSERT(cur->x < cur->next->x);
303 cur = cur->next;
304 }
305 STBRP_ASSERT(cur->next == NULL);
306
307 {
308 int count=0;
309 cur = context->active_head;
310 while (cur) {
311 cur = cur->next;
312 ++count;
313 }
314 cur = context->free_head;
315 while (cur) {
316 cur = cur->next;
317 ++count;
318 }
319 STBRP_ASSERT(count == context->num_nodes+2);
320 }
321#endif
322
323 return res;
324}
325
326static int rect_height_compare(const void *a, const void *b)
327{
328 const stbrp_rect *p = (const stbrp_rect *) a;
329 const stbrp_rect *q = (const stbrp_rect *) b;
330 if (p->h > q->h)
331 return -1;
332 if (p->h < q->h)
333 return 1;
334 return (p->w > q->w) ? -1 : (p->w < q->w);
335}
336
337static int rect_original_order(const void *a, const void *b)
338{
339 const stbrp_rect *p = (const stbrp_rect *) a;
340 const stbrp_rect *q = (const stbrp_rect *) b;
341 return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
342}
343
344#ifdef STBRP_LARGE_RECTS
345#define STBRP__MAXVAL 0xffffffff
346#else
347#define STBRP__MAXVAL 0xffff
348#endif
349
350STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
351{
352 int i, all_rects_packed = 1;
353
354 // we use the 'was_packed' field internally to allow sorting/unsorting
355 for (i=0; i < num_rects; ++i) {
356 rects[i].was_packed = i;
357 }
358
359 // sort according to heuristic
360 STBRP_SORT(base: rects, nmemb: num_rects, size: sizeof(rects[0]), compar: rect_height_compare);
361
362 for (i=0; i < num_rects; ++i) {
363 if (rects[i].w == 0 || rects[i].h == 0) {
364 rects[i].x = rects[i].y = 0; // empty rect needs no space
365 } else {
366 stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, width: rects[i].w, height: rects[i].h);
367 if (fr.prev_link) {
368 rects[i].x = (stbrp_coord) fr.x;
369 rects[i].y = (stbrp_coord) fr.y;
370 } else {
371 rects[i].x = rects[i].y = STBRP__MAXVAL;
372 }
373 }
374 }
375
376 // unsort
377 STBRP_SORT(base: rects, nmemb: num_rects, size: sizeof(rects[0]), compar: rect_original_order);
378
379 // set was_packed flags and all_rects_packed status
380 for (i=0; i < num_rects; ++i) {
381 rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
382 if (!rects[i].was_packed)
383 all_rects_packed = 0;
384 }
385
386 // return the all_rects_packed status
387 return all_rects_packed;
388}
389#endif
390
391/*
392------------------------------------------------------------------------------
393This software is available under 2 licenses -- choose whichever you prefer.
394------------------------------------------------------------------------------
395ALTERNATIVE A - MIT License
396Copyright (c) 2017 Sean Barrett
397Permission is hereby granted, free of charge, to any person obtaining a copy of
398this software and associated documentation files (the "Software"), to deal in
399the Software without restriction, including without limitation the rights to
400use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
401of the Software, and to permit persons to whom the Software is furnished to do
402so, subject to the following conditions:
403The above copyright notice and this permission notice shall be included in all
404copies or substantial portions of the Software.
405THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
406IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
407FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
408AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
409LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
410OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
411SOFTWARE.
412------------------------------------------------------------------------------
413ALTERNATIVE B - Public Domain (www.unlicense.org)
414This is free and unencumbered software released into the public domain.
415Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
416software, either in source code form or as a compiled binary, for any purpose,
417commercial or non-commercial, and by any means.
418In jurisdictions that recognize copyright laws, the author or authors of this
419software dedicate any and all copyright interest in the software to the public
420domain. We make this dedication for the benefit of the public at large and to
421the detriment of our heirs and successors. We intend this dedication to be an
422overt act of relinquishment in perpetuity of all present and future rights to
423this software under copyright law.
424THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
425IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
426FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
427AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
428ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
429WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
430------------------------------------------------------------------------------
431*/
432

source code of gtk/gsk/gl/stb_rect_pack.c