Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> |
3 | | * |
4 | | * This file is part of mpv. |
5 | | * |
6 | | * mpv is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * mpv is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include <inttypes.h> |
21 | | #include <string.h> |
22 | | #include <stdlib.h> |
23 | | #include <stdarg.h> |
24 | | #include <stdbool.h> |
25 | | #include <assert.h> |
26 | | #include <math.h> |
27 | | |
28 | | #include <ass/ass.h> |
29 | | #include <ass/ass_types.h> |
30 | | |
31 | | #include "common/common.h" |
32 | | #include "common/msg.h" |
33 | | #include "options/path.h" |
34 | | #include "ass_mp.h" |
35 | | #include "img_convert.h" |
36 | | #include "osd.h" |
37 | | #include "stream/stream.h" |
38 | | #include "options/options.h" |
39 | | #include "video/out/bitmap_packer.h" |
40 | | #include "video/mp_image.h" |
41 | | |
42 | | // res_y should be track->PlayResY |
43 | | // It determines scaling of font sizes and more. |
44 | | void mp_ass_set_style(ASS_Style *style, double res_y, |
45 | | const struct osd_style_opts *opts) |
46 | 287 | { |
47 | 287 | if (!style) |
48 | 0 | return; |
49 | | |
50 | 287 | if (opts->font) { |
51 | 287 | if (!style->FontName || strcmp(style->FontName, opts->font) != 0) { |
52 | 45 | free(style->FontName); |
53 | 45 | style->FontName = strdup(opts->font); |
54 | 45 | } |
55 | 287 | } |
56 | | |
57 | | // libass_font_size = FontSize * (window_height / res_y) |
58 | | // scale translates parameters from PlayResY=720 to res_y |
59 | 287 | double scale = res_y / 720.0; |
60 | | |
61 | 287 | style->FontSize = opts->font_size * scale; |
62 | 287 | style->PrimaryColour = MP_ASS_COLOR(opts->color); |
63 | 287 | style->SecondaryColour = style->PrimaryColour; |
64 | 287 | style->OutlineColour = MP_ASS_COLOR(opts->outline_color); |
65 | 287 | style->BackColour = MP_ASS_COLOR(opts->back_color); |
66 | 287 | style->BorderStyle = opts->border_style; |
67 | 287 | style->Outline = opts->outline_size * scale; |
68 | 287 | style->Shadow = opts->shadow_offset * scale; |
69 | 287 | style->Spacing = opts->spacing * scale; |
70 | 287 | style->MarginL = opts->margin_x * scale; |
71 | 287 | style->MarginR = style->MarginL; |
72 | 287 | style->MarginV = opts->margin_y * scale; |
73 | 287 | style->ScaleX = 1.; |
74 | 287 | style->ScaleY = 1.; |
75 | 287 | style->Alignment = 1 + (opts->align_x + 1) + (opts->align_y + 2) % 3 * 4; |
76 | 287 | #ifdef ASS_JUSTIFY_LEFT |
77 | 287 | style->Justify = opts->justify; |
78 | 287 | #endif |
79 | 287 | style->Blur = opts->blur; |
80 | 287 | style->Bold = opts->bold; |
81 | 287 | style->Italic = opts->italic; |
82 | 287 | } |
83 | | |
84 | | void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts, |
85 | | struct mpv_global *global, struct mp_log *log) |
86 | 1.03k | { |
87 | 1.03k | void *tmp = talloc_new(NULL); |
88 | 1.03k | char *default_font = mp_find_config_file(tmp, global, "subfont.ttf"); |
89 | 1.03k | char *config = mp_find_config_file(tmp, global, "fonts.conf"); |
90 | | |
91 | 1.03k | if (default_font && !mp_path_exists(default_font)) |
92 | 0 | default_font = NULL; |
93 | | |
94 | 1.03k | int font_provider = ASS_FONTPROVIDER_AUTODETECT; |
95 | 1.03k | if (opts->font_provider == 1) |
96 | 0 | font_provider = ASS_FONTPROVIDER_NONE; |
97 | 1.03k | if (opts->font_provider == 2) |
98 | 0 | font_provider = ASS_FONTPROVIDER_FONTCONFIG; |
99 | | |
100 | 1.03k | mp_verbose(log, "Setting up fonts...\n"); |
101 | 1.03k | ass_set_fonts(priv, default_font, opts->font, font_provider, config, 1); |
102 | 1.03k | mp_verbose(log, "Done.\n"); |
103 | | |
104 | 1.03k | talloc_free(tmp); |
105 | 1.03k | } |
106 | | |
107 | | static const int map_ass_level[] = { |
108 | | MSGL_ERR, // 0 "FATAL errors" |
109 | | MSGL_WARN, |
110 | | MSGL_INFO, |
111 | | MSGL_V, |
112 | | MSGL_V, |
113 | | MSGL_DEBUG, // 5 application recommended level |
114 | | MSGL_TRACE, |
115 | | MSGL_TRACE, // 7 "verbose DEBUG" |
116 | | }; |
117 | | |
118 | | MP_PRINTF_ATTRIBUTE(2, 0) |
119 | | static void message_callback(int level, const char *format, va_list va, void *ctx) |
120 | 16.3k | { |
121 | 16.3k | struct mp_log *log = ctx; |
122 | 16.3k | if (!log) |
123 | 0 | return; |
124 | 16.3k | level = map_ass_level[level]; |
125 | 16.3k | mp_msg_va(log, level, format, va); |
126 | | // libass messages lack trailing \n |
127 | 16.3k | mp_msg(log, level, "\n"); |
128 | 16.3k | } |
129 | | |
130 | | ASS_Library *mp_ass_init(struct mpv_global *global, |
131 | | struct osd_style_opts *opts, struct mp_log *log) |
132 | 1.03k | { |
133 | 1.03k | char *path = opts->fonts_dir && opts->fonts_dir[0] ? |
134 | 0 | mp_get_user_path(NULL, global, opts->fonts_dir) : |
135 | 1.03k | mp_find_config_file(NULL, global, "fonts"); |
136 | 1.03k | mp_dbg(log, "ASS library version: 0x%x (runtime 0x%x)\n", |
137 | 1.03k | (unsigned)LIBASS_VERSION, ass_library_version()); |
138 | 1.03k | ASS_Library *priv = ass_library_init(); |
139 | 1.03k | if (!priv) |
140 | 0 | abort(); |
141 | 1.03k | ass_set_message_cb(priv, message_callback, log); |
142 | 1.03k | if (path) |
143 | 0 | ass_set_fonts_dir(priv, path); |
144 | 1.03k | talloc_free(path); |
145 | 1.03k | return priv; |
146 | 1.03k | } |
147 | | |
148 | | void mp_ass_flush_old_events(ASS_Track *track, long long ts) |
149 | 0 | { |
150 | 0 | int n = 0; |
151 | 0 | for (; n < track->n_events; n++) { |
152 | 0 | if ((track->events[n].Start + track->events[n].Duration) >= ts) |
153 | 0 | break; |
154 | 0 | ass_free_event(track, n); |
155 | 0 | track->n_events--; |
156 | 0 | } |
157 | 0 | for (int i = 0; n > 0 && i < track->n_events; i++) { |
158 | 0 | track->events[i] = track->events[i+n]; |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | static void draw_ass_rgba(unsigned char *src, int src_w, int src_h, |
163 | | int src_stride, unsigned char *dst, size_t dst_stride, |
164 | | int dst_x, int dst_y, uint32_t color) |
165 | 0 | { |
166 | 0 | const unsigned int r = (color >> 24) & 0xff; |
167 | 0 | const unsigned int g = (color >> 16) & 0xff; |
168 | 0 | const unsigned int b = (color >> 8) & 0xff; |
169 | 0 | const unsigned int a = 0xff - (color & 0xff); |
170 | |
|
171 | 0 | dst += dst_y * dst_stride + dst_x * 4; |
172 | |
|
173 | 0 | for (int y = 0; y < src_h; y++, dst += dst_stride, src += src_stride) { |
174 | 0 | uint32_t *dstrow = (uint32_t *) dst; |
175 | 0 | for (int x = 0; x < src_w; x++) { |
176 | 0 | const unsigned int v = src[x]; |
177 | 0 | int rr = (r * a * v); |
178 | 0 | int gg = (g * a * v); |
179 | 0 | int bb = (b * a * v); |
180 | 0 | int aa = a * v; |
181 | 0 | uint32_t dstpix = dstrow[x]; |
182 | 0 | unsigned int dstb = dstpix & 0xFF; |
183 | 0 | unsigned int dstg = (dstpix >> 8) & 0xFF; |
184 | 0 | unsigned int dstr = (dstpix >> 16) & 0xFF; |
185 | 0 | unsigned int dsta = (dstpix >> 24) & 0xFF; |
186 | 0 | dstb = (bb + dstb * (255 * 255 - aa)) / (255 * 255); |
187 | 0 | dstg = (gg + dstg * (255 * 255 - aa)) / (255 * 255); |
188 | 0 | dstr = (rr + dstr * (255 * 255 - aa)) / (255 * 255); |
189 | 0 | dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255); |
190 | 0 | dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); |
191 | 0 | } |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | | struct mp_ass_packer { |
196 | | struct sub_bitmap *cached_parts; // only for the array memory |
197 | | struct mp_image *cached_img; |
198 | | struct sub_bitmaps cached_subs; |
199 | | bool cached_subs_valid; |
200 | | struct sub_bitmap rgba_imgs[MP_SUB_BB_LIST_MAX]; |
201 | | struct bitmap_packer *packer; |
202 | | }; |
203 | | |
204 | | // Free with talloc_free(). |
205 | | struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent) |
206 | 1.03k | { |
207 | 1.03k | struct mp_ass_packer *p = talloc_zero(ta_parent, struct mp_ass_packer); |
208 | 1.03k | p->packer = talloc_zero(p, struct bitmap_packer); |
209 | 1.03k | p->packer->padding = 1; // assume bilinear sampling |
210 | 1.03k | return p; |
211 | 1.03k | } |
212 | | |
213 | | static bool pack(struct mp_ass_packer *p, struct sub_bitmaps *res, int imgfmt) |
214 | 76 | { |
215 | 76 | packer_set_size(p->packer, res->num_parts); |
216 | | |
217 | 457 | for (int n = 0; n < res->num_parts; n++) |
218 | 381 | p->packer->in[n] = (struct pos){res->parts[n].w, res->parts[n].h}; |
219 | | |
220 | 76 | if (p->packer->count == 0 || packer_pack(p->packer) < 0) |
221 | 62 | return false; |
222 | | |
223 | 14 | struct pos bb[2]; |
224 | 14 | packer_get_bb(p->packer, bb); |
225 | | |
226 | 14 | res->packed_w = bb[1].x; |
227 | 14 | res->packed_h = bb[1].y; |
228 | | |
229 | 14 | if (!p->cached_img || p->cached_img->w < res->packed_w || |
230 | 3 | p->cached_img->h < res->packed_h || |
231 | 3 | p->cached_img->imgfmt != imgfmt) |
232 | 11 | { |
233 | 11 | talloc_free(p->cached_img); |
234 | 11 | p->cached_img = mp_image_alloc(imgfmt, p->packer->w, p->packer->h); |
235 | 11 | if (!p->cached_img) { |
236 | 0 | packer_reset(p->packer); |
237 | 0 | return false; |
238 | 0 | } |
239 | 11 | talloc_steal(p, p->cached_img); |
240 | 11 | } |
241 | | |
242 | 14 | if (!mp_image_make_writeable(p->cached_img)) { |
243 | 0 | packer_reset(p->packer); |
244 | 0 | return false; |
245 | 0 | } |
246 | | |
247 | 14 | res->packed = p->cached_img; |
248 | | |
249 | 395 | for (int n = 0; n < res->num_parts; n++) { |
250 | 381 | struct sub_bitmap *b = &res->parts[n]; |
251 | 381 | struct pos pos = p->packer->result[n]; |
252 | | |
253 | 381 | b->src_x = pos.x; |
254 | 381 | b->src_y = pos.y; |
255 | 381 | } |
256 | | |
257 | 14 | return true; |
258 | 14 | } |
259 | | |
260 | | static void fill_padding_1(uint8_t *base, int w, int h, int stride, int padding) |
261 | 381 | { |
262 | 6.56k | for (int row = 0; row < h; ++row) { |
263 | 6.18k | uint8_t *row_ptr = base + row * stride; |
264 | 6.18k | uint8_t left_pixel = row_ptr[0]; |
265 | 6.18k | uint8_t right_pixel = row_ptr[w - 1]; |
266 | | |
267 | 12.3k | for (int i = 1; i <= padding; ++i) |
268 | 6.18k | row_ptr[-i] = left_pixel; |
269 | | |
270 | 12.3k | for (int i = 0; i < padding; ++i) |
271 | 6.18k | row_ptr[w + i] = right_pixel; |
272 | 6.18k | } |
273 | | |
274 | 381 | int row_bytes = (w + 2 * padding); |
275 | 381 | uint8_t *top_row = base - padding; |
276 | 762 | for (int i = 1; i <= padding; ++i) |
277 | 381 | memcpy(base - i * stride - padding, top_row, row_bytes); |
278 | | |
279 | 381 | uint8_t *last_row = base + (h - 1) * stride - padding; |
280 | 762 | for (int i = 0; i < padding; ++i) |
281 | 381 | memcpy(base + (h + i) * stride - padding, last_row, row_bytes); |
282 | 381 | } |
283 | | |
284 | | static void fill_padding_4(uint8_t *base, int w, int h, int stride, int padding) |
285 | 0 | { |
286 | 0 | for (int row = 0; row < h; ++row) { |
287 | 0 | uint32_t *row_ptr = (uint32_t *)(base + row * stride); |
288 | 0 | uint32_t left_pixel = row_ptr[0]; |
289 | 0 | uint32_t right_pixel = row_ptr[w - 1]; |
290 | |
|
291 | 0 | for (int i = 1; i <= padding; ++i) |
292 | 0 | row_ptr[-i] = left_pixel; |
293 | |
|
294 | 0 | for (int i = 0; i < padding; ++i) |
295 | 0 | row_ptr[w + i] = right_pixel; |
296 | 0 | } |
297 | |
|
298 | 0 | int row_bytes = (w + 2 * padding) * 4; |
299 | 0 | uint8_t *top_row = base - padding * 4; |
300 | 0 | for (int i = 1; i <= padding; ++i) |
301 | 0 | memcpy(base - i * stride - padding * 4, top_row, row_bytes); |
302 | |
|
303 | 0 | uint8_t *last_row = base + (h - 1) * stride - padding * 4; |
304 | 0 | for (int i = 0; i < padding; ++i) |
305 | 0 | memcpy(base + (h + i) * stride - padding * 4, last_row, row_bytes); |
306 | 0 | } |
307 | | |
308 | | static bool pack_libass(struct mp_ass_packer *p, struct sub_bitmaps *res) |
309 | 76 | { |
310 | 76 | if (!pack(p, res, IMGFMT_Y8)) |
311 | 62 | return false; |
312 | | |
313 | 14 | int padding = p->packer->padding; |
314 | 14 | uint8_t *base = res->packed->planes[0]; |
315 | 14 | int stride = res->packed->stride[0]; |
316 | | |
317 | 395 | for (int n = 0; n < res->num_parts; n++) { |
318 | 381 | struct sub_bitmap *b = &res->parts[n]; |
319 | 381 | void *pdata = base + b->src_y * stride + b->src_x; |
320 | 381 | memcpy_pic(pdata, b->bitmap, b->w, b->h, stride, b->stride); |
321 | 381 | fill_padding_1(pdata, b->w, b->h, stride, padding); |
322 | | |
323 | 381 | b->bitmap = pdata; |
324 | 381 | b->stride = stride; |
325 | 381 | } |
326 | | |
327 | 14 | return true; |
328 | 76 | } |
329 | | |
330 | | static bool pack_rgba(struct mp_ass_packer *p, struct sub_bitmaps *res) |
331 | 0 | { |
332 | 0 | struct mp_rect bb_list[MP_SUB_BB_LIST_MAX]; |
333 | 0 | int num_bb = mp_get_sub_bb_list(res, bb_list, MP_SUB_BB_LIST_MAX); |
334 | |
|
335 | 0 | struct sub_bitmaps imgs = { |
336 | 0 | .change_id = res->change_id, |
337 | 0 | .format = SUBBITMAP_BGRA, |
338 | 0 | .parts = p->rgba_imgs, |
339 | 0 | .num_parts = num_bb, |
340 | 0 | }; |
341 | |
|
342 | 0 | for (int n = 0; n < imgs.num_parts; n++) { |
343 | 0 | imgs.parts[n].w = bb_list[n].x1 - bb_list[n].x0; |
344 | 0 | imgs.parts[n].h = bb_list[n].y1 - bb_list[n].y0; |
345 | 0 | } |
346 | |
|
347 | 0 | if (!pack(p, &imgs, IMGFMT_BGRA)) |
348 | 0 | return false; |
349 | | |
350 | 0 | int padding = p->packer->padding; |
351 | 0 | uint8_t *base = imgs.packed->planes[0]; |
352 | 0 | int stride = imgs.packed->stride[0]; |
353 | |
|
354 | 0 | for (int n = 0; n < num_bb; n++) { |
355 | 0 | struct mp_rect bb = bb_list[n]; |
356 | 0 | struct sub_bitmap *b = &imgs.parts[n]; |
357 | |
|
358 | 0 | b->x = bb.x0; |
359 | 0 | b->y = bb.y0; |
360 | 0 | b->w = b->dw = mp_rect_w(bb); |
361 | 0 | b->h = b->dh = mp_rect_h(bb); |
362 | 0 | b->stride = stride; |
363 | 0 | b->bitmap = base + b->stride * b->src_y + b->src_x * 4; |
364 | 0 | memset_pic(b->bitmap, 0, b->w * 4, b->h, b->stride); |
365 | |
|
366 | 0 | for (int i = 0; i < res->num_parts; i++) { |
367 | 0 | struct sub_bitmap *s = &res->parts[i]; |
368 | | |
369 | | // Assume mp_get_sub_bb_list() never splits sub bitmaps |
370 | | // So we don't clip/adjust the size of the sub bitmap |
371 | 0 | if (s->x >= b->x + b->w || s->x + s->w <= b->x || |
372 | 0 | s->y >= b->y + b->h || s->y + s->h <= b->y) |
373 | 0 | continue; |
374 | | |
375 | 0 | draw_ass_rgba(s->bitmap, s->w, s->h, s->stride, |
376 | 0 | b->bitmap, b->stride, |
377 | 0 | s->x - b->x, s->y - b->y, |
378 | 0 | s->libass.color); |
379 | 0 | } |
380 | 0 | fill_padding_4(b->bitmap, b->w, b->h, b->stride, padding); |
381 | 0 | } |
382 | |
|
383 | 0 | *res = imgs; |
384 | 0 | return true; |
385 | 0 | } |
386 | | |
387 | | // Pack the contents of image_lists[0] to image_lists[num_image_lists-1] into |
388 | | // a single image, and make *out point to it. *out is completely overwritten. |
389 | | // If libass reported any change, image_lists_changed must be set (it then |
390 | | // repacks all images). preferred_osd_format can be set to a desired |
391 | | // sub_bitmap_format. Currently, only SUBBITMAP_LIBASS is supported. |
392 | | void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, |
393 | | int num_image_lists, bool image_lists_changed, bool video_color_space, |
394 | | int preferred_osd_format, struct sub_bitmaps *out) |
395 | 84 | { |
396 | 84 | int format = preferred_osd_format == SUBBITMAP_BGRA ? SUBBITMAP_BGRA |
397 | 84 | : SUBBITMAP_LIBASS; |
398 | | |
399 | 84 | if (p->cached_subs_valid && !image_lists_changed && |
400 | 8 | p->cached_subs.format == format) |
401 | 8 | { |
402 | 8 | *out = p->cached_subs; |
403 | 8 | return; |
404 | 8 | } |
405 | | |
406 | 76 | *out = (struct sub_bitmaps){.change_id = 1}; |
407 | 76 | p->cached_subs_valid = false; |
408 | | |
409 | 76 | struct sub_bitmaps res = { |
410 | 76 | .change_id = image_lists_changed, |
411 | 76 | .format = SUBBITMAP_LIBASS, |
412 | 76 | .parts = p->cached_parts, |
413 | 76 | .video_color_space = video_color_space, |
414 | 76 | }; |
415 | | |
416 | 152 | for (int n = 0; n < num_image_lists; n++) { |
417 | 457 | for (struct ass_image *img = image_lists[n]; img; img = img->next) { |
418 | 381 | if (img->w == 0 || img->h == 0) |
419 | 0 | continue; |
420 | 381 | MP_TARRAY_GROW(p, p->cached_parts, res.num_parts); |
421 | 381 | res.parts = p->cached_parts; |
422 | 381 | struct sub_bitmap *b = &res.parts[res.num_parts]; |
423 | 381 | b->bitmap = img->bitmap; |
424 | 381 | b->stride = img->stride; |
425 | 381 | b->libass.color = img->color; |
426 | 381 | b->dw = b->w = img->w; |
427 | 381 | b->dh = b->h = img->h; |
428 | 381 | b->x = img->dst_x; |
429 | 381 | b->y = img->dst_y; |
430 | 381 | res.num_parts++; |
431 | 381 | } |
432 | 76 | } |
433 | | |
434 | 76 | bool r = false; |
435 | 76 | if (format == SUBBITMAP_BGRA) { |
436 | 0 | r = pack_rgba(p, &res); |
437 | 76 | } else { |
438 | 76 | r = pack_libass(p, &res); |
439 | 76 | } |
440 | | |
441 | 76 | if (!r) |
442 | 62 | return; |
443 | | |
444 | 14 | *out = res; |
445 | 14 | p->cached_subs = res; |
446 | 14 | p->cached_subs.change_id = 0; |
447 | 14 | p->cached_subs_valid = true; |
448 | 14 | } |
449 | | |
450 | | // Set *out_rc to [x0, y0, x1, y1] of the graphical bounding box in script |
451 | | // coordinates. |
452 | | // Set it to [inf, inf, -inf, -inf] if empty. |
453 | | void mp_ass_get_bb(ASS_Image *image_list, ASS_Track *track, |
454 | | struct mp_osd_res *res, double *out_rc) |
455 | 0 | { |
456 | 0 | double rc[4] = {INFINITY, INFINITY, -INFINITY, -INFINITY}; |
457 | |
|
458 | 0 | for (ASS_Image *img = image_list; img; img = img->next) { |
459 | 0 | if (img->w == 0 || img->h == 0) |
460 | 0 | continue; |
461 | 0 | rc[0] = MPMIN(rc[0], img->dst_x); |
462 | 0 | rc[1] = MPMIN(rc[1], img->dst_y); |
463 | 0 | rc[2] = MPMAX(rc[2], img->dst_x + img->w); |
464 | 0 | rc[3] = MPMAX(rc[3], img->dst_y + img->h); |
465 | 0 | } |
466 | |
|
467 | 0 | double scale = track->PlayResY / (double)MPMAX(res->h, 1); |
468 | 0 | if (scale > 0) { |
469 | 0 | for (int i = 0; i < 4; i++) |
470 | 0 | out_rc[i] = rc[i] * scale; |
471 | 0 | } |
472 | 0 | } |