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