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