Mercurial > mplayer.hg
annotate libass/ass_render.c @ 35572:0827fa4c3401
While playing, allow extensive editing of the playlist without stopping.
If the current file is still present in the list after editing, playback
of the file (at adjusted position) seamlessly continues. Otherwise, or
during stop mode, playback starts with track 1 again - automatically in
playing mode, to be restarted by the user in stop mode.
Based on a patch by Hans-Dieter Kosch, hdkosch kabelbw de.
author | ib |
---|---|
date | Tue, 11 Dec 2012 14:51:17 +0000 |
parents | 49fc594fda43 |
children | c7866af24567 |
rev | line source |
---|---|
20008
fa122b7c71c6
Add copyright notice and vim/emacs comments to libass and vf_ass.c.
eugeni
parents:
19965
diff
changeset
|
1 /* |
26723 | 2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> |
3 * | |
26738
588ce97b44f2
Speak of libass instead of MPlayer in the libass license headers.
diego
parents:
26723
diff
changeset
|
4 * This file is part of libass. |
26723 | 5 * |
34011 | 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. | |
26723 | 9 * |
34011 | 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. | |
26723 | 17 */ |
20008
fa122b7c71c6
Add copyright notice and vim/emacs comments to libass and vf_ass.c.
eugeni
parents:
19965
diff
changeset
|
18 |
18937 | 19 #include "config.h" |
20 | |
21 #include <assert.h> | |
22 #include <math.h> | |
23 | |
30200 | 24 #include "ass_render.h" |
25 #include "ass_parse.h" | |
34295 | 26 #include "ass_shaper.h" |
22258
9c1160622400
Reallocate event_images_t, removing limit on simultanious events count.
eugeni
parents:
22221
diff
changeset
|
27 |
30200 | 28 #define MAX_GLYPHS_INITIAL 1024 |
29 #define MAX_LINES_INITIAL 64 | |
30 #define SUBPIXEL_MASK 63 | |
31853 | 31 #define SUBPIXEL_ACCURACY 7 |
18937 | 32 |
30200 | 33 ASS_Renderer *ass_renderer_init(ASS_Library *library) |
18937 | 34 { |
30200 | 35 int error; |
36 FT_Library ft; | |
37 ASS_Renderer *priv = 0; | |
38 int vmajor, vminor, vpatch; | |
20202
9b67ed06f721
Zerofill libass static variables during initialization.
eugeni
parents:
20201
diff
changeset
|
39 |
30200 | 40 error = FT_Init_FreeType(&ft); |
41 if (error) { | |
42 ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType"); | |
43 goto ass_init_exit; | |
44 } | |
18937 | 45 |
30200 | 46 FT_Library_Version(ft, &vmajor, &vminor, &vpatch); |
34295 | 47 ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d", |
30200 | 48 vmajor, vminor, vpatch); |
49 | |
50 priv = calloc(1, sizeof(ASS_Renderer)); | |
51 if (!priv) { | |
52 FT_Done_FreeType(ft); | |
53 goto ass_init_exit; | |
54 } | |
26032
93dcb01491cf
Print FreeType version in libass init. Makes error logs slightly more helpful.
eugeni
parents:
25513
diff
changeset
|
55 |
30200 | 56 priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS); |
57 | |
58 priv->library = library; | |
59 priv->ftlibrary = ft; | |
60 // images_root and related stuff is zero-filled in calloc | |
19846
bcc792bfa431
Store bitmap glyphs in a separate struct, instead of FreeType's internal buffer.
eugeni
parents:
19825
diff
changeset
|
61 |
34295 | 62 priv->cache.font_cache = ass_font_cache_create(); |
63 priv->cache.bitmap_cache = ass_bitmap_cache_create(); | |
64 priv->cache.composite_cache = ass_composite_cache_create(); | |
65 priv->cache.outline_cache = ass_outline_cache_create(); | |
30200 | 66 priv->cache.glyph_max = GLYPH_CACHE_MAX; |
67 priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
68 |
30200 | 69 priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; |
70 priv->text_info.max_lines = MAX_LINES_INITIAL; | |
31853 | 71 priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); |
30200 | 72 priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); |
18937 | 73 |
31853 | 74 priv->settings.font_size_coeff = 1.; |
75 | |
34295 | 76 priv->shaper = ass_shaper_new(0); |
77 ass_shaper_info(library); | |
78 #ifdef CONFIG_HARFBUZZ | |
79 priv->settings.shaper = ASS_SHAPING_COMPLEX; | |
80 #else | |
81 priv->settings.shaper = ASS_SHAPING_SIMPLE; | |
82 #endif | |
83 | |
30200 | 84 ass_init_exit: |
85 if (priv) | |
34295 | 86 ass_msg(library, MSGL_V, "Initialized"); |
30200 | 87 else |
34295 | 88 ass_msg(library, MSGL_ERR, "Initialization failed"); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
89 |
30200 | 90 return priv; |
18937 | 91 } |
92 | |
30200 | 93 static void free_list_clear(ASS_Renderer *render_priv) |
94 { | |
95 if (render_priv->free_head) { | |
96 FreeList *item = render_priv->free_head; | |
97 while(item) { | |
98 FreeList *oi = item; | |
99 free(item->object); | |
100 item = item->next; | |
101 free(oi); | |
102 } | |
103 render_priv->free_head = NULL; | |
104 } | |
105 } | |
106 | |
107 void ass_renderer_done(ASS_Renderer *render_priv) | |
18937 | 108 { |
34295 | 109 ass_cache_done(render_priv->cache.font_cache); |
110 ass_cache_done(render_priv->cache.bitmap_cache); | |
111 ass_cache_done(render_priv->cache.composite_cache); | |
112 ass_cache_done(render_priv->cache.outline_cache); | |
30200 | 113 |
114 ass_free_images(render_priv->images_root); | |
115 ass_free_images(render_priv->prev_images_root); | |
116 | |
117 if (render_priv->state.stroker) { | |
118 FT_Stroker_Done(render_priv->state.stroker); | |
119 render_priv->state.stroker = 0; | |
120 } | |
31875 | 121 if (render_priv->ftlibrary) |
30200 | 122 FT_Done_FreeType(render_priv->ftlibrary); |
31875 | 123 if (render_priv->fontconfig_priv) |
30200 | 124 fontconfig_done(render_priv->fontconfig_priv); |
31875 | 125 if (render_priv->synth_priv) |
30200 | 126 ass_synth_done(render_priv->synth_priv); |
34295 | 127 ass_shaper_free(render_priv->shaper); |
31875 | 128 free(render_priv->eimg); |
30200 | 129 free(render_priv->text_info.glyphs); |
130 free(render_priv->text_info.lines); | |
131 | |
132 free(render_priv->settings.default_font); | |
133 free(render_priv->settings.default_family); | |
134 | |
135 free_list_clear(render_priv); | |
136 free(render_priv); | |
18937 | 137 } |
138 | |
139 /** | |
30200 | 140 * \brief Create a new ASS_Image |
141 * Parameters are the same as ASS_Image fields. | |
18937 | 142 */ |
30200 | 143 static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w, |
144 int bitmap_h, int stride, int dst_x, | |
145 int dst_y, uint32_t color) | |
18937 | 146 { |
31853 | 147 ASS_Image *img = malloc(sizeof(ASS_Image)); |
30200 | 148 |
31853 | 149 if (img) { |
150 img->w = bitmap_w; | |
151 img->h = bitmap_h; | |
152 img->stride = stride; | |
153 img->bitmap = bitmap; | |
154 img->color = color; | |
155 img->dst_x = dst_x; | |
156 img->dst_y = dst_y; | |
157 } | |
30200 | 158 |
159 return img; | |
160 } | |
161 | |
31853 | 162 /** |
163 * \brief Mapping between script and screen coordinates | |
164 */ | |
165 static double x2scr(ASS_Renderer *render_priv, double x) | |
166 { | |
167 return x * render_priv->orig_width_nocrop / render_priv->font_scale_x / | |
168 render_priv->track->PlayResX + | |
169 FFMAX(render_priv->settings.left_margin, 0); | |
170 } | |
171 static double x2scr_pos(ASS_Renderer *render_priv, double x) | |
172 { | |
173 return x * render_priv->orig_width / render_priv->font_scale_x / render_priv->track->PlayResX + | |
174 render_priv->settings.left_margin; | |
175 } | |
176 static double x2scr_scaled(ASS_Renderer *render_priv, double x) | |
177 { | |
178 return x * render_priv->orig_width_nocrop / | |
179 render_priv->track->PlayResX + | |
180 FFMAX(render_priv->settings.left_margin, 0); | |
181 } | |
182 static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x) | |
183 { | |
184 return x * render_priv->orig_width / render_priv->track->PlayResX + | |
185 render_priv->settings.left_margin; | |
186 } | |
187 /** | |
188 * \brief Mapping between script and screen coordinates | |
189 */ | |
190 static double y2scr(ASS_Renderer *render_priv, double y) | |
191 { | |
192 return y * render_priv->orig_height_nocrop / | |
193 render_priv->track->PlayResY + | |
194 FFMAX(render_priv->settings.top_margin, 0); | |
195 } | |
196 static double y2scr_pos(ASS_Renderer *render_priv, double y) | |
197 { | |
198 return y * render_priv->orig_height / render_priv->track->PlayResY + | |
199 render_priv->settings.top_margin; | |
200 } | |
201 | |
202 // the same for toptitles | |
203 static double y2scr_top(ASS_Renderer *render_priv, double y) | |
204 { | |
205 if (render_priv->settings.use_margins) | |
206 return y * render_priv->orig_height_nocrop / | |
207 render_priv->track->PlayResY; | |
208 else | |
209 return y * render_priv->orig_height_nocrop / | |
210 render_priv->track->PlayResY + | |
211 FFMAX(render_priv->settings.top_margin, 0); | |
212 } | |
213 // the same for subtitles | |
214 static double y2scr_sub(ASS_Renderer *render_priv, double y) | |
215 { | |
216 if (render_priv->settings.use_margins) | |
217 return y * render_priv->orig_height_nocrop / | |
218 render_priv->track->PlayResY + | |
219 FFMAX(render_priv->settings.top_margin, 0) | |
220 + FFMAX(render_priv->settings.bottom_margin, 0); | |
221 else | |
222 return y * render_priv->orig_height_nocrop / | |
223 render_priv->track->PlayResY + | |
224 FFMAX(render_priv->settings.top_margin, 0); | |
225 } | |
30200 | 226 |
227 /* | |
228 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping | |
229 * | |
230 * Inverse clipping with the following strategy: | |
231 * - find rectangle from (x0, y0) to (cx0, y1) | |
232 * - find rectangle from (cx0, y0) to (cx1, cy0) | |
233 * - find rectangle from (cx0, cy1) to (cx1, y1) | |
234 * - find rectangle from (cx1, y0) to (x1, y1) | |
235 * These rectangles can be invalid and in this case are discarded. | |
236 * Afterwards, they are clipped against the screen coordinates. | |
237 * In an additional pass, the rectangles need to be split up left/right for | |
238 * karaoke effects. This can result in a lot of bitmaps (6 to be exact). | |
239 */ | |
240 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv, | |
241 Bitmap *bm, int dst_x, int dst_y, | |
242 uint32_t color, uint32_t color2, int brk, | |
243 ASS_Image **tail) | |
244 { | |
245 int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy; | |
246 Rect r[4]; | |
247 ASS_Image *img; | |
248 | |
249 dst_x += bm->left; | |
250 dst_y += bm->top; | |
251 | |
252 // we still need to clip against screen boundaries | |
31853 | 253 zx = x2scr_pos_scaled(render_priv, 0); |
30200 | 254 zy = y2scr_pos(render_priv, 0); |
31853 | 255 sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX); |
30200 | 256 sy = y2scr_pos(render_priv, render_priv->track->PlayResY); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
257 |
30200 | 258 x0 = 0; |
259 y0 = 0; | |
260 x1 = bm->w; | |
261 y1 = bm->h; | |
262 cx0 = render_priv->state.clip_x0 - dst_x; | |
263 cy0 = render_priv->state.clip_y0 - dst_y; | |
264 cx1 = render_priv->state.clip_x1 - dst_x; | |
265 cy1 = render_priv->state.clip_y1 - dst_y; | |
266 | |
267 // calculate rectangles and discard invalid ones while we're at it. | |
268 i = 0; | |
269 r[i].x0 = x0; | |
270 r[i].y0 = y0; | |
271 r[i].x1 = (cx0 > x1) ? x1 : cx0; | |
272 r[i].y1 = y1; | |
273 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; | |
274 r[i].x0 = (cx0 < 0) ? x0 : cx0; | |
275 r[i].y0 = y0; | |
276 r[i].x1 = (cx1 > x1) ? x1 : cx1; | |
277 r[i].y1 = (cy0 > y1) ? y1 : cy0; | |
278 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; | |
279 r[i].x0 = (cx0 < 0) ? x0 : cx0; | |
280 r[i].y0 = (cy1 < 0) ? y0 : cy1; | |
281 r[i].x1 = (cx1 > x1) ? x1 : cx1; | |
282 r[i].y1 = y1; | |
283 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; | |
284 r[i].x0 = (cx1 < 0) ? x0 : cx1; | |
285 r[i].y0 = y0; | |
286 r[i].x1 = x1; | |
287 r[i].y1 = y1; | |
288 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; | |
29383
e9cab9f6ed62
Make sure clip coordinates are inside the screen area.
eugeni
parents:
29382
diff
changeset
|
289 |
30200 | 290 // clip each rectangle to screen coordinates |
291 for (j = 0; j < i; j++) { | |
292 r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0; | |
293 r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0; | |
294 r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1; | |
295 r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1; | |
296 } | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
297 |
30200 | 298 // draw the rectangles |
299 for (j = 0; j < i; j++) { | |
300 int lbrk = brk; | |
301 // kick out rectangles that are invalid now | |
302 if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0) | |
303 continue; | |
304 // split up into left and right for karaoke, if needed | |
305 if (lbrk > r[j].x0) { | |
306 if (lbrk > r[j].x1) lbrk = r[j].x1; | |
34295 | 307 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0, |
30200 | 308 lbrk - r[j].x0, r[j].y1 - r[j].y0, |
34295 | 309 bm->stride, dst_x + r[j].x0, dst_y + r[j].y0, color); |
31853 | 310 if (!img) break; |
30200 | 311 *tail = img; |
312 tail = &img->next; | |
313 } | |
314 if (lbrk < r[j].x1) { | |
315 if (lbrk < r[j].x0) lbrk = r[j].x0; | |
34295 | 316 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk, |
30200 | 317 r[j].x1 - lbrk, r[j].y1 - r[j].y0, |
34295 | 318 bm->stride, dst_x + lbrk, dst_y + r[j].y0, color2); |
31853 | 319 if (!img) break; |
30200 | 320 *tail = img; |
321 tail = &img->next; | |
322 } | |
323 } | |
324 | |
325 return tail; | |
18937 | 326 } |
327 | |
328 /** | |
30200 | 329 * \brief convert bitmap glyph into ASS_Image struct(s) |
18937 | 330 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY |
331 * \param dst_x bitmap x coordinate in video frame | |
332 * \param dst_y bitmap y coordinate in video frame | |
333 * \param color first color, RGBA | |
334 * \param color2 second color, RGBA | |
335 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
336 * \param tail pointer to the last image's next field, head of the generated list should be stored here |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
337 * \return pointer to the new list tail |
18937 | 338 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion. |
339 */ | |
30200 | 340 static ASS_Image ** |
341 render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y, | |
342 uint32_t color, uint32_t color2, int brk, ASS_Image **tail) | |
18937 | 343 { |
30200 | 344 // Inverse clipping in use? |
345 if (render_priv->state.clip_mode) | |
346 return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2, | |
347 brk, tail); | |
18937 | 348 |
30200 | 349 // brk is relative to dst_x |
350 // color = color left of brk | |
351 // color2 = color right of brk | |
352 int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap | |
353 int clip_x0, clip_y0, clip_x1, clip_y1; | |
354 int tmp; | |
355 ASS_Image *img; | |
29382
363310571aae
Cosmetics: make some variables constant to signify their intended use and,
eugeni
parents:
29381
diff
changeset
|
356 |
30200 | 357 dst_x += bm->left; |
358 dst_y += bm->top; | |
359 brk -= bm->left; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
360 |
30200 | 361 // clipping |
362 clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width); | |
363 clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height); | |
364 clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width); | |
365 clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height); | |
366 b_x0 = 0; | |
367 b_y0 = 0; | |
368 b_x1 = bm->w; | |
369 b_y1 = bm->h; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
370 |
30200 | 371 tmp = dst_x - clip_x0; |
372 if (tmp < 0) { | |
373 ass_msg(render_priv->library, MSGL_DBG2, "clip left"); | |
374 b_x0 = -tmp; | |
375 } | |
376 tmp = dst_y - clip_y0; | |
377 if (tmp < 0) { | |
378 ass_msg(render_priv->library, MSGL_DBG2, "clip top"); | |
379 b_y0 = -tmp; | |
380 } | |
381 tmp = clip_x1 - dst_x - bm->w; | |
382 if (tmp < 0) { | |
383 ass_msg(render_priv->library, MSGL_DBG2, "clip right"); | |
384 b_x1 = bm->w + tmp; | |
385 } | |
386 tmp = clip_y1 - dst_y - bm->h; | |
387 if (tmp < 0) { | |
388 ass_msg(render_priv->library, MSGL_DBG2, "clip bottom"); | |
389 b_y1 = bm->h + tmp; | |
390 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
391 |
30200 | 392 if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) |
393 return tail; | |
18937 | 394 |
30200 | 395 if (brk > b_x0) { // draw left part |
396 if (brk > b_x1) | |
397 brk = b_x1; | |
34295 | 398 img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0, |
399 brk - b_x0, b_y1 - b_y0, bm->stride, | |
30200 | 400 dst_x + b_x0, dst_y + b_y0, color); |
31853 | 401 if (!img) return tail; |
30200 | 402 *tail = img; |
403 tail = &img->next; | |
404 } | |
405 if (brk < b_x1) { // draw right part | |
406 if (brk < b_x0) | |
407 brk = b_x0; | |
34295 | 408 img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk, |
409 b_x1 - brk, b_y1 - b_y0, bm->stride, | |
30200 | 410 dst_x + brk, dst_y + b_y0, color2); |
31853 | 411 if (!img) return tail; |
30200 | 412 *tail = img; |
413 tail = &img->next; | |
414 } | |
415 return tail; | |
18937 | 416 } |
417 | |
418 /** | |
30200 | 419 * \brief Replace the bitmap buffer in ASS_Image with a copy |
420 * \param img ASS_Image to operate on | |
421 * \return pointer to old bitmap buffer | |
29381 | 422 */ |
30200 | 423 static unsigned char *clone_bitmap_buffer(ASS_Image *img) |
29381 | 424 { |
30200 | 425 unsigned char *old_bitmap = img->bitmap; |
426 int size = img->stride * (img->h - 1) + img->w; | |
427 img->bitmap = malloc(size); | |
428 memcpy(img->bitmap, old_bitmap, size); | |
429 return old_bitmap; | |
29381 | 430 } |
431 | |
432 /** | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
433 * \brief Calculate overlapping area of two consecutive bitmaps and in case they |
30200 | 434 * overlap, blend them together |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
435 * Mainly useful for translucent glyphs and especially borders, to avoid the |
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
436 * luminance adding up where they overlap (which looks ugly) |
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
437 */ |
30200 | 438 static void |
439 render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail, | |
440 ASS_Image **tail) | |
441 { | |
442 int left, top, bottom, right; | |
443 int old_left, old_top, w, h, cur_left, cur_top; | |
444 int x, y, opos, cpos; | |
445 char m; | |
446 CompositeHashKey hk; | |
447 CompositeHashValue *hv; | |
448 CompositeHashValue chv; | |
449 int ax = (*last_tail)->dst_x; | |
450 int ay = (*last_tail)->dst_y; | |
451 int aw = (*last_tail)->w; | |
452 int as = (*last_tail)->stride; | |
453 int ah = (*last_tail)->h; | |
454 int bx = (*tail)->dst_x; | |
455 int by = (*tail)->dst_y; | |
456 int bw = (*tail)->w; | |
457 int bs = (*tail)->stride; | |
458 int bh = (*tail)->h; | |
459 unsigned char *a; | |
460 unsigned char *b; | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
461 |
30200 | 462 if ((*last_tail)->bitmap == (*tail)->bitmap) |
463 return; | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
464 |
30200 | 465 if ((*last_tail)->color != (*tail)->color) |
466 return; | |
28840
7d0ea9013974
Add a proper color check to the overlap compositing.
greg
parents:
28839
diff
changeset
|
467 |
30200 | 468 // Calculate overlap coordinates |
469 left = (ax > bx) ? ax : bx; | |
470 top = (ay > by) ? ay : by; | |
471 right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); | |
472 bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); | |
473 if ((right <= left) || (bottom <= top)) | |
474 return; | |
475 old_left = left - ax; | |
476 old_top = top - ay; | |
477 w = right - left; | |
478 h = bottom - top; | |
479 cur_left = left - bx; | |
480 cur_top = top - by; | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
481 |
30200 | 482 // Query cache |
483 hk.a = (*last_tail)->bitmap; | |
484 hk.b = (*tail)->bitmap; | |
485 hk.aw = aw; | |
486 hk.ah = ah; | |
487 hk.bw = bw; | |
488 hk.bh = bh; | |
489 hk.ax = ax; | |
490 hk.ay = ay; | |
491 hk.bx = bx; | |
492 hk.by = by; | |
493 hk.as = as; | |
494 hk.bs = bs; | |
34295 | 495 hv = ass_cache_get(render_priv->cache.composite_cache, &hk); |
30200 | 496 if (hv) { |
497 (*last_tail)->bitmap = hv->a; | |
498 (*tail)->bitmap = hv->b; | |
499 return; | |
500 } | |
501 // Allocate new bitmaps and copy over data | |
502 a = clone_bitmap_buffer(*last_tail); | |
503 b = clone_bitmap_buffer(*tail); | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
504 |
30200 | 505 // Blend overlapping area |
506 for (y = 0; y < h; y++) | |
507 for (x = 0; x < w; x++) { | |
508 opos = (old_top + y) * (as) + (old_left + x); | |
509 cpos = (cur_top + y) * (bs) + (cur_left + x); | |
510 m = FFMIN(a[opos] + b[cpos], 0xff); | |
511 (*last_tail)->bitmap[opos] = 0; | |
512 (*tail)->bitmap[cpos] = m; | |
513 } | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
514 |
30200 | 515 // Insert bitmaps into the cache |
516 chv.a = (*last_tail)->bitmap; | |
517 chv.b = (*tail)->bitmap; | |
34295 | 518 ass_cache_put(render_priv->cache.composite_cache, &hk, &chv); |
30200 | 519 } |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
520 |
30200 | 521 static void free_list_add(ASS_Renderer *render_priv, void *object) |
522 { | |
523 if (!render_priv->free_head) { | |
524 render_priv->free_head = calloc(1, sizeof(FreeList)); | |
525 render_priv->free_head->object = object; | |
526 render_priv->free_tail = render_priv->free_head; | |
527 } else { | |
528 FreeList *l = calloc(1, sizeof(FreeList)); | |
529 l->object = object; | |
530 render_priv->free_tail->next = l; | |
531 render_priv->free_tail = render_priv->free_tail->next; | |
532 } | |
28789
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
533 } |
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
534 |
a0ce88ba2557
Combine adjacent overlapping, translucent glyph borders and shadows to
greg
parents:
28788
diff
changeset
|
535 /** |
30200 | 536 * Iterate through a list of bitmaps and blend with clip vector, if |
537 * applicable. The blended bitmaps are added to a free list which is freed | |
538 * at the start of a new frame. | |
18937 | 539 */ |
30200 | 540 static void blend_vector_clip(ASS_Renderer *render_priv, |
541 ASS_Image *head) | |
18937 | 542 { |
34295 | 543 FT_Outline *outline; |
544 Bitmap *clip_bm = NULL; | |
30200 | 545 ASS_Image *cur; |
546 ASS_Drawing *drawing = render_priv->state.clip_drawing; | |
34295 | 547 BitmapHashKey key; |
548 BitmapHashValue *val; | |
30200 | 549 int error; |
550 | |
551 if (!drawing) | |
552 return; | |
18937 | 553 |
31853 | 554 // Try to get mask from cache |
555 memset(&key, 0, sizeof(key)); | |
34295 | 556 key.type = BITMAP_CLIP; |
557 key.u.clip.text = drawing->text; | |
558 val = ass_cache_get(render_priv->cache.bitmap_cache, &key); | |
31853 | 559 |
560 if (val) { | |
34295 | 561 clip_bm = val->bm; |
31853 | 562 } else { |
34295 | 563 BitmapHashValue v; |
31853 | 564 |
565 // Not found in cache, parse and rasterize it | |
34295 | 566 outline = ass_drawing_parse(drawing, 1); |
567 if (!outline) { | |
31853 | 568 ass_msg(render_priv->library, MSGL_WARN, |
569 "Clip vector parsing failed. Skipping."); | |
570 goto blend_vector_error; | |
571 } | |
572 | |
573 // We need to translate the clip according to screen borders | |
574 if (render_priv->settings.left_margin != 0 || | |
575 render_priv->settings.top_margin != 0) { | |
576 FT_Vector trans = { | |
577 .x = int_to_d6(render_priv->settings.left_margin), | |
578 .y = -int_to_d6(render_priv->settings.top_margin), | |
579 }; | |
34295 | 580 FT_Outline_Translate(outline, trans.x, trans.y); |
31853 | 581 } |
582 | |
583 ass_msg(render_priv->library, MSGL_DBG2, | |
584 "Parsed vector clip: scales (%f, %f) string [%s]\n", | |
585 drawing->scale_x, drawing->scale_y, drawing->text); | |
586 | |
34295 | 587 clip_bm = outline_to_bitmap(render_priv->library, |
588 render_priv->ftlibrary, outline, 0); | |
589 if (clip_bm == NULL) { | |
31853 | 590 ass_msg(render_priv->library, MSGL_WARN, |
591 "Clip vector rasterization failed: %d. Skipping.", error); | |
592 } | |
593 | |
594 // Add to cache | |
595 memset(&v, 0, sizeof(v)); | |
34295 | 596 key.u.clip.text = strdup(drawing->text); |
597 v.bm = clip_bm; | |
598 ass_cache_put(render_priv->cache.bitmap_cache, &key, &v); | |
30200 | 599 } |
34295 | 600 blend_vector_error: |
19965 | 601 |
31853 | 602 if (!clip_bm) goto blend_vector_exit; |
19965 | 603 |
30200 | 604 // Iterate through bitmaps and blend/clip them |
605 for (cur = head; cur; cur = cur->next) { | |
606 int left, top, right, bottom, apos, bpos, y, x, w, h; | |
607 int ax, ay, aw, ah, as; | |
608 int bx, by, bw, bh, bs; | |
609 int aleft, atop, bleft, btop; | |
610 unsigned char *abuffer, *bbuffer, *nbuffer; | |
19965 | 611 |
30200 | 612 abuffer = cur->bitmap; |
34295 | 613 bbuffer = clip_bm->buffer; |
30200 | 614 ax = cur->dst_x; |
615 ay = cur->dst_y; | |
616 aw = cur->w; | |
617 ah = cur->h; | |
618 as = cur->stride; | |
619 bx = clip_bm->left; | |
34295 | 620 by = clip_bm->top; |
621 bw = clip_bm->w; | |
622 bh = clip_bm->h; | |
623 bs = clip_bm->stride; | |
18937 | 624 |
30200 | 625 // Calculate overlap coordinates |
626 left = (ax > bx) ? ax : bx; | |
627 top = (ay > by) ? ay : by; | |
628 right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); | |
629 bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); | |
630 aleft = left - ax; | |
631 atop = top - ay; | |
632 w = right - left; | |
633 h = bottom - top; | |
634 bleft = left - bx; | |
635 btop = top - by; | |
636 | |
637 if (render_priv->state.clip_drawing_mode) { | |
638 // Inverse clip | |
639 if (ax + aw < bx || ay + ah < by || ax > bx + bw || | |
640 ay > by + bh) { | |
641 continue; | |
642 } | |
643 | |
644 // Allocate new buffer and add to free list | |
645 nbuffer = malloc(as * ah); | |
31853 | 646 if (!nbuffer) goto blend_vector_exit; |
30200 | 647 free_list_add(render_priv, nbuffer); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
648 |
30200 | 649 // Blend together |
650 memcpy(nbuffer, abuffer, as * (ah - 1) + aw); | |
651 for (y = 0; y < h; y++) | |
652 for (x = 0; x < w; x++) { | |
653 apos = (atop + y) * as + aleft + x; | |
654 bpos = (btop + y) * bs + bleft + x; | |
655 nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); | |
656 } | |
657 } else { | |
658 // Regular clip | |
659 if (ax + aw < bx || ay + ah < by || ax > bx + bw || | |
660 ay > by + bh) { | |
661 cur->w = cur->h = 0; | |
662 continue; | |
663 } | |
18937 | 664 |
30200 | 665 // Allocate new buffer and add to free list |
666 nbuffer = calloc(as, ah); | |
31853 | 667 if (!nbuffer) goto blend_vector_exit; |
30200 | 668 free_list_add(render_priv, nbuffer); |
18937 | 669 |
30200 | 670 // Blend together |
671 for (y = 0; y < h; y++) | |
672 for (x = 0; x < w; x++) { | |
673 apos = (atop + y) * as + aleft + x; | |
674 bpos = (btop + y) * bs + bleft + x; | |
675 nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; | |
676 } | |
677 } | |
678 cur->bitmap = nbuffer; | |
679 } | |
18937 | 680 |
30200 | 681 blend_vector_exit: |
682 ass_drawing_free(render_priv->state.clip_drawing); | |
683 render_priv->state.clip_drawing = 0; | |
18937 | 684 } |
685 | |
34549 | 686 #define SKIP_SYMBOL(x) ((x) == 0 || (x) == '\n' || (x) == '\r') |
687 | |
18937 | 688 /** |
30200 | 689 * \brief Convert TextInfo struct to ASS_Image list |
690 * Splits glyphs in halves when needed (for \kf karaoke). | |
18937 | 691 */ |
31853 | 692 static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x, int dst_y) |
30200 | 693 { |
694 int pen_x, pen_y; | |
695 int i; | |
696 Bitmap *bm; | |
697 ASS_Image *head; | |
698 ASS_Image **tail = &head; | |
699 ASS_Image **last_tail = 0; | |
700 ASS_Image **here_tail = 0; | |
701 TextInfo *text_info = &render_priv->text_info; | |
702 | |
703 for (i = 0; i < text_info->length; ++i) { | |
704 GlyphInfo *info = text_info->glyphs + i; | |
34549 | 705 if (SKIP_SYMBOL(info->symbol) || !info->bm_s |
30200 | 706 || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip) |
707 continue; | |
708 | |
34295 | 709 while (info) { |
710 if (!info->bm_s) { | |
711 info = info->next; | |
712 continue; | |
713 } | |
30200 | 714 |
34295 | 715 pen_x = |
716 dst_x + (info->pos.x >> 6) + | |
717 (int) (info->shadow_x * render_priv->border_scale); | |
718 pen_y = | |
719 dst_y + (info->pos.y >> 6) + | |
720 (int) (info->shadow_y * render_priv->border_scale); | |
721 bm = info->bm_s; | |
30200 | 722 |
34295 | 723 here_tail = tail; |
724 tail = | |
725 render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0, | |
726 1000000, tail); | |
727 | |
728 if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) | |
729 render_overlap(render_priv, last_tail, here_tail); | |
730 last_tail = here_tail; | |
731 | |
732 info = info->next; | |
733 } | |
30200 | 734 } |
735 | |
736 last_tail = 0; | |
737 for (i = 0; i < text_info->length; ++i) { | |
738 GlyphInfo *info = text_info->glyphs + i; | |
34549 | 739 if (SKIP_SYMBOL(info->symbol) || !info->bm_o |
30200 | 740 || info->skip) |
741 continue; | |
742 | |
34295 | 743 while (info) { |
744 if (!info->bm_o) { | |
745 info = info->next; | |
746 continue; | |
747 } | |
748 | |
749 pen_x = dst_x + (info->pos.x >> 6); | |
750 pen_y = dst_y + (info->pos.y >> 6); | |
751 bm = info->bm_o; | |
30200 | 752 |
34295 | 753 if ((info->effect_type == EF_KARAOKE_KO) |
754 && (info->effect_timing <= (info->bbox.xMax >> 6))) { | |
755 // do nothing | |
756 } else { | |
757 here_tail = tail; | |
758 tail = | |
759 render_glyph(render_priv, bm, pen_x, pen_y, info->c[2], | |
760 0, 1000000, tail); | |
761 if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) | |
762 render_overlap(render_priv, last_tail, here_tail); | |
30200 | 763 |
34295 | 764 last_tail = here_tail; |
765 } | |
766 info = info->next; | |
30200 | 767 } |
768 } | |
769 | |
770 for (i = 0; i < text_info->length; ++i) { | |
771 GlyphInfo *info = text_info->glyphs + i; | |
34549 | 772 if (SKIP_SYMBOL(info->symbol) || !info->bm |
30200 | 773 || info->skip) |
774 continue; | |
775 | |
34295 | 776 while (info) { |
777 if (!info->bm) { | |
778 info = info->next; | |
779 continue; | |
780 } | |
781 | |
782 pen_x = dst_x + (info->pos.x >> 6); | |
783 pen_y = dst_y + (info->pos.y >> 6); | |
784 bm = info->bm; | |
30200 | 785 |
34295 | 786 if ((info->effect_type == EF_KARAOKE) |
787 || (info->effect_type == EF_KARAOKE_KO)) { | |
788 if (info->effect_timing > (info->bbox.xMax >> 6)) | |
789 tail = | |
790 render_glyph(render_priv, bm, pen_x, pen_y, | |
791 info->c[0], 0, 1000000, tail); | |
792 else | |
793 tail = | |
794 render_glyph(render_priv, bm, pen_x, pen_y, | |
795 info->c[1], 0, 1000000, tail); | |
796 } else if (info->effect_type == EF_KARAOKE_KF) { | |
30200 | 797 tail = |
34295 | 798 render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], |
799 info->c[1], info->effect_timing, tail); | |
800 } else | |
30200 | 801 tail = |
34295 | 802 render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], |
803 0, 1000000, tail); | |
804 info = info->next; | |
805 } | |
30200 | 806 } |
807 | |
808 *tail = 0; | |
809 blend_vector_clip(render_priv, head); | |
810 | |
811 return head; | |
28748
06983ef189b9
With pan-and-scan, keep positioned events in their original positions
eugeni
parents:
28719
diff
changeset
|
812 } |
29383
e9cab9f6ed62
Make sure clip coordinates are inside the screen area.
eugeni
parents:
29382
diff
changeset
|
813 |
34295 | 814 static void compute_string_bbox(TextInfo *text, DBBox *bbox) |
18937 | 815 { |
30200 | 816 int i; |
19556 | 817 |
34295 | 818 if (text->length > 0) { |
30200 | 819 bbox->xMin = 32000; |
820 bbox->xMax = -32000; | |
34295 | 821 bbox->yMin = -1 * text->lines[0].asc + d6_to_double(text->glyphs[0].pos.y); |
822 bbox->yMax = text->height - text->lines[0].asc + | |
823 d6_to_double(text->glyphs[0].pos.y); | |
19556 | 824 |
34295 | 825 for (i = 0; i < text->length; ++i) { |
826 GlyphInfo *info = text->glyphs + i; | |
827 if (info->skip) continue; | |
828 while (info) { | |
829 double s = d6_to_double(info->pos.x); | |
830 double e = s + d6_to_double(info->advance.x); | |
831 bbox->xMin = FFMIN(bbox->xMin, s); | |
832 bbox->xMax = FFMAX(bbox->xMax, e); | |
833 info = info->next; | |
834 } | |
30200 | 835 } |
836 } else | |
837 bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.; | |
19556 | 838 } |
839 | |
18937 | 840 /** |
19873 | 841 * \brief partially reset render_context to style values |
842 * Works like {\r}: resets some style overrides | |
843 */ | |
35262 | 844 void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style) |
19873 | 845 { |
35262 | 846 if (!style) |
847 style = render_priv->state.style; | |
848 | |
849 render_priv->state.c[0] = style->PrimaryColour; | |
850 render_priv->state.c[1] = style->SecondaryColour; | |
851 render_priv->state.c[2] = style->OutlineColour; | |
852 render_priv->state.c[3] = style->BackColour; | |
30200 | 853 render_priv->state.flags = |
35262 | 854 (style->Underline ? DECO_UNDERLINE : 0) | |
855 (style->StrikeOut ? DECO_STRIKETHROUGH : 0); | |
856 render_priv->state.font_size = style->FontSize; | |
30200 | 857 |
858 free(render_priv->state.family); | |
859 render_priv->state.family = NULL; | |
35262 | 860 render_priv->state.family = strdup(style->FontName); |
30200 | 861 render_priv->state.treat_family_as_pattern = |
35262 | 862 style->treat_fontname_as_pattern; |
863 render_priv->state.bold = style->Bold; | |
864 render_priv->state.italic = style->Italic; | |
30200 | 865 update_font(render_priv); |
866 | |
35262 | 867 render_priv->state.border_style = style->BorderStyle; |
868 calc_border(render_priv, style->Outline, style->Outline); | |
869 change_border(render_priv, render_priv->state.border_x, render_priv->state.border_y); | |
870 render_priv->state.scale_x = style->ScaleX; | |
871 render_priv->state.scale_y = style->ScaleY; | |
872 render_priv->state.hspacing = style->Spacing; | |
30200 | 873 render_priv->state.be = 0; |
874 render_priv->state.blur = 0.0; | |
35262 | 875 render_priv->state.shadow_x = style->Shadow; |
876 render_priv->state.shadow_y = style->Shadow; | |
30200 | 877 render_priv->state.frx = render_priv->state.fry = 0.; |
35262 | 878 render_priv->state.frz = M_PI * style->Angle / 180.; |
30200 | 879 render_priv->state.fax = render_priv->state.fay = 0.; |
880 render_priv->state.wrap_style = render_priv->track->WrapStyle; | |
35262 | 881 render_priv->state.font_encoding = style->Encoding; |
30200 | 882 } |
883 | |
884 /** | |
885 * \brief Start new event. Reset render_priv->state. | |
886 */ | |
887 static void | |
888 init_render_context(ASS_Renderer *render_priv, ASS_Event *event) | |
889 { | |
890 render_priv->state.event = event; | |
891 render_priv->state.style = render_priv->track->styles + event->Style; | |
34011 | 892 render_priv->state.parsed_tags = 0; |
30200 | 893 |
35262 | 894 reset_render_context(render_priv, render_priv->state.style); |
19873 | 895 |
30200 | 896 render_priv->state.evt_type = EVENT_NORMAL; |
897 render_priv->state.alignment = render_priv->state.style->Alignment; | |
898 render_priv->state.pos_x = 0; | |
899 render_priv->state.pos_y = 0; | |
900 render_priv->state.org_x = 0; | |
901 render_priv->state.org_y = 0; | |
902 render_priv->state.have_origin = 0; | |
903 render_priv->state.clip_x0 = 0; | |
904 render_priv->state.clip_y0 = 0; | |
905 render_priv->state.clip_x1 = render_priv->track->PlayResX; | |
906 render_priv->state.clip_y1 = render_priv->track->PlayResY; | |
907 render_priv->state.clip_mode = 0; | |
908 render_priv->state.detect_collisions = 1; | |
909 render_priv->state.fade = 0; | |
910 render_priv->state.drawing_mode = 0; | |
911 render_priv->state.effect_type = EF_NONE; | |
912 render_priv->state.effect_timing = 0; | |
913 render_priv->state.effect_skip_timing = 0; | |
34295 | 914 render_priv->state.bm_run_id = 0; |
31875 | 915 ass_drawing_free(render_priv->state.drawing); |
34295 | 916 render_priv->state.drawing = ass_drawing_new(render_priv->library, |
917 render_priv->ftlibrary); | |
19873 | 918 |
30200 | 919 apply_transition_effects(render_priv, event); |
920 } | |
921 | |
922 static void free_render_context(ASS_Renderer *render_priv) | |
923 { | |
924 free(render_priv->state.family); | |
925 ass_drawing_free(render_priv->state.drawing); | |
926 | |
927 render_priv->state.family = NULL; | |
928 render_priv->state.drawing = NULL; | |
929 } | |
19873 | 930 |
30200 | 931 /* |
932 * Replace the outline of a glyph by a contour which makes up a simple | |
933 * opaque rectangle. | |
934 */ | |
34295 | 935 static void draw_opaque_box(ASS_Renderer *render_priv, int asc, int desc, |
936 FT_Outline *ol, FT_Vector advance, int sx, int sy) | |
18937 | 937 { |
30200 | 938 int i; |
34295 | 939 int adv = advance.x; |
30200 | 940 double scale_y = render_priv->state.scale_y; |
31853 | 941 double scale_x = render_priv->state.scale_x; |
30200 | 942 |
943 // to avoid gaps | |
944 sx = FFMAX(64, sx); | |
945 sy = FFMAX(64, sy); | |
946 | |
947 // Emulate the WTFish behavior of VSFilter, i.e. double-scale | |
948 // the sizes of the opaque box. | |
949 adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale | |
950 * scale_x); | |
951 adv *= scale_x; | |
952 sx *= scale_x; | |
953 sy *= scale_y; | |
954 desc *= scale_y; | |
955 desc += asc * (scale_y - 1.0); | |
956 | |
957 FT_Vector points[4] = { | |
958 { .x = -sx, .y = asc + sy }, | |
959 { .x = adv + sx, .y = asc + sy }, | |
960 { .x = adv + sx, .y = -desc - sy }, | |
961 { .x = -sx, .y = -desc - sy }, | |
962 }; | |
963 | |
34295 | 964 FT_Outline_New(render_priv->ftlibrary, 4, 1, ol); |
30200 | 965 |
966 ol->n_points = ol->n_contours = 0; | |
967 for (i = 0; i < 4; i++) { | |
968 ol->points[ol->n_points] = points[i]; | |
969 ol->tags[ol->n_points++] = 1; | |
970 } | |
971 ol->contours[ol->n_contours++] = ol->n_points - 1; | |
972 } | |
973 | |
974 /* | |
975 * Stroke an outline glyph in x/y direction. Applies various fixups to get | |
976 * around limitations of the FreeType stroker. | |
977 */ | |
34295 | 978 static void stroke_outline(ASS_Renderer *render_priv, FT_Outline *outline, |
979 int sx, int sy) | |
30200 | 980 { |
981 if (sx <= 0 && sy <= 0) | |
982 return; | |
983 | |
34295 | 984 fix_freetype_stroker(outline, sx, sy); |
30200 | 985 |
986 // Borders are equal; use the regular stroker | |
987 if (sx == sy && render_priv->state.stroker) { | |
988 int error; | |
34295 | 989 unsigned n_points, n_contours; |
990 | |
991 FT_StrokerBorder border = FT_Outline_GetOutsideBorder(outline); | |
992 error = FT_Stroker_ParseOutline(render_priv->state.stroker, outline, 0); | |
993 if (error) { | |
30200 | 994 ass_msg(render_priv->library, MSGL_WARN, |
34295 | 995 "FT_Stroker_ParseOutline failed, error: %d", error); |
996 } | |
997 error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border, | |
998 &n_points, &n_contours); | |
999 if (error) { | |
1000 ass_msg(render_priv->library, MSGL_WARN, | |
1001 "FT_Stroker_GetBorderCounts failed, error: %d", error); | |
1002 } | |
1003 FT_Outline_Done(render_priv->ftlibrary, outline); | |
1004 FT_Outline_New(render_priv->ftlibrary, n_points, n_contours, outline); | |
1005 outline->n_points = outline->n_contours = 0; | |
1006 FT_Stroker_ExportBorder(render_priv->state.stroker, border, outline); | |
30200 | 1007 |
1008 // "Stroke" with the outline emboldener in two passes. | |
1009 // The outlines look uglier, but the emboldening never adds any points | |
1010 } else { | |
1011 int i; | |
1012 FT_Outline nol; | |
1013 | |
34295 | 1014 FT_Outline_New(render_priv->ftlibrary, outline->n_points, |
1015 outline->n_contours, &nol); | |
1016 FT_Outline_Copy(outline, &nol); | |
1017 | |
1018 FT_Outline_Embolden(outline, sx * 2); | |
1019 FT_Outline_Translate(outline, -sx, -sx); | |
30200 | 1020 FT_Outline_Embolden(&nol, sy * 2); |
1021 FT_Outline_Translate(&nol, -sy, -sy); | |
1022 | |
34295 | 1023 for (i = 0; i < outline->n_points; i++) |
1024 outline->points[i].y = nol.points[i].y; | |
30200 | 1025 |
1026 FT_Outline_Done(render_priv->ftlibrary, &nol); | |
1027 } | |
18937 | 1028 } |
1029 | |
23179 | 1030 /** |
31853 | 1031 * \brief Prepare glyph hash |
1032 */ | |
1033 static void | |
34295 | 1034 fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key, |
1035 GlyphInfo *info) | |
31853 | 1036 { |
34295 | 1037 if (info->drawing) { |
1038 DrawingHashKey *key = &outline_key->u.drawing; | |
1039 outline_key->type = OUTLINE_DRAWING; | |
1040 key->scale_x = double_to_d16(info->scale_x); | |
1041 key->scale_y = double_to_d16(info->scale_y); | |
1042 key->outline.x = double_to_d16(info->border_x); | |
1043 key->outline.y = double_to_d16(info->border_y); | |
35262 | 1044 key->border_style = info->border_style; |
34295 | 1045 key->hash = info->drawing->hash; |
1046 key->text = info->drawing->text; | |
1047 key->pbo = info->drawing->pbo; | |
1048 key->scale = info->drawing->scale; | |
31853 | 1049 } else { |
34295 | 1050 GlyphHashKey *key = &outline_key->u.glyph; |
1051 outline_key->type = OUTLINE_GLYPH; | |
1052 key->font = info->font; | |
1053 key->size = info->font_size; | |
1054 key->face_index = info->face_index; | |
1055 key->glyph_index = info->glyph_index; | |
1056 key->bold = info->bold; | |
1057 key->italic = info->italic; | |
1058 key->scale_x = double_to_d16(info->scale_x); | |
1059 key->scale_y = double_to_d16(info->scale_y); | |
1060 key->outline.x = double_to_d16(info->border_x); | |
1061 key->outline.y = double_to_d16(info->border_y); | |
1062 key->flags = info->flags; | |
35262 | 1063 key->border_style = info->border_style; |
31853 | 1064 } |
1065 } | |
1066 | |
1067 /** | |
23179 | 1068 * \brief Get normal and outline (border) glyphs |
1069 * \param info out: struct filled with extracted data | |
1070 * Tries to get both glyphs from cache. | |
1071 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker, | |
1072 * and add them to cache. | |
1073 * The glyphs are returned in info->glyph and info->outline_glyph | |
1074 */ | |
30200 | 1075 static void |
34295 | 1076 get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info) |
23021
a81c390d4a22
Move outline glyph generation to a separate function, using outline glyph
eugeni
parents:
23017
diff
changeset
|
1077 { |
34295 | 1078 OutlineHashValue *val; |
1079 OutlineHashKey key; | |
31853 | 1080 |
34295 | 1081 memset(&info->hash_key, 0, sizeof(key)); |
23021
a81c390d4a22
Move outline glyph generation to a separate function, using outline glyph
eugeni
parents:
23017
diff
changeset
|
1082 |
34295 | 1083 fill_glyph_hash(priv, &key, info); |
1084 val = ass_cache_get(priv->cache.outline_cache, &key); | |
1085 | |
1086 if (!val) { | |
1087 OutlineHashValue v; | |
1088 memset(&v, 0, sizeof(v)); | |
1089 | |
1090 if (info->drawing) { | |
1091 ASS_Drawing *drawing = info->drawing; | |
1092 ass_drawing_hash(drawing); | |
30200 | 1093 if(!ass_drawing_parse(drawing, 0)) |
1094 return; | |
34295 | 1095 outline_copy(priv->ftlibrary, &drawing->outline, |
1096 &v.outline); | |
1097 v.advance.x = drawing->advance.x; | |
1098 v.advance.y = drawing->advance.y; | |
1099 v.asc = drawing->asc; | |
1100 v.desc = drawing->desc; | |
1101 key.u.drawing.text = strdup(drawing->text); | |
30200 | 1102 } else { |
35262 | 1103 // arbitrary, not too small to prevent grid fitting rounding effects |
1104 // XXX: this is a rather crude hack | |
1105 const double ft_size = 256.0; | |
1106 ass_face_set_size(info->font->faces[info->face_index], ft_size); | |
1107 ass_font_set_transform(info->font, | |
1108 info->scale_x * info->font_size / ft_size, | |
1109 info->scale_y * info->font_size / ft_size, | |
1110 NULL); | |
34295 | 1111 FT_Glyph glyph = |
1112 ass_font_get_glyph(priv->fontconfig_priv, info->font, | |
1113 info->symbol, info->face_index, info->glyph_index, | |
1114 priv->settings.hinting, info->flags); | |
1115 if (glyph != NULL) { | |
1116 outline_copy(priv->ftlibrary, | |
1117 &((FT_OutlineGlyph)glyph)->outline, &v.outline); | |
1118 if (priv->settings.shaper == ASS_SHAPING_SIMPLE) { | |
1119 v.advance.x = d16_to_d6(glyph->advance.x); | |
1120 v.advance.y = d16_to_d6(glyph->advance.y); | |
1121 } | |
1122 FT_Done_Glyph(glyph); | |
1123 ass_font_get_asc_desc(info->font, info->symbol, | |
1124 &v.asc, &v.desc); | |
35262 | 1125 v.asc *= info->scale_y * info->font_size / ft_size; |
1126 v.desc *= info->scale_y * info->font_size / ft_size; | |
34295 | 1127 } |
30200 | 1128 } |
34295 | 1129 |
1130 if (!v.outline) | |
30200 | 1131 return; |
31853 | 1132 |
34295 | 1133 FT_Outline_Get_CBox(v.outline, &v.bbox_scaled); |
1134 | |
35262 | 1135 if (info->border_style == 3 && |
34295 | 1136 (info->border_x > 0 || info->border_y > 0)) { |
1137 FT_Vector advance; | |
1138 | |
1139 v.border = calloc(1, sizeof(FT_Outline)); | |
23021
a81c390d4a22
Move outline glyph generation to a separate function, using outline glyph
eugeni
parents:
23017
diff
changeset
|
1140 |
34295 | 1141 if (priv->settings.shaper == ASS_SHAPING_SIMPLE || info->drawing) |
1142 advance = v.advance; | |
1143 else | |
1144 advance = info->advance; | |
23025
ab0943242d1a
Store outline_glyph (glyph border) in glyph cache.
eugeni
parents:
23024
diff
changeset
|
1145 |
34295 | 1146 draw_opaque_box(priv, v.asc, v.desc, v.border, advance, |
1147 double_to_d6(info->border_x * priv->border_scale), | |
1148 double_to_d6(info->border_y * priv->border_scale)); | |
1149 | |
1150 } else if ((info->border_x > 0 || info->border_y > 0) | |
1151 && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) { | |
1152 | |
35262 | 1153 change_border(priv, info->border_x, info->border_y); |
34295 | 1154 outline_copy(priv->ftlibrary, v.outline, &v.border); |
1155 stroke_outline(priv, v.border, | |
1156 double_to_d6(info->border_x * priv->border_scale), | |
1157 double_to_d6(info->border_y * priv->border_scale)); | |
30200 | 1158 } |
1159 | |
34295 | 1160 v.lib = priv->ftlibrary; |
1161 val = ass_cache_put(priv->cache.outline_cache, &key, &v); | |
30200 | 1162 } |
34295 | 1163 |
1164 info->hash_key.u.outline.outline = val; | |
1165 info->outline = val->outline; | |
1166 info->border = val->border; | |
1167 info->bbox = val->bbox_scaled; | |
1168 if (info->drawing || priv->settings.shaper == ASS_SHAPING_SIMPLE) { | |
1169 info->cluster_advance.x = info->advance.x = val->advance.x; | |
1170 info->cluster_advance.y = info->advance.y = val->advance.y; | |
1171 } | |
1172 info->asc = val->asc; | |
1173 info->desc = val->desc; | |
1174 | |
1175 ass_drawing_free(info->drawing); | |
23021
a81c390d4a22
Move outline glyph generation to a separate function, using outline glyph
eugeni
parents:
23017
diff
changeset
|
1176 } |
a81c390d4a22
Move outline glyph generation to a separate function, using outline glyph
eugeni
parents:
23017
diff
changeset
|
1177 |
31853 | 1178 /** |
1179 * \brief Apply transformation to outline points of a glyph | |
1180 * Applies rotations given by frx, fry and frz and projects the points back | |
1181 * onto the screen plane. | |
1182 */ | |
1183 static void | |
34295 | 1184 transform_3d_points(FT_Vector shift, FT_Outline *outline, double frx, double fry, |
31853 | 1185 double frz, double fax, double fay, double scale, |
1186 int yshift) | |
1187 { | |
1188 double sx = sin(frx); | |
1189 double sy = sin(fry); | |
1190 double sz = sin(frz); | |
1191 double cx = cos(frx); | |
1192 double cy = cos(fry); | |
1193 double cz = cos(frz); | |
1194 FT_Vector *p = outline->points; | |
1195 double x, y, z, xx, yy, zz; | |
1196 int i, dist; | |
1197 | |
1198 dist = 20000 * scale; | |
1199 for (i = 0; i < outline->n_points; i++) { | |
1200 x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y)); | |
1201 y = (double) p[i].y + shift.y + (-fay * p[i].x); | |
1202 z = 0.; | |
1203 | |
1204 xx = x * cz + y * sz; | |
1205 yy = -(x * sz - y * cz); | |
1206 zz = z; | |
1207 | |
1208 x = xx; | |
1209 y = yy * cx + zz * sx; | |
1210 z = yy * sx - zz * cx; | |
1211 | |
1212 xx = x * cy + z * sy; | |
1213 yy = y; | |
1214 zz = x * sy - z * cy; | |
1215 | |
1216 zz = FFMAX(zz, 1000 - dist); | |
1217 | |
1218 x = (xx * dist) / (zz + dist); | |
1219 y = (yy * dist) / (zz + dist); | |
1220 p[i].x = x - shift.x + 0.5; | |
1221 p[i].y = y - shift.y + 0.5; | |
1222 } | |
1223 } | |
1224 | |
1225 /** | |
1226 * \brief Apply 3d transformation to several objects | |
1227 * \param shift FreeType vector | |
1228 * \param glyph FreeType glyph | |
1229 * \param glyph2 FreeType glyph | |
1230 * \param frx x-axis rotation angle | |
1231 * \param fry y-axis rotation angle | |
1232 * \param frz z-axis rotation angle | |
1233 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it. | |
1234 */ | |
1235 static void | |
34295 | 1236 transform_3d(FT_Vector shift, FT_Outline *outline, FT_Outline *border, |
31853 | 1237 double frx, double fry, double frz, double fax, double fay, |
1238 double scale, int yshift) | |
1239 { | |
1240 frx = -frx; | |
1241 frz = -frz; | |
1242 if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) { | |
34295 | 1243 if (outline) |
1244 transform_3d_points(shift, outline, frx, fry, frz, | |
31853 | 1245 fax, fay, scale, yshift); |
1246 | |
34295 | 1247 if (border) |
1248 transform_3d_points(shift, border, frx, fry, frz, | |
31853 | 1249 fax, fay, scale, yshift); |
1250 } | |
1251 } | |
23173 | 1252 |
18937 | 1253 /** |
23179 | 1254 * \brief Get bitmaps for a glyph |
1255 * \param info glyph info | |
1256 * Tries to get glyph bitmaps from bitmap cache. | |
1257 * If they can't be found, they are generated by rotating and rendering the glyph. | |
1258 * After that, bitmaps are added to the cache. | |
1259 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow). | |
18937 | 1260 */ |
30200 | 1261 static void |
1262 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info) | |
18937 | 1263 { |
30200 | 1264 BitmapHashValue *val; |
34295 | 1265 OutlineBitmapHashKey *key = &info->hash_key.u.outline; |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1266 |
34295 | 1267 if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip) |
1268 return; | |
1269 | |
1270 val = ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key); | |
1271 | |
1272 if (!val) { | |
30200 | 1273 FT_Vector shift; |
1274 BitmapHashValue hash_val; | |
1275 int error; | |
1276 double fax_scaled, fay_scaled; | |
34295 | 1277 FT_Outline *outline, *border; |
1278 double scale_x = render_priv->font_scale_x; | |
1279 | |
1280 hash_val.bm = hash_val.bm_o = hash_val.bm_s = 0; | |
1281 | |
1282 outline_copy(render_priv->ftlibrary, info->outline, &outline); | |
1283 outline_copy(render_priv->ftlibrary, info->border, &border); | |
31853 | 1284 |
34295 | 1285 // calculating rotation shift vector (from rotation origin to the glyph basepoint) |
1286 shift.x = key->shift_x; | |
1287 shift.y = key->shift_y; | |
35262 | 1288 fax_scaled = info->fax / info->scale_y * info->scale_x; |
1289 fay_scaled = info->fay / info->scale_x * info->scale_y; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1290 |
34295 | 1291 // apply rotation |
1292 transform_3d(shift, outline, border, | |
1293 info->frx, info->fry, info->frz, fax_scaled, | |
1294 fay_scaled, render_priv->font_scale, info->asc); | |
1295 | |
1296 // PAR correction scaling | |
1297 FT_Matrix m = { double_to_d16(scale_x), 0, | |
1298 0, double_to_d16(1.0) }; | |
31853 | 1299 |
34295 | 1300 // subpixel shift |
1301 if (outline) { | |
1302 if (scale_x != 1.0) | |
1303 FT_Outline_Transform(outline, &m); | |
1304 FT_Outline_Translate(outline, key->advance.x, -key->advance.y); | |
1305 } | |
1306 if (border) { | |
1307 if (scale_x != 1.0) | |
1308 FT_Outline_Transform(border, &m); | |
1309 FT_Outline_Translate(border, key->advance.x, -key->advance.y); | |
1310 } | |
23177
134a2baca452
Move glyph_to_bitmap() call and outline glyph deallocation to
eugeni
parents:
23175
diff
changeset
|
1311 |
34295 | 1312 // render glyph |
1313 error = outline_to_bitmap3(render_priv->library, | |
1314 render_priv->synth_priv, | |
1315 render_priv->ftlibrary, | |
1316 outline, border, | |
1317 &hash_val.bm, &hash_val.bm_o, | |
1318 &hash_val.bm_s, info->be, | |
1319 info->blur * render_priv->border_scale, | |
1320 key->shadow_offset, | |
35262 | 1321 info->border_style); |
34295 | 1322 if (error) |
1323 info->symbol = 0; | |
31853 | 1324 |
34295 | 1325 val = ass_cache_put(render_priv->cache.bitmap_cache, &info->hash_key, |
1326 &hash_val); | |
1327 | |
1328 outline_free(render_priv->ftlibrary, outline); | |
1329 outline_free(render_priv->ftlibrary, border); | |
30200 | 1330 } |
34011 | 1331 |
34295 | 1332 info->bm = val->bm; |
1333 info->bm_o = val->bm_o; | |
1334 info->bm_s = val->bm_s; | |
1335 | |
34011 | 1336 // VSFilter compatibility: invisible fill and no border? |
1337 // In this case no shadow is supposed to be rendered. | |
34295 | 1338 if (!info->border && (info->c[0] & 0xFF) == 0xFF) |
34011 | 1339 info->bm_s = 0; |
18937 | 1340 } |
1341 | |
1342 /** | |
19932
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1343 * This function goes through text_info and calculates text parameters. |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1344 * The following text_info fields are filled: |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1345 * height |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1346 * lines[].height |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1347 * lines[].asc |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1348 * lines[].desc |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1349 */ |
30200 | 1350 static void measure_text(ASS_Renderer *render_priv) |
1351 { | |
1352 TextInfo *text_info = &render_priv->text_info; | |
1353 int cur_line = 0; | |
1354 double max_asc = 0., max_desc = 0.; | |
1355 GlyphInfo *last = NULL; | |
1356 int i; | |
1357 int empty_line = 1; | |
1358 text_info->height = 0.; | |
1359 for (i = 0; i < text_info->length + 1; ++i) { | |
1360 if ((i == text_info->length) || text_info->glyphs[i].linebreak) { | |
1361 if (empty_line && cur_line > 0 && last && i < text_info->length) { | |
1362 max_asc = d6_to_double(last->asc) / 2.0; | |
1363 max_desc = d6_to_double(last->desc) / 2.0; | |
1364 } | |
1365 text_info->lines[cur_line].asc = max_asc; | |
1366 text_info->lines[cur_line].desc = max_desc; | |
1367 text_info->height += max_asc + max_desc; | |
1368 cur_line++; | |
1369 max_asc = max_desc = 0.; | |
1370 empty_line = 1; | |
1371 } else | |
1372 empty_line = 0; | |
1373 if (i < text_info->length) { | |
1374 GlyphInfo *cur = text_info->glyphs + i; | |
1375 if (d6_to_double(cur->asc) > max_asc) | |
1376 max_asc = d6_to_double(cur->asc); | |
1377 if (d6_to_double(cur->desc) > max_desc) | |
1378 max_desc = d6_to_double(cur->desc); | |
1379 if (cur->symbol != '\n' && cur->symbol != 0) | |
1380 last = cur; | |
1381 } | |
1382 } | |
1383 text_info->height += | |
1384 (text_info->n_lines - | |
1385 1) * render_priv->settings.line_spacing; | |
1386 } | |
1387 | |
1388 /** | |
1389 * Mark extra whitespace for later removal. | |
1390 */ | |
1391 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \ | |
1392 && !x->linebreak) | |
1393 static void trim_whitespace(ASS_Renderer *render_priv) | |
19932
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1394 { |
30200 | 1395 int i, j; |
1396 GlyphInfo *cur; | |
1397 TextInfo *ti = &render_priv->text_info; | |
1398 | |
1399 // Mark trailing spaces | |
1400 i = ti->length - 1; | |
1401 cur = ti->glyphs + i; | |
1402 while (i && IS_WHITESPACE(cur)) { | |
1403 cur->skip++; | |
1404 cur = ti->glyphs + --i; | |
1405 } | |
1406 | |
1407 // Mark leading whitespace | |
1408 i = 0; | |
1409 cur = ti->glyphs; | |
1410 while (i < ti->length && IS_WHITESPACE(cur)) { | |
1411 cur->skip++; | |
1412 cur = ti->glyphs + ++i; | |
1413 } | |
1414 | |
1415 // Mark all extraneous whitespace inbetween | |
1416 for (i = 0; i < ti->length; ++i) { | |
1417 cur = ti->glyphs + i; | |
1418 if (cur->linebreak) { | |
1419 // Mark whitespace before | |
1420 j = i - 1; | |
1421 cur = ti->glyphs + j; | |
1422 while (j && IS_WHITESPACE(cur)) { | |
1423 cur->skip++; | |
1424 cur = ti->glyphs + --j; | |
1425 } | |
1426 // A break itself can contain a whitespace, too | |
1427 cur = ti->glyphs + i; | |
34011 | 1428 if (cur->symbol == ' ') { |
30200 | 1429 cur->skip++; |
34011 | 1430 // Mark whitespace after |
1431 j = i + 1; | |
1432 cur = ti->glyphs + j; | |
1433 while (j < ti->length && IS_WHITESPACE(cur)) { | |
1434 cur->skip++; | |
1435 cur = ti->glyphs + ++j; | |
1436 } | |
1437 i = j - 1; | |
30200 | 1438 } |
1439 } | |
1440 } | |
19932
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1441 } |
30200 | 1442 #undef IS_WHITESPACE |
19932
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1443 |
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1444 /** |
18937 | 1445 * \brief rearrange text between lines |
1446 * \param max_text_width maximal text line width in pixels | |
1447 * The algo is similar to the one in libvo/sub.c: | |
1448 * 1. Place text, wrapping it when current line is full | |
1449 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces | |
1450 * the difference in lengths between this two lines. | |
1451 * The result may not be optimal, but usually is good enough. | |
30200 | 1452 * |
31853 | 1453 * FIXME: implement style 0 and 3 correctly |
18937 | 1454 */ |
30200 | 1455 static void |
1456 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) | |
18937 | 1457 { |
30200 | 1458 int i; |
1459 GlyphInfo *cur, *s1, *e1, *s2, *s3, *w; | |
1460 int last_space; | |
1461 int break_type; | |
1462 int exit; | |
1463 double pen_shift_x; | |
1464 double pen_shift_y; | |
1465 int cur_line; | |
34295 | 1466 int run_offset; |
30200 | 1467 TextInfo *text_info = &render_priv->text_info; |
18937 | 1468 |
30200 | 1469 last_space = -1; |
1470 text_info->n_lines = 1; | |
1471 break_type = 0; | |
1472 s1 = text_info->glyphs; // current line start | |
1473 for (i = 0; i < text_info->length; ++i) { | |
34011 | 1474 int break_at = -1; |
30200 | 1475 double s_offset, len; |
1476 cur = text_info->glyphs + i; | |
1477 s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); | |
1478 len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset; | |
18937 | 1479 |
30200 | 1480 if (cur->symbol == '\n') { |
1481 break_type = 2; | |
1482 break_at = i; | |
1483 ass_msg(render_priv->library, MSGL_DBG2, | |
1484 "forced line break at %d", break_at); | |
34011 | 1485 } else if (cur->symbol == ' ') { |
1486 last_space = i; | |
1487 } else if (len >= max_text_width | |
1488 && (render_priv->state.wrap_style != 2)) { | |
30200 | 1489 break_type = 1; |
1490 break_at = last_space; | |
34011 | 1491 if (break_at >= 0) |
1492 ass_msg(render_priv->library, MSGL_DBG2, "line break at %d", | |
1493 break_at); | |
30200 | 1494 } |
18937 | 1495 |
30200 | 1496 if (break_at != -1) { |
1497 // need to use one more line | |
1498 // marking break_at+1 as start of a new line | |
1499 int lead = break_at + 1; // the first symbol of the new line | |
1500 if (text_info->n_lines >= text_info->max_lines) { | |
1501 // Raise maximum number of lines | |
1502 text_info->max_lines *= 2; | |
1503 text_info->lines = realloc(text_info->lines, | |
1504 sizeof(LineInfo) * | |
1505 text_info->max_lines); | |
1506 } | |
34295 | 1507 if (lead < text_info->length) { |
30200 | 1508 text_info->glyphs[lead].linebreak = break_type; |
34295 | 1509 last_space = -1; |
1510 s1 = text_info->glyphs + lead; | |
1511 s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); | |
1512 text_info->n_lines++; | |
1513 } | |
30200 | 1514 } |
1515 } | |
18937 | 1516 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) |
30200 | 1517 exit = 0; |
1518 while (!exit && render_priv->state.wrap_style != 1) { | |
1519 exit = 1; | |
1520 w = s3 = text_info->glyphs; | |
1521 s1 = s2 = 0; | |
1522 for (i = 0; i <= text_info->length; ++i) { | |
1523 cur = text_info->glyphs + i; | |
1524 if ((i == text_info->length) || cur->linebreak) { | |
1525 s1 = s2; | |
1526 s2 = s3; | |
1527 s3 = cur; | |
1528 if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' | |
1529 double l1, l2, l1_new, l2_new; | |
18937 | 1530 |
30200 | 1531 w = s2; |
1532 do { | |
1533 --w; | |
1534 } while ((w > s1) && (w->symbol == ' ')); | |
1535 while ((w > s1) && (w->symbol != ' ')) { | |
1536 --w; | |
1537 } | |
1538 e1 = w; | |
1539 while ((e1 > s1) && (e1->symbol == ' ')) { | |
1540 --e1; | |
1541 } | |
1542 if (w->symbol == ' ') | |
1543 ++w; | |
18937 | 1544 |
30200 | 1545 l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) - |
1546 (s1->bbox.xMin + s1->pos.x)); | |
1547 l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - | |
1548 (s2->bbox.xMin + s2->pos.x)); | |
1549 l1_new = d6_to_double( | |
1550 (e1->bbox.xMax + e1->pos.x) - | |
1551 (s1->bbox.xMin + s1->pos.x)); | |
1552 l2_new = d6_to_double( | |
1553 ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - | |
1554 (w->bbox.xMin + w->pos.x)); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1555 |
30200 | 1556 if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { |
1557 w->linebreak = 1; | |
1558 s2->linebreak = 0; | |
1559 exit = 0; | |
1560 } | |
1561 } | |
1562 } | |
1563 if (i == text_info->length) | |
1564 break; | |
1565 } | |
1566 | |
1567 } | |
1568 assert(text_info->n_lines >= 1); | |
18937 | 1569 #undef DIFF |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1570 |
30200 | 1571 measure_text(render_priv); |
1572 trim_whitespace(render_priv); | |
1573 | |
1574 pen_shift_x = 0.; | |
1575 pen_shift_y = 0.; | |
1576 cur_line = 1; | |
34295 | 1577 run_offset = 0; |
30200 | 1578 |
1579 i = 0; | |
1580 cur = text_info->glyphs + i; | |
1581 while (i < text_info->length && cur->skip) | |
1582 cur = text_info->glyphs + ++i; | |
1583 pen_shift_x = d6_to_double(-cur->pos.x); | |
19932
0b5b9cbbc74e
Move calculation of text parameters (number of lines, height, etc.) from
eugeni
parents:
19931
diff
changeset
|
1584 |
30200 | 1585 for (i = 0; i < text_info->length; ++i) { |
1586 cur = text_info->glyphs + i; | |
1587 if (cur->linebreak) { | |
1588 while (i < text_info->length && cur->skip && cur->symbol != '\n') | |
1589 cur = text_info->glyphs + ++i; | |
1590 double height = | |
1591 text_info->lines[cur_line - 1].desc + | |
1592 text_info->lines[cur_line].asc; | |
34295 | 1593 text_info->lines[cur_line - 1].len = i - |
1594 text_info->lines[cur_line - 1].offset; | |
1595 text_info->lines[cur_line].offset = i; | |
30200 | 1596 cur_line++; |
34295 | 1597 run_offset++; |
30200 | 1598 pen_shift_x = d6_to_double(-cur->pos.x); |
1599 pen_shift_y += height + render_priv->settings.line_spacing; | |
1600 ass_msg(render_priv->library, MSGL_DBG2, | |
1601 "shifting from %d to %d by (%f, %f)", i, | |
1602 text_info->length - 1, pen_shift_x, pen_shift_y); | |
1603 } | |
34295 | 1604 cur->bm_run_id += run_offset; |
30200 | 1605 cur->pos.x += double_to_d6(pen_shift_x); |
1606 cur->pos.y += double_to_d6(pen_shift_y); | |
1607 } | |
34295 | 1608 text_info->lines[cur_line - 1].len = |
1609 text_info->length - text_info->lines[cur_line - 1].offset; | |
18937 | 1610 |
34295 | 1611 #if 0 |
1612 // print line info | |
1613 for (i = 0; i < text_info->n_lines; i++) { | |
1614 printf("line %d offset %d length %d\n", i, text_info->lines[i].offset, | |
1615 text_info->lines[i].len); | |
30200 | 1616 } |
34295 | 1617 #endif |
18937 | 1618 } |
1619 | |
1620 /** | |
20294
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1621 * \brief Calculate base point for positioning and rotation |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1622 * \param bbox text bbox |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1623 * \param alignment alignment |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1624 * \param bx, by out: base point coordinates |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1625 */ |
30200 | 1626 static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by) |
20294
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1627 { |
30200 | 1628 const int halign = alignment & 3; |
1629 const int valign = alignment & 12; | |
1630 if (bx) | |
1631 switch (halign) { | |
1632 case HALIGN_LEFT: | |
1633 *bx = bbox->xMin; | |
1634 break; | |
1635 case HALIGN_CENTER: | |
1636 *bx = (bbox->xMax + bbox->xMin) / 2.0; | |
1637 break; | |
1638 case HALIGN_RIGHT: | |
1639 *bx = bbox->xMax; | |
1640 break; | |
1641 } | |
1642 if (by) | |
1643 switch (valign) { | |
1644 case VALIGN_TOP: | |
1645 *by = bbox->yMin; | |
1646 break; | |
1647 case VALIGN_CENTER: | |
1648 *by = (bbox->yMax + bbox->yMin) / 2.0; | |
1649 break; | |
1650 case VALIGN_SUB: | |
1651 *by = bbox->yMax; | |
1652 break; | |
1653 } | |
20294
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1654 } |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1655 |
4d7c8478e523
Move base point calculation to a separate function. Will be reused soon.
eugeni
parents:
20293
diff
changeset
|
1656 /** |
31853 | 1657 * Prepare bitmap hash key of a glyph |
22215
fb365c2b3d05
Implement \frx and \fry (and reimplement \frz) as 3d rotations.
eugeni
parents:
22214
diff
changeset
|
1658 */ |
30200 | 1659 static void |
34295 | 1660 fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info, |
1661 OutlineBitmapHashKey *hash_key) | |
30200 | 1662 { |
34295 | 1663 hash_key->frx = rot_key(info->frx); |
1664 hash_key->fry = rot_key(info->fry); | |
1665 hash_key->frz = rot_key(info->frz); | |
1666 hash_key->fax = double_to_d16(info->fax); | |
1667 hash_key->fay = double_to_d16(info->fay); | |
1668 hash_key->be = info->be; | |
1669 hash_key->blur = info->blur; | |
31853 | 1670 hash_key->shadow_offset.x = double_to_d6( |
34295 | 1671 info->shadow_x * priv->border_scale - |
1672 (int) (info->shadow_x * priv->border_scale)); | |
31853 | 1673 hash_key->shadow_offset.y = double_to_d6( |
34295 | 1674 info->shadow_y * priv->border_scale - |
1675 (int) (info->shadow_y * priv->border_scale)); | |
22215
fb365c2b3d05
Implement \frx and \fry (and reimplement \frz) as 3d rotations.
eugeni
parents:
22214
diff
changeset
|
1676 } |
fb365c2b3d05
Implement \frx and \fry (and reimplement \frz) as 3d rotations.
eugeni
parents:
22214
diff
changeset
|
1677 |
fb365c2b3d05
Implement \frx and \fry (and reimplement \frz) as 3d rotations.
eugeni
parents:
22214
diff
changeset
|
1678 /** |
18937 | 1679 * \brief Main ass rendering function, glues everything together |
1680 * \param event event to render | |
29048
584ff003cce9
Document the ass_render_event event_images parameter.
reimar
parents:
29047
diff
changeset
|
1681 * \param event_images struct containing resulting images, will also be initialized |
30200 | 1682 * Process event, appending resulting ASS_Image's to images_root. |
18937 | 1683 */ |
30200 | 1684 static int |
1685 ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, | |
1686 EventImages *event_images) | |
18937 | 1687 { |
30200 | 1688 char *p; |
1689 FT_UInt previous; | |
1690 FT_UInt num_glyphs; | |
1691 FT_Vector pen; | |
1692 unsigned code; | |
1693 DBBox bbox; | |
1694 int i, j; | |
1695 int MarginL, MarginR, MarginV; | |
1696 int last_break; | |
1697 int alignment, halign, valign; | |
1698 double device_x = 0; | |
1699 double device_y = 0; | |
1700 TextInfo *text_info = &render_priv->text_info; | |
31853 | 1701 GlyphInfo *glyphs = render_priv->text_info.glyphs; |
30200 | 1702 ASS_Drawing *drawing; |
18937 | 1703 |
30200 | 1704 if (event->Style >= render_priv->track->n_styles) { |
1705 ass_msg(render_priv->library, MSGL_WARN, "No style found"); | |
1706 return 1; | |
1707 } | |
1708 if (!event->Text) { | |
1709 ass_msg(render_priv->library, MSGL_WARN, "Empty event"); | |
1710 return 1; | |
1711 } | |
19650 | 1712 |
30200 | 1713 init_render_context(render_priv, event); |
18937 | 1714 |
30200 | 1715 drawing = render_priv->state.drawing; |
1716 text_info->length = 0; | |
1717 num_glyphs = 0; | |
1718 p = event->Text; | |
34295 | 1719 |
35262 | 1720 int in_tag = 0; |
1721 | |
30200 | 1722 // Event parsing. |
1723 while (1) { | |
1724 // get next char, executing style override | |
1725 // this affects render_context | |
1726 do { | |
35262 | 1727 code = 0; |
1728 if (!in_tag && *p == '{') { // '\0' goes here | |
1729 p++; | |
1730 in_tag = 1; | |
1731 } | |
1732 if (in_tag) { | |
1733 int prev_drawing_mode = render_priv->state.drawing_mode; | |
1734 p = parse_tag(render_priv, p, 1.); | |
1735 if (*p == '}') { // end of tag | |
1736 p++; | |
1737 in_tag = 0; | |
1738 } else if (*p != '\\') { | |
1739 ass_msg(render_priv->library, MSGL_V, | |
1740 "Unable to parse: '%.30s'", p); | |
1741 } | |
1742 if (prev_drawing_mode && !render_priv->state.drawing_mode) { | |
1743 // Drawing mode was just disabled. We must exit and draw it | |
1744 // immediately, instead of letting further tags affect it. | |
1745 // See bug #47. | |
1746 break; | |
1747 } | |
1748 } else { | |
1749 code = get_next_char(render_priv, &p); | |
1750 if (code && render_priv->state.drawing_mode) { | |
1751 ass_drawing_add_char(drawing, (char) code); | |
1752 continue; // skip everything in drawing mode | |
1753 } | |
1754 break; | |
1755 } | |
1756 } while (*p); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1757 |
34295 | 1758 if (text_info->length >= text_info->max_glyphs) { |
1759 // Raise maximum number of glyphs | |
1760 text_info->max_glyphs *= 2; | |
1761 text_info->glyphs = glyphs = | |
1762 realloc(text_info->glyphs, | |
1763 sizeof(GlyphInfo) * text_info->max_glyphs); | |
1764 } | |
1765 | |
1766 // Clear current GlyphInfo | |
1767 memset(&glyphs[text_info->length], 0, sizeof(GlyphInfo)); | |
1768 | |
30200 | 1769 // Parse drawing |
1770 if (drawing->i) { | |
1771 drawing->scale_x = render_priv->state.scale_x * | |
1772 render_priv->font_scale; | |
1773 drawing->scale_y = render_priv->state.scale_y * | |
1774 render_priv->font_scale; | |
34295 | 1775 code = 0xfffc; // object replacement character |
1776 glyphs[text_info->length].drawing = drawing; | |
30200 | 1777 } |
18937 | 1778 |
30200 | 1779 // face could have been changed in get_next_char |
1780 if (!render_priv->state.font) { | |
1781 free_render_context(render_priv); | |
1782 return 1; | |
1783 } | |
18937 | 1784 |
30200 | 1785 if (code == 0) |
1786 break; | |
1787 | |
34295 | 1788 // Fill glyph information |
31853 | 1789 glyphs[text_info->length].symbol = code; |
34295 | 1790 glyphs[text_info->length].font = render_priv->state.font; |
30200 | 1791 for (i = 0; i < 4; ++i) { |
1792 uint32_t clr = render_priv->state.c[i]; | |
1793 change_alpha(&clr, | |
1794 mult_alpha(_a(clr), render_priv->state.fade), 1.); | |
31853 | 1795 glyphs[text_info->length].c[i] = clr; |
30200 | 1796 } |
31853 | 1797 glyphs[text_info->length].effect_type = render_priv->state.effect_type; |
1798 glyphs[text_info->length].effect_timing = | |
30200 | 1799 render_priv->state.effect_timing; |
31853 | 1800 glyphs[text_info->length].effect_skip_timing = |
30200 | 1801 render_priv->state.effect_skip_timing; |
35262 | 1802 glyphs[text_info->length].font_size = |
1803 render_priv->state.font_size * render_priv->font_scale; | |
31853 | 1804 glyphs[text_info->length].be = render_priv->state.be; |
1805 glyphs[text_info->length].blur = render_priv->state.blur; | |
1806 glyphs[text_info->length].shadow_x = render_priv->state.shadow_x; | |
1807 glyphs[text_info->length].shadow_y = render_priv->state.shadow_y; | |
34295 | 1808 glyphs[text_info->length].scale_x= render_priv->state.scale_x; |
1809 glyphs[text_info->length].scale_y = render_priv->state.scale_y; | |
35262 | 1810 glyphs[text_info->length].border_style = render_priv->state.border_style; |
34295 | 1811 glyphs[text_info->length].border_x= render_priv->state.border_x; |
1812 glyphs[text_info->length].border_y = render_priv->state.border_y; | |
35262 | 1813 glyphs[text_info->length].hspacing = render_priv->state.hspacing; |
34295 | 1814 glyphs[text_info->length].bold = render_priv->state.bold; |
1815 glyphs[text_info->length].italic = render_priv->state.italic; | |
1816 glyphs[text_info->length].flags = render_priv->state.flags; | |
31853 | 1817 glyphs[text_info->length].frx = render_priv->state.frx; |
1818 glyphs[text_info->length].fry = render_priv->state.fry; | |
1819 glyphs[text_info->length].frz = render_priv->state.frz; | |
1820 glyphs[text_info->length].fax = render_priv->state.fax; | |
1821 glyphs[text_info->length].fay = render_priv->state.fay; | |
34295 | 1822 glyphs[text_info->length].bm_run_id = render_priv->state.bm_run_id; |
21614
5d2ca7ca18b5
Move ascender, descender, and kerning computation to ass_font.c.
eugeni
parents:
21506
diff
changeset
|
1823 |
34295 | 1824 if (glyphs[text_info->length].drawing) { |
1825 drawing = render_priv->state.drawing = | |
1826 ass_drawing_new(render_priv->library, render_priv->ftlibrary); | |
30200 | 1827 } |
19716
e4e492fcc2f7
Bugfix: timing for empty karaoke words was lost, resulting
eugeni
parents:
19693
diff
changeset
|
1828 |
30200 | 1829 text_info->length++; |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1830 |
30200 | 1831 render_priv->state.effect_type = EF_NONE; |
1832 render_priv->state.effect_timing = 0; | |
1833 render_priv->state.effect_skip_timing = 0; | |
19931
399bb1fcdc94
Move variable declaration to a more deeply nested block. It is not used outside of it.
eugeni
parents:
19919
diff
changeset
|
1834 |
30200 | 1835 } |
19556 | 1836 |
30200 | 1837 if (text_info->length == 0) { |
1838 // no valid symbols in the event; this can be smth like {comment} | |
1839 free_render_context(render_priv); | |
1840 return 1; | |
1841 } | |
31853 | 1842 |
34295 | 1843 // Find shape runs and shape text |
1844 ass_shaper_set_base_direction(render_priv->shaper, | |
1845 resolve_base_direction(render_priv->state.font_encoding)); | |
1846 ass_shaper_find_runs(render_priv->shaper, render_priv, glyphs, | |
1847 text_info->length); | |
1848 ass_shaper_shape(render_priv->shaper, text_info); | |
1849 | |
1850 // Retrieve glyphs | |
1851 for (i = 0; i < text_info->length; i++) { | |
1852 GlyphInfo *info = glyphs + i; | |
1853 while (info) { | |
1854 get_outline_glyph(render_priv, info); | |
1855 info = info->next; | |
1856 } | |
1857 info = glyphs + i; | |
1858 | |
1859 // Add additional space after italic to non-italic style changes | |
1860 if (i && glyphs[i - 1].italic && !info->italic) { | |
1861 int back = i - 1; | |
1862 GlyphInfo *og = &glyphs[back]; | |
1863 while (back && og->bbox.xMax - og->bbox.xMin == 0 | |
1864 && og->italic) | |
1865 og = &glyphs[--back]; | |
1866 if (og->bbox.xMax > og->cluster_advance.x) | |
1867 og->cluster_advance.x = og->bbox.xMax; | |
1868 } | |
1869 | |
1870 // add horizontal letter spacing | |
35262 | 1871 info->cluster_advance.x += double_to_d6(info->hspacing * |
34295 | 1872 render_priv->font_scale * info->scale_x); |
1873 | |
1874 // add displacement for vertical shearing | |
35262 | 1875 info->cluster_advance.y += (info->fay / info->scale_x * info->scale_y) * info->cluster_advance.x; |
34295 | 1876 |
1877 } | |
1878 | |
1879 // Preliminary layout (for line wrapping) | |
1880 previous = 0; | |
1881 pen.x = 0; | |
1882 pen.y = 0; | |
1883 for (i = 0; i < text_info->length; i++) { | |
1884 GlyphInfo *info = glyphs + i; | |
1885 FT_Vector cluster_pen = pen; | |
1886 while (info) { | |
1887 info->pos.x = cluster_pen.x; | |
1888 info->pos.y = cluster_pen.y; | |
1889 | |
1890 cluster_pen.x += info->advance.x; | |
1891 cluster_pen.y += info->advance.y; | |
1892 | |
1893 // fill bitmap hash | |
1894 info->hash_key.type = BITMAP_OUTLINE; | |
1895 fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline); | |
1896 | |
1897 info = info->next; | |
1898 } | |
1899 info = glyphs + i; | |
1900 pen.x += info->cluster_advance.x; | |
1901 pen.y += info->cluster_advance.y; | |
1902 previous = info->symbol; | |
1903 } | |
1904 | |
1905 | |
30200 | 1906 // depends on glyph x coordinates being monotonous, so it should be done before line wrap |
1907 process_karaoke_effects(render_priv); | |
18937 | 1908 |
30200 | 1909 // alignments |
1910 alignment = render_priv->state.alignment; | |
1911 halign = alignment & 3; | |
1912 valign = alignment & 12; | |
1913 | |
1914 MarginL = | |
31853 | 1915 (event->MarginL) ? event->MarginL : render_priv->state.style->MarginL; |
30200 | 1916 MarginR = |
31853 | 1917 (event->MarginR) ? event->MarginR : render_priv->state.style->MarginR; |
30200 | 1918 MarginV = |
31853 | 1919 (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV; |
30200 | 1920 |
34295 | 1921 // calculate max length of a line |
1922 double max_text_width = | |
1923 x2scr(render_priv, render_priv->track->PlayResX - MarginR) - | |
1924 x2scr(render_priv, MarginL); | |
30200 | 1925 |
34295 | 1926 // wrap lines |
1927 if (render_priv->state.evt_type != EVENT_HSCROLL) { | |
30200 | 1928 // rearrange text in several lines |
1929 wrap_lines_smart(render_priv, max_text_width); | |
34295 | 1930 } else { |
1931 // no breaking or wrapping, everything in a single line | |
1932 text_info->lines[0].offset = 0; | |
1933 text_info->lines[0].len = text_info->length; | |
1934 text_info->n_lines = 1; | |
1935 measure_text(render_priv); | |
1936 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
1937 |
34295 | 1938 // Reorder text into visual order |
1939 FriBidiStrIndex *cmap = ass_shaper_reorder(render_priv->shaper, text_info); | |
30200 | 1940 |
34295 | 1941 // Reposition according to the map |
1942 pen.x = 0; | |
1943 pen.y = 0; | |
1944 int lineno = 1; | |
1945 for (i = 0; i < text_info->length; i++) { | |
1946 GlyphInfo *info = glyphs + cmap[i]; | |
1947 if (glyphs[i].linebreak) { | |
35262 | 1948 pen.y -= (info->fay / info->scale_x * info->scale_y) * pen.x; |
34295 | 1949 pen.x = 0; |
1950 pen.y += double_to_d6(text_info->lines[lineno-1].desc); | |
1951 pen.y += double_to_d6(text_info->lines[lineno].asc); | |
1952 pen.y += double_to_d6(render_priv->settings.line_spacing); | |
1953 lineno++; | |
1954 } | |
1955 if (info->skip) continue; | |
1956 FT_Vector cluster_pen = pen; | |
1957 while (info) { | |
1958 info->pos.x = info->offset.x + cluster_pen.x; | |
1959 info->pos.y = info->offset.y + cluster_pen.y; | |
1960 cluster_pen.x += info->advance.x; | |
1961 cluster_pen.y += info->advance.y; | |
1962 info = info->next; | |
1963 } | |
1964 info = glyphs + cmap[i]; | |
1965 pen.x += info->cluster_advance.x; | |
1966 pen.y += info->cluster_advance.y; | |
1967 } | |
30200 | 1968 |
34295 | 1969 // align lines |
1970 if (render_priv->state.evt_type != EVENT_HSCROLL) { | |
1971 last_break = -1; | |
1972 double width = 0; | |
1973 for (i = 0; i <= text_info->length; ++i) { // (text_info->length + 1) is the end of the last line | |
1974 if ((i == text_info->length) || glyphs[i].linebreak) { | |
1975 // remove letter spacing (which is included in cluster_advance) | |
1976 if (i > 0) | |
1977 width -= render_priv->state.hspacing * render_priv->font_scale * | |
1978 glyphs[i-1].scale_x; | |
1979 double shift = 0; | |
30200 | 1980 if (halign == HALIGN_LEFT) { // left aligned, no action |
1981 shift = 0; | |
1982 } else if (halign == HALIGN_RIGHT) { // right aligned | |
1983 shift = max_text_width - width; | |
1984 } else if (halign == HALIGN_CENTER) { // centered | |
1985 shift = (max_text_width - width) / 2.0; | |
1986 } | |
1987 for (j = last_break + 1; j < i; ++j) { | |
34295 | 1988 GlyphInfo *info = glyphs + j; |
1989 while (info) { | |
1990 info->pos.x += double_to_d6(shift); | |
1991 info = info->next; | |
1992 } | |
30200 | 1993 } |
1994 last_break = i - 1; | |
34295 | 1995 width = 0; |
1996 } | |
1997 if (i < text_info->length && !glyphs[i].skip && | |
1998 glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) { | |
1999 width += d6_to_double(glyphs[i].cluster_advance.x); | |
30200 | 2000 } |
2001 } | |
2002 } | |
2003 | |
2004 // determing text bounding box | |
2005 compute_string_bbox(text_info, &bbox); | |
2006 | |
2007 // determine device coordinates for text | |
19556 | 2008 |
30200 | 2009 // x coordinate for everything except positioned events |
2010 if (render_priv->state.evt_type == EVENT_NORMAL || | |
2011 render_priv->state.evt_type == EVENT_VSCROLL) { | |
2012 device_x = x2scr(render_priv, MarginL); | |
2013 } else if (render_priv->state.evt_type == EVENT_HSCROLL) { | |
2014 if (render_priv->state.scroll_direction == SCROLL_RL) | |
2015 device_x = | |
2016 x2scr(render_priv, | |
2017 render_priv->track->PlayResX - | |
2018 render_priv->state.scroll_shift); | |
2019 else if (render_priv->state.scroll_direction == SCROLL_LR) | |
2020 device_x = | |
2021 x2scr(render_priv, | |
2022 render_priv->state.scroll_shift) - (bbox.xMax - | |
2023 bbox.xMin); | |
2024 } | |
31853 | 2025 |
30200 | 2026 // y coordinate for everything except positioned events |
2027 if (render_priv->state.evt_type == EVENT_NORMAL || | |
2028 render_priv->state.evt_type == EVENT_HSCROLL) { | |
2029 if (valign == VALIGN_TOP) { // toptitle | |
2030 device_y = | |
2031 y2scr_top(render_priv, | |
2032 MarginV) + text_info->lines[0].asc; | |
2033 } else if (valign == VALIGN_CENTER) { // midtitle | |
2034 double scr_y = | |
2035 y2scr(render_priv, render_priv->track->PlayResY / 2.0); | |
2036 device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0; | |
2037 } else { // subtitle | |
35262 | 2038 double scr_top, scr_bottom, scr_y0; |
30200 | 2039 if (valign != VALIGN_SUB) |
2040 ass_msg(render_priv->library, MSGL_V, | |
31853 | 2041 "Invalid valign, assuming 0 (subtitle)"); |
35262 | 2042 scr_bottom = |
30200 | 2043 y2scr_sub(render_priv, |
2044 render_priv->track->PlayResY - MarginV); | |
35262 | 2045 scr_top = y2scr_top(render_priv, 0); //xxx not always 0? |
2046 device_y = scr_bottom + (scr_top - scr_bottom) * | |
2047 render_priv->settings.line_position / 100.0; | |
30200 | 2048 device_y -= text_info->height; |
2049 device_y += text_info->lines[0].asc; | |
35262 | 2050 // clip to top to avoid confusion if line_position is very high, |
2051 // turning the subtitle into a toptitle | |
2052 // also, don't change behavior if line_position is not used | |
2053 scr_y0 = scr_top + text_info->lines[0].asc; | |
2054 if (device_y < scr_y0 && render_priv->settings.line_position > 0) { | |
2055 device_y = scr_y0; | |
2056 } | |
30200 | 2057 } |
2058 } else if (render_priv->state.evt_type == EVENT_VSCROLL) { | |
2059 if (render_priv->state.scroll_direction == SCROLL_TB) | |
2060 device_y = | |
2061 y2scr(render_priv, | |
2062 render_priv->state.clip_y0 + | |
2063 render_priv->state.scroll_shift) - (bbox.yMax - | |
2064 bbox.yMin); | |
2065 else if (render_priv->state.scroll_direction == SCROLL_BT) | |
2066 device_y = | |
2067 y2scr(render_priv, | |
2068 render_priv->state.clip_y1 - | |
2069 render_priv->state.scroll_shift); | |
2070 } | |
31853 | 2071 |
30200 | 2072 // positioned events are totally different |
2073 if (render_priv->state.evt_type == EVENT_POSITIONED) { | |
2074 double base_x = 0; | |
2075 double base_y = 0; | |
2076 ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f", | |
2077 render_priv->state.pos_x, render_priv->state.pos_y); | |
2078 get_base_point(&bbox, alignment, &base_x, &base_y); | |
2079 device_x = | |
2080 x2scr_pos(render_priv, render_priv->state.pos_x) - base_x; | |
2081 device_y = | |
2082 y2scr_pos(render_priv, render_priv->state.pos_y) - base_y; | |
2083 } | |
31853 | 2084 |
30200 | 2085 // fix clip coordinates (they depend on alignment) |
2086 if (render_priv->state.evt_type == EVENT_NORMAL || | |
2087 render_priv->state.evt_type == EVENT_HSCROLL || | |
2088 render_priv->state.evt_type == EVENT_VSCROLL) { | |
2089 render_priv->state.clip_x0 = | |
31853 | 2090 x2scr_scaled(render_priv, render_priv->state.clip_x0); |
30200 | 2091 render_priv->state.clip_x1 = |
31853 | 2092 x2scr_scaled(render_priv, render_priv->state.clip_x1); |
30200 | 2093 if (valign == VALIGN_TOP) { |
2094 render_priv->state.clip_y0 = | |
2095 y2scr_top(render_priv, render_priv->state.clip_y0); | |
2096 render_priv->state.clip_y1 = | |
2097 y2scr_top(render_priv, render_priv->state.clip_y1); | |
2098 } else if (valign == VALIGN_CENTER) { | |
2099 render_priv->state.clip_y0 = | |
2100 y2scr(render_priv, render_priv->state.clip_y0); | |
2101 render_priv->state.clip_y1 = | |
2102 y2scr(render_priv, render_priv->state.clip_y1); | |
2103 } else if (valign == VALIGN_SUB) { | |
2104 render_priv->state.clip_y0 = | |
2105 y2scr_sub(render_priv, render_priv->state.clip_y0); | |
2106 render_priv->state.clip_y1 = | |
2107 y2scr_sub(render_priv, render_priv->state.clip_y1); | |
2108 } | |
2109 } else if (render_priv->state.evt_type == EVENT_POSITIONED) { | |
2110 render_priv->state.clip_x0 = | |
31853 | 2111 x2scr_pos_scaled(render_priv, render_priv->state.clip_x0); |
30200 | 2112 render_priv->state.clip_x1 = |
31853 | 2113 x2scr_pos_scaled(render_priv, render_priv->state.clip_x1); |
30200 | 2114 render_priv->state.clip_y0 = |
2115 y2scr_pos(render_priv, render_priv->state.clip_y0); | |
2116 render_priv->state.clip_y1 = | |
2117 y2scr_pos(render_priv, render_priv->state.clip_y1); | |
2118 } | |
31853 | 2119 |
30200 | 2120 // calculate rotation parameters |
2121 { | |
2122 DVector center; | |
29383
e9cab9f6ed62
Make sure clip coordinates are inside the screen area.
eugeni
parents:
29382
diff
changeset
|
2123 |
30200 | 2124 if (render_priv->state.have_origin) { |
2125 center.x = x2scr(render_priv, render_priv->state.org_x); | |
2126 center.y = y2scr(render_priv, render_priv->state.org_y); | |
2127 } else { | |
2128 double bx = 0., by = 0.; | |
2129 get_base_point(&bbox, alignment, &bx, &by); | |
2130 center.x = device_x + bx; | |
2131 center.y = device_y + by; | |
2132 } | |
2133 | |
2134 for (i = 0; i < text_info->length; ++i) { | |
31853 | 2135 GlyphInfo *info = glyphs + i; |
34295 | 2136 while (info) { |
2137 OutlineBitmapHashKey *key = &info->hash_key.u.outline; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2138 |
34295 | 2139 if (key->frx || key->fry || key->frz || key->fax || key->fay) { |
2140 key->shift_x = info->pos.x + double_to_d6(device_x - center.x); | |
2141 key->shift_y = -(info->pos.y + double_to_d6(device_y - center.y)); | |
2142 } else { | |
2143 key->shift_x = 0; | |
2144 key->shift_y = 0; | |
2145 } | |
2146 info = info->next; | |
30200 | 2147 } |
2148 } | |
2149 } | |
18937 | 2150 |
30200 | 2151 // convert glyphs to bitmaps |
34295 | 2152 int left = render_priv->settings.left_margin; |
2153 device_x = (device_x - left) * render_priv->font_scale_x + left; | |
30200 | 2154 for (i = 0; i < text_info->length; ++i) { |
34295 | 2155 GlyphInfo *info = glyphs + i; |
2156 while (info) { | |
2157 OutlineBitmapHashKey *key = &info->hash_key.u.outline; | |
2158 info->pos.x *= render_priv->font_scale_x; | |
2159 key->advance.x = | |
2160 double_to_d6(device_x - (int) device_x + | |
2161 d6_to_double(info->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; | |
2162 key->advance.y = | |
2163 double_to_d6(device_y - (int) device_y + | |
2164 d6_to_double(info->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; | |
2165 get_bitmap_glyph(render_priv, info); | |
2166 info = info->next; | |
2167 } | |
30200 | 2168 } |
18937 | 2169 |
30200 | 2170 memset(event_images, 0, sizeof(*event_images)); |
2171 event_images->top = device_y - text_info->lines[0].asc; | |
2172 event_images->height = text_info->height; | |
31853 | 2173 event_images->left = |
2174 (device_x + bbox.xMin * render_priv->font_scale_x) + 0.5; | |
2175 event_images->width = | |
2176 (bbox.xMax - bbox.xMin) * render_priv->font_scale_x + 0.5; | |
30200 | 2177 event_images->detect_collisions = render_priv->state.detect_collisions; |
2178 event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; | |
2179 event_images->event = event; | |
2180 event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y); | |
23174 | 2181 |
34295 | 2182 ass_shaper_cleanup(render_priv->shaper, text_info); |
30200 | 2183 free_render_context(render_priv); |
18937 | 2184 |
30200 | 2185 return 0; |
18937 | 2186 } |
2187 | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2188 /** |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2189 * \brief deallocate image list |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2190 * \param img list pointer |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2191 */ |
31853 | 2192 void ass_free_images(ASS_Image *img) |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2193 { |
30200 | 2194 while (img) { |
2195 ASS_Image *next = img->next; | |
2196 free(img); | |
2197 img = next; | |
2198 } | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2199 } |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2200 |
31853 | 2201 /** |
2202 * \brief Check cache limits and reset cache if they are exceeded | |
2203 */ | |
2204 static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache) | |
20446
e8adc3778348
Split ass_configure() into several smaller functions.
eugeni
parents:
20320
diff
changeset
|
2205 { |
34295 | 2206 if (ass_cache_empty(cache->bitmap_cache, cache->bitmap_max_size)) { |
2207 ass_cache_empty(cache->composite_cache, 0); | |
31853 | 2208 ass_free_images(priv->prev_images_root); |
2209 priv->prev_images_root = 0; | |
35262 | 2210 priv->cache_cleared = 1; |
30200 | 2211 } |
34295 | 2212 if (ass_cache_empty(cache->outline_cache, cache->glyph_max)) { |
2213 ass_cache_empty(cache->bitmap_cache, 0); | |
2214 ass_cache_empty(cache->composite_cache, 0); | |
2215 ass_free_images(priv->prev_images_root); | |
2216 priv->prev_images_root = 0; | |
35262 | 2217 priv->cache_cleared = 1; |
31853 | 2218 } |
26582
62ac4f8062ee
Remove libass dependency on global font_fontconfig variable.
eugeni
parents:
26033
diff
changeset
|
2219 } |
62ac4f8062ee
Remove libass dependency on global font_fontconfig variable.
eugeni
parents:
26033
diff
changeset
|
2220 |
18937 | 2221 /** |
2222 * \brief Start a new frame | |
2223 */ | |
30200 | 2224 static int |
2225 ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track, | |
2226 long long now) | |
18937 | 2227 { |
30200 | 2228 ASS_Settings *settings_priv = &render_priv->settings; |
2229 | |
2230 if (!render_priv->settings.frame_width | |
2231 && !render_priv->settings.frame_height) | |
2232 return 1; // library not initialized | |
2233 | |
2234 if (render_priv->library != track->library) | |
2235 return 1; | |
2236 | |
31853 | 2237 if (!render_priv->fontconfig_priv) |
2238 return 1; | |
2239 | |
30200 | 2240 free_list_clear(render_priv); |
2241 | |
2242 if (track->n_events == 0) | |
2243 return 1; // nothing to do | |
18937 | 2244 |
30200 | 2245 render_priv->track = track; |
2246 render_priv->time = now; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2247 |
34295 | 2248 ass_lazy_track_init(render_priv->library, render_priv->track); |
30200 | 2249 |
2250 render_priv->font_scale = settings_priv->font_size_coeff * | |
2251 render_priv->orig_height / render_priv->track->PlayResY; | |
2252 if (render_priv->track->ScaledBorderAndShadow) | |
2253 render_priv->border_scale = | |
2254 ((double) render_priv->orig_height) / | |
2255 render_priv->track->PlayResY; | |
2256 else | |
2257 render_priv->border_scale = 1.; | |
2258 | |
34295 | 2259 ass_shaper_set_kerning(render_priv->shaper, track->Kerning); |
2260 if (track->Language) | |
2261 ass_shaper_set_language(render_priv->shaper, track->Language); | |
2262 ass_shaper_set_level(render_priv->shaper, render_priv->settings.shaper); | |
2263 | |
30200 | 2264 // PAR correction |
2265 render_priv->font_scale_x = render_priv->settings.aspect / | |
2266 render_priv->settings.storage_aspect; | |
2267 | |
2268 render_priv->prev_images_root = render_priv->images_root; | |
2269 render_priv->images_root = 0; | |
18937 | 2270 |
31853 | 2271 check_cache_limits(render_priv, &render_priv->cache); |
19825
f351a3fc3e42
Make font outline width proportional to movie resolution.
eugeni
parents:
19716
diff
changeset
|
2272 |
30200 | 2273 return 0; |
18937 | 2274 } |
2275 | |
30200 | 2276 static int cmp_event_layer(const void *p1, const void *p2) |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2277 { |
30200 | 2278 ASS_Event *e1 = ((EventImages *) p1)->event; |
2279 ASS_Event *e2 = ((EventImages *) p2)->event; | |
2280 if (e1->Layer < e2->Layer) | |
2281 return -1; | |
2282 if (e1->Layer > e2->Layer) | |
2283 return 1; | |
2284 if (e1->ReadOrder < e2->ReadOrder) | |
2285 return -1; | |
2286 if (e1->ReadOrder > e2->ReadOrder) | |
2287 return 1; | |
2288 return 0; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2289 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2290 |
30200 | 2291 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv, |
2292 ASS_Event *event) | |
18937 | 2293 { |
30200 | 2294 if (!event->render_priv) |
2295 event->render_priv = calloc(1, sizeof(ASS_RenderPriv)); | |
2296 if (render_priv->render_id != event->render_priv->render_id) { | |
2297 memset(event->render_priv, 0, sizeof(ASS_RenderPriv)); | |
2298 event->render_priv->render_id = render_priv->render_id; | |
2299 } | |
2300 | |
2301 return event->render_priv; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2302 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2303 |
30200 | 2304 static int overlap(Segment *s1, Segment *s2) |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2305 { |
30200 | 2306 if (s1->a >= s2->b || s2->a >= s1->b || |
2307 s1->ha >= s2->hb || s2->ha >= s1->hb) | |
2308 return 0; | |
2309 return 1; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2310 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2311 |
30200 | 2312 static int cmp_segment(const void *p1, const void *p2) |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2313 { |
30200 | 2314 return ((Segment *) p1)->a - ((Segment *) p2)->a; |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2315 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2316 |
30200 | 2317 static void |
2318 shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift) | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2319 { |
30200 | 2320 ASS_Image *cur = ei->imgs; |
2321 while (cur) { | |
2322 cur->dst_y += shift; | |
2323 // clip top and bottom | |
2324 if (cur->dst_y < 0) { | |
2325 int clip = -cur->dst_y; | |
2326 cur->h -= clip; | |
2327 cur->bitmap += clip * cur->stride; | |
2328 cur->dst_y = 0; | |
2329 } | |
2330 if (cur->dst_y + cur->h >= render_priv->height) { | |
2331 int clip = cur->dst_y + cur->h - render_priv->height; | |
2332 cur->h -= clip; | |
2333 } | |
2334 if (cur->h <= 0) { | |
2335 cur->h = 0; | |
2336 cur->dst_y = 0; | |
2337 } | |
2338 cur = cur->next; | |
2339 } | |
2340 ei->top += shift; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2341 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2342 |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2343 // dir: 1 - move down |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2344 // -1 - move up |
30200 | 2345 static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir) |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2346 { |
30200 | 2347 int i; |
2348 int shift = 0; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2349 |
30200 | 2350 if (dir == 1) // move down |
2351 for (i = 0; i < *cnt; ++i) { | |
2352 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || | |
2353 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) | |
2354 continue; | |
2355 shift = fixed[i].b - s->a; | |
2356 } else // dir == -1, move up | |
2357 for (i = *cnt - 1; i >= 0; --i) { | |
2358 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || | |
2359 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) | |
2360 continue; | |
2361 shift = fixed[i].a - s->b; | |
2362 } | |
21097
77316737de63
Fix collision detection. The old method tried to avoid gaps between subtitles
eugeni
parents:
21066
diff
changeset
|
2363 |
30200 | 2364 fixed[*cnt].a = s->a + shift; |
2365 fixed[*cnt].b = s->b + shift; | |
2366 fixed[*cnt].ha = s->ha; | |
2367 fixed[*cnt].hb = s->hb; | |
2368 (*cnt)++; | |
2369 qsort(fixed, *cnt, sizeof(Segment), cmp_segment); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2370 |
30200 | 2371 return shift; |
18937 | 2372 } |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2373 |
30200 | 2374 static void |
2375 fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt) | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2376 { |
30200 | 2377 Segment *used = malloc(cnt * sizeof(*used)); |
2378 int cnt_used = 0; | |
2379 int i, j; | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2380 |
30200 | 2381 // fill used[] with fixed events |
2382 for (i = 0; i < cnt; ++i) { | |
2383 ASS_RenderPriv *priv; | |
2384 if (!imgs[i].detect_collisions) | |
2385 continue; | |
2386 priv = get_render_priv(render_priv, imgs[i].event); | |
2387 if (priv->height > 0) { // it's a fixed event | |
2388 Segment s; | |
2389 s.a = priv->top; | |
2390 s.b = priv->top + priv->height; | |
2391 s.ha = priv->left; | |
2392 s.hb = priv->left + priv->width; | |
2393 if (priv->height != imgs[i].height) { // no, it's not | |
2394 ass_msg(render_priv->library, MSGL_WARN, | |
31853 | 2395 "Event height has changed"); |
30200 | 2396 priv->top = 0; |
2397 priv->height = 0; | |
2398 priv->left = 0; | |
2399 priv->width = 0; | |
2400 } | |
2401 for (j = 0; j < cnt_used; ++j) | |
2402 if (overlap(&s, used + j)) { // no, it's not | |
2403 priv->top = 0; | |
2404 priv->height = 0; | |
2405 priv->left = 0; | |
2406 priv->width = 0; | |
2407 } | |
2408 if (priv->height > 0) { // still a fixed event | |
2409 used[cnt_used].a = priv->top; | |
2410 used[cnt_used].b = priv->top + priv->height; | |
2411 used[cnt_used].ha = priv->left; | |
2412 used[cnt_used].hb = priv->left + priv->width; | |
2413 cnt_used++; | |
2414 shift_event(render_priv, imgs + i, priv->top - imgs[i].top); | |
2415 } | |
2416 } | |
2417 } | |
2418 qsort(used, cnt_used, sizeof(Segment), cmp_segment); | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2419 |
30200 | 2420 // try to fit other events in free spaces |
2421 for (i = 0; i < cnt; ++i) { | |
2422 ASS_RenderPriv *priv; | |
2423 if (!imgs[i].detect_collisions) | |
2424 continue; | |
2425 priv = get_render_priv(render_priv, imgs[i].event); | |
2426 if (priv->height == 0) { // not a fixed event | |
2427 int shift; | |
2428 Segment s; | |
2429 s.a = imgs[i].top; | |
2430 s.b = imgs[i].top + imgs[i].height; | |
2431 s.ha = imgs[i].left; | |
2432 s.hb = imgs[i].left + imgs[i].width; | |
2433 shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); | |
2434 if (shift) | |
2435 shift_event(render_priv, imgs + i, shift); | |
2436 // make it fixed | |
2437 priv->top = imgs[i].top; | |
2438 priv->height = imgs[i].height; | |
2439 priv->left = imgs[i].left; | |
2440 priv->width = imgs[i].width; | |
2441 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2442 |
30200 | 2443 } |
2444 | |
2445 free(used); | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2446 } |
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2447 |
18937 | 2448 /** |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2449 * \brief compare two images |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2450 * \param i1 first image |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2451 * \param i2 second image |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2452 * \return 0 if identical, 1 if different positions, 2 if different content |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2453 */ |
30200 | 2454 static int ass_image_compare(ASS_Image *i1, ASS_Image *i2) |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2455 { |
30200 | 2456 if (i1->w != i2->w) |
2457 return 2; | |
2458 if (i1->h != i2->h) | |
2459 return 2; | |
2460 if (i1->stride != i2->stride) | |
2461 return 2; | |
2462 if (i1->color != i2->color) | |
2463 return 2; | |
2464 if (i1->bitmap != i2->bitmap) | |
2465 return 2; | |
2466 if (i1->dst_x != i2->dst_x) | |
2467 return 1; | |
2468 if (i1->dst_y != i2->dst_y) | |
2469 return 1; | |
2470 return 0; | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2471 } |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2472 |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2473 /** |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2474 * \brief compare current and previous image list |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2475 * \param priv library handle |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2476 * \return 0 if identical, 1 if different positions, 2 if different content |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2477 */ |
30200 | 2478 static int ass_detect_change(ASS_Renderer *priv) |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2479 { |
30200 | 2480 ASS_Image *img, *img2; |
2481 int diff; | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2482 |
35262 | 2483 if (priv->cache_cleared) |
2484 return 2; | |
2485 | |
30200 | 2486 img = priv->prev_images_root; |
2487 img2 = priv->images_root; | |
2488 diff = 0; | |
2489 while (img && diff < 2) { | |
2490 ASS_Image *next, *next2; | |
2491 next = img->next; | |
2492 if (img2) { | |
2493 int d = ass_image_compare(img, img2); | |
2494 if (d > diff) | |
2495 diff = d; | |
2496 next2 = img2->next; | |
2497 } else { | |
2498 // previous list is shorter | |
2499 diff = 2; | |
2500 break; | |
2501 } | |
2502 img = next; | |
2503 img2 = next2; | |
2504 } | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2505 |
30200 | 2506 // is the previous list longer? |
2507 if (img2) | |
2508 diff = 2; | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2509 |
30200 | 2510 return diff; |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2511 } |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2512 |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2513 /** |
18937 | 2514 * \brief render a frame |
2515 * \param priv library handle | |
2516 * \param track track | |
2517 * \param now current video timestamp (ms) | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2518 * \param detect_change a value describing how the new images differ from the previous ones will be written here: |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2519 * 0 if identical, 1 if different positions, 2 if different content. |
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2520 * Can be NULL, in that case no detection is performed. |
18937 | 2521 */ |
30200 | 2522 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track, |
2523 long long now, int *detect_change) | |
18937 | 2524 { |
30200 | 2525 int i, cnt, rc; |
2526 EventImages *last; | |
2527 ASS_Image **tail; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2528 |
30200 | 2529 // init frame |
2530 rc = ass_start_frame(priv, track, now); | |
35262 | 2531 if (rc != 0) { |
2532 if (detect_change) { | |
2533 *detect_change = 2; | |
2534 } | |
30200 | 2535 return 0; |
35262 | 2536 } |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2537 |
30200 | 2538 // render events separately |
2539 cnt = 0; | |
2540 for (i = 0; i < track->n_events; ++i) { | |
2541 ASS_Event *event = track->events + i; | |
2542 if ((event->Start <= now) | |
2543 && (now < (event->Start + event->Duration))) { | |
2544 if (cnt >= priv->eimg_size) { | |
2545 priv->eimg_size += 100; | |
2546 priv->eimg = | |
2547 realloc(priv->eimg, | |
2548 priv->eimg_size * sizeof(EventImages)); | |
2549 } | |
2550 rc = ass_render_event(priv, event, priv->eimg + cnt); | |
2551 if (!rc) | |
2552 ++cnt; | |
2553 } | |
2554 } | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2555 |
30200 | 2556 // sort by layer |
2557 qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer); | |
2558 | |
2559 // call fix_collisions for each group of events with the same layer | |
2560 last = priv->eimg; | |
2561 for (i = 1; i < cnt; ++i) | |
2562 if (last->event->Layer != priv->eimg[i].event->Layer) { | |
2563 fix_collisions(priv, last, priv->eimg + i - last); | |
2564 last = priv->eimg + i; | |
2565 } | |
2566 if (cnt > 0) | |
2567 fix_collisions(priv, last, priv->eimg + cnt - last); | |
19638
a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
eugeni
parents:
19636
diff
changeset
|
2568 |
30200 | 2569 // concat lists |
2570 tail = &priv->images_root; | |
2571 for (i = 0; i < cnt; ++i) { | |
2572 ASS_Image *cur = priv->eimg[i].imgs; | |
2573 while (cur) { | |
2574 *tail = cur; | |
2575 tail = &cur->next; | |
2576 cur = cur->next; | |
2577 } | |
2578 } | |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2579 |
30200 | 2580 if (detect_change) |
2581 *detect_change = ass_detect_change(priv); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29178
diff
changeset
|
2582 |
30200 | 2583 // free the previous image list |
2584 ass_free_images(priv->prev_images_root); | |
2585 priv->prev_images_root = 0; | |
35262 | 2586 priv->cache_cleared = 0; |
21506
8174acbf0633
Speed up ASS subtitles display by detecting changes between two consecutive
eugeni
parents:
21460
diff
changeset
|
2587 |
30200 | 2588 return priv->images_root; |
18937 | 2589 } |