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