Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stddef.h> |
19 | | #include <stdbool.h> |
20 | | #include <assert.h> |
21 | | #include <math.h> |
22 | | #include <inttypes.h> |
23 | | |
24 | | #include "common/common.h" |
25 | | #include "draw_bmp.h" |
26 | | #include "img_convert.h" |
27 | | #include "video/mp_image.h" |
28 | | #include "video/repack.h" |
29 | | #include "video/sws_utils.h" |
30 | | #include "video/img_format.h" |
31 | | #include "video/csputils.h" |
32 | | |
33 | | const bool mp_draw_sub_formats[SUBBITMAP_COUNT] = { |
34 | | [SUBBITMAP_LIBASS] = true, |
35 | | [SUBBITMAP_BGRA] = true, |
36 | | }; |
37 | | |
38 | | struct part { |
39 | | int change_id; |
40 | | // Sub-bitmaps scaled to final sizes. |
41 | | int num_imgs; |
42 | | struct mp_image **imgs; |
43 | | }; |
44 | | |
45 | | // Must be a power of 2. Height is 1, but mark_rect() effectively operates on |
46 | | // multiples of chroma sized macro-pixels. (E.g. 4:2:0 -> every second line is |
47 | | // the same as the previous one, and x0%2==x1%2==0.) |
48 | 225k | #define SLICE_W 256u |
49 | | |
50 | | // Whether to scale in tiles. Faster, but can't use correct chroma position. |
51 | | // Should be a runtime option. SLICE_W is used as tile width. The tile size |
52 | | // should probably be small; too small or too big will cause overhead when |
53 | | // scaling. |
54 | 84 | #define SCALE_IN_TILES 1 |
55 | 84 | #define TILE_H 4u |
56 | | |
57 | | struct slice { |
58 | | uint16_t x0, x1; |
59 | | }; |
60 | | |
61 | | struct mp_draw_sub_cache |
62 | | { |
63 | | struct mpv_global *global; |
64 | | |
65 | | // Possibly cached parts. Also implies what's in the video_overlay. |
66 | | struct part parts[MAX_OSD_PARTS]; |
67 | | int64_t change_id; |
68 | | |
69 | | struct mp_image_params params; // target image params |
70 | | |
71 | | int w, h; // like params.w/h, but rounded up to chroma |
72 | | unsigned align_x, align_y; // alignment for all video pixels |
73 | | |
74 | | struct mp_image *rgba_overlay; // all OSD in RGBA |
75 | | struct mp_image *video_overlay; // rgba_overlay converted to video colorspace |
76 | | struct mp_image *alpha_overlay; // alpha plane ref. to video_overlay |
77 | | struct mp_image *calpha_overlay; // alpha_overlay scaled to chroma plane size |
78 | | |
79 | | unsigned s_w; // number of slices per line |
80 | | struct slice *slices; // slices[y * s_w + x / SLICE_W] |
81 | | bool any_osd; |
82 | | |
83 | | struct mp_sws_context *rgba_to_overlay; // scaler for rgba -> video csp. |
84 | | struct mp_sws_context *alpha_to_calpha; // scaler for overlay -> calpha |
85 | | bool scale_in_tiles; |
86 | | |
87 | | struct mp_sws_context *sub_scale; // scaler for SUBBITMAP_BGRA |
88 | | |
89 | | struct mp_repack *overlay_to_f32; // convert video_overlay to float |
90 | | struct mp_image *overlay_tmp; // slice in float32 |
91 | | |
92 | | struct mp_repack *calpha_to_f32; // convert video_overlay to float |
93 | | struct mp_image *calpha_tmp; // slice in float32 |
94 | | |
95 | | struct mp_repack *video_to_f32; // convert video to float |
96 | | struct mp_repack *video_from_f32; // convert float back to video |
97 | | struct mp_image *video_tmp; // slice in float32 |
98 | | |
99 | | struct mp_sws_context *premul; // video -> premultiplied video |
100 | | struct mp_sws_context *unpremul; // reverse |
101 | | struct mp_image *premul_tmp; |
102 | | |
103 | | // Function that works on the _f32 data. |
104 | | void (*blend_line)(void *dst, void *src, void *src_a, int w); |
105 | | |
106 | | struct mp_image res_overlay; // returned by mp_draw_sub_overlay() |
107 | | }; |
108 | | |
109 | | static void blend_line_f32(void *dst, void *src, void *src_a, int w) |
110 | 0 | { |
111 | 0 | float *dst_f = dst; |
112 | 0 | float *src_f = src; |
113 | 0 | float *src_a_f = src_a; |
114 | |
|
115 | 0 | for (int x = 0; x < w; x++) |
116 | 0 | dst_f[x] = src_f[x] + dst_f[x] * (1.0f - src_a_f[x]); |
117 | 0 | } |
118 | | |
119 | | static void blend_line_u8(void *dst, void *src, void *src_a, int w) |
120 | 32.5k | { |
121 | 32.5k | uint8_t *dst_i = dst; |
122 | 32.5k | uint8_t *src_i = src; |
123 | 32.5k | uint8_t *src_a_i = src_a; |
124 | | |
125 | 8.37M | for (int x = 0; x < w; x++) |
126 | 8.34M | dst_i[x] = src_i[x] + dst_i[x] * (255u - src_a_i[x]) / 255u; |
127 | 32.5k | } |
128 | | |
129 | | static void blend_slice(struct mp_draw_sub_cache *p) |
130 | 10.8k | { |
131 | 10.8k | struct mp_image *ov = p->overlay_tmp; |
132 | 10.8k | struct mp_image *ca = p->calpha_tmp; |
133 | 10.8k | struct mp_image *vid = p->video_tmp; |
134 | | |
135 | 43.4k | for (int plane = 0; plane < vid->num_planes; plane++) { |
136 | 32.5k | int xs = vid->fmt.xs[plane]; |
137 | 32.5k | int ys = vid->fmt.ys[plane]; |
138 | 32.5k | int h = (1 << vid->fmt.chroma_ys) - (1 << ys) + 1; |
139 | 32.5k | int cw = mp_chroma_div_up(vid->w, xs); |
140 | 65.1k | for (int y = 0; y < h; y++) { |
141 | 32.5k | p->blend_line(mp_image_pixel_ptr_ny(vid, plane, 0, y), |
142 | 32.5k | mp_image_pixel_ptr_ny(ov, plane, 0, y), |
143 | 32.5k | xs || ys ? mp_image_pixel_ptr_ny(ca, 0, 0, y) |
144 | 32.5k | : mp_image_pixel_ptr_ny(ov, ov->num_planes - 1, 0, y), |
145 | 32.5k | cw); |
146 | 32.5k | } |
147 | 32.5k | } |
148 | 10.8k | } |
149 | | |
150 | | static bool blend_overlay_with_video(struct mp_draw_sub_cache *p, |
151 | | struct mp_image *dst) |
152 | 84 | { |
153 | 84 | if (!repack_config_buffers(p->video_to_f32, 0, p->video_tmp, 0, dst, NULL)) |
154 | 0 | return false; |
155 | 84 | if (!repack_config_buffers(p->video_from_f32, 0, dst, 0, p->video_tmp, NULL)) |
156 | 0 | return false; |
157 | | |
158 | 84 | int xs = dst->fmt.chroma_xs; |
159 | 84 | int ys = dst->fmt.chroma_ys; |
160 | | |
161 | 15.2k | for (int y = 0; y < dst->h; y += p->align_y) { |
162 | 15.1k | struct slice *line = &p->slices[y * p->s_w]; |
163 | | |
164 | 45.3k | for (int sx = 0; sx < p->s_w; sx++) { |
165 | 30.2k | struct slice *s = &line[sx]; |
166 | | |
167 | 30.2k | int w = s->x1 - s->x0; |
168 | 30.2k | if (w <= 0) |
169 | 19.3k | continue; |
170 | 10.8k | int x = sx * SLICE_W + s->x0; |
171 | | |
172 | 10.8k | mp_assert(MP_IS_ALIGNED(x, p->align_x)); |
173 | 10.8k | mp_assert(MP_IS_ALIGNED(w, p->align_x)); |
174 | 10.8k | mp_assert(x + w <= p->w); |
175 | | |
176 | 10.8k | repack_line(p->overlay_to_f32, 0, 0, x, y, w); |
177 | 10.8k | repack_line(p->video_to_f32, 0, 0, x, y, w); |
178 | 10.8k | if (p->calpha_to_f32) |
179 | 0 | repack_line(p->calpha_to_f32, 0, 0, x >> xs, y >> ys, w >> xs); |
180 | | |
181 | 10.8k | blend_slice(p); |
182 | | |
183 | 10.8k | repack_line(p->video_from_f32, x, y, 0, 0, w); |
184 | 10.8k | } |
185 | 15.1k | } |
186 | | |
187 | 84 | return true; |
188 | 84 | } |
189 | | |
190 | | static bool convert_overlay_part(struct mp_draw_sub_cache *p, |
191 | | int x0, int y0, int w, int h) |
192 | 0 | { |
193 | 0 | struct mp_image src = *p->rgba_overlay; |
194 | 0 | struct mp_image dst = *p->video_overlay; |
195 | |
|
196 | 0 | mp_image_crop(&src, x0, y0, x0 + w, y0 + h); |
197 | 0 | mp_image_crop(&dst, x0, y0, x0 + w, y0 + h); |
198 | |
|
199 | 0 | if (mp_sws_scale(p->rgba_to_overlay, &dst, &src) < 0) |
200 | 0 | return false; |
201 | | |
202 | 0 | if (p->calpha_overlay) { |
203 | 0 | src = *p->alpha_overlay; |
204 | 0 | dst = *p->calpha_overlay; |
205 | |
|
206 | 0 | int xs = p->video_overlay->fmt.chroma_xs; |
207 | 0 | int ys = p->video_overlay->fmt.chroma_ys; |
208 | 0 | mp_image_crop(&src, x0, y0, x0 + w, y0 + h); |
209 | 0 | mp_image_crop(&dst, x0 >> xs, y0 >> ys, (x0 + w) >> xs, (y0 + h) >> ys); |
210 | |
|
211 | 0 | if (mp_sws_scale(p->alpha_to_calpha, &dst, &src) < 0) |
212 | 0 | return false; |
213 | 0 | } |
214 | | |
215 | 0 | return true; |
216 | 0 | } |
217 | | |
218 | | static bool convert_to_video_overlay(struct mp_draw_sub_cache *p) |
219 | 84 | { |
220 | 84 | if (!p->video_overlay) |
221 | 84 | return true; |
222 | | |
223 | 0 | if (p->scale_in_tiles) { |
224 | 0 | int t_h = p->rgba_overlay->h / TILE_H; |
225 | 0 | for (int ty = 0; ty < t_h; ty++) { |
226 | 0 | for (int sx = 0; sx < p->s_w; sx++) { |
227 | 0 | struct slice *s = &p->slices[ty * TILE_H * p->s_w + sx]; |
228 | 0 | bool pixels_set = false; |
229 | 0 | for (int y = 0; y < TILE_H; y++) { |
230 | 0 | if (s[0].x0 < s[0].x1) { |
231 | 0 | pixels_set = true; |
232 | 0 | break; |
233 | 0 | } |
234 | 0 | s += p->s_w; |
235 | 0 | } |
236 | 0 | if (!pixels_set) |
237 | 0 | continue; |
238 | 0 | if (!convert_overlay_part(p, sx * SLICE_W, ty * TILE_H, |
239 | 0 | SLICE_W, TILE_H)) |
240 | 0 | return false; |
241 | 0 | } |
242 | 0 | } |
243 | 0 | } else { |
244 | 0 | if (!convert_overlay_part(p, 0, 0, p->rgba_overlay->w, p->rgba_overlay->h)) |
245 | 0 | return false; |
246 | 0 | } |
247 | | |
248 | 0 | return true; |
249 | 0 | } |
250 | | |
251 | | // Mark the given rectangle of pixels as possibly non-transparent. |
252 | | // The rectangle must have been pre-clipped. |
253 | | static void mark_rect(struct mp_draw_sub_cache *p, int x0, int y0, int x1, int y1) |
254 | 2.89k | { |
255 | 2.89k | x0 = MP_ALIGN_DOWN(x0, p->align_x); |
256 | 2.89k | y0 = MP_ALIGN_DOWN(y0, p->align_y); |
257 | 2.89k | x1 = MP_ALIGN_UP(x1, p->align_x); |
258 | 2.89k | y1 = MP_ALIGN_UP(y1, p->align_y); |
259 | | |
260 | 2.89k | mp_assert(x0 >= 0 && x0 <= x1 && x1 <= p->w); |
261 | 2.89k | mp_assert(y0 >= 0 && y0 <= y1 && y1 <= p->h); |
262 | | |
263 | 2.89k | const int sx0 = x0 / SLICE_W; |
264 | 2.89k | const int sx1 = MPMIN(x1 / SLICE_W, p->s_w - 1); |
265 | | |
266 | 48.8k | for (int y = y0; y < y1; y++) { |
267 | 45.9k | struct slice *line = &p->slices[y * p->s_w]; |
268 | | |
269 | 45.9k | struct slice *s0 = &line[sx0]; |
270 | 45.9k | struct slice *s1 = &line[sx1]; |
271 | | |
272 | 45.9k | s0->x0 = MPMIN(s0->x0, x0 % SLICE_W); |
273 | 45.9k | s1->x1 = MPMAX(s1->x1, ((x1 - 1) % SLICE_W) + 1); |
274 | | |
275 | 45.9k | if (s0 != s1) { |
276 | 0 | s0->x1 = SLICE_W; |
277 | 0 | s1->x0 = 0; |
278 | |
|
279 | 0 | for (int x = sx0 + 1; x < sx1; x++) { |
280 | 0 | struct slice *s = &line[x]; |
281 | 0 | s->x0 = 0; |
282 | 0 | s->x1 = SLICE_W; |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | | // Ensure the very last slice does not extend |
287 | | // beyond the total width. |
288 | 45.9k | struct slice *last_s = &line[p->s_w - 1]; |
289 | 45.9k | last_s->x1 = MPMIN(p->w - ((p->s_w - 1) * SLICE_W), last_s->x1); |
290 | | |
291 | 45.9k | p->any_osd = true; |
292 | 45.9k | } |
293 | 2.89k | } |
294 | | |
295 | | static void draw_ass_rgba(uint8_t *dst, ptrdiff_t dst_stride, |
296 | | uint8_t *src, ptrdiff_t src_stride, |
297 | | int w, int h, uint32_t color) |
298 | 2.89k | { |
299 | 2.89k | const unsigned int r = (color >> 24) & 0xff; |
300 | 2.89k | const unsigned int g = (color >> 16) & 0xff; |
301 | 2.89k | const unsigned int b = (color >> 8) & 0xff; |
302 | 2.89k | const unsigned int a = 0xff - (color & 0xff); |
303 | | |
304 | 48.8k | for (int y = 0; y < h; y++) { |
305 | 45.9k | uint32_t *dstrow = (uint32_t *) dst; |
306 | 1.11M | for (int x = 0; x < w; x++) { |
307 | 1.06M | const unsigned int v = src[x]; |
308 | 1.06M | unsigned int aa = a * v; |
309 | 1.06M | uint32_t dstpix = dstrow[x]; |
310 | 1.06M | unsigned int dstb = dstpix & 0xFF; |
311 | 1.06M | unsigned int dstg = (dstpix >> 8) & 0xFF; |
312 | 1.06M | unsigned int dstr = (dstpix >> 16) & 0xFF; |
313 | 1.06M | unsigned int dsta = (dstpix >> 24) & 0xFF; |
314 | 1.06M | dstb = (v * b * a + dstb * (255 * 255 - aa)) / (255 * 255); |
315 | 1.06M | dstg = (v * g * a + dstg * (255 * 255 - aa)) / (255 * 255); |
316 | 1.06M | dstr = (v * r * a + dstr * (255 * 255 - aa)) / (255 * 255); |
317 | 1.06M | dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255); |
318 | 1.06M | dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); |
319 | 1.06M | } |
320 | 45.9k | dst += dst_stride; |
321 | 45.9k | src += src_stride; |
322 | 45.9k | } |
323 | 2.89k | } |
324 | | |
325 | | static void render_ass(struct mp_draw_sub_cache *p, struct sub_bitmaps *sb) |
326 | 84 | { |
327 | 84 | mp_assert(sb->format == SUBBITMAP_LIBASS); |
328 | | |
329 | 2.98k | for (int i = 0; i < sb->num_parts; i++) { |
330 | 2.89k | struct sub_bitmap *s = &sb->parts[i]; |
331 | | |
332 | 2.89k | draw_ass_rgba(mp_image_pixel_ptr(p->rgba_overlay, 0, s->x, s->y), |
333 | 2.89k | p->rgba_overlay->stride[0], s->bitmap, s->stride, |
334 | 2.89k | s->w, s->h, s->libass.color); |
335 | | |
336 | 2.89k | mark_rect(p, s->x, s->y, s->x + s->w, s->y + s->h); |
337 | 2.89k | } |
338 | 84 | } |
339 | | |
340 | | static void draw_rgba(uint8_t *dst, ptrdiff_t dst_stride, |
341 | | uint8_t *src, ptrdiff_t src_stride, int w, int h) |
342 | 0 | { |
343 | 0 | for (int y = 0; y < h; y++) { |
344 | 0 | uint32_t *srcrow = (uint32_t *)src; |
345 | 0 | uint32_t *dstrow = (uint32_t *)dst; |
346 | 0 | for (int x = 0; x < w; x++) { |
347 | 0 | uint32_t srcpix = srcrow[x]; |
348 | 0 | uint32_t dstpix = dstrow[x]; |
349 | 0 | unsigned int srcb = srcpix & 0xFF; |
350 | 0 | unsigned int srcg = (srcpix >> 8) & 0xFF; |
351 | 0 | unsigned int srcr = (srcpix >> 16) & 0xFF; |
352 | 0 | unsigned int srca = (srcpix >> 24) & 0xFF; |
353 | 0 | unsigned int dstb = dstpix & 0xFF; |
354 | 0 | unsigned int dstg = (dstpix >> 8) & 0xFF; |
355 | 0 | unsigned int dstr = (dstpix >> 16) & 0xFF; |
356 | 0 | unsigned int dsta = (dstpix >> 24) & 0xFF; |
357 | 0 | dstb = srcb + dstb * (255 * 255 - srca) / (255 * 255); |
358 | 0 | dstg = srcg + dstg * (255 * 255 - srca) / (255 * 255); |
359 | 0 | dstr = srcr + dstr * (255 * 255 - srca) / (255 * 255); |
360 | 0 | dsta = srca + dsta * (255 * 255 - srca) / (255 * 255); |
361 | 0 | dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); |
362 | 0 | } |
363 | 0 | dst += dst_stride; |
364 | 0 | src += src_stride; |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | | static bool render_rgba(struct mp_draw_sub_cache *p, struct part *part, |
369 | | struct sub_bitmaps *sb) |
370 | 0 | { |
371 | 0 | mp_assert(sb->format == SUBBITMAP_BGRA); |
372 | | |
373 | 0 | if (part->change_id != sb->change_id) { |
374 | 0 | for (int n = 0; n < part->num_imgs; n++) |
375 | 0 | talloc_free(part->imgs[n]); |
376 | 0 | part->num_imgs = sb->num_parts; |
377 | 0 | MP_TARRAY_GROW(p, part->imgs, part->num_imgs); |
378 | 0 | for (int n = 0; n < part->num_imgs; n++) |
379 | 0 | part->imgs[n] = NULL; |
380 | |
|
381 | 0 | part->change_id = sb->change_id; |
382 | 0 | } |
383 | |
|
384 | 0 | for (int i = 0; i < sb->num_parts; i++) { |
385 | 0 | struct sub_bitmap *s = &sb->parts[i]; |
386 | | |
387 | | // Clipping is rare but necessary. |
388 | 0 | int sx0 = s->x; |
389 | 0 | int sy0 = s->y; |
390 | 0 | int sx1 = s->x + s->dw; |
391 | 0 | int sy1 = s->y + s->dh; |
392 | |
|
393 | 0 | int x0 = MPCLAMP(sx0, 0, p->w); |
394 | 0 | int y0 = MPCLAMP(sy0, 0, p->h); |
395 | 0 | int x1 = MPCLAMP(sx1, 0, p->w); |
396 | 0 | int y1 = MPCLAMP(sy1, 0, p->h); |
397 | |
|
398 | 0 | int dw = x1 - x0; |
399 | 0 | int dh = y1 - y0; |
400 | 0 | if (dw <= 0 || dh <= 0) |
401 | 0 | continue; |
402 | | |
403 | | // We clip the source instead of the scaled image, because that might |
404 | | // avoid excessive memory usage when applying a ridiculous scale factor, |
405 | | // even if that stretches it to up to 1 pixel due to integer rounding. |
406 | 0 | int sx = 0; |
407 | 0 | int sy = 0; |
408 | 0 | int sw = s->w; |
409 | 0 | int sh = s->h; |
410 | 0 | if (x0 != sx0 || y0 != sy0 || x1 != sx1 || y1 != sy1) { |
411 | 0 | double fx = s->dw / (double)s->w; |
412 | 0 | double fy = s->dh / (double)s->h; |
413 | 0 | sx = MPCLAMP((x0 - sx0) / fx, 0, s->w); |
414 | 0 | sy = MPCLAMP((y0 - sy0) / fy, 0, s->h); |
415 | 0 | sw = MPCLAMP(dw / fx, 1, s->w); |
416 | 0 | sh = MPCLAMP(dh / fy, 1, s->h); |
417 | 0 | } |
418 | |
|
419 | 0 | mp_assert(sx >= 0 && sw > 0 && sx + sw <= s->w); |
420 | 0 | mp_assert(sy >= 0 && sh > 0 && sy + sh <= s->h); |
421 | | |
422 | 0 | ptrdiff_t s_stride = s->stride; |
423 | 0 | void *s_ptr = (char *)s->bitmap + s_stride * sy + sx * 4; |
424 | |
|
425 | 0 | if (dw != sw || dh != sh) { |
426 | 0 | struct mp_image *scaled = part->imgs[i]; |
427 | |
|
428 | 0 | if (!scaled) { |
429 | 0 | struct mp_image src_img = {0}; |
430 | 0 | mp_image_setfmt(&src_img, IMGFMT_BGRA); |
431 | 0 | mp_image_set_size(&src_img, sw, sh); |
432 | 0 | src_img.planes[0] = s_ptr; |
433 | 0 | src_img.stride[0] = s_stride; |
434 | 0 | src_img.params.repr.alpha = PL_ALPHA_PREMULTIPLIED; |
435 | |
|
436 | 0 | scaled = mp_image_alloc(IMGFMT_BGRA, dw, dh); |
437 | 0 | if (!scaled) |
438 | 0 | return false; |
439 | 0 | part->imgs[i] = talloc_steal(p, scaled); |
440 | 0 | mp_image_copy_attributes(scaled, &src_img); |
441 | |
|
442 | 0 | if (mp_sws_scale(p->sub_scale, scaled, &src_img) < 0) |
443 | 0 | return false; |
444 | 0 | } |
445 | | |
446 | 0 | mp_assert(scaled->w == dw); |
447 | 0 | mp_assert(scaled->h == dh); |
448 | | |
449 | 0 | s_stride = scaled->stride[0]; |
450 | 0 | s_ptr = scaled->planes[0]; |
451 | 0 | } |
452 | | |
453 | 0 | draw_rgba(mp_image_pixel_ptr(p->rgba_overlay, 0, x0, y0), |
454 | 0 | p->rgba_overlay->stride[0], s_ptr, s_stride, dw, dh); |
455 | |
|
456 | 0 | mark_rect(p, x0, y0, x1, y1); |
457 | 0 | } |
458 | | |
459 | 0 | return true; |
460 | 0 | } |
461 | | |
462 | | static bool render_sb(struct mp_draw_sub_cache *p, struct sub_bitmaps *sb) |
463 | 84 | { |
464 | 84 | struct part *part = &p->parts[sb->render_index]; |
465 | | |
466 | 84 | switch (sb->format) { |
467 | 84 | case SUBBITMAP_LIBASS: |
468 | 84 | render_ass(p, sb); |
469 | 84 | return true; |
470 | 0 | case SUBBITMAP_BGRA: |
471 | 0 | return render_rgba(p, part, sb); |
472 | 84 | } |
473 | | |
474 | 0 | return false; |
475 | 84 | } |
476 | | |
477 | | static void clear_rgba_overlay(struct mp_draw_sub_cache *p) |
478 | 168 | { |
479 | 168 | mp_assert(p->rgba_overlay->imgfmt == IMGFMT_BGRA); |
480 | | |
481 | 30.4k | for (int y = 0; y < p->rgba_overlay->h; y++) { |
482 | 30.2k | uint32_t *px = mp_image_pixel_ptr(p->rgba_overlay, 0, 0, y); |
483 | 30.2k | struct slice *line = &p->slices[y * p->s_w]; |
484 | | |
485 | 90.7k | for (int sx = 0; sx < p->s_w; sx++) { |
486 | 60.4k | struct slice *s = &line[sx]; |
487 | | |
488 | | // Ensure this final slice doesn't extend beyond the width of p->s_w |
489 | 60.4k | if (s->x1 == SLICE_W && sx == p->s_w - 1 && y == p->rgba_overlay->h - 1) |
490 | 0 | s->x1 = MPMIN(p->w - ((p->s_w - 1) * SLICE_W), s->x1); |
491 | | |
492 | 60.4k | if (s->x0 <= s->x1) { |
493 | 30.2k | memset(px + s->x0, 0, (s->x1 - s->x0) * 4); |
494 | 30.2k | *s = (struct slice){SLICE_W, 0}; |
495 | 30.2k | } |
496 | | |
497 | 60.4k | px += SLICE_W; |
498 | 60.4k | } |
499 | 30.2k | } |
500 | | |
501 | 168 | p->any_osd = false; |
502 | 168 | } |
503 | | |
504 | | static struct mp_sws_context *alloc_scaler(struct mp_draw_sub_cache *p) |
505 | 84 | { |
506 | 84 | struct mp_sws_context *s = mp_sws_alloc(p); |
507 | 84 | mp_sws_enable_cmdline_opts(s, p->global); |
508 | 84 | return s; |
509 | 84 | } |
510 | | |
511 | | static void init_general(struct mp_draw_sub_cache *p) |
512 | 84 | { |
513 | 84 | p->sub_scale = alloc_scaler(p); |
514 | | |
515 | 84 | p->s_w = MP_ALIGN_UP(p->rgba_overlay->w, SLICE_W) / SLICE_W; |
516 | | |
517 | 84 | p->slices = talloc_zero_array(p, struct slice, p->s_w * p->rgba_overlay->h); |
518 | | |
519 | 84 | mp_image_clear(p->rgba_overlay, 0, 0, p->w, p->h); |
520 | 84 | clear_rgba_overlay(p); |
521 | 84 | } |
522 | | |
523 | | static bool reinit_to_video(struct mp_draw_sub_cache *p) |
524 | 84 | { |
525 | 84 | struct mp_image_params *params = &p->params; |
526 | 84 | mp_image_params_guess_csp(params); |
527 | | |
528 | 84 | bool need_premul = params->repr.alpha != PL_ALPHA_PREMULTIPLIED && |
529 | 84 | (mp_imgfmt_get_desc(params->imgfmt).flags & MP_IMGFLAG_ALPHA); |
530 | | |
531 | | // Intermediate format for video_overlay. Requirements: |
532 | | // - same subsampling as video |
533 | | // - uses video colorspace |
534 | | // - has alpha |
535 | | // - repacker support (to the format used in p->blend_line) |
536 | | // - probably 8 bit per component rather than something wasteful or strange |
537 | 84 | struct mp_regular_imgfmt vfdesc = {0}; |
538 | | |
539 | 84 | int rflags = REPACK_CREATE_EXPAND_8BIT; |
540 | 84 | bool use_shortcut = false; |
541 | | |
542 | 84 | p->video_to_f32 = mp_repack_create_planar(params->imgfmt, false, rflags); |
543 | 84 | talloc_steal(p, p->video_to_f32); |
544 | 84 | if (!p->video_to_f32) |
545 | 0 | return false; |
546 | 84 | mp_get_regular_imgfmt(&vfdesc, mp_repack_get_format_dst(p->video_to_f32)); |
547 | 84 | mp_assert(vfdesc.num_planes); // must have succeeded |
548 | | |
549 | 84 | if (params->repr.sys == PL_COLOR_SYSTEM_RGB && vfdesc.num_planes >= 3) { |
550 | 84 | use_shortcut = true; |
551 | | |
552 | 84 | if (vfdesc.component_type == MP_COMPONENT_TYPE_UINT && |
553 | 84 | vfdesc.component_size == 1 && vfdesc.component_pad == 0) |
554 | 84 | p->blend_line = blend_line_u8; |
555 | 84 | } |
556 | | |
557 | | // If no special blender is available, blend in float. |
558 | 84 | if (!p->blend_line) { |
559 | 0 | TA_FREEP(&p->video_to_f32); |
560 | |
|
561 | 0 | rflags |= REPACK_CREATE_PLANAR_F32; |
562 | |
|
563 | 0 | p->video_to_f32 = mp_repack_create_planar(params->imgfmt, false, rflags); |
564 | 0 | talloc_steal(p, p->video_to_f32); |
565 | 0 | if (!p->video_to_f32) |
566 | 0 | return false; |
567 | | |
568 | 0 | mp_get_regular_imgfmt(&vfdesc, mp_repack_get_format_dst(p->video_to_f32)); |
569 | 0 | mp_assert(vfdesc.component_type == MP_COMPONENT_TYPE_FLOAT); |
570 | | |
571 | 0 | p->blend_line = blend_line_f32; |
572 | 0 | } |
573 | | |
574 | 84 | p->scale_in_tiles = SCALE_IN_TILES; |
575 | | |
576 | 84 | int vid_f32_fmt = mp_repack_get_format_dst(p->video_to_f32); |
577 | | |
578 | 84 | p->video_from_f32 = mp_repack_create_planar(params->imgfmt, true, rflags); |
579 | 84 | talloc_steal(p, p->video_from_f32); |
580 | 84 | if (!p->video_from_f32) |
581 | 0 | return false; |
582 | | |
583 | 84 | mp_assert(mp_repack_get_format_dst(p->video_to_f32) == |
584 | 84 | mp_repack_get_format_src(p->video_from_f32)); |
585 | | |
586 | 84 | int overlay_fmt = 0; |
587 | 84 | if (use_shortcut) { |
588 | | // No point in doing anything fancy. |
589 | 84 | overlay_fmt = IMGFMT_BGRA; |
590 | 84 | p->scale_in_tiles = false; |
591 | 84 | } else { |
592 | 0 | struct mp_regular_imgfmt odesc = vfdesc; |
593 | | // Just use 8 bit as well (should be fine, may use less memory). |
594 | 0 | odesc.component_type = MP_COMPONENT_TYPE_UINT; |
595 | 0 | odesc.component_size = 1; |
596 | 0 | odesc.component_pad = 0; |
597 | | |
598 | | // Ensure there's alpha. |
599 | 0 | if (odesc.planes[odesc.num_planes - 1].components[0] != 4) { |
600 | 0 | if (odesc.num_planes >= 4) |
601 | 0 | return false; // wat |
602 | 0 | odesc.planes[odesc.num_planes++] = |
603 | 0 | (struct mp_regular_imgfmt_plane){1, {4}}; |
604 | 0 | } |
605 | | |
606 | 0 | overlay_fmt = mp_find_regular_imgfmt(&odesc); |
607 | 0 | p->scale_in_tiles = odesc.chroma_xs || odesc.chroma_ys; |
608 | 0 | } |
609 | 84 | if (!overlay_fmt) |
610 | 0 | return false; |
611 | | |
612 | 84 | p->overlay_to_f32 = mp_repack_create_planar(overlay_fmt, false, rflags); |
613 | 84 | talloc_steal(p, p->overlay_to_f32); |
614 | 84 | if (!p->overlay_to_f32) |
615 | 0 | return false; |
616 | | |
617 | 84 | int render_fmt = mp_repack_get_format_dst(p->overlay_to_f32); |
618 | | |
619 | 84 | struct mp_regular_imgfmt ofdesc = {0}; |
620 | 84 | mp_get_regular_imgfmt(&ofdesc, render_fmt); |
621 | | |
622 | 84 | if (ofdesc.planes[ofdesc.num_planes - 1].components[0] != 4) |
623 | 0 | return false; |
624 | | |
625 | | // The formats must be the same, minus possible lack of alpha in vfdesc. |
626 | 84 | if (ofdesc.num_planes != vfdesc.num_planes && |
627 | 84 | ofdesc.num_planes - 1 != vfdesc.num_planes) |
628 | 0 | return false; |
629 | 336 | for (int n = 0; n < vfdesc.num_planes; n++) { |
630 | 252 | if (vfdesc.planes[n].components[0] != ofdesc.planes[n].components[0]) |
631 | 0 | return false; |
632 | 252 | } |
633 | | |
634 | 84 | p->align_x = mp_repack_get_align_x(p->video_to_f32); |
635 | 84 | p->align_y = mp_repack_get_align_y(p->video_to_f32); |
636 | | |
637 | 84 | mp_assert(p->align_x >= mp_repack_get_align_x(p->overlay_to_f32)); |
638 | 84 | mp_assert(p->align_y >= mp_repack_get_align_y(p->overlay_to_f32)); |
639 | | |
640 | 84 | if (p->align_x > SLICE_W || p->align_y > TILE_H) |
641 | 0 | return false; |
642 | | |
643 | 84 | p->w = MP_ALIGN_UP(params->w, p->align_x); |
644 | 84 | int slice_h = p->align_y; |
645 | 84 | p->h = MP_ALIGN_UP(params->h, slice_h); |
646 | | |
647 | | // Size of the overlay. If scaling in tiles, round up to tiles, so we don't |
648 | | // need to reinit the scale for right/bottom tiles. |
649 | 84 | int w = p->w; |
650 | 84 | int h = p->h; |
651 | 84 | if (p->scale_in_tiles) { |
652 | 0 | w = MP_ALIGN_UP(w, SLICE_W); |
653 | 0 | h = MP_ALIGN_UP(h, TILE_H); |
654 | 0 | } |
655 | | |
656 | 84 | p->rgba_overlay = talloc_steal(p, mp_image_alloc(IMGFMT_BGRA, w, h)); |
657 | 84 | p->overlay_tmp = talloc_steal(p, mp_image_alloc(render_fmt, SLICE_W, slice_h)); |
658 | 84 | p->video_tmp = talloc_steal(p, mp_image_alloc(vid_f32_fmt, SLICE_W, slice_h)); |
659 | 84 | if (!p->rgba_overlay || !p->overlay_tmp || !p->video_tmp) |
660 | 0 | return false; |
661 | | |
662 | 84 | mp_image_params_guess_csp(&p->rgba_overlay->params); |
663 | 84 | p->rgba_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; |
664 | | |
665 | 84 | p->overlay_tmp->params.repr = params->repr; |
666 | 84 | p->overlay_tmp->params.color = params->color; |
667 | 84 | p->video_tmp->params.repr = params->repr; |
668 | 84 | p->video_tmp->params.color = params->color; |
669 | | |
670 | 84 | if (p->rgba_overlay->imgfmt == overlay_fmt) { |
671 | 84 | if (!repack_config_buffers(p->overlay_to_f32, 0, p->overlay_tmp, |
672 | 84 | 0, p->rgba_overlay, NULL)) |
673 | 0 | return false; |
674 | 84 | } else { |
675 | | // Generally non-RGB. |
676 | 0 | p->video_overlay = talloc_steal(p, mp_image_alloc(overlay_fmt, w, h)); |
677 | 0 | if (!p->video_overlay) |
678 | 0 | return false; |
679 | | |
680 | 0 | p->video_overlay->params.repr = params->repr; |
681 | 0 | p->video_overlay->params.color = params->color; |
682 | 0 | p->video_overlay->params.chroma_location = params->chroma_location; |
683 | 0 | p->video_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; |
684 | |
|
685 | 0 | if (p->scale_in_tiles) |
686 | 0 | p->video_overlay->params.chroma_location = PL_CHROMA_CENTER; |
687 | |
|
688 | 0 | p->rgba_to_overlay = alloc_scaler(p); |
689 | 0 | p->rgba_to_overlay->allow_zimg = true; |
690 | 0 | if (!mp_sws_supports_formats(p->rgba_to_overlay, |
691 | 0 | p->video_overlay->imgfmt, p->rgba_overlay->imgfmt)) |
692 | 0 | return false; |
693 | | |
694 | 0 | if (!repack_config_buffers(p->overlay_to_f32, 0, p->overlay_tmp, |
695 | 0 | 0, p->video_overlay, NULL)) |
696 | 0 | return false; |
697 | | |
698 | | // Setup a scaled alpha plane if chroma-subsampling is present. |
699 | 0 | int xs = p->video_overlay->fmt.chroma_xs; |
700 | 0 | int ys = p->video_overlay->fmt.chroma_ys; |
701 | 0 | if (xs || ys) { |
702 | | // Require float so format selection becomes simpler (maybe). |
703 | 0 | mp_assert(rflags & REPACK_CREATE_PLANAR_F32); |
704 | | |
705 | | // For extracting the alpha plane, construct a gray format that is |
706 | | // compatible with the alpha one. |
707 | 0 | struct mp_regular_imgfmt odesc = {0}; |
708 | 0 | mp_get_regular_imgfmt(&odesc, overlay_fmt); |
709 | 0 | mp_assert(odesc.component_size); |
710 | 0 | int aplane = odesc.num_planes - 1; |
711 | 0 | mp_assert(odesc.planes[aplane].num_components == 1); |
712 | 0 | mp_assert(odesc.planes[aplane].components[0] == 4); |
713 | 0 | struct mp_regular_imgfmt cadesc = odesc; |
714 | 0 | cadesc.num_planes = 1; |
715 | 0 | cadesc.planes[0] = (struct mp_regular_imgfmt_plane){1, {1}}; |
716 | 0 | cadesc.chroma_xs = cadesc.chroma_ys = 0; |
717 | |
|
718 | 0 | int calpha_fmt = mp_find_regular_imgfmt(&cadesc); |
719 | 0 | if (!calpha_fmt) |
720 | 0 | return false; |
721 | | |
722 | | // Unscaled alpha plane from p->video_overlay. |
723 | 0 | p->alpha_overlay = talloc_zero(p, struct mp_image); |
724 | 0 | mp_image_setfmt(p->alpha_overlay, calpha_fmt); |
725 | 0 | mp_image_set_size(p->alpha_overlay, w, h); |
726 | 0 | p->alpha_overlay->planes[0] = p->video_overlay->planes[aplane]; |
727 | 0 | p->alpha_overlay->stride[0] = p->video_overlay->stride[aplane]; |
728 | | |
729 | | // Full range gray always has the same range as alpha. |
730 | 0 | p->alpha_overlay->params.repr.levels = PL_COLOR_LEVELS_FULL; |
731 | 0 | mp_image_params_guess_csp(&p->alpha_overlay->params); |
732 | |
|
733 | 0 | p->calpha_overlay = |
734 | 0 | talloc_steal(p, mp_image_alloc(calpha_fmt, w >> xs, h >> ys)); |
735 | 0 | if (!p->calpha_overlay) |
736 | 0 | return false; |
737 | 0 | p->calpha_overlay->params.repr = p->alpha_overlay->params.repr; |
738 | 0 | p->calpha_overlay->params.color = p->alpha_overlay->params.color; |
739 | |
|
740 | 0 | p->calpha_to_f32 = mp_repack_create_planar(calpha_fmt, false, rflags); |
741 | 0 | talloc_steal(p, p->calpha_to_f32); |
742 | 0 | if (!p->calpha_to_f32) |
743 | 0 | return false; |
744 | | |
745 | 0 | int af32_fmt = mp_repack_get_format_dst(p->calpha_to_f32); |
746 | 0 | p->calpha_tmp = talloc_steal(p, mp_image_alloc(af32_fmt, SLICE_W, 1)); |
747 | 0 | if (!p->calpha_tmp) |
748 | 0 | return false; |
749 | | |
750 | 0 | if (!repack_config_buffers(p->calpha_to_f32, 0, p->calpha_tmp, |
751 | 0 | 0, p->calpha_overlay, NULL)) |
752 | 0 | return false; |
753 | | |
754 | 0 | p->alpha_to_calpha = alloc_scaler(p); |
755 | 0 | if (!mp_sws_supports_formats(p->alpha_to_calpha, |
756 | 0 | calpha_fmt, calpha_fmt)) |
757 | 0 | return false; |
758 | 0 | } |
759 | 0 | } |
760 | | |
761 | 84 | if (need_premul) { |
762 | 0 | p->premul = alloc_scaler(p); |
763 | 0 | p->unpremul = alloc_scaler(p); |
764 | 0 | p->premul_tmp = mp_image_alloc(params->imgfmt, params->w, params->h); |
765 | 0 | talloc_steal(p, p->premul_tmp); |
766 | 0 | if (!p->premul_tmp) |
767 | 0 | return false; |
768 | 0 | mp_image_set_params(p->premul_tmp, params); |
769 | 0 | p->premul_tmp->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; |
770 | | |
771 | | // Only zimg supports this. |
772 | 0 | p->premul->force_scaler = MP_SWS_ZIMG; |
773 | 0 | p->unpremul->force_scaler = MP_SWS_ZIMG; |
774 | 0 | } |
775 | | |
776 | 84 | init_general(p); |
777 | | |
778 | 84 | return true; |
779 | 84 | } |
780 | | |
781 | | static bool reinit_to_overlay(struct mp_draw_sub_cache *p) |
782 | 0 | { |
783 | 0 | p->align_x = 1; |
784 | 0 | p->align_y = 1; |
785 | |
|
786 | 0 | p->w = p->params.w; |
787 | 0 | p->h = p->params.h; |
788 | |
|
789 | 0 | p->rgba_overlay = talloc_steal(p, mp_image_alloc(IMGFMT_BGRA, p->w, p->h)); |
790 | 0 | if (!p->rgba_overlay) |
791 | 0 | return false; |
792 | | |
793 | 0 | mp_image_params_guess_csp(&p->rgba_overlay->params); |
794 | 0 | p->rgba_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; |
795 | | |
796 | | // Some non-sense with the intention to somewhat isolate the returned image. |
797 | 0 | mp_image_setfmt(&p->res_overlay, p->rgba_overlay->imgfmt); |
798 | 0 | mp_image_set_size(&p->res_overlay, p->rgba_overlay->w, p->rgba_overlay->h); |
799 | 0 | mp_image_copy_attributes(&p->res_overlay, p->rgba_overlay); |
800 | 0 | p->res_overlay.planes[0] = p->rgba_overlay->planes[0]; |
801 | 0 | p->res_overlay.stride[0] = p->rgba_overlay->stride[0]; |
802 | |
|
803 | 0 | init_general(p); |
804 | | |
805 | | // Mark all dirty (for full reinit of user state). |
806 | 0 | for (int y = 0; y < p->rgba_overlay->h; y++) { |
807 | 0 | for (int sx = 0; sx < p->s_w; sx++) |
808 | 0 | p->slices[y * p->s_w + sx] = (struct slice){0, SLICE_W}; |
809 | 0 | } |
810 | |
|
811 | 0 | return true; |
812 | 0 | } |
813 | | |
814 | | static bool check_reinit(struct mp_draw_sub_cache *p, |
815 | | struct mp_image_params *params, bool to_video) |
816 | 84 | { |
817 | 84 | if (!mp_image_params_equal(&p->params, params) || !p->rgba_overlay) { |
818 | 84 | talloc_free_children(p); |
819 | 84 | *p = (struct mp_draw_sub_cache){.global = p->global, .params = *params}; |
820 | 84 | if (!(to_video ? reinit_to_video(p) : reinit_to_overlay(p))) { |
821 | 0 | talloc_free_children(p); |
822 | 0 | *p = (struct mp_draw_sub_cache){.global = p->global}; |
823 | 0 | return false; |
824 | 0 | } |
825 | 84 | } |
826 | 84 | return true; |
827 | 84 | } |
828 | | |
829 | | char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *p) |
830 | 0 | { |
831 | 0 | mp_assert(p); |
832 | | |
833 | 0 | return talloc_asprintf(NULL, |
834 | 0 | "align=%d:%d ov=%-7s, ov_f=%s, v_f=%s, a=%s, ca=%s, ca_f=%s", |
835 | 0 | p->align_x, p->align_y, |
836 | 0 | mp_imgfmt_to_name(p->video_overlay ? p->video_overlay->imgfmt : 0), |
837 | 0 | mp_imgfmt_to_name(p->overlay_tmp->imgfmt), |
838 | 0 | mp_imgfmt_to_name(p->video_tmp->imgfmt), |
839 | 0 | mp_imgfmt_to_name(p->alpha_overlay ? p->alpha_overlay->imgfmt : 0), |
840 | 0 | mp_imgfmt_to_name(p->calpha_overlay ? p->calpha_overlay->imgfmt : 0), |
841 | 0 | mp_imgfmt_to_name(p->calpha_tmp ? p->calpha_tmp->imgfmt : 0)); |
842 | 0 | } |
843 | | |
844 | | struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent, struct mpv_global *g) |
845 | 42 | { |
846 | 42 | struct mp_draw_sub_cache *c = talloc_zero(ta_parent, struct mp_draw_sub_cache); |
847 | 42 | c->global = g; |
848 | 42 | return c; |
849 | 42 | } |
850 | | |
851 | | // For tests. |
852 | | struct mp_draw_sub_cache *mp_draw_sub_alloc_test(struct mp_image *dst) |
853 | 0 | { |
854 | 0 | struct mp_draw_sub_cache *c = talloc_zero(NULL, struct mp_draw_sub_cache); |
855 | 0 | reinit_to_video(c); |
856 | 0 | return c; |
857 | 0 | } |
858 | | |
859 | | bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *p, struct mp_image *dst, |
860 | | struct sub_bitmap_list *sbs_list) |
861 | 84 | { |
862 | 84 | bool ok = false; |
863 | | |
864 | | // dst must at least be as large as the bounding box, or you may get memory |
865 | | // corruption. |
866 | 84 | mp_assert(dst->w >= sbs_list->w); |
867 | 84 | mp_assert(dst->h >= sbs_list->h); |
868 | | |
869 | 84 | if (!check_reinit(p, &dst->params, true)) |
870 | 0 | return false; |
871 | | |
872 | 84 | if (p->change_id != sbs_list->change_id) { |
873 | 84 | p->change_id = sbs_list->change_id; |
874 | | |
875 | 84 | clear_rgba_overlay(p); |
876 | | |
877 | 168 | for (int n = 0; n < sbs_list->num_items; n++) { |
878 | 84 | if (!render_sb(p, sbs_list->items[n])) |
879 | 0 | goto done; |
880 | 84 | } |
881 | | |
882 | 84 | if (!convert_to_video_overlay(p)) |
883 | 0 | goto done; |
884 | 84 | } |
885 | | |
886 | 84 | if (p->any_osd) { |
887 | 84 | struct mp_image *target = dst; |
888 | 84 | if (p->premul_tmp) { |
889 | 0 | if (mp_sws_scale(p->premul, p->premul_tmp, dst) < 0) |
890 | 0 | goto done; |
891 | 0 | target = p->premul_tmp; |
892 | 0 | } |
893 | | |
894 | 84 | if (!blend_overlay_with_video(p, target)) |
895 | 0 | goto done; |
896 | | |
897 | 84 | if (target != dst) { |
898 | 0 | if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0) |
899 | 0 | goto done; |
900 | 0 | } |
901 | 84 | } |
902 | | |
903 | 84 | ok = true; |
904 | | |
905 | 84 | done: |
906 | 84 | return ok; |
907 | 84 | } |
908 | | |
909 | | // Bounding boxes for mp_draw_sub_overlay() API. For simplicity, each rectangle |
910 | | // covers a fixed tile on the screen, starts out empty, but is not extended |
911 | | // beyond the tile. In the simplest case, there's only 1 rect/tile for everything. |
912 | | struct rc_grid { |
913 | | unsigned w, h; // size in grid tiles |
914 | | unsigned r_w, r_h; // size of a grid tile in pixels |
915 | | struct mp_rect *rcs; // rcs[x * w + y] |
916 | | }; |
917 | | |
918 | | static void init_rc_grid(struct rc_grid *gr, struct mp_draw_sub_cache *p, |
919 | | struct mp_rect *rcs, int max_rcs) |
920 | 0 | { |
921 | 0 | *gr = (struct rc_grid){ .w = max_rcs ? 1 : 0, .h = max_rcs ? 1 : 0, |
922 | 0 | .rcs = rcs, .r_w = p->s_w * SLICE_W, .r_h = p->h, }; |
923 | | |
924 | | // Dumb iteration to figure out max. size because I'm stupid. |
925 | 0 | bool more = true; |
926 | 0 | while (more) { |
927 | 0 | more = false; |
928 | 0 | if (gr->r_h >= 128) { |
929 | 0 | if (gr->w * gr->h * 2 > max_rcs) |
930 | 0 | break; |
931 | 0 | gr->h *= 2; |
932 | 0 | gr->r_h = (p->h + gr->h - 1) / gr->h; |
933 | 0 | more = true; |
934 | 0 | } |
935 | 0 | if (gr->r_w >= SLICE_W * 2) { |
936 | 0 | if (gr->w * gr->h * 2 > max_rcs) |
937 | 0 | break; |
938 | 0 | gr->w *= 2; |
939 | 0 | gr->r_w = (p->s_w + gr->w - 1) / gr->w * SLICE_W; |
940 | 0 | more = true; |
941 | 0 | } |
942 | 0 | } |
943 | |
|
944 | 0 | mp_assert(gr->r_h * gr->h >= p->h); |
945 | 0 | mp_assert(!(gr->r_w & (SLICE_W - 1))); |
946 | 0 | mp_assert(gr->r_w * gr->w >= p->w); |
947 | | |
948 | | // Init with empty (degenerate) rectangles. |
949 | 0 | for (int y = 0; y < gr->h; y++) { |
950 | 0 | for (int x = 0; x < gr->w; x++) { |
951 | 0 | struct mp_rect *rc = &gr->rcs[y * gr->w + x]; |
952 | 0 | rc->x1 = x * gr->r_w; |
953 | 0 | rc->y1 = y * gr->r_h; |
954 | 0 | rc->x0 = rc->x1 + gr->r_w; |
955 | 0 | rc->y0 = rc->y1 + gr->r_h; |
956 | 0 | } |
957 | 0 | } |
958 | 0 | } |
959 | | |
960 | | // Extend given grid with contents of p->slices. |
961 | | static void mark_rcs(struct mp_draw_sub_cache *p, struct rc_grid *gr) |
962 | 0 | { |
963 | 0 | for (int y = 0; y < p->h; y++) { |
964 | 0 | struct slice *line = &p->slices[y * p->s_w]; |
965 | 0 | struct mp_rect *rcs = &gr->rcs[y / gr->r_h * gr->w]; |
966 | |
|
967 | 0 | for (int sx = 0; sx < p->s_w; sx++) { |
968 | 0 | struct slice *s = &line[sx]; |
969 | 0 | if (s->x0 < s->x1) { |
970 | 0 | unsigned xpos = sx * SLICE_W; |
971 | 0 | struct mp_rect *rc = &rcs[xpos / gr->r_w]; |
972 | 0 | rc->y0 = MPMIN(rc->y0, y); |
973 | 0 | rc->y1 = MPMAX(rc->y1, y + 1); |
974 | 0 | rc->x0 = MPMIN(rc->x0, xpos + s->x0); |
975 | | // Ensure this does not extend beyond the total width |
976 | 0 | rc->x1 = MPCLAMP(xpos + s->x1, rc->x1, p->w); |
977 | 0 | } |
978 | 0 | } |
979 | 0 | } |
980 | 0 | } |
981 | | |
982 | | // Remove empty RCs, and return rc count. |
983 | | static int return_rcs(struct rc_grid *gr) |
984 | 0 | { |
985 | 0 | int num = 0, cnt = gr->w * gr->h; |
986 | 0 | for (int n = 0; n < cnt; n++) { |
987 | 0 | struct mp_rect *rc = &gr->rcs[n]; |
988 | 0 | if (rc->x0 < rc->x1 && rc->y0 < rc->y1) |
989 | 0 | gr->rcs[num++] = *rc; |
990 | 0 | } |
991 | 0 | return num; |
992 | 0 | } |
993 | | |
994 | | struct mp_image *mp_draw_sub_overlay(struct mp_draw_sub_cache *p, |
995 | | struct sub_bitmap_list *sbs_list, |
996 | | struct mp_rect *act_rcs, |
997 | | int max_act_rcs, |
998 | | int *num_act_rcs, |
999 | | struct mp_rect *mod_rcs, |
1000 | | int max_mod_rcs, |
1001 | | int *num_mod_rcs) |
1002 | 0 | { |
1003 | 0 | *num_act_rcs = 0; |
1004 | 0 | *num_mod_rcs = 0; |
1005 | |
|
1006 | 0 | struct mp_image_params params = {.w = sbs_list->w, .h = sbs_list->h}; |
1007 | 0 | if (!check_reinit(p, ¶ms, false)) |
1008 | 0 | return NULL; |
1009 | | |
1010 | 0 | struct rc_grid gr_act, gr_mod; |
1011 | 0 | init_rc_grid(&gr_act, p, act_rcs, max_act_rcs); |
1012 | 0 | init_rc_grid(&gr_mod, p, mod_rcs, max_mod_rcs); |
1013 | |
|
1014 | 0 | if (p->change_id != sbs_list->change_id) { |
1015 | 0 | p->change_id = sbs_list->change_id; |
1016 | |
|
1017 | 0 | mark_rcs(p, &gr_mod); |
1018 | |
|
1019 | 0 | clear_rgba_overlay(p); |
1020 | |
|
1021 | 0 | for (int n = 0; n < sbs_list->num_items; n++) { |
1022 | 0 | if (!render_sb(p, sbs_list->items[n])) { |
1023 | 0 | p->change_id = 0; |
1024 | 0 | return NULL; |
1025 | 0 | } |
1026 | 0 | } |
1027 | | |
1028 | 0 | mark_rcs(p, &gr_mod); |
1029 | 0 | } |
1030 | | |
1031 | 0 | mark_rcs(p, &gr_act); |
1032 | |
|
1033 | 0 | *num_act_rcs = return_rcs(&gr_act); |
1034 | 0 | *num_mod_rcs = return_rcs(&gr_mod); |
1035 | |
|
1036 | 0 | return &p->res_overlay; |
1037 | 0 | } |
1038 | | |
1039 | | // vim: ts=4 sw=4 et tw=80 |