Mercurial > mplayer.hg
annotate libass/ass_render.c @ 32910:f34e21c5dc2f
Improve documentation on dlabel.
Throughout the skin documentation 'width' is used, so use it
instead of 'length' for the dlabel documentation, too.
Additionally, the German translation has been revised.
author | ib |
---|---|
date | Wed, 02 Mar 2011 13:30:44 +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 } |