/src/libass/libass/ass_render.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> |
3 | | * |
4 | | * This file is part of libass. |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | #include "ass_compat.h" |
21 | | |
22 | | #include <assert.h> |
23 | | #include <math.h> |
24 | | #include <string.h> |
25 | | #include <stdbool.h> |
26 | | |
27 | | #ifdef CONFIG_UNIBREAK |
28 | | #include <linebreak.h> |
29 | | #endif |
30 | | |
31 | | #include "ass.h" |
32 | | #include "ass_outline.h" |
33 | | #include "ass_render.h" |
34 | | #include "ass_parse.h" |
35 | | #include "ass_priv.h" |
36 | | #include "ass_shaper.h" |
37 | | |
38 | 47.7k | #define MAX_GLYPHS_INITIAL 1024 |
39 | 23.8k | #define MAX_LINES_INITIAL 64 |
40 | 23.8k | #define MAX_BITMAPS_INITIAL 16 |
41 | 167k | #define MAX_SUB_BITMAPS_INITIAL 64 |
42 | 4.85k | #define SUBPIXEL_MASK 63 |
43 | 1.49M | #define STROKER_PRECISION 16 // stroker error in integer units, unrelated to final accuracy |
44 | 11.9k | #define RASTERIZER_PRECISION 16 // rasterizer spline approximation error in 1/64 pixel units |
45 | 2.86M | #define POSITION_PRECISION 8.0 // rough estimate of transform error in 1/64 pixel units |
46 | | #define MAX_PERSP_SCALE 16.0 |
47 | 5.96M | #define SUBPIXEL_ORDER 3 // ~ log2(64 / POSITION_PRECISION) |
48 | 345k | #define BLUR_PRECISION (1.0 / 256) // blur error as fraction of full input range |
49 | | |
50 | | |
51 | | static bool text_info_init(TextInfo* text_info) |
52 | 11.9k | { |
53 | 11.9k | text_info->max_bitmaps = MAX_BITMAPS_INITIAL; |
54 | 11.9k | text_info->max_glyphs = MAX_GLYPHS_INITIAL; |
55 | 11.9k | text_info->max_lines = MAX_LINES_INITIAL; |
56 | 11.9k | text_info->n_bitmaps = 0; |
57 | 11.9k | text_info->combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo)); |
58 | 11.9k | text_info->glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); |
59 | 11.9k | text_info->event_text = calloc(MAX_GLYPHS_INITIAL, sizeof(FriBidiChar)); |
60 | 11.9k | text_info->breaks = malloc(MAX_GLYPHS_INITIAL); |
61 | 11.9k | text_info->lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); |
62 | | |
63 | 11.9k | if (!text_info->combined_bitmaps || !text_info->glyphs || !text_info->lines || |
64 | 11.9k | !text_info->breaks || !text_info->event_text) |
65 | 0 | return false; |
66 | | |
67 | 11.9k | return true; |
68 | 11.9k | } |
69 | | |
70 | | static void text_info_done(TextInfo* text_info) |
71 | 11.9k | { |
72 | 11.9k | free(text_info->glyphs); |
73 | 11.9k | free(text_info->event_text); |
74 | 11.9k | free(text_info->breaks); |
75 | 11.9k | free(text_info->lines); |
76 | 11.9k | free(text_info->combined_bitmaps); |
77 | 11.9k | } |
78 | | |
79 | | static bool render_context_init(RenderContext *state, ASS_Renderer *priv) |
80 | 11.9k | { |
81 | 11.9k | state->renderer = priv; |
82 | | |
83 | 11.9k | if (!text_info_init(&state->text_info)) |
84 | 0 | return false; |
85 | | |
86 | 11.9k | if (!(state->shaper = ass_shaper_new(priv->cache.metrics_cache, priv->cache.face_size_metrics_cache))) |
87 | 0 | return false; |
88 | | |
89 | 11.9k | return ass_rasterizer_init(&priv->engine, &state->rasterizer, RASTERIZER_PRECISION); |
90 | 11.9k | } |
91 | | |
92 | | static void render_context_done(RenderContext *state) |
93 | 11.9k | { |
94 | 11.9k | ass_rasterizer_done(&state->rasterizer); |
95 | | |
96 | 11.9k | if (state->shaper) |
97 | 11.9k | ass_shaper_free(state->shaper); |
98 | | |
99 | 11.9k | text_info_done(&state->text_info); |
100 | 11.9k | } |
101 | | |
102 | | ASS_Renderer *ass_renderer_init(ASS_Library *library) |
103 | 11.9k | { |
104 | 11.9k | int error; |
105 | 11.9k | FT_Library ft; |
106 | 11.9k | ASS_Renderer *priv = 0; |
107 | 11.9k | int vmajor, vminor, vpatch; |
108 | | |
109 | 11.9k | ass_msg(library, MSGL_INFO, "libass API version: 0x%X", LIBASS_VERSION); |
110 | 11.9k | ass_msg(library, MSGL_INFO, "libass source: %s", CONFIG_SOURCEVERSION); |
111 | | |
112 | 11.9k | error = FT_Init_FreeType(&ft); |
113 | 11.9k | if (error) { |
114 | 0 | ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType"); |
115 | 0 | goto fail; |
116 | 0 | } |
117 | | |
118 | 11.9k | FT_Library_Version(ft, &vmajor, &vminor, &vpatch); |
119 | 11.9k | ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d", |
120 | 11.9k | vmajor, vminor, vpatch); |
121 | | |
122 | 11.9k | priv = calloc(1, sizeof(ASS_Renderer)); |
123 | 11.9k | if (!priv) { |
124 | 0 | FT_Done_FreeType(ft); |
125 | 0 | goto fail; |
126 | 0 | } |
127 | | |
128 | 11.9k | priv->library = library; |
129 | 11.9k | priv->ftlibrary = ft; |
130 | | // images_root and related stuff is zero-filled in calloc |
131 | | |
132 | 11.9k | unsigned flags = ASS_CPU_FLAG_ALL; |
133 | | #if CONFIG_LARGE_TILES |
134 | | flags |= ASS_FLAG_LARGE_TILES; |
135 | | #endif |
136 | 11.9k | priv->engine = ass_bitmap_engine_init(flags); |
137 | | |
138 | 11.9k | priv->cache.font_cache = ass_font_cache_create(); |
139 | 11.9k | priv->cache.bitmap_cache = ass_bitmap_cache_create(); |
140 | 11.9k | priv->cache.composite_cache = ass_composite_cache_create(); |
141 | 11.9k | priv->cache.outline_cache = ass_outline_cache_create(); |
142 | 11.9k | priv->cache.face_size_metrics_cache = ass_face_size_metrics_cache_create(); |
143 | 11.9k | priv->cache.metrics_cache = ass_glyph_metrics_cache_create(); |
144 | 11.9k | if (!priv->cache.font_cache || !priv->cache.bitmap_cache || |
145 | 11.9k | !priv->cache.composite_cache || !priv->cache.outline_cache || |
146 | 11.9k | !priv->cache.face_size_metrics_cache || !priv->cache.metrics_cache) |
147 | 0 | goto fail; |
148 | | |
149 | 11.9k | priv->cache.glyph_max = GLYPH_CACHE_MAX; |
150 | 11.9k | priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; |
151 | 11.9k | priv->cache.composite_max_size = COMPOSITE_CACHE_MAX_SIZE; |
152 | | |
153 | 11.9k | if (!render_context_init(&priv->state, priv)) |
154 | 0 | goto fail; |
155 | | |
156 | 11.9k | priv->user_override_style.Name = "OverrideStyle"; // name insignificant |
157 | | |
158 | 11.9k | priv->settings.font_size_coeff = 1.; |
159 | 11.9k | priv->settings.selective_style_overrides = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; |
160 | | |
161 | 11.9k | ass_shaper_info(library); |
162 | 11.9k | priv->settings.shaper = ASS_SHAPING_COMPLEX; |
163 | | |
164 | 11.9k | ass_msg(library, MSGL_V, "Initialized"); |
165 | | |
166 | 11.9k | return priv; |
167 | | |
168 | 0 | fail: |
169 | 0 | ass_msg(library, MSGL_ERR, "Initialization failed"); |
170 | 0 | ass_renderer_done(priv); |
171 | |
|
172 | 0 | return NULL; |
173 | 11.9k | } |
174 | | |
175 | | void ass_renderer_done(ASS_Renderer *render_priv) |
176 | 11.9k | { |
177 | 11.9k | if (!render_priv) |
178 | 0 | return; |
179 | | |
180 | 11.9k | ass_frame_unref(render_priv->images_root); |
181 | 11.9k | ass_frame_unref(render_priv->prev_images_root); |
182 | | |
183 | 11.9k | ass_cache_done(render_priv->cache.composite_cache); |
184 | 11.9k | ass_cache_done(render_priv->cache.bitmap_cache); |
185 | 11.9k | ass_cache_done(render_priv->cache.outline_cache); |
186 | 11.9k | ass_cache_done(render_priv->cache.face_size_metrics_cache); |
187 | 11.9k | ass_cache_done(render_priv->cache.metrics_cache); |
188 | 11.9k | ass_cache_done(render_priv->cache.font_cache); |
189 | | |
190 | 11.9k | if (render_priv->fontselect) |
191 | 11.9k | ass_fontselect_free(render_priv->fontselect); |
192 | 11.9k | if (render_priv->ftlibrary) |
193 | 11.9k | FT_Done_FreeType(render_priv->ftlibrary); |
194 | 11.9k | free(render_priv->eimg); |
195 | | |
196 | 11.9k | render_context_done(&render_priv->state); |
197 | | |
198 | 11.9k | free(render_priv->settings.default_font); |
199 | 11.9k | free(render_priv->settings.default_family); |
200 | | |
201 | 11.9k | free(render_priv->user_override_style.FontName); |
202 | | |
203 | 11.9k | free(render_priv); |
204 | 11.9k | } |
205 | | |
206 | | /** |
207 | | * \brief Create a new ASS_Image |
208 | | * Parameters are the same as ASS_Image fields. |
209 | | */ |
210 | | static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w, |
211 | | int bitmap_h, int stride, int dst_x, |
212 | | int dst_y, uint32_t color, |
213 | | CompositeHashValue *source) |
214 | 227k | { |
215 | 227k | ASS_ImagePriv *img = malloc(sizeof(ASS_ImagePriv)); |
216 | 227k | if (!img) { |
217 | 0 | if (!source) |
218 | 0 | ass_aligned_free(bitmap); |
219 | 0 | return NULL; |
220 | 0 | } |
221 | | |
222 | 227k | img->result.w = bitmap_w; |
223 | 227k | img->result.h = bitmap_h; |
224 | 227k | img->result.stride = stride; |
225 | 227k | img->result.bitmap = bitmap; |
226 | 227k | img->result.color = color; |
227 | 227k | img->result.dst_x = dst_x; |
228 | 227k | img->result.dst_y = dst_y; |
229 | | |
230 | 227k | img->source = source; |
231 | 227k | ass_cache_inc_ref(source); |
232 | 227k | img->buffer = source ? NULL : bitmap; |
233 | 227k | img->ref_count = 0; |
234 | | |
235 | 227k | return &img->result; |
236 | 227k | } |
237 | | |
238 | | /** |
239 | | * \brief Mapping between script and screen coordinates |
240 | | */ |
241 | | static double x2scr_pos(ASS_Renderer *render_priv, double x) |
242 | 258k | { |
243 | 258k | return x * render_priv->frame_content_width / render_priv->par_scale_x / render_priv->track->PlayResX + |
244 | 258k | render_priv->settings.left_margin; |
245 | 258k | } |
246 | | static double x2scr_left(RenderContext *state, double x) |
247 | 169k | { |
248 | 169k | ASS_Renderer *render_priv = state->renderer; |
249 | 169k | if (state->explicit || !render_priv->settings.use_margins) |
250 | 169k | return x2scr_pos(render_priv, x); |
251 | 0 | return x * render_priv->fit_width / render_priv->par_scale_x / |
252 | 0 | render_priv->track->PlayResX; |
253 | 169k | } |
254 | | static double x2scr_right(RenderContext *state, double x) |
255 | 85.9k | { |
256 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
257 | 85.9k | if (state->explicit || !render_priv->settings.use_margins) |
258 | 85.9k | return x2scr_pos(render_priv, x); |
259 | 0 | return x * render_priv->fit_width / render_priv->par_scale_x / |
260 | 0 | render_priv->track->PlayResX + |
261 | 0 | (render_priv->width - render_priv->fit_width); |
262 | 85.9k | } |
263 | | static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x) |
264 | 172k | { |
265 | 172k | return x * render_priv->frame_content_width / render_priv->track->PlayResX + |
266 | 172k | render_priv->settings.left_margin; |
267 | 172k | } |
268 | | /** |
269 | | * \brief Mapping between script and screen coordinates |
270 | | */ |
271 | | static double y2scr_pos(ASS_Renderer *render_priv, double y) |
272 | 341k | { |
273 | 341k | return y * render_priv->frame_content_height / render_priv->track->PlayResY + |
274 | 341k | render_priv->settings.top_margin; |
275 | 341k | } |
276 | | static double y2scr(RenderContext *state, double y) |
277 | 710 | { |
278 | 710 | ASS_Renderer *render_priv = state->renderer; |
279 | 710 | if (state->explicit || !render_priv->settings.use_margins) |
280 | 710 | return y2scr_pos(render_priv, y); |
281 | 0 | return y * render_priv->fit_height / |
282 | 0 | render_priv->track->PlayResY + |
283 | 0 | (render_priv->height - render_priv->fit_height) * 0.5; |
284 | 710 | } |
285 | | |
286 | | // the same for toptitles |
287 | | static double y2scr_top(RenderContext *state, double y) |
288 | 83.5k | { |
289 | 83.5k | ASS_Renderer *render_priv = state->renderer; |
290 | 83.5k | if (state->explicit || !render_priv->settings.use_margins) |
291 | 83.5k | return y2scr_pos(render_priv, y); |
292 | 0 | return y * render_priv->fit_height / |
293 | 0 | render_priv->track->PlayResY; |
294 | 83.5k | } |
295 | | // the same for subtitles |
296 | | static double y2scr_sub(RenderContext *state, double y) |
297 | 83.1k | { |
298 | 83.1k | ASS_Renderer *render_priv = state->renderer; |
299 | 83.1k | if (state->explicit || !render_priv->settings.use_margins) |
300 | 83.1k | return y2scr_pos(render_priv, y); |
301 | 0 | return y * render_priv->fit_height / |
302 | 0 | render_priv->track->PlayResY + |
303 | 0 | (render_priv->height - render_priv->fit_height); |
304 | 83.1k | } |
305 | | |
306 | | /* |
307 | | * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping |
308 | | * |
309 | | * Inverse clipping with the following strategy: |
310 | | * - find rectangle from (x0, y0) to (cx0, y1) |
311 | | * - find rectangle from (cx0, y0) to (cx1, cy0) |
312 | | * - find rectangle from (cx0, cy1) to (cx1, y1) |
313 | | * - find rectangle from (cx1, y0) to (x1, y1) |
314 | | * These rectangles can be invalid and in this case are discarded. |
315 | | * Afterwards, they are clipped against the screen coordinates. |
316 | | * In an additional pass, the rectangles need to be split up left/right for |
317 | | * karaoke effects. This can result in a lot of bitmaps (6 to be exact). |
318 | | */ |
319 | | static ASS_Image **render_glyph_i(RenderContext *state, |
320 | | Bitmap *bm, int dst_x, int dst_y, |
321 | | uint32_t color, uint32_t color2, int brk, |
322 | | ASS_Image **tail, unsigned type, |
323 | | CompositeHashValue *source) |
324 | 63 | { |
325 | 63 | ASS_Renderer *render_priv = state->renderer; |
326 | 63 | int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy; |
327 | 63 | Rect r[4]; |
328 | 63 | ASS_Image *img; |
329 | | |
330 | 63 | dst_x += bm->left; |
331 | 63 | dst_y += bm->top; |
332 | 63 | brk -= dst_x; |
333 | | |
334 | | // we still need to clip against screen boundaries |
335 | 63 | zx = x2scr_pos_scaled(render_priv, 0); |
336 | 63 | zy = y2scr_pos(render_priv, 0); |
337 | 63 | sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX); |
338 | 63 | sy = y2scr_pos(render_priv, render_priv->track->PlayResY); |
339 | | |
340 | 63 | x0 = 0; |
341 | 63 | y0 = 0; |
342 | 63 | x1 = bm->w; |
343 | 63 | y1 = bm->h; |
344 | 63 | cx0 = state->clip_x0 - dst_x; |
345 | 63 | cy0 = state->clip_y0 - dst_y; |
346 | 63 | cx1 = state->clip_x1 - dst_x; |
347 | 63 | cy1 = state->clip_y1 - dst_y; |
348 | | |
349 | | // calculate rectangles and discard invalid ones while we're at it. |
350 | 63 | i = 0; |
351 | 63 | r[i].x0 = x0; |
352 | 63 | r[i].y0 = y0; |
353 | 63 | r[i].x1 = (cx0 > x1) ? x1 : cx0; |
354 | 63 | r[i].y1 = y1; |
355 | 63 | if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; |
356 | 63 | r[i].x0 = (cx0 < 0) ? x0 : cx0; |
357 | 63 | r[i].y0 = y0; |
358 | 63 | r[i].x1 = (cx1 > x1) ? x1 : cx1; |
359 | 63 | r[i].y1 = (cy0 > y1) ? y1 : cy0; |
360 | 63 | if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; |
361 | 63 | r[i].x0 = (cx0 < 0) ? x0 : cx0; |
362 | 63 | r[i].y0 = (cy1 < 0) ? y0 : cy1; |
363 | 63 | r[i].x1 = (cx1 > x1) ? x1 : cx1; |
364 | 63 | r[i].y1 = y1; |
365 | 63 | if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; |
366 | 63 | r[i].x0 = (cx1 < 0) ? x0 : cx1; |
367 | 63 | r[i].y0 = y0; |
368 | 63 | r[i].x1 = x1; |
369 | 63 | r[i].y1 = y1; |
370 | 63 | if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; |
371 | | |
372 | | // clip each rectangle to screen coordinates |
373 | 126 | for (j = 0; j < i; j++) { |
374 | 63 | r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0; |
375 | 63 | r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0; |
376 | 63 | r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1; |
377 | 63 | r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1; |
378 | 63 | } |
379 | | |
380 | | // draw the rectangles |
381 | 126 | for (j = 0; j < i; j++) { |
382 | 63 | int lbrk = brk; |
383 | | // kick out rectangles that are invalid now |
384 | 63 | if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0) |
385 | 0 | continue; |
386 | | // split up into left and right for karaoke, if needed |
387 | 63 | if (lbrk > r[j].x0) { |
388 | 63 | if (lbrk > r[j].x1) lbrk = r[j].x1; |
389 | 63 | img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0, |
390 | 63 | lbrk - r[j].x0, r[j].y1 - r[j].y0, bm->stride, |
391 | 63 | dst_x + r[j].x0, dst_y + r[j].y0, color, source); |
392 | 63 | if (!img) break; |
393 | 63 | img->type = type; |
394 | 63 | *tail = img; |
395 | 63 | tail = &img->next; |
396 | 63 | } |
397 | 63 | if (lbrk < r[j].x1) { |
398 | 0 | if (lbrk < r[j].x0) lbrk = r[j].x0; |
399 | 0 | img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk, |
400 | 0 | r[j].x1 - lbrk, r[j].y1 - r[j].y0, bm->stride, |
401 | 0 | dst_x + lbrk, dst_y + r[j].y0, color2, source); |
402 | 0 | if (!img) break; |
403 | 0 | img->type = type; |
404 | 0 | *tail = img; |
405 | 0 | tail = &img->next; |
406 | 0 | } |
407 | 63 | } |
408 | | |
409 | 63 | return tail; |
410 | 63 | } |
411 | | |
412 | | /** |
413 | | * \brief convert bitmap glyph into ASS_Image struct(s) |
414 | | * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY |
415 | | * \param dst_x bitmap x coordinate in video frame |
416 | | * \param dst_y bitmap y coordinate in video frame |
417 | | * \param color first color, RGBA |
418 | | * \param color2 second color, RGBA |
419 | | * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right |
420 | | * \param tail pointer to the last image's next field, head of the generated list should be stored here |
421 | | * \return pointer to the new list tail |
422 | | * Performs clipping. Uses my_draw_bitmap for actual bitmap conversion. |
423 | | */ |
424 | | static ASS_Image ** |
425 | | render_glyph(RenderContext *state, Bitmap *bm, int dst_x, int dst_y, |
426 | | uint32_t color, uint32_t color2, int brk, ASS_Image **tail, |
427 | | unsigned type, CompositeHashValue *source) |
428 | 233k | { |
429 | | // Inverse clipping in use? |
430 | 233k | if (state->clip_mode) |
431 | 63 | return render_glyph_i(state, bm, dst_x, dst_y, color, color2, |
432 | 63 | brk, tail, type, source); |
433 | | |
434 | | // brk is absolute |
435 | | // color = color left of brk |
436 | | // color2 = color right of brk |
437 | 233k | int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap |
438 | 233k | int clip_x0, clip_y0, clip_x1, clip_y1; |
439 | 233k | int tmp; |
440 | 233k | ASS_Image *img; |
441 | 233k | ASS_Renderer *render_priv = state->renderer; |
442 | | |
443 | 233k | dst_x += bm->left; |
444 | 233k | dst_y += bm->top; |
445 | 233k | brk -= dst_x; |
446 | | |
447 | | // clipping |
448 | 233k | clip_x0 = FFMINMAX(state->clip_x0, 0, render_priv->width); |
449 | 233k | clip_y0 = FFMINMAX(state->clip_y0, 0, render_priv->height); |
450 | 233k | clip_x1 = FFMINMAX(state->clip_x1, 0, render_priv->width); |
451 | 233k | clip_y1 = FFMINMAX(state->clip_y1, 0, render_priv->height); |
452 | 233k | b_x0 = 0; |
453 | 233k | b_y0 = 0; |
454 | 233k | b_x1 = bm->w; |
455 | 233k | b_y1 = bm->h; |
456 | | |
457 | 233k | tmp = dst_x - clip_x0; |
458 | 233k | if (tmp < 0) |
459 | 4.95k | b_x0 = -tmp; |
460 | 233k | tmp = dst_y - clip_y0; |
461 | 233k | if (tmp < 0) |
462 | 4.40k | b_y0 = -tmp; |
463 | 233k | tmp = clip_x1 - dst_x - bm->w; |
464 | 233k | if (tmp < 0) |
465 | 2.36k | b_x1 = bm->w + tmp; |
466 | 233k | tmp = clip_y1 - dst_y - bm->h; |
467 | 233k | if (tmp < 0) |
468 | 2.61k | b_y1 = bm->h + tmp; |
469 | | |
470 | 233k | if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) |
471 | 6.22k | return tail; |
472 | | |
473 | 227k | if (brk > b_x0) { // draw left part |
474 | 226k | if (brk > b_x1) |
475 | 226k | brk = b_x1; |
476 | 226k | img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0, |
477 | 226k | brk - b_x0, b_y1 - b_y0, bm->stride, |
478 | 226k | dst_x + b_x0, dst_y + b_y0, color, source); |
479 | 226k | if (!img) return tail; |
480 | 226k | img->type = type; |
481 | 226k | *tail = img; |
482 | 226k | tail = &img->next; |
483 | 226k | } |
484 | 227k | if (brk < b_x1) { // draw right part |
485 | 422 | if (brk < b_x0) |
486 | 188 | brk = b_x0; |
487 | 422 | img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk, |
488 | 422 | b_x1 - brk, b_y1 - b_y0, bm->stride, |
489 | 422 | dst_x + brk, dst_y + b_y0, color2, source); |
490 | 422 | if (!img) return tail; |
491 | 422 | img->type = type; |
492 | 422 | *tail = img; |
493 | 422 | tail = &img->next; |
494 | 422 | } |
495 | 227k | return tail; |
496 | 227k | } |
497 | | |
498 | | static bool quantize_transform(double m[3][3], ASS_Vector *pos, |
499 | | ASS_DVector *offset, bool first, |
500 | | BitmapHashKey *key) |
501 | 989k | { |
502 | | // Full transform: |
503 | | // x_out = (m_xx * x + m_xy * y + m_xz) / z, |
504 | | // y_out = (m_yx * x + m_yy * y + m_yz) / z, |
505 | | // z = m_zx * x + m_zy * y + m_zz. |
506 | | |
507 | 989k | const double max_val = 1000000; |
508 | | |
509 | 989k | const ASS_Rect *bbox = &key->outline->cbox; |
510 | 989k | double x0 = (bbox->x_min + bbox->x_max) / 2.0; |
511 | 989k | double y0 = (bbox->y_min + bbox->y_max) / 2.0; |
512 | 989k | double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64; |
513 | 989k | double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64; |
514 | | |
515 | | // Change input coordinates' origin to (x0, y0), |
516 | | // after that transformation x:[-dx, dx], y:[-dy, dy], |
517 | | // max|x| = dx and max|y| = dy. |
518 | 3.95M | for (int i = 0; i < 3; i++) |
519 | 2.96M | m[i][2] += m[i][0] * x0 + m[i][1] * y0; |
520 | | |
521 | 989k | if (m[2][2] <= 0) |
522 | 0 | return false; |
523 | | |
524 | 989k | double w = 1 / m[2][2]; |
525 | | // Transformed center of bounding box |
526 | 989k | double center[2] = { m[0][2] * w, m[1][2] * w }; |
527 | | // Change output coordinates' origin to center, |
528 | | // m_xz and m_yz is skipped as it becomes 0 and no longer needed. |
529 | 2.96M | for (int i = 0; i < 2; i++) |
530 | 5.93M | for (int j = 0; j < 2; j++) |
531 | 3.95M | m[i][j] -= m[2][j] * center[i]; |
532 | | |
533 | 989k | double delta[2] = {0}; |
534 | 989k | if (!first) { |
535 | 904k | delta[0] = offset->x; |
536 | 904k | delta[1] = offset->y; |
537 | 904k | } |
538 | | |
539 | 989k | int32_t qr[2]; // quantized center position |
540 | 2.96M | for (int i = 0; i < 2; i++) { |
541 | 1.97M | center[i] /= 64 >> SUBPIXEL_ORDER; |
542 | 1.97M | center[i] -= delta[i]; |
543 | 1.97M | if (!(fabs(center[i]) < max_val)) |
544 | 1.65k | return false; |
545 | 1.97M | qr[i] = ass_lrint(center[i]); |
546 | 1.97M | } |
547 | | |
548 | | // Minimal bounding box z coordinate |
549 | 987k | double z0 = m[2][2] - fabs(m[2][0]) * dx - fabs(m[2][1]) * dy; |
550 | | // z0 clamped to z_center / MAX_PERSP_SCALE to mitigate problems with small z |
551 | 987k | w = 1.0 / POSITION_PRECISION / FFMAX(z0, m[2][2] / MAX_PERSP_SCALE); |
552 | 987k | double mul[2] = { dx * w, dy * w }; // 1 / q_x, 1 / q_y |
553 | | |
554 | | // z0 = m_zz - |m_zx| * dx - |m_zy| * dy, |
555 | | // m_zz = z0 + |m_zx| * dx + |m_zy| * dy, |
556 | | // z = m_zx * x + m_zy * y + m_zz |
557 | | // = m_zx * (x + sign(m_zx) * dx) + m_zy * (y + sign(m_zy) * dy) + z0. |
558 | | |
559 | | // Let D(f) denote the absolute error of a quantity f. |
560 | | // Our goal is to determine tolerable error for matrix coefficients, |
561 | | // so that the total error of the output x_out, y_out is still acceptable. |
562 | | // As glyph dimensions are usually larger than a couple of pixels, errors |
563 | | // will be relatively small and we can use first order approximation. |
564 | | |
565 | | // z0 is effectively a scale factor and can thus be treated as a constant. |
566 | | // Error of constants is obviously zero, so: D(dx) = D(dy) = D(z0) = 0. |
567 | | // For arbitrary quantities A, B, C with C not zero, the following holds true: |
568 | | // D(A * B) <= D(A) * max|B| + max|A| * D(B), |
569 | | // D(1 / C) <= D(C) * max|1 / C^2|. |
570 | | // Write ~ for 'same magnitude' and ~= for 'approximately'. |
571 | | |
572 | | // D(x_out) = D((m_xx * x + m_xy * y) / z) |
573 | | // <= D(m_xx * x + m_xy * y) * max|1 / z| + max|m_xx * x + m_xy * y| * D(1 / z) |
574 | | // <= (D(m_xx) * dx + D(m_xy) * dy) / z0 + (|m_xx| * dx + |m_xy| * dy) * D(z) / z0^2, |
575 | | // D(y_out) = D((m_yx * x + m_yy * y) / z) |
576 | | // <= D(m_yx * x + m_yy * y) * max|1 / z| + max|m_yx * x + m_yy * y| * D(1 / z) |
577 | | // <= (D(m_yx) * dx + D(m_yy) * dy) / z0 + (|m_yx| * dx + |m_yy| * dy) * D(z) / z0^2, |
578 | | // |m_xx| * dx + |m_xy| * dy = x_lim, |
579 | | // |m_yx| * dx + |m_yy| * dy = y_lim, |
580 | | // D(z) <= 2 * (D(m_zx) * dx + D(m_zy) * dy), |
581 | | // D(x_out) <= (D(m_xx) * dx + D(m_xy) * dy) / z0 |
582 | | // + 2 * (D(m_zx) * dx + D(m_zy) * dy) * x_lim / z0^2, |
583 | | // D(y_out) <= (D(m_yx) * dx + D(m_yy) * dy) / z0 |
584 | | // + 2 * (D(m_zx) * dx + D(m_zy) * dy) * y_lim / z0^2. |
585 | | |
586 | | // To estimate acceptable error in a matrix coefficient, pick ACCURACY for this substep, |
587 | | // set error in all other coefficients to zero and solve the system |
588 | | // D(x_out) <= ACCURACY, D(y_out) <= ACCURACY for desired D(m_ij). |
589 | | // Note that ACCURACY isn't equal to total error. |
590 | | // Total error is larger than each ACCURACY, but still of the same magnitude. |
591 | | // Via our choice of ACCURACY, we get a total error of up to several POSITION_PRECISION. |
592 | | |
593 | | // Quantization steps (pick: ACCURACY = POSITION_PRECISION): |
594 | | // D(m_xx), D(m_yx) ~ q_x = POSITION_PRECISION * z0 / dx, |
595 | | // D(m_xy), D(m_yy) ~ q_y = POSITION_PRECISION * z0 / dy, |
596 | | // qm_xx = round(m_xx / q_x), qm_xy = round(m_xy / q_y), |
597 | | // qm_yx = round(m_yx / q_x), qm_yy = round(m_yy / q_y). |
598 | | |
599 | 987k | int32_t qm[3][2]; |
600 | 2.96M | for (int i = 0; i < 2; i++) |
601 | 5.92M | for (int j = 0; j < 2; j++) { |
602 | 3.95M | double val = m[i][j] * mul[j]; |
603 | 3.95M | if (!(fabs(val) < max_val)) |
604 | 0 | return false; |
605 | 3.95M | qm[i][j] = ass_lrint(val); |
606 | 3.95M | } |
607 | | |
608 | | // x_lim = |m_xx| * dx + |m_xy| * dy |
609 | | // ~= |qm_xx| * q_x * dx + |qm_xy| * q_y * dy |
610 | | // = (|qm_xx| + |qm_xy|) * POSITION_PRECISION * z0, |
611 | | // y_lim = |m_yx| * dx + |m_yy| * dy |
612 | | // ~= |qm_yx| * q_x * dx + |qm_yy| * q_y * dy |
613 | | // = (|qm_yx| + |qm_yy|) * POSITION_PRECISION * z0, |
614 | | // max(x_lim, y_lim) / z0 ~= w |
615 | | // = max(|qm_xx| + |qm_xy|, |qm_yx| + |qm_yy|) * POSITION_PRECISION. |
616 | | |
617 | | // Quantization steps (pick: ACCURACY = 2 * POSITION_PRECISION): |
618 | | // D(m_zx) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dx ~= q_zx = q_x / w, |
619 | | // D(m_zy) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dy ~= q_zy = q_y / w, |
620 | | // qm_zx = round(m_zx / q_zx), qm_zy = round(m_zy / q_zy). |
621 | | |
622 | 987k | int32_t qmx = abs(qm[0][0]) + abs(qm[0][1]); |
623 | 987k | int32_t qmy = abs(qm[1][0]) + abs(qm[1][1]); |
624 | 987k | w = POSITION_PRECISION * FFMAX(qmx, qmy); |
625 | 987k | mul[0] *= w; |
626 | 987k | mul[1] *= w; |
627 | | |
628 | 2.96M | for (int j = 0; j < 2; j++) { |
629 | 1.97M | double val = m[2][j] * mul[j]; |
630 | 1.97M | if (!(fabs(val) < max_val)) |
631 | 0 | return false; |
632 | 1.97M | qm[2][j] = ass_lrint(val); |
633 | 1.97M | } |
634 | | |
635 | 987k | if (first && offset) { |
636 | 83.5k | offset->x = center[0] - qr[0]; |
637 | 83.5k | offset->y = center[1] - qr[1]; |
638 | 83.5k | } |
639 | 987k | *pos = (ASS_Vector) { |
640 | 987k | .x = qr[0] >> SUBPIXEL_ORDER, |
641 | 987k | .y = qr[1] >> SUBPIXEL_ORDER, |
642 | 987k | }; |
643 | 987k | key->offset.x = qr[0] & ((1 << SUBPIXEL_ORDER) - 1); |
644 | 987k | key->offset.y = qr[1] & ((1 << SUBPIXEL_ORDER) - 1); |
645 | 987k | key->matrix_x.x = qm[0][0]; key->matrix_x.y = qm[0][1]; |
646 | 987k | key->matrix_y.x = qm[1][0]; key->matrix_y.y = qm[1][1]; |
647 | 987k | key->matrix_z.x = qm[2][0]; key->matrix_z.y = qm[2][1]; |
648 | 987k | return true; |
649 | 987k | } |
650 | | |
651 | | static void restore_transform(double m[3][3], const BitmapHashKey *key) |
652 | 17.5k | { |
653 | 17.5k | const ASS_Rect *bbox = &key->outline->cbox; |
654 | 17.5k | double x0 = (bbox->x_min + bbox->x_max) / 2.0; |
655 | 17.5k | double y0 = (bbox->y_min + bbox->y_max) / 2.0; |
656 | 17.5k | double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64; |
657 | 17.5k | double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64; |
658 | | |
659 | | // Arbitrary scale has chosen so that z0 = 1 |
660 | 17.5k | double q_x = POSITION_PRECISION / dx; |
661 | 17.5k | double q_y = POSITION_PRECISION / dy; |
662 | 17.5k | m[0][0] = key->matrix_x.x * q_x; |
663 | 17.5k | m[0][1] = key->matrix_x.y * q_y; |
664 | 17.5k | m[1][0] = key->matrix_y.x * q_x; |
665 | 17.5k | m[1][1] = key->matrix_y.y * q_y; |
666 | | |
667 | 17.5k | int32_t qmx = abs(key->matrix_x.x) + abs(key->matrix_x.y); |
668 | 17.5k | int32_t qmy = abs(key->matrix_y.x) + abs(key->matrix_y.y); |
669 | 17.5k | double scale_z = 1.0 / POSITION_PRECISION / FFMAX(qmx, qmy); |
670 | 17.5k | m[2][0] = key->matrix_z.x * q_x * scale_z; // qm_zx * q_zx |
671 | 17.5k | m[2][1] = key->matrix_z.y * q_y * scale_z; // qm_zy * q_zy |
672 | | |
673 | 17.5k | m[0][2] = m[1][2] = 0; |
674 | 17.5k | m[2][2] = 1 + fabs(m[2][0]) * dx + fabs(m[2][1]) * dy; |
675 | 17.5k | m[2][2] = FFMIN(m[2][2], MAX_PERSP_SCALE); |
676 | | |
677 | 17.5k | double center[2] = { |
678 | 17.5k | key->offset.x * (64 >> SUBPIXEL_ORDER), |
679 | 17.5k | key->offset.y * (64 >> SUBPIXEL_ORDER), |
680 | 17.5k | }; |
681 | 52.5k | for (int i = 0; i < 2; i++) |
682 | 140k | for (int j = 0; j < 3; j++) |
683 | 105k | m[i][j] += m[2][j] * center[i]; |
684 | | |
685 | 70.0k | for (int i = 0; i < 3; i++) |
686 | 52.5k | m[i][2] -= m[i][0] * x0 + m[i][1] * y0; |
687 | 17.5k | } |
688 | | |
689 | | // Calculate bitmap memory footprint |
690 | | static inline size_t bitmap_size(const Bitmap *bm) |
691 | 24.8k | { |
692 | 24.8k | return bm->stride * bm->h; |
693 | 24.8k | } |
694 | | |
695 | | /** |
696 | | * Iterate through a list of bitmaps and blend with clip vector, if |
697 | | * applicable. The blended bitmaps are added to a free list which is freed |
698 | | * at the start of a new frame. |
699 | | */ |
700 | | static void blend_vector_clip(RenderContext *state, ASS_Image *head) |
701 | 85.9k | { |
702 | 85.9k | if (!state->clip_drawing_text.str) |
703 | 85.8k | return; |
704 | | |
705 | 132 | ASS_Renderer *render_priv = state->renderer; |
706 | | |
707 | 132 | OutlineHashKey ol_key; |
708 | 132 | ol_key.type = OUTLINE_DRAWING; |
709 | 132 | ol_key.u.drawing.text = state->clip_drawing_text; |
710 | | |
711 | 132 | double m[3][3] = {{0}}; |
712 | 132 | int32_t scale_base = lshiftwrapi(1, state->clip_drawing_scale - 1); |
713 | 132 | double w = scale_base > 0 ? (1.0 / scale_base) : 0; |
714 | 132 | m[0][0] = state->screen_scale_x * w; |
715 | 132 | m[1][1] = state->screen_scale_y * w; |
716 | 132 | m[2][2] = 1; |
717 | | |
718 | 132 | m[0][2] = int_to_d6(render_priv->settings.left_margin); |
719 | 132 | m[1][2] = int_to_d6(render_priv->settings.top_margin); |
720 | | |
721 | 132 | ASS_Vector pos; |
722 | 132 | BitmapHashKey key; |
723 | 132 | key.outline = ass_cache_get(render_priv->cache.outline_cache, &ol_key, render_priv); |
724 | 132 | if (!key.outline || !key.outline->valid || |
725 | 132 | !quantize_transform(m, &pos, NULL, true, &key)) |
726 | 0 | return; |
727 | | |
728 | 132 | Bitmap *clip_bm = ass_cache_get(render_priv->cache.bitmap_cache, &key, state); |
729 | 132 | if (!clip_bm) |
730 | 0 | return; |
731 | | |
732 | | // Iterate through bitmaps and blend/clip them |
733 | 528 | for (ASS_Image *cur = head; cur; cur = cur->next) { |
734 | 396 | int left, top, right, bottom, w, h; |
735 | 396 | int ax, ay, aw, ah, as; |
736 | 396 | int bx, by, bw, bh, bs; |
737 | 396 | int aleft, atop, bleft, btop; |
738 | 396 | unsigned char *abuffer, *bbuffer, *nbuffer; |
739 | | |
740 | 396 | abuffer = cur->bitmap; |
741 | 396 | bbuffer = clip_bm->buffer; |
742 | 396 | ax = cur->dst_x; |
743 | 396 | ay = cur->dst_y; |
744 | 396 | aw = cur->w; |
745 | 396 | ah = cur->h; |
746 | 396 | as = cur->stride; |
747 | 396 | bx = pos.x + clip_bm->left; |
748 | 396 | by = pos.y + clip_bm->top; |
749 | 396 | bw = clip_bm->w; |
750 | 396 | bh = clip_bm->h; |
751 | 396 | bs = clip_bm->stride; |
752 | | |
753 | | // Calculate overlap coordinates |
754 | 396 | left = (ax > bx) ? ax : bx; |
755 | 396 | top = (ay > by) ? ay : by; |
756 | 396 | right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); |
757 | 396 | bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); |
758 | 396 | aleft = left - ax; |
759 | 396 | atop = top - ay; |
760 | 396 | w = right - left; |
761 | 396 | h = bottom - top; |
762 | 396 | bleft = left - bx; |
763 | 396 | btop = top - by; |
764 | | |
765 | 396 | unsigned align = 1 << render_priv->engine.align_order; |
766 | 396 | if (state->clip_drawing_mode) { |
767 | | // Inverse clip |
768 | 66 | if (ax + aw < bx || ay + ah < by || ax > bx + bw || |
769 | 66 | ay > by + bh || !h || !w) { |
770 | 66 | continue; |
771 | 66 | } |
772 | | |
773 | | // Allocate new buffer and add to free list |
774 | 0 | nbuffer = ass_aligned_alloc(align, as * ah + align, false); |
775 | 0 | if (!nbuffer) |
776 | 0 | break; |
777 | | |
778 | | // Blend together |
779 | 0 | memcpy(nbuffer, abuffer, ((ah - 1) * as) + aw); |
780 | 0 | render_priv->engine.imul_bitmaps(nbuffer + atop * as + aleft, as, |
781 | 0 | bbuffer + btop * bs + bleft, bs, |
782 | 0 | w, h); |
783 | 330 | } else { |
784 | | // Regular clip |
785 | 330 | if (ax + aw < bx || ay + ah < by || ax > bx + bw || |
786 | 330 | ay > by + bh || !h || !w) { |
787 | 330 | cur->w = cur->h = cur->stride = 0; |
788 | 330 | continue; |
789 | 330 | } |
790 | | |
791 | | // Allocate new buffer and add to free list |
792 | 0 | unsigned ns = ass_align(align, w); |
793 | 0 | nbuffer = ass_aligned_alloc(align, ns * h + align, false); |
794 | 0 | if (!nbuffer) |
795 | 0 | break; |
796 | | |
797 | | // Blend together |
798 | 0 | render_priv->engine.mul_bitmaps(nbuffer, ns, |
799 | 0 | abuffer + atop * as + aleft, as, |
800 | 0 | bbuffer + btop * bs + bleft, bs, |
801 | 0 | w, h); |
802 | 0 | cur->dst_x += aleft; |
803 | 0 | cur->dst_y += atop; |
804 | 0 | cur->w = w; |
805 | 0 | cur->h = h; |
806 | 0 | cur->stride = ns; |
807 | 0 | } |
808 | | |
809 | 0 | ASS_ImagePriv *priv = (ASS_ImagePriv *) cur; |
810 | 0 | priv->buffer = cur->bitmap = nbuffer; |
811 | 0 | ass_cache_dec_ref(priv->source); |
812 | 0 | priv->source = NULL; |
813 | 0 | } |
814 | 132 | } |
815 | | |
816 | | /** |
817 | | * \brief Convert TextInfo struct to ASS_Image list |
818 | | * Splits glyphs in halves when needed (for \kf karaoke). |
819 | | */ |
820 | | static ASS_Image *render_text(RenderContext *state) |
821 | 85.9k | { |
822 | 85.9k | ASS_Image *head; |
823 | 85.9k | ASS_Image **tail = &head; |
824 | 85.9k | unsigned n_bitmaps = state->text_info.n_bitmaps; |
825 | 85.9k | CombinedBitmapInfo *bitmaps = state->text_info.combined_bitmaps; |
826 | | |
827 | 169k | for (unsigned i = 0; i < n_bitmaps; i++) { |
828 | 83.8k | CombinedBitmapInfo *info = &bitmaps[i]; |
829 | 83.8k | if (!info->bm_s || state->border_style == 4) |
830 | 5.97k | continue; |
831 | | |
832 | 77.9k | tail = |
833 | 77.9k | render_glyph(state, info->bm_s, info->x, info->y, info->c[3], 0, |
834 | 77.9k | 1000000, tail, IMAGE_TYPE_SHADOW, info->image); |
835 | 77.9k | } |
836 | | |
837 | 169k | for (unsigned i = 0; i < n_bitmaps; i++) { |
838 | 83.8k | CombinedBitmapInfo *info = &bitmaps[i]; |
839 | 83.8k | if (!info->bm_o) |
840 | 6.32k | continue; |
841 | | |
842 | 77.5k | if ((info->effect_type == EF_KARAOKE_KO) |
843 | 367 | && (info->effect_timing <= 0)) { |
844 | | // do nothing |
845 | 77.5k | } else { |
846 | 77.5k | tail = |
847 | 77.5k | render_glyph(state, info->bm_o, info->x, info->y, info->c[2], |
848 | 77.5k | 0, 1000000, tail, IMAGE_TYPE_OUTLINE, info->image); |
849 | 77.5k | } |
850 | 77.5k | } |
851 | | |
852 | 169k | for (unsigned i = 0; i < n_bitmaps; i++) { |
853 | 83.8k | CombinedBitmapInfo *info = &bitmaps[i]; |
854 | 83.8k | if (!info->bm) |
855 | 5.80k | continue; |
856 | | |
857 | 78.0k | if ((info->effect_type == EF_KARAOKE) |
858 | 77.0k | || (info->effect_type == EF_KARAOKE_KO)) { |
859 | 1.38k | if (info->effect_timing > 0) |
860 | 1.26k | tail = |
861 | 1.26k | render_glyph(state, info->bm, info->x, info->y, |
862 | 1.26k | info->c[0], 0, 1000000, tail, |
863 | 1.26k | IMAGE_TYPE_CHARACTER, info->image); |
864 | 114 | else |
865 | 114 | tail = |
866 | 114 | render_glyph(state, info->bm, info->x, info->y, |
867 | 114 | info->c[1], 0, 1000000, tail, |
868 | 114 | IMAGE_TYPE_CHARACTER, info->image); |
869 | 76.7k | } else if (info->effect_type == EF_KARAOKE_KF) { |
870 | 1.49k | tail = |
871 | 1.49k | render_glyph(state, info->bm, info->x, info->y, info->c[0], |
872 | 1.49k | info->c[1], info->effect_timing, tail, |
873 | 1.49k | IMAGE_TYPE_CHARACTER, info->image); |
874 | 1.49k | } else |
875 | 75.2k | tail = |
876 | 75.2k | render_glyph(state, info->bm, info->x, info->y, info->c[0], |
877 | 75.2k | 0, 1000000, tail, IMAGE_TYPE_CHARACTER, info->image); |
878 | 78.0k | } |
879 | | |
880 | 85.9k | *tail = 0; |
881 | 85.9k | blend_vector_clip(state, head); |
882 | | |
883 | 85.9k | return head; |
884 | 85.9k | } |
885 | | |
886 | | static void compute_string_bbox(TextInfo *text, ASS_DRect *bbox) |
887 | 85.9k | { |
888 | 85.9k | if (text->length > 0) { |
889 | 85.9k | bbox->x_min = +32000; |
890 | 85.9k | bbox->x_max = -32000; |
891 | 85.9k | bbox->y_min = -text->lines[0].asc; |
892 | 85.9k | bbox->y_max = bbox->y_min + text->height; |
893 | | |
894 | 659k | for (int i = 0; i < text->length; i++) { |
895 | 573k | GlyphInfo *info = text->glyphs + i; |
896 | 573k | if (info->skip) continue; |
897 | 569k | double s = d6_to_double(info->pos.x); |
898 | 569k | double e = s + d6_to_double(info->cluster_advance.x); |
899 | 569k | bbox->x_min = FFMIN(bbox->x_min, s); |
900 | 569k | bbox->x_max = FFMAX(bbox->x_max, e); |
901 | 569k | } |
902 | 85.9k | } else |
903 | 0 | bbox->x_min = bbox->x_max = bbox->y_min = bbox->y_max = 0; |
904 | 85.9k | } |
905 | | |
906 | | static ASS_Style *handle_selective_style_overrides(RenderContext *state, |
907 | | ASS_Style *rstyle) |
908 | 322k | { |
909 | | // The script style is the one the event was declared with. |
910 | 322k | ASS_Renderer *render_priv = state->renderer; |
911 | 322k | ASS_Style *script = render_priv->track->styles + |
912 | 322k | state->event->Style; |
913 | | // The user style was set with ass_set_selective_style_override(). |
914 | 322k | ASS_Style *user = &render_priv->user_override_style; |
915 | 322k | ASS_Style *new = &state->override_style_temp_storage; |
916 | 322k | int explicit = state->explicit; |
917 | 322k | int requested = render_priv->settings.selective_style_overrides; |
918 | 322k | double scale; |
919 | | |
920 | | // Either the event's style, or the style forced with a \r tag. |
921 | 322k | if (!rstyle) |
922 | 321k | rstyle = script; |
923 | | |
924 | | // Create a new style that contains a mix of the original style and |
925 | | // user_style (the user's override style). Copy only fields from the |
926 | | // script's style that are deemed necessary. |
927 | 322k | *new = *rstyle; |
928 | | |
929 | 322k | state->apply_font_scale = |
930 | 322k | !explicit || !(requested & ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE); |
931 | | |
932 | | // On positioned events, do not apply most overrides. |
933 | 322k | if (explicit) |
934 | 34.0k | requested = 0; |
935 | | |
936 | 322k | if (requested & ASS_OVERRIDE_BIT_STYLE) |
937 | 0 | requested |= ASS_OVERRIDE_BIT_FONT_NAME | |
938 | 0 | ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS | |
939 | 0 | ASS_OVERRIDE_BIT_COLORS | |
940 | 0 | ASS_OVERRIDE_BIT_BORDER | |
941 | 0 | ASS_OVERRIDE_BIT_ATTRIBUTES; |
942 | | |
943 | | // Copies fields even not covered by any of the other bits. |
944 | 322k | if (requested & ASS_OVERRIDE_FULL_STYLE) |
945 | 0 | *new = *user; |
946 | | |
947 | | // The user style is supposed to be independent of the script resolution. |
948 | | // Treat the user style's values as if they were specified for a script with |
949 | | // PlayResY=288, and rescale the values to the current script. |
950 | 322k | scale = render_priv->track->PlayResY / 288.0; |
951 | | |
952 | 322k | if (requested & ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS) { |
953 | 0 | new->FontSize = user->FontSize * scale; |
954 | 0 | new->Spacing = user->Spacing * scale; |
955 | 0 | new->ScaleX = user->ScaleX; |
956 | 0 | new->ScaleY = user->ScaleY; |
957 | 0 | } |
958 | | |
959 | 322k | if (requested & ASS_OVERRIDE_BIT_FONT_NAME) { |
960 | 0 | new->FontName = user->FontName; |
961 | 0 | new->treat_fontname_as_pattern = user->treat_fontname_as_pattern; |
962 | 0 | } |
963 | | |
964 | 322k | if (requested & ASS_OVERRIDE_BIT_COLORS) { |
965 | 0 | new->PrimaryColour = user->PrimaryColour; |
966 | 0 | new->SecondaryColour = user->SecondaryColour; |
967 | 0 | new->OutlineColour = user->OutlineColour; |
968 | 0 | new->BackColour = user->BackColour; |
969 | 0 | } |
970 | | |
971 | 322k | if (requested & ASS_OVERRIDE_BIT_ATTRIBUTES) { |
972 | 0 | new->Bold = user->Bold; |
973 | 0 | new->Italic = user->Italic; |
974 | 0 | new->Underline = user->Underline; |
975 | 0 | new->StrikeOut = user->StrikeOut; |
976 | 0 | } |
977 | | |
978 | 322k | if (requested & ASS_OVERRIDE_BIT_BORDER) { |
979 | 0 | new->BorderStyle = user->BorderStyle; |
980 | 0 | new->Outline = user->Outline * scale; |
981 | 0 | new->Shadow = user->Shadow * scale; |
982 | 0 | } |
983 | | |
984 | 322k | if (requested & ASS_OVERRIDE_BIT_BLUR) |
985 | 0 | new->Blur = user->Blur * scale; |
986 | | |
987 | 322k | if (requested & ASS_OVERRIDE_BIT_ALIGNMENT) |
988 | 0 | new->Alignment = user->Alignment; |
989 | | |
990 | 322k | if (requested & ASS_OVERRIDE_BIT_JUSTIFY) |
991 | 0 | new->Justify = user->Justify; |
992 | | |
993 | 322k | if (requested & ASS_OVERRIDE_BIT_MARGINS) { |
994 | 0 | new->MarginL = user->MarginL; |
995 | 0 | new->MarginR = user->MarginR; |
996 | 0 | new->MarginV = user->MarginV; |
997 | 0 | } |
998 | | |
999 | 322k | if (!new->FontName) |
1000 | 0 | new->FontName = rstyle->FontName; |
1001 | | |
1002 | 322k | state->style = new; |
1003 | 322k | state->overrides = requested; |
1004 | | |
1005 | 322k | return new; |
1006 | 322k | } |
1007 | | |
1008 | | ASS_Vector ass_layout_res(ASS_Renderer *render_priv) |
1009 | 485k | { |
1010 | 485k | ASS_Track *track = render_priv->track; |
1011 | 485k | if (track->LayoutResX > 0 && track->LayoutResY > 0) |
1012 | 12.1k | return (ASS_Vector) { track->LayoutResX, track->LayoutResY }; |
1013 | | |
1014 | 473k | ASS_Settings *settings = &render_priv->settings; |
1015 | 473k | if (settings->storage_width > 0 && settings->storage_height > 0) |
1016 | 473k | return (ASS_Vector) { settings->storage_width, settings->storage_height }; |
1017 | | |
1018 | 0 | if (settings->par <= 0 || settings->par == 1 || |
1019 | 0 | !render_priv->frame_content_width || !render_priv->frame_content_height) |
1020 | 0 | return (ASS_Vector) { track->PlayResX, track->PlayResY }; |
1021 | 0 | if (settings->par > 1) |
1022 | 0 | return (ASS_Vector) { |
1023 | 0 | FFMAX(1, lround(track->PlayResY * render_priv->frame_content_width |
1024 | 0 | / render_priv->frame_content_height / settings->par)), |
1025 | 0 | track->PlayResY |
1026 | 0 | }; |
1027 | 0 | else |
1028 | 0 | return (ASS_Vector) { |
1029 | 0 | track->PlayResX, |
1030 | 0 | FFMAX(1, lround(track->PlayResX * render_priv->frame_content_height |
1031 | 0 | / render_priv->frame_content_width * settings->par)) |
1032 | 0 | }; |
1033 | 0 | } |
1034 | | |
1035 | | static void init_font_scale(RenderContext *state) |
1036 | 322k | { |
1037 | 322k | ASS_Renderer *render_priv = state->renderer; |
1038 | 322k | ASS_Settings *settings_priv = &render_priv->settings; |
1039 | | |
1040 | 322k | double font_scr_w = render_priv->frame_content_width; |
1041 | 322k | double font_scr_h = render_priv->frame_content_height; |
1042 | 322k | if (!state->explicit && render_priv->settings.use_margins) { |
1043 | 0 | font_scr_w = render_priv->fit_width; |
1044 | 0 | font_scr_h = render_priv->fit_height; |
1045 | 0 | } |
1046 | | |
1047 | 322k | state->screen_scale_x = font_scr_w / render_priv->track->PlayResX; |
1048 | 322k | state->screen_scale_y = font_scr_h / render_priv->track->PlayResY; |
1049 | | |
1050 | 322k | ASS_Vector layout_res = ass_layout_res(render_priv); |
1051 | 322k | state->blur_scale_x = font_scr_w / layout_res.x; |
1052 | 322k | state->blur_scale_y = font_scr_h / layout_res.y; |
1053 | 322k | if (render_priv->track->ScaledBorderAndShadow) { |
1054 | 2.68k | state->border_scale_x = state->screen_scale_x; |
1055 | 2.68k | state->border_scale_y = state->screen_scale_y; |
1056 | 319k | } else { |
1057 | 319k | state->border_scale_x = state->blur_scale_x; |
1058 | 319k | state->border_scale_y = state->blur_scale_y; |
1059 | 319k | } |
1060 | | |
1061 | 322k | if (state->apply_font_scale) { |
1062 | 288k | state->screen_scale_x *= settings_priv->font_size_coeff; |
1063 | 288k | state->screen_scale_y *= settings_priv->font_size_coeff; |
1064 | 288k | state->border_scale_x *= settings_priv->font_size_coeff; |
1065 | 288k | state->border_scale_y *= settings_priv->font_size_coeff; |
1066 | 288k | state->blur_scale_x *= settings_priv->font_size_coeff; |
1067 | 288k | state->blur_scale_y *= settings_priv->font_size_coeff; |
1068 | 288k | } |
1069 | 322k | } |
1070 | | |
1071 | | /** |
1072 | | * \brief partially reset render_context to style values |
1073 | | * Works like {\r}: resets some style overrides |
1074 | | */ |
1075 | | void ass_reset_render_context(RenderContext *state, ASS_Style *style) |
1076 | 322k | { |
1077 | 322k | style = handle_selective_style_overrides(state, style); |
1078 | | |
1079 | 322k | init_font_scale(state); |
1080 | | |
1081 | 322k | state->c[0] = style->PrimaryColour; |
1082 | 322k | state->c[1] = style->SecondaryColour; |
1083 | 322k | state->c[2] = style->OutlineColour; |
1084 | 322k | state->c[3] = style->BackColour; |
1085 | 322k | state->flags = |
1086 | 322k | (style->Underline ? DECO_UNDERLINE : 0) | |
1087 | 322k | (style->StrikeOut ? DECO_STRIKETHROUGH : 0); |
1088 | 322k | state->font_size = style->FontSize; |
1089 | | |
1090 | 322k | state->family.str = style->FontName; |
1091 | 322k | state->family.len = strlen(style->FontName); |
1092 | 322k | state->treat_family_as_pattern = style->treat_fontname_as_pattern; |
1093 | 322k | state->bold = style->Bold; |
1094 | 322k | state->italic = style->Italic; |
1095 | 322k | ass_update_font(state); |
1096 | | |
1097 | 322k | state->border_style = style->BorderStyle; |
1098 | 322k | state->border_x = style->Outline; |
1099 | 322k | state->border_y = style->Outline; |
1100 | 322k | state->scale_x = style->ScaleX; |
1101 | 322k | state->scale_y = style->ScaleY; |
1102 | 322k | state->hspacing = style->Spacing; |
1103 | 322k | state->be = 0; |
1104 | 322k | state->blur = style->Blur; |
1105 | 322k | state->shadow_x = style->Shadow; |
1106 | 322k | state->shadow_y = style->Shadow; |
1107 | 322k | state->frx = state->fry = 0.; |
1108 | 322k | state->frz = style->Angle; |
1109 | 322k | state->fax = state->fay = 0.; |
1110 | 322k | state->font_encoding = style->Encoding; |
1111 | 322k | } |
1112 | | |
1113 | | /** |
1114 | | * \brief Start new event. Reset state. |
1115 | | */ |
1116 | | static void |
1117 | | init_render_context(RenderContext *state, ASS_Event *event) |
1118 | 313k | { |
1119 | 313k | ASS_Renderer *render_priv = state->renderer; |
1120 | | |
1121 | 313k | state->event = event; |
1122 | 313k | state->parsed_tags = 0; |
1123 | 313k | state->evt_type = EVENT_NORMAL; |
1124 | | |
1125 | 313k | state->wrap_style = render_priv->track->WrapStyle; |
1126 | | |
1127 | 313k | state->pos_x = 0; |
1128 | 313k | state->pos_y = 0; |
1129 | 313k | state->org_x = 0; |
1130 | 313k | state->org_y = 0; |
1131 | 313k | state->have_origin = 0; |
1132 | 313k | state->clip_x0 = 0; |
1133 | 313k | state->clip_y0 = 0; |
1134 | 313k | state->clip_x1 = render_priv->track->PlayResX; |
1135 | 313k | state->clip_y1 = render_priv->track->PlayResY; |
1136 | 313k | state->clip_mode = 0; |
1137 | 313k | state->detect_collisions = 1; |
1138 | 313k | state->fade = 0; |
1139 | 313k | state->drawing_scale = 0; |
1140 | 313k | state->pbo = 0; |
1141 | 313k | state->effect_type = EF_NONE; |
1142 | 313k | state->effect_timing = 0; |
1143 | 313k | state->effect_skip_timing = 0; |
1144 | 313k | state->reset_effect = false; |
1145 | | |
1146 | 313k | ass_apply_transition_effects(state); |
1147 | 313k | state->explicit = state->evt_type != EVENT_NORMAL || |
1148 | 306k | ass_event_has_hard_overrides(event->Text); |
1149 | | |
1150 | 313k | ass_reset_render_context(state, NULL); |
1151 | 313k | state->alignment = state->style->Alignment; |
1152 | 313k | state->justify = state->style->Justify; |
1153 | 313k | } |
1154 | | |
1155 | | static void free_render_context(RenderContext *state) |
1156 | 626k | { |
1157 | 626k | state->font = NULL; |
1158 | 626k | state->family.str = NULL; |
1159 | 626k | state->family.len = 0; |
1160 | 626k | state->clip_drawing_text.str = NULL; |
1161 | 626k | state->clip_drawing_text.len = 0; |
1162 | 626k | state->text_info.length = 0; |
1163 | 626k | } |
1164 | | |
1165 | | /** |
1166 | | * \brief Get normal and outline (border) glyphs |
1167 | | * \param info out: struct filled with extracted data |
1168 | | * Tries to get both glyphs from cache. |
1169 | | * If they can't be found, gets a glyph from font face, generates outline, |
1170 | | * and add them to cache. |
1171 | | */ |
1172 | | static void |
1173 | | get_outline_glyph(RenderContext *state, GlyphInfo *info) |
1174 | 573k | { |
1175 | 573k | ASS_Renderer *priv = state->renderer; |
1176 | 573k | OutlineHashValue *val; |
1177 | 573k | ASS_DVector scale, offset = {0}; |
1178 | | |
1179 | 573k | int32_t asc, desc; |
1180 | 573k | OutlineHashKey key; |
1181 | 573k | if (info->drawing_text.str) { |
1182 | 3.19k | key.type = OUTLINE_DRAWING; |
1183 | 3.19k | key.u.drawing.text = info->drawing_text; |
1184 | 3.19k | val = ass_cache_get(priv->cache.outline_cache, &key, priv); |
1185 | 3.19k | if (!val || !val->valid) |
1186 | 0 | return; |
1187 | | |
1188 | 3.19k | int32_t scale_base = lshiftwrapi(1, info->drawing_scale - 1); |
1189 | 3.19k | double w = scale_base > 0 ? (1.0 / scale_base) : 0; |
1190 | 3.19k | scale.x = info->scale_x * w * state->screen_scale_x / priv->par_scale_x; |
1191 | 3.19k | scale.y = info->scale_y * w * state->screen_scale_y; |
1192 | 3.19k | desc = 64 * info->drawing_pbo; |
1193 | 3.19k | asc = val->asc - desc; |
1194 | | |
1195 | 3.19k | offset.y = -asc * scale.y; |
1196 | 569k | } else { |
1197 | 569k | key.type = OUTLINE_GLYPH; |
1198 | 569k | GlyphHashKey *k = &key.u.glyph; |
1199 | 569k | k->font = info->font; |
1200 | 569k | k->size = info->font_size; |
1201 | 569k | k->face_index = info->face_index; |
1202 | 569k | k->glyph_index = info->glyph_index; |
1203 | 569k | k->bold = info->bold; |
1204 | 569k | k->italic = info->italic; |
1205 | 569k | k->flags = info->flags; |
1206 | | |
1207 | 569k | val = ass_cache_get(priv->cache.outline_cache, &key, priv); |
1208 | 569k | if (!val || !val->valid) |
1209 | 64.0k | return; |
1210 | | |
1211 | 505k | scale.x = info->scale_x; |
1212 | 505k | scale.y = info->scale_y; |
1213 | 505k | asc = val->asc; |
1214 | 505k | desc = val->desc; |
1215 | 505k | } |
1216 | | |
1217 | 509k | info->outline = val; |
1218 | 509k | info->transform.scale = scale; |
1219 | 509k | info->transform.offset = offset; |
1220 | | |
1221 | 509k | info->bbox.x_min = ass_lrint(val->cbox.x_min * scale.x + offset.x); |
1222 | 509k | info->bbox.y_min = ass_lrint(val->cbox.y_min * scale.y + offset.y); |
1223 | 509k | info->bbox.x_max = ass_lrint(val->cbox.x_max * scale.x + offset.x); |
1224 | 509k | info->bbox.y_max = ass_lrint(val->cbox.y_max * scale.y + offset.y); |
1225 | | |
1226 | 509k | if (info->drawing_text.str || priv->settings.shaper == ASS_SHAPING_SIMPLE) { |
1227 | 3.19k | info->cluster_advance.x = info->advance.x = ass_lrint(val->advance * scale.x); |
1228 | 3.19k | info->cluster_advance.y = info->advance.y = 0; |
1229 | 3.19k | } |
1230 | 509k | info->asc = ass_lrint(asc * scale.y); |
1231 | 509k | info->desc = ass_lrint(desc * scale.y); |
1232 | 509k | } |
1233 | | |
1234 | | size_t ass_outline_construct(void *key, void *value, void *priv) |
1235 | 11.6k | { |
1236 | 11.6k | ASS_Renderer *render_priv = priv; |
1237 | 11.6k | OutlineHashKey *outline_key = key; |
1238 | 11.6k | OutlineHashValue *v = value; |
1239 | 11.6k | memset(v, 0, sizeof(*v)); |
1240 | | |
1241 | 11.6k | switch (outline_key->type) { |
1242 | 5.81k | case OUTLINE_GLYPH: |
1243 | 5.81k | { |
1244 | 5.81k | GlyphHashKey *k = &outline_key->u.glyph; |
1245 | 5.81k | ass_face_set_size(k->font->faces[k->face_index], k->size); |
1246 | 5.81k | if (!ass_font_get_glyph(k->font, k->face_index, k->glyph_index, |
1247 | 5.81k | render_priv->settings.hinting)) |
1248 | 80 | return 1; |
1249 | 5.73k | if (!ass_get_glyph_outline(&v->outline[0], &v->advance, |
1250 | 5.73k | k->font->faces[k->face_index], |
1251 | 5.73k | k->flags)) |
1252 | 0 | return 1; |
1253 | 5.73k | ass_font_get_asc_desc(k->font, k->face_index, |
1254 | 5.73k | &v->asc, &v->desc); |
1255 | 5.73k | break; |
1256 | 5.73k | } |
1257 | 127 | case OUTLINE_DRAWING: |
1258 | 127 | { |
1259 | 127 | ASS_Rect bbox; |
1260 | 127 | const char *text = outline_key->u.drawing.text.str; // always zero-terminated |
1261 | 127 | if (!ass_drawing_parse(&v->outline[0], &bbox, text, render_priv->library)) |
1262 | 0 | return 1; |
1263 | | |
1264 | 127 | v->advance = bbox.x_max - bbox.x_min; |
1265 | 127 | v->asc = bbox.y_max - bbox.y_min; |
1266 | 127 | v->desc = 0; |
1267 | 127 | break; |
1268 | 127 | } |
1269 | 5.69k | case OUTLINE_BORDER: |
1270 | 5.69k | { |
1271 | 5.69k | BorderHashKey *k = &outline_key->u.border; |
1272 | 5.69k | if (!k->border.x && !k->border.y) |
1273 | 0 | break; |
1274 | 5.69k | if (!k->outline->outline[0].n_points) |
1275 | 194 | break; |
1276 | | |
1277 | 5.50k | ASS_Outline src; |
1278 | 5.50k | if (!ass_outline_scale_pow2(&src, &k->outline->outline[0], |
1279 | 5.50k | k->scale_ord_x, k->scale_ord_y)) |
1280 | 0 | return 1; |
1281 | 5.50k | if (!ass_outline_stroke(&v->outline[0], &v->outline[1], &src, |
1282 | 5.50k | k->border.x * STROKER_PRECISION, |
1283 | 5.50k | k->border.y * STROKER_PRECISION, |
1284 | 5.50k | STROKER_PRECISION)) { |
1285 | 0 | ass_msg(render_priv->library, MSGL_WARN, "Cannot stroke outline"); |
1286 | 0 | ass_outline_free(&v->outline[0]); |
1287 | 0 | ass_outline_free(&v->outline[1]); |
1288 | 0 | ass_outline_free(&src); |
1289 | 0 | return 1; |
1290 | 0 | } |
1291 | 5.50k | ass_outline_free(&src); |
1292 | 5.50k | break; |
1293 | 5.50k | } |
1294 | 0 | case OUTLINE_BOX: |
1295 | 0 | { |
1296 | 0 | ASS_Outline *ol = &v->outline[0]; |
1297 | 0 | if (!ass_outline_alloc(ol, 4, 4)) |
1298 | 0 | return 1; |
1299 | 0 | ol->points[0].x = ol->points[3].x = 0; |
1300 | 0 | ol->points[1].x = ol->points[2].x = 64; |
1301 | 0 | ol->points[0].y = ol->points[1].y = 0; |
1302 | 0 | ol->points[2].y = ol->points[3].y = 64; |
1303 | 0 | ol->segments[0] = OUTLINE_LINE_SEGMENT; |
1304 | 0 | ol->segments[1] = OUTLINE_LINE_SEGMENT; |
1305 | 0 | ol->segments[2] = OUTLINE_LINE_SEGMENT; |
1306 | 0 | ol->segments[3] = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END; |
1307 | 0 | ol->n_points = ol->n_segments = 4; |
1308 | 0 | break; |
1309 | 0 | } |
1310 | 0 | default: |
1311 | 0 | return 1; |
1312 | 11.6k | } |
1313 | | |
1314 | 11.5k | rectangle_reset(&v->cbox); |
1315 | 11.5k | ass_outline_update_cbox(&v->outline[0], &v->cbox); |
1316 | 11.5k | ass_outline_update_cbox(&v->outline[1], &v->cbox); |
1317 | 11.5k | if (v->cbox.x_min > v->cbox.x_max || v->cbox.y_min > v->cbox.y_max) |
1318 | 442 | v->cbox.x_min = v->cbox.y_min = v->cbox.x_max = v->cbox.y_max = 0; |
1319 | 11.5k | v->valid = true; |
1320 | 11.5k | return 1; |
1321 | 11.6k | } |
1322 | | |
1323 | | /** |
1324 | | * \brief Calculate outline transformation matrix |
1325 | | */ |
1326 | | static void calc_transform_matrix(RenderContext *state, |
1327 | | GlyphInfo *info, double m[3][3]) |
1328 | 496k | { |
1329 | 496k | ASS_Renderer *render_priv = state->renderer; |
1330 | | |
1331 | 496k | double frx = ASS_PI / 180 * info->frx; |
1332 | 496k | double fry = ASS_PI / 180 * info->fry; |
1333 | 496k | double frz = ASS_PI / 180 * info->frz; |
1334 | | |
1335 | 496k | double sx = -sin(frx), cx = cos(frx); |
1336 | 496k | double sy = sin(fry), cy = cos(fry); |
1337 | 496k | double sz = -sin(frz), cz = cos(frz); |
1338 | | |
1339 | 496k | double fax = info->fax * info->scale_x / info->scale_y; |
1340 | 496k | double fay = info->fay * info->scale_y / info->scale_x; |
1341 | 496k | double x1[3] = { 1, fax, info->shift.x + info->asc * fax }; |
1342 | 496k | double y1[3] = { fay, 1, info->shift.y }; |
1343 | | |
1344 | 496k | double x2[3], y2[3]; |
1345 | 1.98M | for (int i = 0; i < 3; i++) { |
1346 | 1.48M | x2[i] = x1[i] * cz - y1[i] * sz; |
1347 | 1.48M | y2[i] = x1[i] * sz + y1[i] * cz; |
1348 | 1.48M | } |
1349 | | |
1350 | 496k | double y3[3], z3[3]; |
1351 | 1.98M | for (int i = 0; i < 3; i++) { |
1352 | 1.48M | y3[i] = y2[i] * cx; |
1353 | 1.48M | z3[i] = y2[i] * sx; |
1354 | 1.48M | } |
1355 | | |
1356 | 496k | double x4[3], z4[3]; |
1357 | 1.98M | for (int i = 0; i < 3; i++) { |
1358 | 1.48M | x4[i] = x2[i] * cy - z3[i] * sy; |
1359 | 1.48M | z4[i] = x2[i] * sy + z3[i] * cy; |
1360 | 1.48M | } |
1361 | | |
1362 | 496k | double dist = 20000 * state->blur_scale_y; |
1363 | 496k | z4[2] += dist; |
1364 | | |
1365 | 496k | double scale_x = dist * render_priv->par_scale_x; |
1366 | 496k | double offs_x = info->pos.x - info->shift.x * render_priv->par_scale_x; |
1367 | 496k | double offs_y = info->pos.y - info->shift.y; |
1368 | 1.98M | for (int i = 0; i < 3; i++) { |
1369 | 1.48M | m[0][i] = z4[i] * offs_x + x4[i] * scale_x; |
1370 | 1.48M | m[1][i] = z4[i] * offs_y + y3[i] * dist; |
1371 | 1.48M | m[2][i] = z4[i]; |
1372 | 1.48M | } |
1373 | 496k | } |
1374 | | |
1375 | | /** |
1376 | | * \brief Get bitmaps for a glyph |
1377 | | * \param info glyph info |
1378 | | * Tries to get glyph bitmaps from bitmap cache. |
1379 | | * If they can't be found, they are generated by rotating and rendering the glyph. |
1380 | | * After that, bitmaps are added to the cache. |
1381 | | * They are returned in info->bm (glyph), info->bm_o (outline). |
1382 | | */ |
1383 | | static void |
1384 | | get_bitmap_glyph(RenderContext *state, GlyphInfo *info, |
1385 | | int32_t *leftmost_x, |
1386 | | ASS_Vector *pos, ASS_Vector *pos_o, |
1387 | | ASS_DVector *offset, bool first, int flags) |
1388 | 560k | { |
1389 | 560k | ASS_Renderer *render_priv = state->renderer; |
1390 | | |
1391 | 560k | if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip) |
1392 | 63.9k | return; |
1393 | | |
1394 | 496k | double m1[3][3], m2[3][3], m[3][3]; |
1395 | 496k | const ASS_Transform *tr = &info->transform; |
1396 | 496k | calc_transform_matrix(state, info, m1); |
1397 | 1.98M | for (int i = 0; i < 3; i++) { |
1398 | 1.48M | m2[i][0] = m1[i][0] * tr->scale.x; |
1399 | 1.48M | m2[i][1] = m1[i][1] * tr->scale.y; |
1400 | 1.48M | m2[i][2] = m1[i][0] * tr->offset.x + m1[i][1] * tr->offset.y + m1[i][2]; |
1401 | 1.48M | } |
1402 | 496k | memcpy(m, m2, sizeof(m)); |
1403 | | |
1404 | 496k | if (info->effect_type == EF_KARAOKE_KF) |
1405 | 8.96k | ass_outline_update_min_transformed_x(&info->outline->outline[0], m, leftmost_x); |
1406 | | |
1407 | 496k | BitmapHashKey key; |
1408 | 496k | key.outline = info->outline; |
1409 | 496k | if (!quantize_transform(m, pos, offset, first, &key)) |
1410 | 1.65k | return; |
1411 | | |
1412 | 494k | info->bm = ass_cache_get(render_priv->cache.bitmap_cache, &key, state); |
1413 | 494k | if (!info->bm || !info->bm->buffer) |
1414 | 16.9k | info->bm = NULL; |
1415 | | |
1416 | 494k | *pos_o = *pos; |
1417 | | |
1418 | 494k | OutlineHashKey ol_key; |
1419 | 494k | if (flags & FILTER_BORDER_STYLE_3) { |
1420 | 0 | if (!(flags & (FILTER_NONZERO_BORDER | FILTER_NONZERO_SHADOW))) |
1421 | 0 | return; |
1422 | | |
1423 | 0 | ol_key.type = OUTLINE_BOX; |
1424 | |
|
1425 | 0 | ASS_DVector bord = { |
1426 | 0 | 64 * info->border_x * state->border_scale_x / |
1427 | 0 | render_priv->par_scale_x, |
1428 | 0 | 64 * info->border_y * state->border_scale_y, |
1429 | 0 | }; |
1430 | 0 | double width = info->hspacing_scaled + info->advance.x; |
1431 | 0 | double height = info->asc + info->desc; |
1432 | |
|
1433 | 0 | ASS_DVector orig_scale; |
1434 | 0 | orig_scale.x = info->scale_x * info->scale_fix; |
1435 | 0 | orig_scale.y = info->scale_y * info->scale_fix; |
1436 | | |
1437 | | // Emulate the WTFish behavior of VSFilter, i.e. double-scale |
1438 | | // the sizes of the opaque box. |
1439 | 0 | bord.x *= orig_scale.x; |
1440 | 0 | bord.y *= orig_scale.y; |
1441 | 0 | width *= orig_scale.x; |
1442 | 0 | height *= orig_scale.y; |
1443 | | |
1444 | | // to avoid gaps |
1445 | 0 | bord.x = FFMAX(64, bord.x); |
1446 | 0 | bord.y = FFMAX(64, bord.y); |
1447 | |
|
1448 | 0 | ASS_DVector scale = { |
1449 | 0 | (width + 2 * bord.x) / 64, |
1450 | 0 | (height + 2 * bord.y) / 64, |
1451 | 0 | }; |
1452 | 0 | ASS_DVector offset = { -bord.x, -bord.y - info->asc }; |
1453 | 0 | for (int i = 0; i < 3; i++) { |
1454 | 0 | m[i][0] = m1[i][0] * scale.x; |
1455 | 0 | m[i][1] = m1[i][1] * scale.y; |
1456 | 0 | m[i][2] = m1[i][0] * offset.x + m1[i][1] * offset.y + m1[i][2]; |
1457 | 0 | } |
1458 | 494k | } else { |
1459 | 494k | if (!(flags & FILTER_NONZERO_BORDER)) |
1460 | 1.43k | return; |
1461 | | |
1462 | 493k | ol_key.type = OUTLINE_BORDER; |
1463 | 493k | BorderHashKey *k = &ol_key.u.border; |
1464 | 493k | k->outline = info->outline; |
1465 | | |
1466 | 493k | double bord_x = |
1467 | 493k | 64 * state->border_scale_x * info->border_x / tr->scale.x / |
1468 | 493k | render_priv->par_scale_x; |
1469 | 493k | double bord_y = |
1470 | 493k | 64 * state->border_scale_y * info->border_y / tr->scale.y; |
1471 | | |
1472 | 493k | const ASS_Rect *bbox = &info->outline->cbox; |
1473 | | // Estimate bounding box half size after stroking |
1474 | 493k | double dx = (bbox->x_max - bbox->x_min) / 2.0 + (bord_x + 64); |
1475 | 493k | double dy = (bbox->y_max - bbox->y_min) / 2.0 + (bord_y + 64); |
1476 | | |
1477 | | // Matrix after quantize_transform() has |
1478 | | // input and output origin at bounding box center. |
1479 | 493k | double mxx = fabs(m[0][0]), mxy = fabs(m[0][1]); |
1480 | 493k | double myx = fabs(m[1][0]), myy = fabs(m[1][1]); |
1481 | 493k | double mzx = fabs(m[2][0]), mzy = fabs(m[2][1]); |
1482 | | |
1483 | 493k | double z0 = m[2][2] - mzx * dx - mzy * dy; |
1484 | 493k | double w = 1 / FFMAX(z0, m[2][2] / MAX_PERSP_SCALE); |
1485 | | |
1486 | | // Notation from quantize_transform(). |
1487 | | // Note that goal here is to estimate acceptable error for stroking, i. e. D(x) and D(y). |
1488 | | // Matrix coefficients are constants now, so D(m_ij) = 0 for all i, j from {x, y, z}. |
1489 | | |
1490 | | // D(z) <= |m_zx| * D(x) + |m_zy| * D(y), |
1491 | | // D(x_out) = D((m_xx * x + m_xy * y) / z) |
1492 | | // <= (|m_xx| * D(x) + |m_xy| * D(y)) / z0 + x_lim * D(z) / z0^2 |
1493 | | // <= (|m_xx| / z0 + |m_zx| * x_lim / z0^2) * D(x) |
1494 | | // + (|m_xy| / z0 + |m_zy| * x_lim / z0^2) * D(y), |
1495 | | // D(y_out) = D((m_yx * x + m_yy * y) / z) |
1496 | | // <= (|m_yx| * D(x) + |m_yy| * D(y)) / z0 + y_lim * D(z) / z0^2 |
1497 | | // <= (|m_yx| / z0 + |m_zx| * y_lim / z0^2) * D(x) |
1498 | | // + (|m_yy| / z0 + |m_zy| * y_lim / z0^2) * D(y). |
1499 | | |
1500 | | // Quantization steps (pick: ACCURACY = POSITION_PRECISION): |
1501 | | // STROKER_PRECISION / 2^scale_ord_x ~ D(x) ~ POSITION_PRECISION / |
1502 | | // (max(|m_xx|, |m_yx|) / z0 + |m_zx| * max(x_lim, y_lim) / z0^2), |
1503 | | // STROKER_PRECISION / 2^scale_ord_y ~ D(y) ~ POSITION_PRECISION / |
1504 | | // (max(|m_xy|, |m_yy|) / z0 + |m_zy| * max(x_lim, y_lim) / z0^2). |
1505 | | |
1506 | 493k | double x_lim = mxx * dx + mxy * dy; |
1507 | 493k | double y_lim = myx * dx + myy * dy; |
1508 | 493k | double rz = FFMAX(x_lim, y_lim) * w; |
1509 | | |
1510 | 493k | w *= STROKER_PRECISION / POSITION_PRECISION; |
1511 | 493k | frexp(w * (FFMAX(mxx, myx) + mzx * rz), &k->scale_ord_x); |
1512 | 493k | frexp(w * (FFMAX(mxy, myy) + mzy * rz), &k->scale_ord_y); |
1513 | 493k | bord_x = ldexp(bord_x, k->scale_ord_x); |
1514 | 493k | bord_y = ldexp(bord_y, k->scale_ord_y); |
1515 | 493k | if (!(bord_x < OUTLINE_MAX && bord_y < OUTLINE_MAX)) |
1516 | 0 | return; |
1517 | 493k | k->border.x = ass_lrint(bord_x / STROKER_PRECISION); |
1518 | 493k | k->border.y = ass_lrint(bord_y / STROKER_PRECISION); |
1519 | 493k | if (!k->border.x && !k->border.y) { |
1520 | 0 | info->bm_o = info->bm; |
1521 | 0 | return; |
1522 | 0 | } |
1523 | | |
1524 | 1.97M | for (int i = 0; i < 3; i++) { |
1525 | 1.47M | m[i][0] = ldexp(m2[i][0], -k->scale_ord_x); |
1526 | 1.47M | m[i][1] = ldexp(m2[i][1], -k->scale_ord_y); |
1527 | 1.47M | m[i][2] = m2[i][2]; |
1528 | 1.47M | } |
1529 | 493k | } |
1530 | | |
1531 | 493k | key.outline = ass_cache_get(render_priv->cache.outline_cache, &ol_key, render_priv); |
1532 | 493k | if (!key.outline || !key.outline->valid || |
1533 | 493k | !quantize_transform(m, pos_o, offset, false, &key)) |
1534 | 0 | return; |
1535 | | |
1536 | 493k | info->bm_o = ass_cache_get(render_priv->cache.bitmap_cache, &key, state); |
1537 | 493k | if (!info->bm_o || !info->bm_o->buffer) { |
1538 | 16.9k | info->bm_o = NULL; |
1539 | 16.9k | *pos_o = *pos; |
1540 | 476k | } else if (!info->bm) |
1541 | 0 | *pos = *pos_o; |
1542 | 493k | } |
1543 | | |
1544 | | static inline size_t outline_size(const ASS_Outline* outline) |
1545 | 35.0k | { |
1546 | 35.0k | return sizeof(ASS_Vector) * outline->n_points + outline->n_segments; |
1547 | 35.0k | } |
1548 | | |
1549 | | size_t ass_bitmap_construct(void *key, void *value, void *priv) |
1550 | 17.5k | { |
1551 | 17.5k | RenderContext *state = priv; |
1552 | 17.5k | BitmapHashKey *k = key; |
1553 | 17.5k | Bitmap *bm = value; |
1554 | | |
1555 | 17.5k | double m[3][3]; |
1556 | 17.5k | restore_transform(m, k); |
1557 | | |
1558 | 17.5k | ASS_Outline outline[2]; |
1559 | 17.5k | if (k->matrix_z.x || k->matrix_z.y) { |
1560 | 2 | ass_outline_transform_3d(&outline[0], &k->outline->outline[0], m); |
1561 | 2 | ass_outline_transform_3d(&outline[1], &k->outline->outline[1], m); |
1562 | 17.4k | } else { |
1563 | 17.4k | ass_outline_transform_2d(&outline[0], &k->outline->outline[0], m); |
1564 | 17.4k | ass_outline_transform_2d(&outline[1], &k->outline->outline[1], m); |
1565 | 17.4k | } |
1566 | | |
1567 | 17.5k | if (!ass_outline_to_bitmap(state, bm, &outline[0], &outline[1])) |
1568 | 478 | memset(bm, 0, sizeof(*bm)); |
1569 | 17.5k | ass_outline_free(&outline[0]); |
1570 | 17.5k | ass_outline_free(&outline[1]); |
1571 | | |
1572 | 17.5k | return sizeof(BitmapHashKey) + sizeof(Bitmap) + bitmap_size(bm) + |
1573 | 17.5k | sizeof(OutlineHashValue) + outline_size(&k->outline->outline[0]) + outline_size(&k->outline->outline[1]); |
1574 | 17.5k | } |
1575 | | |
1576 | | static void measure_text_on_eol(RenderContext *state, double scale, int cur_line, |
1577 | | int max_asc, int max_desc, |
1578 | | double max_border_x, double max_border_y) |
1579 | 86.6k | { |
1580 | 86.6k | TextInfo *text_info = &state->text_info; |
1581 | 86.6k | text_info->lines[cur_line].asc = scale * max_asc; |
1582 | 86.6k | text_info->lines[cur_line].desc = scale * max_desc; |
1583 | 86.6k | text_info->height += scale * max_asc + scale * max_desc; |
1584 | | // For *VSFilter compatibility do biased rounding on max_border* |
1585 | | // https://github.com/Cyberbeing/xy-VSFilter/blob/xy_sub_filter_rc4@%7B2020-05-17%7D/src/subtitles/RTS.cpp#L1465 |
1586 | 86.6k | text_info->border_bottom = (int) (state->border_scale_y * max_border_y + 0.5); |
1587 | 86.6k | if (cur_line == 0) |
1588 | 85.9k | text_info->border_top = text_info->border_bottom; |
1589 | | // VSFilter takes max \bordx into account for collision, even if far from edge |
1590 | 86.6k | text_info->border_x = FFMAX(text_info->border_x, |
1591 | 86.6k | (int) (state->border_scale_x * max_border_x + 0.5)); |
1592 | 86.6k | } |
1593 | | |
1594 | | |
1595 | | /** |
1596 | | * This function goes through text_info and calculates text parameters. |
1597 | | * The following text_info fields are filled: |
1598 | | * height |
1599 | | * border_top |
1600 | | * border_bottom |
1601 | | * border_x |
1602 | | * lines[].asc |
1603 | | * lines[].desc |
1604 | | */ |
1605 | | static void measure_text(RenderContext *state) |
1606 | 85.9k | { |
1607 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
1608 | 85.9k | TextInfo *text_info = &state->text_info; |
1609 | 85.9k | text_info->height = 0; |
1610 | 85.9k | text_info->border_x = 0; |
1611 | | |
1612 | 85.9k | int cur_line = 0; |
1613 | 85.9k | double scale = 0.5 / 64; |
1614 | 85.9k | int max_asc = 0, max_desc = 0; |
1615 | 85.9k | double max_border_y = 0, max_border_x = 0; |
1616 | 85.9k | bool empty_trimmed_line = true; |
1617 | 659k | for (int i = 0; i < text_info->length; i++) { |
1618 | 573k | if (text_info->glyphs[i].linebreak) { |
1619 | 653 | measure_text_on_eol(state, scale, cur_line, |
1620 | 653 | max_asc, max_desc, max_border_x, max_border_y); |
1621 | 653 | empty_trimmed_line = true; |
1622 | 653 | max_asc = max_desc = 0; |
1623 | 653 | max_border_y = max_border_x = 0; |
1624 | 653 | scale = 0.5 / 64; |
1625 | 653 | cur_line++; |
1626 | 653 | } |
1627 | 573k | GlyphInfo *cur = text_info->glyphs + i; |
1628 | | // VSFilter ignores metrics of line-leading/trailing (trimmed) |
1629 | | // whitespace, except when the line becomes empty after trimming |
1630 | 573k | if (empty_trimmed_line && !cur->is_trimmed_whitespace) { |
1631 | 84.4k | empty_trimmed_line = false; |
1632 | | // Forget metrics of line-leading whitespace |
1633 | 84.4k | max_asc = max_desc = 0; |
1634 | 84.4k | max_border_y = max_border_x = 0; |
1635 | 488k | } else if (!empty_trimmed_line && cur->is_trimmed_whitespace) { |
1636 | | // Ignore metrics of line-trailing whitespace |
1637 | 3 | continue; |
1638 | 3 | } |
1639 | 573k | max_asc = FFMAX(max_asc, cur->asc); |
1640 | 573k | max_desc = FFMAX(max_desc, cur->desc); |
1641 | 573k | max_border_y = FFMAX(max_border_y, cur->border_y); |
1642 | 573k | max_border_x = FFMAX(max_border_x, cur->border_x); |
1643 | 573k | if (cur->symbol != '\n') |
1644 | 571k | scale = 1.0 / 64; |
1645 | 573k | } |
1646 | 85.9k | assert(cur_line == text_info->n_lines - 1); |
1647 | 85.9k | measure_text_on_eol(state, scale, cur_line, |
1648 | 85.9k | max_asc, max_desc, max_border_x, max_border_y); |
1649 | 85.9k | text_info->height += cur_line * render_priv->settings.line_spacing; |
1650 | 85.9k | } |
1651 | | |
1652 | | /** |
1653 | | * Mark extra whitespace for later removal. |
1654 | | */ |
1655 | 145k | #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \ |
1656 | 145k | && !x->linebreak) |
1657 | | static void trim_whitespace(RenderContext *state) |
1658 | 85.9k | { |
1659 | 85.9k | int i, j; |
1660 | 85.9k | GlyphInfo *cur; |
1661 | 85.9k | TextInfo *ti = &state->text_info; |
1662 | | |
1663 | | // Mark trailing spaces |
1664 | 85.9k | i = ti->length - 1; |
1665 | 85.9k | cur = ti->glyphs + i; |
1666 | 85.9k | while (i && IS_WHITESPACE(cur)) { |
1667 | 0 | cur->skip = true; |
1668 | 0 | cur->is_trimmed_whitespace = true; |
1669 | 0 | cur = ti->glyphs + --i; |
1670 | 0 | } |
1671 | | |
1672 | | // Mark leading whitespace |
1673 | 85.9k | i = 0; |
1674 | 85.9k | cur = ti->glyphs; |
1675 | 89.8k | while (i < ti->length && IS_WHITESPACE(cur)) { |
1676 | 3.85k | cur->skip = true; |
1677 | 3.85k | cur->is_trimmed_whitespace = true; |
1678 | 3.85k | cur = ti->glyphs + ++i; |
1679 | 3.85k | } |
1680 | 85.9k | if (i < ti->length) |
1681 | 84.4k | cur->starts_new_run = true; |
1682 | | |
1683 | | // Mark all extraneous whitespace inbetween |
1684 | 659k | for (i = 0; i < ti->length; ++i) { |
1685 | 573k | cur = ti->glyphs + i; |
1686 | 573k | if (cur->linebreak) { |
1687 | | // Mark whitespace before |
1688 | 653 | j = i - 1; |
1689 | 653 | cur = ti->glyphs + j; |
1690 | 656 | while (j && IS_WHITESPACE(cur)) { |
1691 | 3 | cur->skip = true; |
1692 | 3 | cur->is_trimmed_whitespace = true; |
1693 | 3 | cur = ti->glyphs + --j; |
1694 | 3 | } |
1695 | | // A break itself can contain a whitespace, too |
1696 | 653 | cur = ti->glyphs + i; |
1697 | 653 | if (cur->symbol == ' ' || cur->symbol == '\n') { |
1698 | 0 | cur->skip = true; |
1699 | 0 | cur->is_trimmed_whitespace = true; |
1700 | | // Mark whitespace after |
1701 | 0 | j = i + 1; |
1702 | 0 | cur = ti->glyphs + j; |
1703 | 0 | while (j < ti->length && IS_WHITESPACE(cur)) { |
1704 | 0 | cur->skip = true; |
1705 | 0 | cur->is_trimmed_whitespace = true; |
1706 | 0 | cur = ti->glyphs + ++j; |
1707 | 0 | } |
1708 | 0 | i = j - 1; |
1709 | 0 | } |
1710 | 653 | if (cur < ti->glyphs + ti->length) |
1711 | 653 | cur->starts_new_run = true; |
1712 | 653 | } |
1713 | 573k | } |
1714 | 85.9k | } |
1715 | | #undef IS_WHITESPACE |
1716 | | |
1717 | | #ifdef CONFIG_UNIBREAK |
1718 | | #define ALLOWBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_ALLOWBREAK : glyph == ' ') |
1719 | | #define FORCEBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_MUSTBREAK : glyph == '\n') |
1720 | | #else |
1721 | 573k | #define ALLOWBREAK(glyph, index) (glyph == ' ') |
1722 | 573k | #define FORCEBREAK(glyph, index) (glyph == '\n') |
1723 | | #endif |
1724 | | |
1725 | | /* |
1726 | | * Starts a new line on the first breakable character after overflow |
1727 | | */ |
1728 | | static void |
1729 | | wrap_lines_naive(RenderContext *state, double max_text_width, char *unibrks) |
1730 | 85.9k | { |
1731 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
1732 | 85.9k | TextInfo *text_info = &state->text_info; |
1733 | 85.9k | GlyphInfo *s1 = text_info->glyphs; // current line start |
1734 | 85.9k | int last_breakable = -1; |
1735 | 85.9k | int break_type = 0; |
1736 | | |
1737 | 85.9k | text_info->n_lines = 1; |
1738 | 659k | for (int i = 0; i < text_info->length; ++i) { |
1739 | 573k | GlyphInfo *cur = text_info->glyphs + i; |
1740 | 573k | int break_at = -1; |
1741 | 573k | double s_offset = d6_to_double(s1->bbox.x_min + s1->pos.x); |
1742 | 573k | double len = d6_to_double(cur->bbox.x_max + cur->pos.x) - s_offset; |
1743 | | |
1744 | 573k | if (FORCEBREAK(cur->symbol, i)) { |
1745 | 1.44k | break_type = 2; |
1746 | 1.44k | break_at = i; |
1747 | 1.44k | ass_msg(render_priv->library, MSGL_DBG2, |
1748 | 1.44k | "forced line break at %d", break_at); |
1749 | 571k | } else if (len >= max_text_width && |
1750 | 2.78k | cur->symbol != ' ' /* get trimmed */ && |
1751 | 2.78k | (state->wrap_style != 2)) { |
1752 | 2.78k | break_type = 1; |
1753 | 2.78k | break_at = last_breakable; |
1754 | 2.78k | if (break_at >= 0) |
1755 | 3 | ass_msg(render_priv->library, MSGL_DBG2, "line break at %d", |
1756 | 3 | break_at); |
1757 | 2.78k | } |
1758 | 573k | if (ALLOWBREAK(cur->symbol, i)) { |
1759 | 16.0k | last_breakable = i; |
1760 | 16.0k | } |
1761 | | |
1762 | 573k | if (break_at != -1) { |
1763 | | // need to use one more line |
1764 | | // marking break_at+1 as start of a new line |
1765 | 1.44k | int lead = break_at + 1; // the first symbol of the new line |
1766 | 1.44k | if (text_info->n_lines >= text_info->max_lines) { |
1767 | | // Try to raise the maximum number of lines |
1768 | 0 | bool success = false; |
1769 | 0 | if (text_info->max_lines <= INT_MAX / 2) { |
1770 | 0 | text_info->max_lines *= 2; |
1771 | 0 | success = ASS_REALLOC_ARRAY(text_info->lines, text_info->max_lines); |
1772 | 0 | } |
1773 | | // If realloc fails it's screwed and due to error-info not propagating (FIXME), |
1774 | | // the best we can do is to avoid UB by discarding the previous break |
1775 | 0 | if (!success) { |
1776 | 0 | s1->linebreak = 0; |
1777 | 0 | text_info->n_lines--; |
1778 | 0 | } |
1779 | 0 | } |
1780 | 1.44k | if (lead < text_info->length) { |
1781 | 653 | text_info->glyphs[lead].linebreak = break_type; |
1782 | 653 | last_breakable = -1; |
1783 | 653 | s1 = text_info->glyphs + lead; |
1784 | 653 | text_info->n_lines++; |
1785 | 653 | } |
1786 | 1.44k | } |
1787 | 573k | } |
1788 | 85.9k | } |
1789 | | |
1790 | | /* |
1791 | | * Rewind from a linestart position back to the first non-whitespace (0x20) |
1792 | | * character. Trailing ASCII whitespace gets trimmed in rendering. |
1793 | | * Assumes both arguments are part of the same array. |
1794 | | */ |
1795 | | static inline GlyphInfo *rewind_trailing_spaces(GlyphInfo *start1, GlyphInfo* start2) |
1796 | 3 | { |
1797 | 3 | GlyphInfo *g = start2; |
1798 | 6 | do { |
1799 | 6 | --g; |
1800 | 6 | } while ((g > start1) && (g->symbol == ' ')); |
1801 | 3 | return g; |
1802 | 3 | } |
1803 | | |
1804 | | /* |
1805 | | * Shift soft linebreaks to balance out line lengths |
1806 | | * Does not change the linebreak count |
1807 | | * FIXME: implement style 0 and 3 correctly |
1808 | | */ |
1809 | | static void |
1810 | | wrap_lines_rebalance(RenderContext *state, double max_text_width, char *unibrks) |
1811 | 85.9k | { |
1812 | 85.9k | TextInfo *text_info = &state->text_info; |
1813 | 85.9k | int exit = 0; |
1814 | | |
1815 | 85.9k | #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) |
1816 | 171k | while (!exit && state->wrap_style != 1) { |
1817 | 85.9k | exit = 1; |
1818 | 85.9k | GlyphInfo *s1, *s2, *s3; |
1819 | 85.9k | s3 = text_info->glyphs; |
1820 | 85.9k | s1 = s2 = 0; |
1821 | 659k | for (int i = 0; i <= text_info->length; ++i) { |
1822 | 659k | GlyphInfo *cur = text_info->glyphs + i; |
1823 | 659k | if ((i == text_info->length) || cur->linebreak) { |
1824 | 86.6k | s1 = s2; |
1825 | 86.6k | s2 = s3; |
1826 | 86.6k | s3 = cur; |
1827 | 86.6k | if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' |
1828 | 3 | double l1, l2, l1_new, l2_new; |
1829 | | |
1830 | | // Find last word of line and trim surrounding whitespace before measuring |
1831 | | // (whitespace ' ' will also get trimmed in rendering) |
1832 | 3 | GlyphInfo *w = rewind_trailing_spaces(s1, s2); |
1833 | 3 | GlyphInfo *e1_old = w; |
1834 | 756 | while ((w > s1) && (!ALLOWBREAK(w->symbol, w - text_info->glyphs))) { |
1835 | 753 | --w; |
1836 | 753 | } |
1837 | 3 | GlyphInfo *e1 = w; |
1838 | 3 | while ((e1 > s1) && (e1->symbol == ' ')) { |
1839 | 0 | --e1; |
1840 | 0 | } |
1841 | 3 | if (w->symbol == ' ') |
1842 | 0 | ++w; |
1843 | 3 | if (w == s1) |
1844 | 3 | continue; // Merging linebreaks is never beneficial |
1845 | | |
1846 | 0 | GlyphInfo *e2 = rewind_trailing_spaces(s2, s3); |
1847 | |
|
1848 | 0 | l1 = d6_to_double( |
1849 | 0 | (e1_old->bbox.x_max + e1_old->pos.x) - |
1850 | 0 | (s1->bbox.x_min + s1->pos.x)); |
1851 | 0 | l2 = d6_to_double( |
1852 | 0 | (e2->bbox.x_max + e2->pos.x) - |
1853 | 0 | (s2->bbox.x_min + s2->pos.x)); |
1854 | 0 | l1_new = d6_to_double( |
1855 | 0 | (e1->bbox.x_max + e1->pos.x) - |
1856 | 0 | (s1->bbox.x_min + s1->pos.x)); |
1857 | 0 | l2_new = d6_to_double( |
1858 | 0 | (e2->bbox.x_max + e2->pos.x) - |
1859 | 0 | (w->bbox.x_min + w->pos.x)); |
1860 | |
|
1861 | 0 | if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { |
1862 | 0 | w->linebreak = 1; |
1863 | 0 | s2->linebreak = 0; |
1864 | 0 | s2 = w; |
1865 | 0 | exit = 0; |
1866 | 0 | } |
1867 | 0 | } |
1868 | 86.6k | } |
1869 | 659k | if (i == text_info->length) |
1870 | 85.9k | break; |
1871 | 659k | } |
1872 | | |
1873 | 85.9k | } |
1874 | 85.9k | assert(text_info->n_lines >= 1); |
1875 | 85.9k | #undef DIFF |
1876 | 85.9k | } |
1877 | | |
1878 | | static void |
1879 | | wrap_lines_measure(RenderContext *state, char *unibrks) |
1880 | 85.9k | { |
1881 | 85.9k | TextInfo *text_info = &state->text_info; |
1882 | 85.9k | int cur_line = 1; |
1883 | 85.9k | int i = 0; |
1884 | | |
1885 | 89.8k | while (i < text_info->length && text_info->glyphs[i].skip) |
1886 | 3.85k | ++i; |
1887 | 85.9k | double pen_shift_x = d6_to_double(-text_info->glyphs[i].pos.x); |
1888 | 85.9k | double pen_shift_y = 0.; |
1889 | | |
1890 | 659k | for (i = 0; i < text_info->length; ++i) { |
1891 | 573k | GlyphInfo *cur = text_info->glyphs + i; |
1892 | 573k | if (cur->linebreak) { |
1893 | 653 | while (i < text_info->length && cur->skip && !FORCEBREAK(cur->symbol, i)) |
1894 | 0 | cur = text_info->glyphs + ++i; |
1895 | 653 | double height = |
1896 | 653 | text_info->lines[cur_line - 1].desc + |
1897 | 653 | text_info->lines[cur_line].asc; |
1898 | 653 | text_info->lines[cur_line - 1].len = i - |
1899 | 653 | text_info->lines[cur_line - 1].offset; |
1900 | 653 | text_info->lines[cur_line].offset = i; |
1901 | 653 | cur_line++; |
1902 | 653 | pen_shift_x = d6_to_double(-cur->pos.x); |
1903 | 653 | pen_shift_y += height + state->renderer->settings.line_spacing; |
1904 | 653 | } |
1905 | 573k | cur->pos.x += double_to_d6(pen_shift_x); |
1906 | 573k | cur->pos.y += double_to_d6(pen_shift_y); |
1907 | 573k | } |
1908 | 85.9k | text_info->lines[cur_line - 1].len = |
1909 | 85.9k | text_info->length - text_info->lines[cur_line - 1].offset; |
1910 | 85.9k | } |
1911 | | |
1912 | | #undef ALLOWBREAK |
1913 | | #undef FORCEBREAK |
1914 | | |
1915 | | /** |
1916 | | * \brief rearrange text between lines |
1917 | | * \param max_text_width maximal text line width in pixels |
1918 | | * The algo is similar to the one in libvo/sub.c: |
1919 | | * 1. Place text, wrapping it when current line is full |
1920 | | * 2. Try moving words from the end of a line to the beginning of the next one while it reduces |
1921 | | * the difference in lengths between this two lines. |
1922 | | * The result may not be optimal, but usually is good enough. |
1923 | | * |
1924 | | * FIXME: implement style 0 and 3 correctly |
1925 | | */ |
1926 | | static void |
1927 | | wrap_lines_smart(RenderContext *state, double max_text_width) |
1928 | 85.9k | { |
1929 | 85.9k | char *unibrks = NULL; |
1930 | | |
1931 | | #ifdef CONFIG_UNIBREAK |
1932 | | ASS_Renderer *render_priv = state->renderer; |
1933 | | TextInfo *text_info = &state->text_info; |
1934 | | if (render_priv->track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE)) { |
1935 | | unibrks = text_info->breaks; |
1936 | | set_linebreaks_utf32( |
1937 | | text_info->event_text, text_info->length, |
1938 | | render_priv->track->Language, unibrks); |
1939 | | #if UNIBREAK_VERSION < 0x0500UL |
1940 | | // Prior to 5.0 libunibreaks always ended text with LINE_BREAKMUSTBREAK, matching |
1941 | | // Unicode spec, but messing with our text-overflow detection. |
1942 | | // Thus reevaluate the last char in a different context. |
1943 | | // (Later versions set either MUSTBREAK or the newly added INDETERMINATE) |
1944 | | unibrks[text_info->length - 1] = is_line_breakable( |
1945 | | text_info->event_text[text_info->length - 1], |
1946 | | ' ', |
1947 | | render_priv->track->Language |
1948 | | ); |
1949 | | #endif |
1950 | | } |
1951 | | #endif |
1952 | | |
1953 | 85.9k | wrap_lines_naive(state, max_text_width, unibrks); |
1954 | 85.9k | wrap_lines_rebalance(state, max_text_width, unibrks); |
1955 | | |
1956 | 85.9k | trim_whitespace(state); |
1957 | 85.9k | measure_text(state); |
1958 | 85.9k | wrap_lines_measure(state, unibrks); |
1959 | 85.9k | } |
1960 | | |
1961 | | /** |
1962 | | * \brief Calculate base point for positioning and rotation |
1963 | | * \param bbox text bbox |
1964 | | * \param alignment alignment |
1965 | | * \param bx, by out: base point coordinates |
1966 | | */ |
1967 | | static void get_base_point(ASS_DRect *bbox, int alignment, double *bx, double *by) |
1968 | 87.7k | { |
1969 | 87.7k | const int halign = alignment & 3; |
1970 | 87.7k | const int valign = alignment & 12; |
1971 | 87.7k | if (bx) |
1972 | 87.7k | switch (halign) { |
1973 | 1.17k | case HALIGN_LEFT: |
1974 | 1.17k | *bx = bbox->x_min; |
1975 | 1.17k | break; |
1976 | 84.3k | case HALIGN_CENTER: |
1977 | 84.3k | *bx = (bbox->x_max + bbox->x_min) / 2.0; |
1978 | 84.3k | break; |
1979 | 139 | case HALIGN_RIGHT: |
1980 | 139 | *bx = bbox->x_max; |
1981 | 139 | break; |
1982 | 87.7k | } |
1983 | 87.7k | if (by) |
1984 | 87.7k | switch (valign) { |
1985 | 393 | case VALIGN_TOP: |
1986 | 393 | *by = bbox->y_min; |
1987 | 393 | break; |
1988 | 684 | case VALIGN_CENTER: |
1989 | 684 | *by = (bbox->y_max + bbox->y_min) / 2.0; |
1990 | 684 | break; |
1991 | 86.6k | case VALIGN_SUB: |
1992 | 86.6k | *by = bbox->y_max; |
1993 | 86.6k | break; |
1994 | 87.7k | } |
1995 | 87.7k | } |
1996 | | |
1997 | | /** |
1998 | | * \brief Adjust the glyph's font size and scale factors to ensure smooth |
1999 | | * scaling and handle pathological font sizes. The main problem here is |
2000 | | * freetype's grid fitting, which destroys animations by font size, or will |
2001 | | * result in incorrect final text size if font sizes are very small and |
2002 | | * scale factors very large. See Google Code issue #46. |
2003 | | * \param priv guess what |
2004 | | * \param glyph the glyph to be modified |
2005 | | */ |
2006 | | static void |
2007 | | fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph) |
2008 | 569k | { |
2009 | 569k | double ft_size; |
2010 | 569k | if (priv->settings.hinting == ASS_HINTING_NONE) { |
2011 | | // arbitrary, not too small to prevent grid fitting rounding effects |
2012 | | // XXX: this is a rather crude hack |
2013 | 569k | ft_size = 256.0; |
2014 | 569k | } else { |
2015 | | // If hinting is enabled, we want to pass the real font size |
2016 | | // to freetype. Normalize scale_y to 1.0. |
2017 | 0 | ft_size = glyph->scale_y * glyph->font_size; |
2018 | 0 | } |
2019 | | |
2020 | 569k | if (!ft_size || !glyph->font_size) |
2021 | 64.0k | return; |
2022 | | |
2023 | 505k | double mul = glyph->font_size / ft_size; |
2024 | 505k | glyph->scale_fix = 1 / mul; |
2025 | 505k | glyph->scale_x *= mul; |
2026 | 505k | glyph->scale_y *= mul; |
2027 | 505k | glyph->font_size = ft_size; |
2028 | 505k | } |
2029 | | |
2030 | | // Initial run splitting based purely on the characters' styles |
2031 | | static void split_style_runs(RenderContext *state) |
2032 | 85.9k | { |
2033 | 85.9k | TextInfo *text_info = &state->text_info; |
2034 | 85.9k | Effect last_effect_type = text_info->glyphs[0].effect_type; |
2035 | 85.9k | text_info->glyphs[0].starts_new_run = true; |
2036 | 573k | for (int i = 1; i < text_info->length; i++) { |
2037 | 487k | GlyphInfo *info = text_info->glyphs + i; |
2038 | 487k | GlyphInfo *last = text_info->glyphs + (i - 1); |
2039 | 487k | Effect effect_type = info->effect_type; |
2040 | 487k | info->starts_new_run = |
2041 | 487k | info->effect_timing || // but ignore effect_skip_timing |
2042 | 487k | (effect_type != EF_NONE && effect_type != last_effect_type) || |
2043 | 487k | info->drawing_text.str || |
2044 | 486k | last->drawing_text.str || |
2045 | 486k | !ass_string_equal(last->font->desc.family, info->font->desc.family) || |
2046 | 486k | last->font->desc.vertical != info->font->desc.vertical || |
2047 | 486k | last->font_size != info->font_size || |
2048 | 486k | last->c[0] != info->c[0] || |
2049 | 486k | last->c[1] != info->c[1] || |
2050 | 486k | last->c[2] != info->c[2] || |
2051 | 486k | last->c[3] != info->c[3] || |
2052 | 486k | last->be != info->be || |
2053 | 486k | last->blur != info->blur || |
2054 | 486k | last->shadow_x != info->shadow_x || |
2055 | 486k | last->shadow_y != info->shadow_y || |
2056 | 486k | last->frx != info->frx || |
2057 | 486k | last->fry != info->fry || |
2058 | 486k | last->frz != info->frz || |
2059 | 486k | last->fax != info->fax || |
2060 | 486k | last->fay != info->fay || |
2061 | 486k | last->scale_x != info->scale_x || |
2062 | 486k | last->scale_y != info->scale_y || |
2063 | 486k | last->border_style != info->border_style || |
2064 | 486k | last->border_x != info->border_x || |
2065 | 486k | last->border_y != info->border_y || |
2066 | 486k | last->hspacing != info->hspacing || |
2067 | 486k | last->italic != info->italic || |
2068 | 486k | last->bold != info->bold || |
2069 | 486k | ((last->flags ^ info->flags) & ~DECO_ROTATE); |
2070 | 487k | if (effect_type != EF_NONE) |
2071 | 0 | last_effect_type = effect_type; |
2072 | 487k | } |
2073 | 85.9k | } |
2074 | | |
2075 | | // Parse event text. |
2076 | | // Fill render_priv->text_info. |
2077 | | static bool parse_events(RenderContext *state, ASS_Event *event) |
2078 | 313k | { |
2079 | 313k | TextInfo *text_info = &state->text_info; |
2080 | 313k | ASS_Renderer *render_priv = state->renderer; |
2081 | | |
2082 | 313k | char *p = event->Text, *q; |
2083 | | |
2084 | | // Event parsing. |
2085 | 886k | while (true) { |
2086 | 886k | ASS_StringView drawing_text = {NULL, 0}; |
2087 | | |
2088 | | // get next char, executing style override |
2089 | | // this affects render_context |
2090 | 886k | unsigned code = 0; |
2091 | 1.02M | while (*p) { |
2092 | 714k | if ((*p == '{') && (q = strchr(p, '}'))) { |
2093 | 140k | p = ass_parse_tags(state, p, q, 1., false); |
2094 | 140k | assert(*p == '}'); |
2095 | 140k | p++; |
2096 | 573k | } else if (state->drawing_scale) { |
2097 | 3.19k | q = p; |
2098 | 3.19k | if (*p == '{') |
2099 | 1.46k | q++; |
2100 | 12.2k | while ((*q != '{') && (*q != 0)) |
2101 | 9.05k | q++; |
2102 | 3.19k | drawing_text.str = p; |
2103 | 3.19k | drawing_text.len = q - p; |
2104 | 3.19k | code = 0xfffc; // object replacement character |
2105 | 3.19k | p = q; |
2106 | 3.19k | break; |
2107 | 569k | } else { |
2108 | 569k | code = ass_get_next_char(state, &p); |
2109 | 569k | break; |
2110 | 569k | } |
2111 | 714k | } |
2112 | | |
2113 | 886k | if (code == 0) |
2114 | 313k | break; |
2115 | | |
2116 | | // face could have been changed in get_next_char |
2117 | 573k | if (!state->font) |
2118 | 0 | goto fail; |
2119 | | |
2120 | 573k | if (text_info->length >= text_info->max_glyphs) { |
2121 | | // Raise maximum number of glyphs |
2122 | 0 | int new_max = 2 * FFMIN(FFMAX(text_info->max_glyphs, text_info->length / 2 + 1), |
2123 | 0 | INT_MAX / 2); |
2124 | 0 | if (text_info->length >= new_max) |
2125 | 0 | goto fail; |
2126 | 0 | if (!ASS_REALLOC_ARRAY(text_info->glyphs, new_max) || |
2127 | 0 | !ASS_REALLOC_ARRAY(text_info->event_text, new_max) || |
2128 | 0 | !ASS_REALLOC_ARRAY(text_info->breaks, new_max)) |
2129 | 0 | goto fail; |
2130 | 0 | text_info->max_glyphs = new_max; |
2131 | 0 | } |
2132 | | |
2133 | 573k | GlyphInfo *info = &text_info->glyphs[text_info->length]; |
2134 | | |
2135 | | // Clear current GlyphInfo |
2136 | 573k | memset(info, 0, sizeof(GlyphInfo)); |
2137 | | |
2138 | | // Parse drawing |
2139 | 573k | if (drawing_text.str) { |
2140 | 3.19k | info->drawing_text = drawing_text; |
2141 | 3.19k | info->drawing_scale = state->drawing_scale; |
2142 | 3.19k | info->drawing_pbo = state->pbo; |
2143 | 3.19k | } |
2144 | | |
2145 | | // Fill glyph information |
2146 | 573k | info->symbol = code; |
2147 | 573k | info->font = state->font; |
2148 | 2.86M | for (int i = 0; i < 4; i++) |
2149 | 2.29M | info->c[i] = state->c[i]; |
2150 | | |
2151 | 573k | info->effect_type = state->effect_type; |
2152 | 573k | info->effect_timing = state->effect_timing; |
2153 | 573k | info->effect_skip_timing = state->effect_skip_timing; |
2154 | 573k | info->reset_effect = state->reset_effect; |
2155 | | // VSFilter compatibility: font glyphs use PlayResY scaling in both dimensions |
2156 | 573k | info->font_size = |
2157 | 573k | fabs(state->font_size * state->screen_scale_y); |
2158 | 573k | info->be = state->be; |
2159 | 573k | info->blur = state->blur; |
2160 | 573k | info->shadow_x = state->shadow_x; |
2161 | 573k | info->shadow_y = state->shadow_y; |
2162 | 573k | info->scale_x = state->scale_x; |
2163 | 573k | info->scale_y = state->scale_y; |
2164 | 573k | info->border_style = state->border_style; |
2165 | 573k | info->border_x = state->border_x; |
2166 | 573k | info->border_y = state->border_y; |
2167 | 573k | info->hspacing = state->hspacing; |
2168 | 573k | info->bold = state->bold; |
2169 | 573k | info->italic = state->italic; |
2170 | 573k | info->flags = state->flags; |
2171 | 573k | if (info->font->desc.vertical && code >= VERTICAL_LOWER_BOUND) |
2172 | 0 | info->flags |= DECO_ROTATE; |
2173 | 573k | info->frx = state->frx; |
2174 | 573k | info->fry = state->fry; |
2175 | 573k | info->frz = state->frz; |
2176 | 573k | info->fax = state->fax; |
2177 | 573k | info->fay = state->fay; |
2178 | 573k | info->fade = state->fade; |
2179 | | |
2180 | 573k | info->hspacing_scaled = 0; |
2181 | 573k | info->scale_fix = 1; |
2182 | | |
2183 | 573k | if (!drawing_text.str) { |
2184 | 569k | info->hspacing_scaled = double_to_d6(info->hspacing * |
2185 | 569k | state->screen_scale_x / render_priv->par_scale_x * |
2186 | 569k | info->scale_x); |
2187 | 569k | fix_glyph_scaling(render_priv, info); |
2188 | 569k | } |
2189 | | |
2190 | 573k | text_info->length++; |
2191 | | |
2192 | 573k | state->effect_type = EF_NONE; |
2193 | 573k | state->effect_timing = 0; |
2194 | 573k | state->effect_skip_timing = 0; |
2195 | 573k | state->reset_effect = false; |
2196 | 573k | } |
2197 | | |
2198 | 313k | return true; |
2199 | | |
2200 | 0 | fail: |
2201 | 0 | free_render_context(state); |
2202 | 0 | return false; |
2203 | 313k | } |
2204 | | |
2205 | | // Process render_priv->text_info and load glyph outlines. |
2206 | | static void retrieve_glyphs(RenderContext *state) |
2207 | 85.9k | { |
2208 | 85.9k | GlyphInfo *glyphs = state->text_info.glyphs; |
2209 | 85.9k | int i; |
2210 | | |
2211 | 659k | for (i = 0; i < state->text_info.length; i++) { |
2212 | 573k | GlyphInfo *info = glyphs + i; |
2213 | 573k | do { |
2214 | 573k | get_outline_glyph(state, info); |
2215 | 573k | info = info->next; |
2216 | 573k | } while (info); |
2217 | 573k | info = glyphs + i; |
2218 | | |
2219 | | // Add additional space after italic to non-italic style changes |
2220 | 573k | if (i && glyphs[i - 1].italic && !info->italic) { |
2221 | 0 | int back = i - 1; |
2222 | 0 | GlyphInfo *og = &glyphs[back]; |
2223 | 0 | while (back && og->bbox.x_max - og->bbox.x_min == 0 |
2224 | 0 | && og->italic) |
2225 | 0 | og = &glyphs[--back]; |
2226 | 0 | if (og->bbox.x_max > og->cluster_advance.x) |
2227 | 0 | og->cluster_advance.x = og->bbox.x_max; |
2228 | 0 | } |
2229 | | |
2230 | | // add horizontal letter spacing |
2231 | 573k | info->cluster_advance.x += info->hspacing_scaled; |
2232 | 573k | } |
2233 | 85.9k | } |
2234 | | |
2235 | | // Preliminary layout (for line wrapping) |
2236 | | static void preliminary_layout(RenderContext *state) |
2237 | 85.9k | { |
2238 | 85.9k | ASS_Vector pen = { 0, 0 }; |
2239 | 659k | for (int i = 0; i < state->text_info.length; i++) { |
2240 | 573k | GlyphInfo *info = state->text_info.glyphs + i; |
2241 | 573k | ASS_Vector cluster_pen = pen; |
2242 | 573k | do { |
2243 | 573k | info->pos.x = cluster_pen.x; |
2244 | 573k | info->pos.y = cluster_pen.y; |
2245 | | |
2246 | 573k | cluster_pen.x += info->advance.x; |
2247 | 573k | cluster_pen.y += info->advance.y; |
2248 | | |
2249 | 573k | info = info->next; |
2250 | 573k | } while (info); |
2251 | 573k | info = state->text_info.glyphs + i; |
2252 | 573k | pen.x += info->cluster_advance.x; |
2253 | 573k | pen.y += info->cluster_advance.y; |
2254 | 573k | } |
2255 | 85.9k | } |
2256 | | |
2257 | | // Reorder text into visual order |
2258 | | static void reorder_text(RenderContext *state) |
2259 | 85.9k | { |
2260 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
2261 | 85.9k | TextInfo *text_info = &state->text_info; |
2262 | 85.9k | FriBidiStrIndex *cmap = ass_shaper_reorder(state->shaper, text_info); |
2263 | 85.9k | if (!cmap) { |
2264 | 0 | ass_msg(render_priv->library, MSGL_ERR, "Failed to reorder text"); |
2265 | 0 | ass_shaper_cleanup(state->shaper, text_info); |
2266 | 0 | free_render_context(state); |
2267 | 0 | return; |
2268 | 0 | } |
2269 | | |
2270 | | // Reposition according to the map |
2271 | 85.9k | ASS_Vector pen = { 0, 0 }; |
2272 | 85.9k | int lineno = 1; |
2273 | 659k | for (int i = 0; i < text_info->length; i++) { |
2274 | 573k | GlyphInfo *info = text_info->glyphs + cmap[i]; |
2275 | 573k | if (text_info->glyphs[i].linebreak) { |
2276 | 653 | pen.x = 0; |
2277 | 653 | pen.y += double_to_d6(text_info->lines[lineno-1].desc); |
2278 | 653 | pen.y += double_to_d6(text_info->lines[lineno].asc); |
2279 | 653 | pen.y += double_to_d6(render_priv->settings.line_spacing); |
2280 | 653 | lineno++; |
2281 | 653 | } |
2282 | 573k | if (info->skip) |
2283 | 3.85k | continue; |
2284 | 569k | ASS_Vector cluster_pen = pen; |
2285 | 569k | pen.x += info->cluster_advance.x; |
2286 | 569k | pen.y += info->cluster_advance.y; |
2287 | 1.13M | while (info) { |
2288 | 569k | info->pos.x = info->offset.x + cluster_pen.x; |
2289 | 569k | info->pos.y = info->offset.y + cluster_pen.y; |
2290 | 569k | cluster_pen.x += info->advance.x; |
2291 | 569k | cluster_pen.y += info->advance.y; |
2292 | 569k | info = info->next; |
2293 | 569k | } |
2294 | 569k | } |
2295 | 85.9k | } |
2296 | | |
2297 | | static void apply_baseline_shear(RenderContext *state) |
2298 | 85.9k | { |
2299 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
2300 | 85.9k | TextInfo *text_info = &state->text_info; |
2301 | 85.9k | FriBidiStrIndex *cmap = ass_shaper_get_reorder_map(state->shaper); |
2302 | 85.9k | int32_t shear = 0; |
2303 | 85.9k | bool whole_text_layout = |
2304 | 85.9k | render_priv->track->parser_priv->feature_flags & |
2305 | 85.9k | FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT); |
2306 | 659k | for (int i = 0; i < text_info->length; i++) { |
2307 | 573k | GlyphInfo *info = text_info->glyphs + cmap[i]; |
2308 | 573k | if (text_info->glyphs[i].linebreak || |
2309 | 572k | (!whole_text_layout && text_info->glyphs[i].starts_new_run)) |
2310 | 88.9k | shear = 0; |
2311 | 573k | if (!info->scale_x || !info->scale_y) |
2312 | 9.14k | info->skip = true; |
2313 | 573k | if (info->skip) |
2314 | 13.0k | continue; |
2315 | 560k | double fay = info->fay / info->scale_x * info->scale_y; |
2316 | 1.12M | for (GlyphInfo *cur = info; cur; cur = cur->next) { |
2317 | 560k | cur->pos.y += shear + fay * cur->offset.x; |
2318 | 560k | shear += fay * cur->advance.x; |
2319 | 560k | } |
2320 | 560k | } |
2321 | 85.9k | } |
2322 | | |
2323 | | static void align_lines(RenderContext *state, double max_text_width) |
2324 | 85.9k | { |
2325 | 85.9k | TextInfo *text_info = &state->text_info; |
2326 | 85.9k | GlyphInfo *glyphs = text_info->glyphs; |
2327 | 85.9k | int i, j; |
2328 | 85.9k | double width = 0; |
2329 | 85.9k | int last_break = -1; |
2330 | 85.9k | int halign = state->alignment & 3; |
2331 | 85.9k | int justify = state->justify; |
2332 | 85.9k | double max_width = 0; |
2333 | | |
2334 | 85.9k | if (state->evt_type & EVENT_HSCROLL) { |
2335 | 252 | justify = halign; |
2336 | 252 | halign = HALIGN_LEFT; |
2337 | 252 | } |
2338 | | |
2339 | 745k | for (i = 0; i <= text_info->length; ++i) { // (text_info->length + 1) is the end of the last line |
2340 | 659k | if ((i == text_info->length) || glyphs[i].linebreak) { |
2341 | 86.6k | max_width = FFMAX(max_width,width); |
2342 | 86.6k | width = 0; |
2343 | 86.6k | } |
2344 | 659k | if (i < text_info->length && !glyphs[i].skip && |
2345 | 569k | glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) { |
2346 | 569k | width += d6_to_double(glyphs[i].cluster_advance.x); |
2347 | 569k | } |
2348 | 659k | } |
2349 | 745k | for (i = 0; i <= text_info->length; ++i) { // (text_info->length + 1) is the end of the last line |
2350 | 659k | if ((i == text_info->length) || glyphs[i].linebreak) { |
2351 | 86.6k | double shift = 0; |
2352 | 86.6k | if (halign == HALIGN_LEFT) { // left aligned, no action |
2353 | 1.42k | if (justify == ASS_JUSTIFY_RIGHT) { |
2354 | 0 | shift = max_width - width; |
2355 | 1.42k | } else if (justify == ASS_JUSTIFY_CENTER) { |
2356 | 252 | shift = (max_width - width) / 2.0; |
2357 | 1.17k | } else { |
2358 | 1.17k | shift = 0; |
2359 | 1.17k | } |
2360 | 85.2k | } else if (halign == HALIGN_RIGHT) { // right aligned |
2361 | 139 | if (justify == ASS_JUSTIFY_LEFT) { |
2362 | 0 | shift = max_text_width - max_width; |
2363 | 139 | } else if (justify == ASS_JUSTIFY_CENTER) { |
2364 | 0 | shift = max_text_width - max_width + (max_width - width) / 2.0; |
2365 | 139 | } else { |
2366 | 139 | shift = max_text_width - width; |
2367 | 139 | } |
2368 | 85.0k | } else if (halign == HALIGN_CENTER) { // centered |
2369 | 83.3k | if (justify == ASS_JUSTIFY_LEFT) { |
2370 | 0 | shift = (max_text_width - max_width) / 2.0; |
2371 | 83.3k | } else if (justify == ASS_JUSTIFY_RIGHT) { |
2372 | 0 | shift = (max_text_width - max_width) / 2.0 + max_width - width; |
2373 | 83.3k | } else { |
2374 | 83.3k | shift = (max_text_width - width) / 2.0; |
2375 | 83.3k | } |
2376 | 83.3k | } |
2377 | 659k | for (j = last_break + 1; j < i; ++j) { |
2378 | 573k | GlyphInfo *info = glyphs + j; |
2379 | 1.14M | while (info) { |
2380 | 573k | info->pos.x += double_to_d6(shift); |
2381 | 573k | info = info->next; |
2382 | 573k | } |
2383 | 573k | } |
2384 | 86.6k | last_break = i - 1; |
2385 | 86.6k | width = 0; |
2386 | 86.6k | } |
2387 | 659k | if (i < text_info->length && !glyphs[i].skip && |
2388 | 569k | glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) { |
2389 | 569k | width += d6_to_double(glyphs[i].cluster_advance.x); |
2390 | 569k | } |
2391 | 659k | } |
2392 | 85.9k | } |
2393 | | |
2394 | | static void calculate_rotation_params(RenderContext *state, ASS_DRect *bbox, |
2395 | | double device_x, double device_y) |
2396 | 85.9k | { |
2397 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
2398 | 85.9k | ASS_DVector center; |
2399 | 85.9k | if (state->have_origin) { |
2400 | 33 | center.x = x2scr_pos(render_priv, state->org_x); |
2401 | 33 | center.y = y2scr_pos(render_priv, state->org_y); |
2402 | 85.9k | } else { |
2403 | 85.9k | double bx = 0., by = 0.; |
2404 | 85.9k | get_base_point(bbox, state->alignment, &bx, &by); |
2405 | 85.9k | center.x = device_x + bx; |
2406 | 85.9k | center.y = device_y + by; |
2407 | 85.9k | } |
2408 | | |
2409 | 85.9k | TextInfo *text_info = &state->text_info; |
2410 | 659k | for (int i = 0; i < text_info->length; i++) { |
2411 | 573k | GlyphInfo *info = text_info->glyphs + i; |
2412 | 1.14M | while (info) { |
2413 | 573k | info->shift.x = info->pos.x + double_to_d6(device_x - center.x + |
2414 | 573k | info->shadow_x * state->border_scale_x / |
2415 | 573k | render_priv->par_scale_x); |
2416 | 573k | info->shift.y = info->pos.y + double_to_d6(device_y - center.y + |
2417 | 573k | info->shadow_y * state->border_scale_y); |
2418 | 573k | info = info->next; |
2419 | 573k | } |
2420 | 573k | } |
2421 | 85.9k | } |
2422 | | |
2423 | | |
2424 | | static int quantize_blur(double radius, int32_t *shadow_mask) |
2425 | 167k | { |
2426 | | // Gaussian filter kernel (1D): |
2427 | | // G(x, r2) = exp(-x^2 / (2 * r2)) / sqrt(2 * pi * r2), |
2428 | | // position unit is 1/64th of pixel, r = 64 * radius, r2 = r^2. |
2429 | | |
2430 | | // Difference between kernels with different but near r2: |
2431 | | // G(x, r2 + dr2) - G(x, r2) ~= dr2 * G(x, r2) * (x^2 - r2) / (2 * r2^2). |
2432 | | // Maximal possible error relative to full pixel value is half of |
2433 | | // integral (from -inf to +inf) of absolute value of that difference. |
2434 | | // E_max ~= dr2 / 2 * integral(G(x, r2) * |x^2 - r2| / (2 * r2^2), x) |
2435 | | // = dr2 / (4 * r2) * integral(G(y, 1) * |y^2 - 1|, y) |
2436 | | // = dr2 / (4 * r2) * 4 / sqrt(2 * pi * e) |
2437 | | // ~ dr2 / (4 * r2) ~= dr / (2 * r). |
2438 | | // E_max ~ BLUR_PRECISION / 2 as we have 2 dimensions. |
2439 | | |
2440 | | // To get discretized blur radius solve the following |
2441 | | // differential equation (n--quantization index): |
2442 | | // dr(n) / dn = BLUR_PRECISION * r + POSITION_PRECISION, r(0) = 0, |
2443 | | // r(n) = (exp(BLUR_PRECISION * n) - 1) * POSITION_PRECISION / BLUR_PRECISION, |
2444 | | // n = log(1 + r * BLUR_PRECISION / POSITION_PRECISION) / BLUR_PRECISION. |
2445 | | |
2446 | | // To get shadow offset quantization estimate difference of |
2447 | | // G(x + dx, r2) - G(x, r2) ~= dx * G(x, r2) * (-x / r2). |
2448 | | // E_max ~= dx / 2 * integral(G(x, r2) * |x| / r2, x) |
2449 | | // = dx / sqrt(2 * pi * r2) ~ dx / (2 * r). |
2450 | | // 2^ord ~ dx ~ BLUR_PRECISION * r + POSITION_PRECISION. |
2451 | | |
2452 | 167k | const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION; |
2453 | 167k | radius *= scale; |
2454 | | |
2455 | 167k | int ord; |
2456 | | // ord = floor(log2(BLUR_PRECISION * r + POSITION_PRECISION)) |
2457 | | // = floor(log2(64 * radius * BLUR_PRECISION + POSITION_PRECISION)) |
2458 | | // = floor(log2((radius * scale + 1) * POSITION_PRECISION)), |
2459 | | // floor(log2(x)) = frexp(x) - 1 = frexp(x / 2). |
2460 | 167k | frexp((1 + radius) * (POSITION_PRECISION / 2), &ord); |
2461 | 167k | *shadow_mask = ((uint32_t) 1 << ord) - 1; |
2462 | 167k | return ass_lrint(log1p(radius) / BLUR_PRECISION); |
2463 | 167k | } |
2464 | | |
2465 | | static double restore_blur(int qblur) |
2466 | 4.89k | { |
2467 | 4.89k | const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION; |
2468 | 4.89k | double sigma = expm1(BLUR_PRECISION * qblur) / scale; |
2469 | 4.89k | return sigma * sigma; |
2470 | 4.89k | } |
2471 | | |
2472 | | // Convert glyphs to bitmaps, combine them, apply blur, generate shadows. |
2473 | | static void render_and_combine_glyphs(RenderContext *state, |
2474 | | double device_x, double device_y) |
2475 | 85.9k | { |
2476 | 85.9k | ASS_Renderer *render_priv = state->renderer; |
2477 | 85.9k | TextInfo *text_info = &state->text_info; |
2478 | 85.9k | int left = render_priv->settings.left_margin; |
2479 | 85.9k | device_x = (device_x - left) * render_priv->par_scale_x + left; |
2480 | 85.9k | unsigned nb_bitmaps = 0; |
2481 | 85.9k | bool new_run = true; |
2482 | 85.9k | CombinedBitmapInfo *combined_info = text_info->combined_bitmaps; |
2483 | 85.9k | CombinedBitmapInfo *current_info = NULL; |
2484 | 85.9k | ASS_DVector offset; |
2485 | 659k | for (int i = 0; i < text_info->length; i++) { |
2486 | 573k | GlyphInfo *info = text_info->glyphs + i; |
2487 | 573k | if (info->starts_new_run) new_run = true; |
2488 | 573k | if (info->skip) |
2489 | 13.0k | continue; |
2490 | | |
2491 | 1.12M | for (; info; info = info->next) { |
2492 | 560k | int flags = 0; |
2493 | 560k | if (info->border_style == 3) |
2494 | 0 | flags |= FILTER_BORDER_STYLE_3; |
2495 | 560k | if (info->border_x || info->border_y) |
2496 | 494k | flags |= FILTER_NONZERO_BORDER; |
2497 | 560k | if (info->shadow_x || info->shadow_y) |
2498 | 495k | flags |= FILTER_NONZERO_SHADOW; |
2499 | 560k | if (flags & FILTER_NONZERO_SHADOW && |
2500 | 495k | (info->effect_type == EF_KARAOKE_KF || |
2501 | 486k | info->effect_type == EF_KARAOKE_KO || |
2502 | 485k | _a(info->c[0]) != 0xFF || |
2503 | 1.72k | info->border_style == 3)) |
2504 | 493k | flags |= FILTER_FILL_IN_SHADOW; |
2505 | 560k | if (!(flags & FILTER_NONZERO_BORDER) && |
2506 | 65.3k | !(flags & FILTER_FILL_IN_SHADOW)) |
2507 | 63.9k | flags &= ~FILTER_NONZERO_SHADOW; |
2508 | 560k | if ((flags & FILTER_NONZERO_BORDER && |
2509 | 494k | _a(info->c[0]) == 0 && |
2510 | 492k | _a(info->c[1]) == 0 && |
2511 | 492k | info->fade == 0) || |
2512 | 68.5k | info->border_style == 3) |
2513 | 491k | flags |= FILTER_FILL_IN_BORDER; |
2514 | | |
2515 | 560k | if (new_run) { |
2516 | 83.8k | if (nb_bitmaps >= text_info->max_bitmaps) { |
2517 | 1 | size_t new_size = 2 * text_info->max_bitmaps; |
2518 | 1 | if (!ASS_REALLOC_ARRAY(text_info->combined_bitmaps, new_size)) |
2519 | 0 | continue; |
2520 | | |
2521 | 1 | text_info->max_bitmaps = new_size; |
2522 | 1 | combined_info = text_info->combined_bitmaps; |
2523 | 1 | } |
2524 | 83.8k | current_info = &combined_info[nb_bitmaps]; |
2525 | | |
2526 | 83.8k | memcpy(¤t_info->c, &info->c, sizeof(info->c)); |
2527 | 419k | for (int i = 0; i < 4; i++) |
2528 | 335k | ass_apply_fade(¤t_info->c[i], info->fade); |
2529 | | |
2530 | 83.8k | current_info->effect_type = info->effect_type; |
2531 | 83.8k | current_info->effect_timing = info->effect_timing; |
2532 | 83.8k | current_info->leftmost_x = OUTLINE_MAX; |
2533 | | |
2534 | 83.8k | FilterDesc *filter = ¤t_info->filter; |
2535 | 83.8k | filter->flags = flags; |
2536 | 83.8k | filter->be = info->be; |
2537 | | |
2538 | 83.8k | int32_t shadow_mask_x, shadow_mask_y; |
2539 | 83.8k | double blur_radius_scale = 2 / sqrt(log(256)); |
2540 | 83.8k | double blur_scale_x = state->blur_scale_x * blur_radius_scale; |
2541 | 83.8k | double blur_scale_y = state->blur_scale_y * blur_radius_scale; |
2542 | 83.8k | filter->blur_x = quantize_blur(info->blur * blur_scale_x, &shadow_mask_x); |
2543 | 83.8k | filter->blur_y = quantize_blur(info->blur * blur_scale_y, &shadow_mask_y); |
2544 | 83.8k | if (flags & FILTER_NONZERO_SHADOW) { |
2545 | 82.0k | int32_t x = double_to_d6(info->shadow_x * state->border_scale_x); |
2546 | 82.0k | int32_t y = double_to_d6(info->shadow_y * state->border_scale_y); |
2547 | 82.0k | filter->shadow.x = (x + (shadow_mask_x >> 1)) & ~shadow_mask_x; |
2548 | 82.0k | filter->shadow.y = (y + (shadow_mask_y >> 1)) & ~shadow_mask_y; |
2549 | 82.0k | } else |
2550 | 1.81k | filter->shadow.x = filter->shadow.y = 0; |
2551 | | |
2552 | 83.8k | current_info->x = current_info->y = INT_MAX; |
2553 | 83.8k | current_info->bm = current_info->bm_o = current_info->bm_s = NULL; |
2554 | 83.8k | current_info->image = NULL; |
2555 | | |
2556 | 83.8k | current_info->bitmap_count = current_info->max_bitmap_count = 0; |
2557 | 83.8k | current_info->bitmaps = malloc(MAX_SUB_BITMAPS_INITIAL * sizeof(BitmapRef)); |
2558 | 83.8k | if (!current_info->bitmaps) |
2559 | 0 | continue; |
2560 | | |
2561 | 83.8k | current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL; |
2562 | | |
2563 | 83.8k | nb_bitmaps++; |
2564 | 83.8k | new_run = false; |
2565 | 83.8k | } |
2566 | 560k | assert(current_info); |
2567 | | |
2568 | 560k | ASS_Vector pos, pos_o; |
2569 | 560k | info->pos.x = double_to_d6(device_x + d6_to_double(info->pos.x) * render_priv->par_scale_x); |
2570 | 560k | info->pos.y = double_to_d6(device_y) + info->pos.y; |
2571 | 560k | get_bitmap_glyph(state, info, ¤t_info->leftmost_x, &pos, &pos_o, |
2572 | 560k | &offset, !current_info->bitmap_count, flags); |
2573 | | |
2574 | 560k | if (!info->bm && !info->bm_o) |
2575 | 82.5k | continue; |
2576 | | |
2577 | 477k | if (current_info->bitmap_count >= current_info->max_bitmap_count) { |
2578 | 420 | size_t new_size = 2 * current_info->max_bitmap_count; |
2579 | 420 | if (!ASS_REALLOC_ARRAY(current_info->bitmaps, new_size)) |
2580 | 0 | continue; |
2581 | | |
2582 | 420 | current_info->max_bitmap_count = new_size; |
2583 | 420 | } |
2584 | 477k | current_info->bitmaps[current_info->bitmap_count].bm = info->bm; |
2585 | 477k | current_info->bitmaps[current_info->bitmap_count].bm_o = info->bm_o; |
2586 | 477k | current_info->bitmaps[current_info->bitmap_count].pos = pos; |
2587 | 477k | current_info->bitmaps[current_info->bitmap_count].pos_o = pos_o; |
2588 | 477k | current_info->bitmap_count++; |
2589 | | |
2590 | 477k | current_info->x = FFMIN(current_info->x, pos.x); |
2591 | 477k | current_info->y = FFMIN(current_info->y, pos.y); |
2592 | 477k | } |
2593 | 560k | } |
2594 | | |
2595 | 169k | for (int i = 0; i < nb_bitmaps; i++) { |
2596 | 83.8k | CombinedBitmapInfo *info = &combined_info[i]; |
2597 | 83.8k | if (!info->bitmap_count) { |
2598 | 5.80k | free(info->bitmaps); |
2599 | 5.80k | continue; |
2600 | 5.80k | } |
2601 | | |
2602 | 78.0k | if (info->effect_type == EF_KARAOKE_KF) |
2603 | 1.49k | info->effect_timing = lround(d6_to_double(info->leftmost_x) + |
2604 | 1.49k | d6_to_double(info->effect_timing) * render_priv->par_scale_x); |
2605 | | |
2606 | 555k | for (int j = 0; j < info->bitmap_count; j++) { |
2607 | 477k | info->bitmaps[j].pos.x -= info->x; |
2608 | 477k | info->bitmaps[j].pos.y -= info->y; |
2609 | 477k | info->bitmaps[j].pos_o.x -= info->x; |
2610 | 477k | info->bitmaps[j].pos_o.y -= info->y; |
2611 | 477k | } |
2612 | | |
2613 | 78.0k | CompositeHashKey key; |
2614 | 78.0k | key.filter = info->filter; |
2615 | 78.0k | key.bitmap_count = info->bitmap_count; |
2616 | 78.0k | key.bitmaps = info->bitmaps; |
2617 | 78.0k | CompositeHashValue *val = ass_cache_get(render_priv->cache.composite_cache, &key, render_priv); |
2618 | 78.0k | if (!val) |
2619 | 0 | continue; |
2620 | | |
2621 | 78.0k | if (val->bm.buffer) |
2622 | 78.0k | info->bm = &val->bm; |
2623 | 78.0k | if (val->bm_o.buffer) |
2624 | 77.5k | info->bm_o = &val->bm_o; |
2625 | 78.0k | if (val->bm_s.buffer) |
2626 | 77.9k | info->bm_s = &val->bm_s; |
2627 | 78.0k | info->image = val; |
2628 | 78.0k | continue; |
2629 | 78.0k | } |
2630 | | |
2631 | 85.9k | text_info->n_bitmaps = nb_bitmaps; |
2632 | 85.9k | } |
2633 | | |
2634 | | static inline void rectangle_combine(ASS_Rect *rect, const Bitmap *bm, ASS_Vector pos) |
2635 | 20.1k | { |
2636 | 20.1k | pos.x += bm->left; |
2637 | 20.1k | pos.y += bm->top; |
2638 | 20.1k | rectangle_update(rect, pos.x, pos.y, pos.x + bm->w, pos.y + bm->h); |
2639 | 20.1k | } |
2640 | | |
2641 | | /* |
2642 | | * To find these values, simulate blur on the border between two |
2643 | | * half-planes, one zero-filled (background) and the other filled |
2644 | | * with the maximum supported value (foreground). Keep incrementing |
2645 | | * the \be argument. The necessary padding is the distance by which |
2646 | | * the blurred foreground image extends beyond the original border |
2647 | | * and into the background. Initially it increases along with \be, |
2648 | | * but very soon it grinds to a halt. At some point, the blurred |
2649 | | * image actually reaches a stationary point and stays unchanged |
2650 | | * forever after, simply _shifting_ by one pixel for each \be |
2651 | | * step--moving in the direction of the non-zero half-plane and |
2652 | | * thus decreasing the necessary padding (although the large |
2653 | | * padding is still needed for intermediate results). In practice, |
2654 | | * images are finite rather than infinite like half-planes, but |
2655 | | * this can only decrease the required padding. Half-planes filled |
2656 | | * with extreme values are the theoretical limit of the worst case. |
2657 | | * Make sure to use the right pixel value range in the simulation! |
2658 | | */ |
2659 | | int ass_be_padding(int be) |
2660 | 2.44k | { |
2661 | 2.44k | if (be <= 3) |
2662 | 2.43k | return be; |
2663 | 10 | if (be <= 7) |
2664 | 0 | return 4; |
2665 | 10 | return 5; |
2666 | 10 | } |
2667 | | |
2668 | | |
2669 | | size_t ass_composite_construct(void *key, void *value, void *priv) |
2670 | 2.44k | { |
2671 | 2.44k | ASS_Renderer *render_priv = priv; |
2672 | 2.44k | CompositeHashKey *k = key; |
2673 | 2.44k | CompositeHashValue *v = value; |
2674 | 2.44k | memset(v, 0, sizeof(*v)); |
2675 | | |
2676 | 2.44k | ASS_Rect rect, rect_o; |
2677 | 2.44k | rectangle_reset(&rect); |
2678 | 2.44k | rectangle_reset(&rect_o); |
2679 | | |
2680 | 2.44k | size_t n_bm = 0, n_bm_o = 0; |
2681 | 2.44k | BitmapRef *last = NULL, *last_o = NULL; |
2682 | 12.5k | for (int i = 0; i < k->bitmap_count; i++) { |
2683 | 10.0k | BitmapRef *ref = &k->bitmaps[i]; |
2684 | 10.0k | if (ref->bm) { |
2685 | 10.0k | rectangle_combine(&rect, ref->bm, ref->pos); |
2686 | 10.0k | last = ref; |
2687 | 10.0k | n_bm++; |
2688 | 10.0k | } |
2689 | 10.0k | if (ref->bm_o) { |
2690 | 10.0k | rectangle_combine(&rect_o, ref->bm_o, ref->pos_o); |
2691 | 10.0k | last_o = ref; |
2692 | 10.0k | n_bm_o++; |
2693 | 10.0k | } |
2694 | 10.0k | } |
2695 | | |
2696 | 2.44k | int bord = ass_be_padding(k->filter.be); |
2697 | 2.44k | if (!bord && n_bm == 1) { |
2698 | 962 | ass_copy_bitmap(&render_priv->engine, &v->bm, last->bm); |
2699 | 962 | v->bm.left += last->pos.x; |
2700 | 962 | v->bm.top += last->pos.y; |
2701 | 1.48k | } else if (n_bm && ass_alloc_bitmap(&render_priv->engine, &v->bm, |
2702 | 1.48k | rect.x_max - rect.x_min + 2 * bord, |
2703 | 1.48k | rect.y_max - rect.y_min + 2 * bord, |
2704 | 1.48k | true)) { |
2705 | 1.48k | Bitmap *dst = &v->bm; |
2706 | 1.48k | dst->left = rect.x_min - bord; |
2707 | 1.48k | dst->top = rect.y_min - bord; |
2708 | 10.5k | for (int i = 0; i < k->bitmap_count; i++) { |
2709 | 9.10k | Bitmap *src = k->bitmaps[i].bm; |
2710 | 9.10k | if (!src) |
2711 | 0 | continue; |
2712 | 9.10k | int x = k->bitmaps[i].pos.x + src->left - dst->left; |
2713 | 9.10k | int y = k->bitmaps[i].pos.y + src->top - dst->top; |
2714 | 9.10k | assert(x >= 0 && x + src->w <= dst->w); |
2715 | 9.10k | assert(y >= 0 && y + src->h <= dst->h); |
2716 | 9.10k | unsigned char *buf = dst->buffer + y * dst->stride + x; |
2717 | 9.10k | render_priv->engine.add_bitmaps(buf, dst->stride, |
2718 | 9.10k | src->buffer, src->stride, |
2719 | 9.10k | src->w, src->h); |
2720 | 9.10k | } |
2721 | 1.48k | } |
2722 | 2.44k | if (!bord && n_bm_o == 1) { |
2723 | 956 | ass_copy_bitmap(&render_priv->engine, &v->bm_o, last_o->bm_o); |
2724 | 956 | v->bm_o.left += last_o->pos_o.x; |
2725 | 956 | v->bm_o.top += last_o->pos_o.y; |
2726 | 1.48k | } else if (n_bm_o && ass_alloc_bitmap(&render_priv->engine, &v->bm_o, |
2727 | 1.47k | rect_o.x_max - rect_o.x_min + 2 * bord, |
2728 | 1.47k | rect_o.y_max - rect_o.y_min + 2 * bord, |
2729 | 1.47k | true)) { |
2730 | 1.47k | Bitmap *dst = &v->bm_o; |
2731 | 1.47k | dst->left = rect_o.x_min - bord; |
2732 | 1.47k | dst->top = rect_o.y_min - bord; |
2733 | 10.5k | for (int i = 0; i < k->bitmap_count; i++) { |
2734 | 9.08k | Bitmap *src = k->bitmaps[i].bm_o; |
2735 | 9.08k | if (!src) |
2736 | 0 | continue; |
2737 | 9.08k | int x = k->bitmaps[i].pos_o.x + src->left - dst->left; |
2738 | 9.08k | int y = k->bitmaps[i].pos_o.y + src->top - dst->top; |
2739 | 9.08k | assert(x >= 0 && x + src->w <= dst->w); |
2740 | 9.08k | assert(y >= 0 && y + src->h <= dst->h); |
2741 | 9.08k | unsigned char *buf = dst->buffer + y * dst->stride + x; |
2742 | 9.08k | render_priv->engine.add_bitmaps(buf, dst->stride, |
2743 | 9.08k | src->buffer, src->stride, |
2744 | 9.08k | src->w, src->h); |
2745 | 9.08k | } |
2746 | 1.47k | } |
2747 | | |
2748 | 2.44k | int flags = k->filter.flags; |
2749 | 2.44k | double r2x = restore_blur(k->filter.blur_x); |
2750 | 2.44k | double r2y = restore_blur(k->filter.blur_y); |
2751 | 2.44k | if (!(flags & FILTER_NONZERO_BORDER) || (flags & FILTER_BORDER_STYLE_3)) |
2752 | 11 | ass_synth_blur(&render_priv->engine, &v->bm, k->filter.be, r2x, r2y); |
2753 | 2.44k | ass_synth_blur(&render_priv->engine, &v->bm_o, k->filter.be, r2x, r2y); |
2754 | | |
2755 | 2.44k | if (!(flags & FILTER_FILL_IN_BORDER) && !(flags & FILTER_FILL_IN_SHADOW)) |
2756 | 14 | ass_fix_outline(&v->bm, &v->bm_o); |
2757 | | |
2758 | 2.44k | if (flags & FILTER_NONZERO_SHADOW) { |
2759 | 2.42k | if (flags & FILTER_NONZERO_BORDER) { |
2760 | 2.41k | ass_copy_bitmap(&render_priv->engine, &v->bm_s, &v->bm_o); |
2761 | 2.41k | if ((flags & FILTER_FILL_IN_BORDER) && !(flags & FILTER_FILL_IN_SHADOW)) |
2762 | 0 | ass_fix_outline(&v->bm, &v->bm_s); |
2763 | 2.41k | } else if (flags & FILTER_BORDER_STYLE_3) { |
2764 | 0 | v->bm_s = v->bm_o; |
2765 | 0 | memset(&v->bm_o, 0, sizeof(v->bm_o)); |
2766 | 11 | } else { |
2767 | 11 | ass_copy_bitmap(&render_priv->engine, &v->bm_s, &v->bm); |
2768 | 11 | } |
2769 | | |
2770 | | // Works right even for negative offsets |
2771 | | // '>>' rounds toward negative infinity, '&' returns correct remainder |
2772 | 2.42k | v->bm_s.left += k->filter.shadow.x >> 6; |
2773 | 2.42k | v->bm_s.top += k->filter.shadow.y >> 6; |
2774 | 2.42k | ass_shift_bitmap(&v->bm_s, k->filter.shadow.x & SUBPIXEL_MASK, k->filter.shadow.y & SUBPIXEL_MASK); |
2775 | 2.42k | } |
2776 | | |
2777 | 2.44k | if ((flags & FILTER_FILL_IN_SHADOW) && !(flags & FILTER_FILL_IN_BORDER)) |
2778 | 44 | ass_fix_outline(&v->bm, &v->bm_o); |
2779 | | |
2780 | 2.44k | return sizeof(CompositeHashKey) + sizeof(CompositeHashValue) + |
2781 | 2.44k | k->bitmap_count * sizeof(BitmapRef) + |
2782 | 2.44k | bitmap_size(&v->bm) + bitmap_size(&v->bm_o) + bitmap_size(&v->bm_s); |
2783 | 2.44k | } |
2784 | | |
2785 | | static void add_background(RenderContext *state, EventImages *event_images) |
2786 | 0 | { |
2787 | 0 | ASS_Renderer *render_priv = state->renderer; |
2788 | 0 | int size_x = state->shadow_x > 0 ? |
2789 | 0 | lround(state->shadow_x * state->border_scale_x) : 0; |
2790 | 0 | int size_y = state->shadow_y > 0 ? |
2791 | 0 | lround(state->shadow_y * state->border_scale_y) : 0; |
2792 | 0 | int left = event_images->left - size_x; |
2793 | 0 | int top = event_images->top - size_y; |
2794 | 0 | int right = event_images->left + event_images->width + size_x; |
2795 | 0 | int bottom = event_images->top + event_images->height + size_y; |
2796 | 0 | left = FFMINMAX(left, 0, render_priv->width); |
2797 | 0 | top = FFMINMAX(top, 0, render_priv->height); |
2798 | 0 | right = FFMINMAX(right, 0, render_priv->width); |
2799 | 0 | bottom = FFMINMAX(bottom, 0, render_priv->height); |
2800 | 0 | int w = right - left; |
2801 | 0 | int h = bottom - top; |
2802 | 0 | if (w < 1 || h < 1) |
2803 | 0 | return; |
2804 | 0 | void *nbuffer = ass_aligned_alloc(1, w * h, false); |
2805 | 0 | if (!nbuffer) |
2806 | 0 | return; |
2807 | 0 | memset(nbuffer, 0xFF, w * h); |
2808 | 0 | uint32_t clr = state->c[3]; |
2809 | 0 | ass_apply_fade(&clr, state->fade); |
2810 | 0 | ASS_Image *img = my_draw_bitmap(nbuffer, w, h, w, left, top, |
2811 | 0 | clr, NULL); |
2812 | 0 | if (img) { |
2813 | 0 | img->next = event_images->imgs; |
2814 | 0 | event_images->imgs = img; |
2815 | 0 | } |
2816 | 0 | } |
2817 | | |
2818 | | /** |
2819 | | * \brief Main ass rendering function, glues everything together |
2820 | | * \param event event to render |
2821 | | * \param event_images struct containing resulting images, will also be initialized |
2822 | | * Process event, appending resulting ASS_Image's to images_root. |
2823 | | */ |
2824 | | static bool |
2825 | | ass_render_event(RenderContext *state, ASS_Event *event, |
2826 | | EventImages *event_images) |
2827 | 313k | { |
2828 | 313k | ASS_Renderer *render_priv = state->renderer; |
2829 | 313k | if (event->Style >= render_priv->track->n_styles) { |
2830 | 0 | ass_msg(render_priv->library, MSGL_WARN, "No style found"); |
2831 | 0 | return false; |
2832 | 0 | } |
2833 | 313k | if (!event->Text) { |
2834 | 0 | ass_msg(render_priv->library, MSGL_WARN, "Empty event"); |
2835 | 0 | return false; |
2836 | 0 | } |
2837 | | |
2838 | 313k | free_render_context(state); |
2839 | 313k | init_render_context(state, event); |
2840 | | |
2841 | 313k | if (!parse_events(state, event)) |
2842 | 0 | return false; |
2843 | | |
2844 | 313k | TextInfo *text_info = &state->text_info; |
2845 | 313k | if (text_info->length == 0) { |
2846 | | // no valid symbols in the event; this can be smth like {comment} |
2847 | 227k | free_render_context(state); |
2848 | 227k | return false; |
2849 | 227k | } |
2850 | | |
2851 | 85.9k | split_style_runs(state); |
2852 | | |
2853 | | // Find shape runs and shape text |
2854 | 85.9k | ass_shaper_set_base_direction(state->shaper, |
2855 | 85.9k | ass_resolve_base_direction(state->font_encoding)); |
2856 | 85.9k | ass_shaper_find_runs(state->shaper, render_priv, text_info->glyphs, |
2857 | 85.9k | text_info->length); |
2858 | 85.9k | if (!ass_shaper_shape(state->shaper, text_info)) { |
2859 | 0 | ass_msg(render_priv->library, MSGL_ERR, "Failed to shape text"); |
2860 | 0 | free_render_context(state); |
2861 | 0 | return false; |
2862 | 0 | } |
2863 | | |
2864 | 85.9k | retrieve_glyphs(state); |
2865 | | |
2866 | 85.9k | preliminary_layout(state); |
2867 | | |
2868 | 85.9k | int valign = state->alignment & 12; |
2869 | | |
2870 | 85.9k | int MarginL = |
2871 | 85.9k | (event->MarginL) ? event->MarginL : state->style->MarginL; |
2872 | 85.9k | int MarginR = |
2873 | 85.9k | (event->MarginR) ? event->MarginR : state->style->MarginR; |
2874 | 85.9k | int MarginV = |
2875 | 85.9k | (event->MarginV) ? event->MarginV : state->style->MarginV; |
2876 | | |
2877 | | // calculate max length of a line |
2878 | 85.9k | double max_text_width = |
2879 | 85.9k | x2scr_right(state, render_priv->track->PlayResX - MarginR) - |
2880 | 85.9k | x2scr_left(state, MarginL); |
2881 | | |
2882 | | // wrap lines |
2883 | 85.9k | wrap_lines_smart(state, max_text_width); |
2884 | | |
2885 | | // depends on glyph x coordinates being monotonous within runs, so it should be done before reorder |
2886 | 85.9k | ass_process_karaoke_effects(state); |
2887 | | |
2888 | 85.9k | reorder_text(state); |
2889 | | |
2890 | 85.9k | align_lines(state, max_text_width); |
2891 | | |
2892 | | // determine text bounding box |
2893 | 85.9k | ASS_DRect bbox; |
2894 | 85.9k | compute_string_bbox(text_info, &bbox); |
2895 | | |
2896 | 85.9k | apply_baseline_shear(state); |
2897 | | |
2898 | | // determine device coordinates for text |
2899 | 85.9k | double device_x = 0; |
2900 | 85.9k | double device_y = 0; |
2901 | | |
2902 | | // handle positioned events first: an event can be both positioned and |
2903 | | // scrolling, and the scrolling effect overrides the position on one axis |
2904 | 85.9k | if (state->evt_type & EVENT_POSITIONED) { |
2905 | 1.78k | double base_x = 0; |
2906 | 1.78k | double base_y = 0; |
2907 | 1.78k | get_base_point(&bbox, state->alignment, &base_x, &base_y); |
2908 | 1.78k | device_x = |
2909 | 1.78k | x2scr_pos(render_priv, state->pos_x) - base_x; |
2910 | 1.78k | device_y = |
2911 | 1.78k | y2scr_pos(render_priv, state->pos_y) - base_y; |
2912 | 1.78k | } |
2913 | | |
2914 | | // x coordinate |
2915 | 85.9k | if (state->evt_type & EVENT_HSCROLL) { |
2916 | 252 | if (state->scroll_direction == SCROLL_RL) |
2917 | 102 | device_x = |
2918 | 102 | x2scr_pos(render_priv, |
2919 | 102 | render_priv->track->PlayResX - |
2920 | 102 | state->scroll_shift); |
2921 | 150 | else if (state->scroll_direction == SCROLL_LR) |
2922 | 150 | device_x = |
2923 | 150 | x2scr_pos(render_priv, state->scroll_shift) - |
2924 | 150 | (bbox.x_max - bbox.x_min); |
2925 | 85.7k | } else if (!(state->evt_type & EVENT_POSITIONED)) { |
2926 | 83.9k | device_x = x2scr_left(state, MarginL); |
2927 | 83.9k | } |
2928 | | |
2929 | | // y coordinate |
2930 | 85.9k | if (state->evt_type & EVENT_VSCROLL) { |
2931 | 26 | if (state->scroll_direction == SCROLL_TB) |
2932 | 0 | device_y = |
2933 | 0 | y2scr(state, |
2934 | 0 | state->scroll_y0 + |
2935 | 0 | state->scroll_shift) - |
2936 | 0 | bbox.y_max; |
2937 | 26 | else if (state->scroll_direction == SCROLL_BT) |
2938 | 26 | device_y = |
2939 | 26 | y2scr(state, |
2940 | 26 | state->scroll_y1 - |
2941 | 26 | state->scroll_shift) - |
2942 | 26 | bbox.y_min; |
2943 | 85.9k | } else if (!(state->evt_type & EVENT_POSITIONED)) { |
2944 | 84.1k | if (valign == VALIGN_TOP) { // toptitle |
2945 | 393 | device_y = |
2946 | 393 | y2scr_top(state, |
2947 | 393 | MarginV) + text_info->lines[0].asc; |
2948 | 83.7k | } else if (valign == VALIGN_CENTER) { // midtitle |
2949 | 684 | double scr_y = |
2950 | 684 | y2scr(state, render_priv->track->PlayResY / 2.0); |
2951 | 684 | device_y = scr_y - (bbox.y_max + bbox.y_min) / 2.0; |
2952 | 83.1k | } else { // subtitle |
2953 | 83.1k | double line_pos = state->explicit ? |
2954 | 72.7k | 0 : render_priv->settings.line_position; |
2955 | 83.1k | double scr_top, scr_bottom, scr_y0; |
2956 | 83.1k | if (valign != VALIGN_SUB) |
2957 | 0 | ass_msg(render_priv->library, MSGL_V, |
2958 | 0 | "Invalid valign, assuming 0 (subtitle)"); |
2959 | 83.1k | scr_bottom = |
2960 | 83.1k | y2scr_sub(state, |
2961 | 83.1k | render_priv->track->PlayResY - MarginV); |
2962 | 83.1k | scr_top = y2scr_top(state, 0); //xxx not always 0? |
2963 | 83.1k | device_y = scr_bottom + (scr_top - scr_bottom) * line_pos / 100.0; |
2964 | 83.1k | device_y -= text_info->height; |
2965 | 83.1k | device_y += text_info->lines[0].asc; |
2966 | | // clip to top to avoid confusion if line_position is very high, |
2967 | | // turning the subtitle into a toptitle |
2968 | | // also, don't change behavior if line_position is not used |
2969 | 83.1k | scr_y0 = scr_top + text_info->lines[0].asc; |
2970 | 83.1k | if (device_y < scr_y0 && line_pos > 0) { |
2971 | 0 | device_y = scr_y0; |
2972 | 0 | } |
2973 | 83.1k | } |
2974 | 84.1k | } |
2975 | | |
2976 | | // fix clip coordinates |
2977 | 85.9k | if (state->explicit || !render_priv->settings.use_margins) { |
2978 | 85.9k | state->clip_x0 = |
2979 | 85.9k | lround(x2scr_pos_scaled(render_priv, state->clip_x0)); |
2980 | 85.9k | state->clip_x1 = |
2981 | 85.9k | lround(x2scr_pos_scaled(render_priv, state->clip_x1)); |
2982 | 85.9k | state->clip_y0 = |
2983 | 85.9k | lround(y2scr_pos(render_priv, state->clip_y0)); |
2984 | 85.9k | state->clip_y1 = |
2985 | 85.9k | lround(y2scr_pos(render_priv, state->clip_y1)); |
2986 | | |
2987 | 85.9k | if (state->explicit) { |
2988 | | // we still need to clip against screen boundaries |
2989 | 12.2k | int zx = render_priv->settings.left_margin; |
2990 | 12.2k | int zy = render_priv->settings.top_margin; |
2991 | 12.2k | int sx = zx + render_priv->frame_content_width; |
2992 | 12.2k | int sy = zy + render_priv->frame_content_height; |
2993 | | |
2994 | 12.2k | state->clip_x0 = FFMAX(state->clip_x0, zx); |
2995 | 12.2k | state->clip_y0 = FFMAX(state->clip_y0, zy); |
2996 | 12.2k | state->clip_x1 = FFMIN(state->clip_x1, sx); |
2997 | 12.2k | state->clip_y1 = FFMIN(state->clip_y1, sy); |
2998 | 12.2k | } |
2999 | 85.9k | } else { |
3000 | | // no \clip (explicit==0) and use_margins => only clip to screen with margins |
3001 | 0 | state->clip_x0 = 0; |
3002 | 0 | state->clip_y0 = 0; |
3003 | 0 | state->clip_x1 = render_priv->settings.frame_width; |
3004 | 0 | state->clip_y1 = render_priv->settings.frame_height; |
3005 | 0 | } |
3006 | | |
3007 | 85.9k | if (state->evt_type & EVENT_VSCROLL) { |
3008 | 26 | int y0 = lround(y2scr_pos(render_priv, state->scroll_y0)); |
3009 | 26 | int y1 = lround(y2scr_pos(render_priv, state->scroll_y1)); |
3010 | | |
3011 | 26 | state->clip_y0 = FFMAX(state->clip_y0, y0); |
3012 | 26 | state->clip_y1 = FFMIN(state->clip_y1, y1); |
3013 | 26 | } |
3014 | | |
3015 | 85.9k | calculate_rotation_params(state, &bbox, device_x, device_y); |
3016 | | |
3017 | 85.9k | render_and_combine_glyphs(state, device_x, device_y); |
3018 | | |
3019 | 85.9k | memset(event_images, 0, sizeof(*event_images)); |
3020 | | // VSFilter does *not* shift lines with a border > margin to be within the |
3021 | | // frame, so negative values for top and left may occur |
3022 | 85.9k | event_images->top = device_y - text_info->lines[0].asc - text_info->border_top; |
3023 | 85.9k | event_images->height = |
3024 | 85.9k | text_info->height + text_info->border_bottom + text_info->border_top; |
3025 | 85.9k | event_images->left = |
3026 | 85.9k | (device_x + bbox.x_min) * render_priv->par_scale_x - text_info->border_x + 0.5; |
3027 | 85.9k | event_images->width = |
3028 | 85.9k | (bbox.x_max - bbox.x_min) * render_priv->par_scale_x |
3029 | 85.9k | + 2 * text_info->border_x + 0.5; |
3030 | 85.9k | event_images->detect_collisions = state->detect_collisions; |
3031 | 85.9k | event_images->shift_direction = (valign == VALIGN_SUB) ? -1 : 1; |
3032 | 85.9k | event_images->event = event; |
3033 | 85.9k | event_images->imgs = render_text(state); |
3034 | | |
3035 | 85.9k | if (state->border_style == 4) |
3036 | 0 | add_background(state, event_images); |
3037 | | |
3038 | 85.9k | ass_shaper_cleanup(state->shaper, text_info); |
3039 | 85.9k | free_render_context(state); |
3040 | | |
3041 | 85.9k | return true; |
3042 | 85.9k | } |
3043 | | |
3044 | | /** |
3045 | | * \brief Check cache limits and reset cache if they are exceeded |
3046 | | */ |
3047 | | static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache) |
3048 | 46.0k | { |
3049 | 46.0k | ass_cache_cut(cache->composite_cache, cache->composite_max_size); |
3050 | 46.0k | ass_cache_cut(cache->bitmap_cache, cache->bitmap_max_size); |
3051 | 46.0k | ass_cache_cut(cache->outline_cache, cache->glyph_max); |
3052 | 46.0k | } |
3053 | | |
3054 | | static void setup_shaper(ASS_Shaper *shaper, ASS_Renderer *render_priv) |
3055 | 46.0k | { |
3056 | 46.0k | ASS_Track *track = render_priv->track; |
3057 | | |
3058 | 46.0k | ass_shaper_set_kerning(shaper, track->Kerning); |
3059 | 46.0k | ass_shaper_set_language(shaper, track->Language); |
3060 | 46.0k | ass_shaper_set_level(shaper, render_priv->settings.shaper); |
3061 | 46.0k | #ifdef USE_FRIBIDI_EX_API |
3062 | 46.0k | ass_shaper_set_bidi_brackets(shaper, |
3063 | 46.0k | track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS)); |
3064 | 46.0k | #endif |
3065 | 46.0k | ass_shaper_set_whole_text_layout(shaper, |
3066 | 46.0k | track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT)); |
3067 | 46.0k | } |
3068 | | |
3069 | | /** |
3070 | | * \brief Start a new frame |
3071 | | */ |
3072 | | static bool |
3073 | | ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track, |
3074 | | long long now) |
3075 | 46.0k | { |
3076 | 46.0k | if (!render_priv->settings.frame_width |
3077 | 0 | && !render_priv->settings.frame_height) |
3078 | 0 | return false; // library not initialized |
3079 | | |
3080 | 46.0k | if (!render_priv->fontselect) |
3081 | 0 | return false; |
3082 | | |
3083 | 46.0k | if (render_priv->library != track->library) |
3084 | 0 | return false; |
3085 | | |
3086 | 46.0k | if (track->n_events == 0) |
3087 | 0 | return false; // nothing to do |
3088 | | |
3089 | 46.0k | render_priv->track = track; |
3090 | 46.0k | render_priv->time = now; |
3091 | | |
3092 | 46.0k | ass_lazy_track_init(render_priv->library, render_priv->track); |
3093 | | |
3094 | 46.0k | if (render_priv->library->num_fontdata != render_priv->num_emfonts) { |
3095 | 0 | assert(render_priv->library->num_fontdata > render_priv->num_emfonts); |
3096 | 0 | render_priv->num_emfonts = ass_update_embedded_fonts( |
3097 | 0 | render_priv->fontselect, render_priv->num_emfonts); |
3098 | 0 | } |
3099 | | |
3100 | 46.0k | setup_shaper(render_priv->state.shaper, render_priv); |
3101 | | |
3102 | | // PAR correction |
3103 | 46.0k | double par = render_priv->settings.par; |
3104 | 46.0k | bool lr_track = track->LayoutResX > 0 && track->LayoutResY > 0; |
3105 | 46.0k | if (par == 0. || lr_track) { |
3106 | 46.0k | if (render_priv->frame_content_width && render_priv->frame_content_height && (lr_track || |
3107 | 46.0k | (render_priv->settings.storage_width && render_priv->settings.storage_height))) { |
3108 | 46.0k | double dar = ((double) render_priv->frame_content_width) / |
3109 | 46.0k | render_priv->frame_content_height; |
3110 | 46.0k | ASS_Vector layout_res = ass_layout_res(render_priv); |
3111 | 46.0k | double sar = ((double) layout_res.x) / layout_res.y; |
3112 | 46.0k | par = dar / sar; |
3113 | 46.0k | } else |
3114 | 0 | par = 1.0; |
3115 | 46.0k | } |
3116 | 46.0k | render_priv->par_scale_x = par; |
3117 | | |
3118 | 46.0k | render_priv->prev_images_root = render_priv->images_root; |
3119 | 46.0k | render_priv->images_root = NULL; |
3120 | | |
3121 | 46.0k | check_cache_limits(render_priv, &render_priv->cache); |
3122 | | |
3123 | 46.0k | return true; |
3124 | 46.0k | } |
3125 | | |
3126 | | static int cmp_event_layer(const void *p1, const void *p2) |
3127 | 110k | { |
3128 | 110k | ASS_Event *e1 = ((EventImages *) p1)->event; |
3129 | 110k | ASS_Event *e2 = ((EventImages *) p2)->event; |
3130 | 110k | if (e1->Layer < e2->Layer) |
3131 | 0 | return -1; |
3132 | 110k | if (e1->Layer > e2->Layer) |
3133 | 0 | return 1; |
3134 | 110k | if (e1->ReadOrder < e2->ReadOrder) |
3135 | 110k | return -1; |
3136 | 0 | if (e1->ReadOrder > e2->ReadOrder) |
3137 | 0 | return 1; |
3138 | 0 | return 0; |
3139 | 0 | } |
3140 | | |
3141 | | static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv, |
3142 | | ASS_Event *event) |
3143 | 159k | { |
3144 | 159k | if (!event->render_priv) { |
3145 | 3.44k | event->render_priv = calloc(1, sizeof(ASS_RenderPriv)); |
3146 | 3.44k | if (!event->render_priv) |
3147 | 0 | return NULL; |
3148 | 3.44k | } |
3149 | 159k | if (render_priv->render_id != event->render_priv->render_id) { |
3150 | 3.44k | memset(event->render_priv, 0, sizeof(ASS_RenderPriv)); |
3151 | 3.44k | event->render_priv->render_id = render_priv->render_id; |
3152 | 3.44k | } |
3153 | | |
3154 | 159k | return event->render_priv; |
3155 | 159k | } |
3156 | | |
3157 | | static int overlap(Rect *s1, Rect *s2) |
3158 | 396k | { |
3159 | 396k | if (s1->y0 >= s2->y1 || s2->y0 >= s1->y1 || |
3160 | 5.45k | s1->x0 >= s2->x1 || s2->x0 >= s1->x1) |
3161 | 396k | return 0; |
3162 | 0 | return 1; |
3163 | 396k | } |
3164 | | |
3165 | | static int cmp_rect_y0(const void *p1, const void *p2) |
3166 | 135k | { |
3167 | 135k | return ((Rect *) p1)->y0 - ((Rect *) p2)->y0; |
3168 | 135k | } |
3169 | | |
3170 | | static void |
3171 | | shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift) |
3172 | 78.2k | { |
3173 | 78.2k | ASS_Image *cur = ei->imgs; |
3174 | 292k | while (cur) { |
3175 | 213k | cur->dst_y += shift; |
3176 | | // clip top and bottom |
3177 | 213k | if (cur->dst_y < 0) { |
3178 | 29.1k | int clip = -cur->dst_y; |
3179 | 29.1k | cur->h -= clip; |
3180 | 29.1k | cur->bitmap += clip * cur->stride; |
3181 | 29.1k | cur->dst_y = 0; |
3182 | 29.1k | } |
3183 | 213k | if (cur->dst_y + cur->h >= render_priv->height) { |
3184 | 463 | int clip = cur->dst_y + cur->h - render_priv->height; |
3185 | 463 | cur->h -= clip; |
3186 | 463 | } |
3187 | 213k | if (cur->h <= 0) { |
3188 | 27.7k | cur->h = 0; |
3189 | 27.7k | cur->dst_y = 0; |
3190 | 27.7k | } |
3191 | 213k | cur = cur->next; |
3192 | 213k | } |
3193 | 78.2k | ei->top += shift; |
3194 | 78.2k | } |
3195 | | |
3196 | | // dir: 1 - move down |
3197 | | // -1 - move up |
3198 | | static int fit_rect(Rect *s, Rect *fixed, int *cnt, int dir) |
3199 | 3.44k | { |
3200 | 3.44k | int i; |
3201 | 3.44k | int shift = 0; |
3202 | | |
3203 | 3.44k | if (dir == 1) // move down |
3204 | 141 | for (i = 0; i < *cnt; ++i) { |
3205 | 95 | if (s->y1 + shift <= fixed[i].y0 || s->y0 + shift >= fixed[i].y1 || |
3206 | 82 | s->x1 <= fixed[i].x0 || s->x0 >= fixed[i].x1) |
3207 | 14 | continue; |
3208 | 81 | shift = fixed[i].y1 - s->y0; |
3209 | 81 | } else // dir == -1, move up |
3210 | 11.4k | for (i = *cnt - 1; i >= 0; --i) { |
3211 | 8.01k | if (s->y1 + shift <= fixed[i].y0 || s->y0 + shift >= fixed[i].y1 || |
3212 | 7.85k | s->x1 <= fixed[i].x0 || s->x0 >= fixed[i].x1) |
3213 | 418 | continue; |
3214 | 7.59k | shift = fixed[i].y0 - s->y1; |
3215 | 7.59k | } |
3216 | | |
3217 | 3.44k | fixed[*cnt].y0 = s->y0 + shift; |
3218 | 3.44k | fixed[*cnt].y1 = s->y1 + shift; |
3219 | 3.44k | fixed[*cnt].x0 = s->x0; |
3220 | 3.44k | fixed[*cnt].x1 = s->x1; |
3221 | 3.44k | (*cnt)++; |
3222 | 3.44k | qsort(fixed, *cnt, sizeof(*fixed), cmp_rect_y0); |
3223 | | |
3224 | 3.44k | return shift; |
3225 | 3.44k | } |
3226 | | |
3227 | | static void |
3228 | | fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt) |
3229 | 20.4k | { |
3230 | 20.4k | Rect *used = ass_realloc_array(NULL, cnt, sizeof(*used)); |
3231 | 20.4k | int cnt_used = 0; |
3232 | 20.4k | int i, j; |
3233 | | |
3234 | 20.4k | if (!used) |
3235 | 0 | return; |
3236 | | |
3237 | | // fill used[] with fixed events |
3238 | 106k | for (i = 0; i < cnt; ++i) { |
3239 | 85.9k | ASS_RenderPriv *priv; |
3240 | | // VSFilter considers events colliding if their intersections area is non-zero, |
3241 | | // zero-area events are therefore effectively fixed as well |
3242 | 85.9k | if (!imgs[i].detect_collisions || !imgs[i].height || !imgs[i].width) |
3243 | 6.26k | continue; |
3244 | 79.7k | priv = get_render_priv(render_priv, imgs[i].event); |
3245 | 79.7k | if (priv && priv->height > 0) { // it's a fixed event |
3246 | 76.2k | Rect s; |
3247 | 76.2k | s.y0 = priv->top; |
3248 | 76.2k | s.y1 = priv->top + priv->height; |
3249 | 76.2k | s.x0 = priv->left; |
3250 | 76.2k | s.x1 = priv->left + priv->width; |
3251 | 76.2k | if (priv->height != imgs[i].height) { // no, it's not |
3252 | 0 | ass_msg(render_priv->library, MSGL_WARN, |
3253 | 0 | "Event height has changed"); |
3254 | 0 | priv->top = 0; |
3255 | 0 | priv->height = 0; |
3256 | 0 | priv->left = 0; |
3257 | 0 | priv->width = 0; |
3258 | 0 | } |
3259 | 472k | for (j = 0; j < cnt_used; ++j) |
3260 | 396k | if (overlap(&s, used + j)) { // no, it's not |
3261 | 0 | priv->top = 0; |
3262 | 0 | priv->height = 0; |
3263 | 0 | priv->left = 0; |
3264 | 0 | priv->width = 0; |
3265 | 0 | } |
3266 | 76.2k | if (priv->height > 0) { // still a fixed event |
3267 | 76.2k | used[cnt_used].y0 = priv->top; |
3268 | 76.2k | used[cnt_used].y1 = priv->top + priv->height; |
3269 | 76.2k | used[cnt_used].x0 = priv->left; |
3270 | 76.2k | used[cnt_used].x1 = priv->left + priv->width; |
3271 | 76.2k | cnt_used++; |
3272 | 76.2k | shift_event(render_priv, imgs + i, priv->top - imgs[i].top); |
3273 | 76.2k | } |
3274 | 76.2k | } |
3275 | 79.7k | } |
3276 | 20.4k | qsort(used, cnt_used, sizeof(*used), cmp_rect_y0); |
3277 | | |
3278 | | // try to fit other events in free spaces |
3279 | 106k | for (i = 0; i < cnt; ++i) { |
3280 | 85.9k | ASS_RenderPriv *priv; |
3281 | 85.9k | if (!imgs[i].detect_collisions || !imgs[i].height || !imgs[i].width) |
3282 | 6.26k | continue; |
3283 | 79.7k | priv = get_render_priv(render_priv, imgs[i].event); |
3284 | 79.7k | if (priv && priv->height == 0) { // not a fixed event |
3285 | 3.44k | int shift; |
3286 | 3.44k | Rect s; |
3287 | 3.44k | s.y0 = imgs[i].top; |
3288 | 3.44k | s.y1 = imgs[i].top + imgs[i].height; |
3289 | 3.44k | s.x0 = imgs[i].left; |
3290 | 3.44k | s.x1 = imgs[i].left + imgs[i].width; |
3291 | 3.44k | shift = fit_rect(&s, used, &cnt_used, imgs[i].shift_direction); |
3292 | 3.44k | if (shift) |
3293 | 1.98k | shift_event(render_priv, imgs + i, shift); |
3294 | | // make it fixed |
3295 | 3.44k | priv->top = imgs[i].top; |
3296 | 3.44k | priv->height = imgs[i].height; |
3297 | 3.44k | priv->left = imgs[i].left; |
3298 | 3.44k | priv->width = imgs[i].width; |
3299 | 3.44k | } |
3300 | | |
3301 | 79.7k | } |
3302 | | |
3303 | 20.4k | free(used); |
3304 | 20.4k | } |
3305 | | |
3306 | | /** |
3307 | | * \brief compare two images |
3308 | | * \param i1 first image |
3309 | | * \param i2 second image |
3310 | | * \return 0 if identical, 1 if different positions, 2 if different content |
3311 | | */ |
3312 | | static int ass_image_compare(ASS_Image *i1, ASS_Image *i2) |
3313 | 148k | { |
3314 | 148k | if (i1->w != i2->w) |
3315 | 2.77k | return 2; |
3316 | 146k | if (i1->h != i2->h) |
3317 | 46 | return 2; |
3318 | 145k | if (i1->stride != i2->stride) |
3319 | 0 | return 2; |
3320 | 145k | if (i1->color != i2->color) |
3321 | 217 | return 2; |
3322 | 145k | if (i1->bitmap != i2->bitmap) |
3323 | 271 | return 2; |
3324 | 145k | if (i1->dst_x != i2->dst_x) |
3325 | 0 | return 1; |
3326 | 145k | if (i1->dst_y != i2->dst_y) |
3327 | 4.89k | return 1; |
3328 | 140k | return 0; |
3329 | 145k | } |
3330 | | |
3331 | | /** |
3332 | | * \brief compare current and previous image list |
3333 | | * \param priv library handle |
3334 | | * \return 0 if identical, 1 if different positions, 2 if different content |
3335 | | */ |
3336 | | static int ass_detect_change(ASS_Renderer *priv) |
3337 | 46.0k | { |
3338 | 46.0k | ASS_Image *img, *img2; |
3339 | 46.0k | int diff; |
3340 | | |
3341 | 46.0k | img = priv->prev_images_root; |
3342 | 46.0k | img2 = priv->images_root; |
3343 | 46.0k | diff = 0; |
3344 | 194k | while (img && diff < 2) { |
3345 | 150k | ASS_Image *next, *next2; |
3346 | 150k | next = img->next; |
3347 | 150k | if (img2) { |
3348 | 148k | int d = ass_image_compare(img, img2); |
3349 | 148k | if (d > diff) |
3350 | 4.11k | diff = d; |
3351 | 148k | next2 = img2->next; |
3352 | 148k | } else { |
3353 | | // previous list is shorter |
3354 | 1.73k | diff = 2; |
3355 | 1.73k | break; |
3356 | 1.73k | } |
3357 | 148k | img = next; |
3358 | 148k | img2 = next2; |
3359 | 148k | } |
3360 | | |
3361 | | // is the previous list longer? |
3362 | 46.0k | if (img2) |
3363 | 6.00k | diff = 2; |
3364 | | |
3365 | 46.0k | return diff; |
3366 | 46.0k | } |
3367 | | |
3368 | | /** |
3369 | | * \brief render a frame |
3370 | | * \param priv library handle |
3371 | | * \param track track |
3372 | | * \param now current video timestamp (ms) |
3373 | | * \param detect_change a value describing how the new images differ from the previous ones will be written here: |
3374 | | * 0 if identical, 1 if different positions, 2 if different content. |
3375 | | * Can be NULL, in that case no detection is performed. |
3376 | | */ |
3377 | | ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track, |
3378 | | long long now, int *detect_change) |
3379 | 46.0k | { |
3380 | | // init frame |
3381 | 46.0k | if (!ass_start_frame(priv, track, now)) { |
3382 | 0 | if (detect_change) |
3383 | 0 | *detect_change = 2; |
3384 | 0 | return NULL; |
3385 | 0 | } |
3386 | | |
3387 | | // render events separately |
3388 | 46.0k | int cnt = 0; |
3389 | 733k | for (int i = 0; i < track->n_events; i++) { |
3390 | 687k | ASS_Event *event = track->events + i; |
3391 | 687k | if ((event->Start <= now) |
3392 | 686k | && (now < (event->Start + event->Duration))) { |
3393 | 313k | if (cnt >= priv->eimg_size) { |
3394 | 6.71k | priv->eimg_size += 100; |
3395 | 6.71k | priv->eimg = |
3396 | 6.71k | realloc(priv->eimg, |
3397 | 6.71k | priv->eimg_size * sizeof(EventImages)); |
3398 | 6.71k | } |
3399 | 313k | if (ass_render_event(&priv->state, event, priv->eimg + cnt)) |
3400 | 85.9k | cnt++; |
3401 | 313k | } |
3402 | 687k | } |
3403 | | |
3404 | | // sort by layer |
3405 | 46.0k | if (cnt > 0) |
3406 | 20.4k | qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer); |
3407 | | |
3408 | | // call fix_collisions for each group of events with the same layer |
3409 | 46.0k | EventImages *last = priv->eimg; |
3410 | 111k | for (int i = 1; i < cnt; i++) |
3411 | 65.5k | if (last->event->Layer != priv->eimg[i].event->Layer) { |
3412 | 0 | fix_collisions(priv, last, priv->eimg + i - last); |
3413 | 0 | last = priv->eimg + i; |
3414 | 0 | } |
3415 | 46.0k | if (cnt > 0) |
3416 | 20.4k | fix_collisions(priv, last, priv->eimg + cnt - last); |
3417 | | |
3418 | | // concat lists |
3419 | 46.0k | ASS_Image **tail = &priv->images_root; |
3420 | 132k | for (int i = 0; i < cnt; i++) { |
3421 | 85.9k | ASS_Image *cur = priv->eimg[i].imgs; |
3422 | 313k | while (cur) { |
3423 | 227k | *tail = cur; |
3424 | 227k | tail = &cur->next; |
3425 | 227k | cur = cur->next; |
3426 | 227k | } |
3427 | 85.9k | } |
3428 | 46.0k | ass_frame_ref(priv->images_root); |
3429 | | |
3430 | 46.0k | if (detect_change) |
3431 | 46.0k | *detect_change = ass_detect_change(priv); |
3432 | | |
3433 | | // free the previous image list |
3434 | 46.0k | ass_frame_unref(priv->prev_images_root); |
3435 | 46.0k | priv->prev_images_root = NULL; |
3436 | | |
3437 | 46.0k | if (track->parser_priv->prune_delay >= 0) |
3438 | 0 | ass_prune_events(track, now - track->parser_priv->prune_delay); |
3439 | | |
3440 | 46.0k | return priv->images_root; |
3441 | 46.0k | } |
3442 | | |
3443 | | /** |
3444 | | * \brief Add reference to a frame image list. |
3445 | | * \param image_list image list returned by ass_render_frame() |
3446 | | */ |
3447 | | void ass_frame_ref(ASS_Image *img) |
3448 | 46.0k | { |
3449 | 46.0k | if (!img) |
3450 | 26.9k | return; |
3451 | 19.0k | ((ASS_ImagePriv *) img)->ref_count++; |
3452 | 19.0k | } |
3453 | | |
3454 | | /** |
3455 | | * \brief Release reference to a frame image list. |
3456 | | * \param image_list image list returned by ass_render_frame() |
3457 | | */ |
3458 | | void ass_frame_unref(ASS_Image *img) |
3459 | 69.9k | { |
3460 | 69.9k | if (!img || --((ASS_ImagePriv *) img)->ref_count) |
3461 | 50.8k | return; |
3462 | 227k | do { |
3463 | 227k | ASS_ImagePriv *priv = (ASS_ImagePriv *) img; |
3464 | 227k | img = img->next; |
3465 | 227k | ass_cache_dec_ref(priv->source); |
3466 | 227k | ass_aligned_free(priv->buffer); |
3467 | 227k | free(priv); |
3468 | 227k | } while (img); |
3469 | 19.0k | } |