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