/src/mpv/video/out/bitmap_packer.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Calculate how to pack bitmap rectangles into a larger surface |
3 | | * |
4 | | * Copyright 2009, 2012 Uoti Urpala |
5 | | * |
6 | | * This file is part of mpv. |
7 | | * |
8 | | * mpv is free software; you can redistribute it and/or |
9 | | * modify it under the terms of the GNU Lesser General Public |
10 | | * License as published by the Free Software Foundation; either |
11 | | * version 2.1 of the License, or (at your option) any later version. |
12 | | * |
13 | | * mpv is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public |
19 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
20 | | */ |
21 | | |
22 | | #include <stdlib.h> |
23 | | #include <assert.h> |
24 | | #include <stdio.h> |
25 | | #include <limits.h> |
26 | | |
27 | | #include "mpv_talloc.h" |
28 | | #include "bitmap_packer.h" |
29 | | #include "common/common.h" |
30 | | |
31 | | #define IS_POWER_OF_2(x) (((x) > 0) && !(((x) - 1) & (x))) |
32 | | |
33 | | void packer_reset(struct bitmap_packer *packer) |
34 | 0 | { |
35 | 0 | struct bitmap_packer old = *packer; |
36 | 0 | *packer = (struct bitmap_packer) { |
37 | 0 | .w_max = old.w_max, |
38 | 0 | .h_max = old.h_max, |
39 | 0 | }; |
40 | 0 | talloc_free_children(packer); |
41 | 0 | } |
42 | | |
43 | | void packer_get_bb(struct bitmap_packer *packer, struct pos out_bb[2]) |
44 | 10 | { |
45 | 10 | out_bb[0] = (struct pos) {0}; |
46 | 10 | out_bb[1] = (struct pos) {packer->used_width, packer->used_height}; |
47 | 10 | } |
48 | | |
49 | 16.3k | #define HEIGHT_SORT_BITS 4 |
50 | | static int size_index(int s) |
51 | 2.19k | { |
52 | 2.19k | int n = mp_log2(s); |
53 | 2.19k | return (n << HEIGHT_SORT_BITS) |
54 | 2.19k | + ((- 1 - (s << HEIGHT_SORT_BITS >> n)) & ((1 << HEIGHT_SORT_BITS) - 1)); |
55 | 2.19k | } |
56 | | |
57 | | /* Pack the given rectangles into an area of size w * h. |
58 | | * The size of each rectangle is read from in[i].x / in[i].y. |
59 | | * The height of each rectangle must be less than 65536. |
60 | | * 'scratch' must point to work memory for num_rects+16 ints. |
61 | | * The packed position for rectangle number i is set in out[i]. |
62 | | * Return 0 on success, -1 if the rectangles did not fit in w*h. |
63 | | * |
64 | | * The rectangles are placed in rows in order approximately sorted by |
65 | | * height (the approximate sorting is simpler than a full one would be, |
66 | | * and allows the algorithm to work in linear time). Additionally, to |
67 | | * reduce wasted space when there are a few tall rectangles, empty |
68 | | * lower-right parts of rows are filled recursively when the size of |
69 | | * rectangles in the row drops past a power-of-two threshold. So if a |
70 | | * row starts with rectangles of size 3x50, 10x40 and 5x20 then the |
71 | | * free rectangle with corners (13, 20)-(w, 50) is filled recursively. |
72 | | */ |
73 | | static int pack_rectangles(struct pos *in, struct pos *out, int num_rects, |
74 | | int w, int h, int *scratch, int *used_width) |
75 | 29 | { |
76 | 29 | int bins[16 << HEIGHT_SORT_BITS]; |
77 | 29 | int sizes[16 << HEIGHT_SORT_BITS] = { 0 }; |
78 | 1.12k | for (int i = 0; i < num_rects; i++) |
79 | 1.09k | sizes[size_index(in[i].y)]++; |
80 | 29 | int idx = 0; |
81 | 493 | for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) { |
82 | 7.88k | for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) { |
83 | 7.42k | bins[i + j] = idx; |
84 | 7.42k | idx += sizes[i + j]; |
85 | 7.42k | } |
86 | 464 | scratch[idx++] = -1; |
87 | 464 | } |
88 | 1.12k | for (int i = 0; i < num_rects; i++) |
89 | 1.09k | scratch[bins[size_index(in[i].y)]++] = i; |
90 | 493 | for (int i = 0; i < 16; i++) |
91 | 464 | bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS]; |
92 | 29 | struct { |
93 | 29 | int size, x, bottom; |
94 | 29 | } stack[16] = {{15, 0, h}}, s = {0}; |
95 | 29 | int stackpos = 1; |
96 | 29 | int y; |
97 | 199 | while (stackpos) { |
98 | 170 | y = s.bottom; |
99 | 170 | s = stack[--stackpos]; |
100 | 170 | s.size++; |
101 | 1.25k | while (s.size--) { |
102 | 1.08k | int maxy = -1; |
103 | 1.08k | int obj; |
104 | 1.62k | while ((obj = scratch[bins[s.size]]) >= 0) { |
105 | 815 | int bottom = y + in[obj].y; |
106 | 815 | if (bottom > s.bottom) |
107 | 35 | break; |
108 | 780 | int right = s.x + in[obj].x; |
109 | 780 | if (right > w) |
110 | 240 | break; |
111 | 540 | bins[s.size]++; |
112 | 540 | out[obj] = (struct pos){s.x, y}; |
113 | 540 | num_rects--; |
114 | 540 | if (maxy < 0) |
115 | 141 | stack[stackpos++] = s; |
116 | 540 | s.x = right; |
117 | 540 | maxy = MPMAX(maxy, bottom); |
118 | 540 | } |
119 | 1.08k | *used_width = MPMAX(*used_width, s.x); |
120 | 1.08k | if (maxy > 0) |
121 | 141 | s.bottom = maxy; |
122 | 1.08k | } |
123 | 170 | } |
124 | 29 | return num_rects ? -1 : y; |
125 | 29 | } |
126 | | |
127 | | int packer_pack(struct bitmap_packer *packer) |
128 | 10 | { |
129 | 10 | if (packer->count == 0) |
130 | 0 | return 0; |
131 | 10 | int w_orig = packer->w, h_orig = packer->h; |
132 | 10 | struct pos *in = packer->in; |
133 | 10 | int xmax = 0, ymax = 0; |
134 | 348 | for (int i = 0; i < packer->count; i++) { |
135 | 338 | if (in[i].x <= 0 || in[i].y <= 0) { |
136 | 0 | in[i] = (struct pos){0, 0}; |
137 | 338 | } else { |
138 | 338 | in[i].x += packer->padding * 2; |
139 | 338 | in[i].y += packer->padding * 2; |
140 | 338 | } |
141 | 338 | if (in[i].x < 0 || in [i].x > 65535 || in[i].y < 0 || in[i].y > 65535) { |
142 | 0 | fprintf(stderr, "Invalid OSD / subtitle bitmap size\n"); |
143 | 0 | abort(); |
144 | 0 | } |
145 | 338 | xmax = MPMAX(xmax, in[i].x); |
146 | 338 | ymax = MPMAX(ymax, in[i].y); |
147 | 338 | } |
148 | 10 | if (xmax > packer->w) |
149 | 7 | packer->w = 1 << (mp_log2(xmax - 1) + 1); |
150 | 10 | if (ymax > packer->h) |
151 | 7 | packer->h = 1 << (mp_log2(ymax - 1) + 1); |
152 | 29 | while (1) { |
153 | 29 | int used_width = 0; |
154 | 29 | int y = pack_rectangles(in, packer->result, packer->count, |
155 | 29 | packer->w, packer->h, |
156 | 29 | packer->scratch, &used_width); |
157 | 29 | if (y >= 0) { |
158 | 10 | packer->used_width = MPMIN(used_width, packer->w); |
159 | 10 | packer->used_height = MPMIN(y, packer->h); |
160 | 10 | mp_assert(packer->w == 0 || IS_POWER_OF_2(packer->w)); |
161 | 10 | mp_assert(packer->h == 0 || IS_POWER_OF_2(packer->h)); |
162 | 10 | if (packer->padding) { |
163 | 348 | for (int i = 0; i < packer->count; i++) { |
164 | 338 | packer->result[i].x += packer->padding; |
165 | 338 | packer->result[i].y += packer->padding; |
166 | 338 | } |
167 | 10 | } |
168 | 10 | return packer->w != w_orig || packer->h != h_orig; |
169 | 10 | } |
170 | 19 | int w_max = packer->w_max > 0 ? packer->w_max : INT_MAX; |
171 | 19 | int h_max = packer->h_max > 0 ? packer->h_max : INT_MAX; |
172 | 19 | if (packer->w <= packer->h && packer->w != w_max) |
173 | 8 | packer->w = MPMIN(packer->w * 2, w_max); |
174 | 11 | else if (packer->h != h_max) |
175 | 11 | packer->h = MPMIN(packer->h * 2, h_max); |
176 | 0 | else { |
177 | 0 | packer->w = w_orig; |
178 | 0 | packer->h = h_orig; |
179 | 0 | return -1; |
180 | 0 | } |
181 | 19 | } |
182 | 10 | } |
183 | | |
184 | | void packer_set_size(struct bitmap_packer *packer, int size) |
185 | 40 | { |
186 | 40 | packer->count = size; |
187 | 40 | if (size <= packer->asize) |
188 | 31 | return; |
189 | 9 | packer->asize = MPMAX(packer->asize * 2, size); |
190 | 9 | talloc_free(packer->result); |
191 | 9 | talloc_free(packer->scratch); |
192 | 9 | packer->in = talloc_realloc(packer, packer->in, struct pos, packer->asize); |
193 | 9 | packer->result = talloc_array_ptrtype(packer, packer->result, |
194 | 9 | packer->asize); |
195 | 9 | packer->scratch = talloc_array_ptrtype(packer, packer->scratch, |
196 | 9 | packer->asize + 16); |
197 | 9 | } |