Raise LIBASS_VERSION, forgotten in r31293.
[mplayer/glamo.git] / libass / ass_render.c
blob6bc0c61baea07f0aeea83c881a0f0ae0ff2ff7cb
1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * libass is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * libass is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with libass; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "config.h"
23 #include <assert.h>
24 #include <math.h>
25 #include <inttypes.h>
26 #include <ft2build.h>
27 #include FT_FREETYPE_H
28 #include FT_STROKER_H
29 #include FT_GLYPH_H
30 #include FT_SYNTHESIS_H
32 #include "ass.h"
33 #include "ass_font.h"
34 #include "ass_bitmap.h"
35 #include "ass_cache.h"
36 #include "ass_utils.h"
37 #include "ass_fontconfig.h"
38 #include "ass_library.h"
39 #include "ass_drawing.h"
40 #include "ass_render.h"
41 #include "ass_parse.h"
43 #define MAX_GLYPHS_INITIAL 1024
44 #define MAX_LINES_INITIAL 64
45 #define SUBPIXEL_MASK 63
46 #define SUBPIXEL_ACCURACY 7 // d6 mask for subpixel accuracy adjustment
47 #define GLYPH_CACHE_MAX 1000
48 #define BITMAP_CACHE_MAX_SIZE 50 * 1048576
50 static void ass_lazy_track_init(ASS_Renderer *render_priv)
52 ASS_Track *track = render_priv->track;
54 if (track->PlayResX && track->PlayResY)
55 return;
56 if (!track->PlayResX && !track->PlayResY) {
57 ass_msg(render_priv->library, MSGL_WARN,
58 "Neither PlayResX nor PlayResY defined. Assuming 384x288");
59 track->PlayResX = 384;
60 track->PlayResY = 288;
61 } else {
62 if (!track->PlayResY && track->PlayResX == 1280) {
63 track->PlayResY = 1024;
64 ass_msg(render_priv->library, MSGL_WARN,
65 "PlayResY undefined, setting to %d", track->PlayResY);
66 } else if (!track->PlayResY) {
67 track->PlayResY = track->PlayResX * 3 / 4;
68 ass_msg(render_priv->library, MSGL_WARN,
69 "PlayResY undefined, setting to %d", track->PlayResY);
70 } else if (!track->PlayResX && track->PlayResY == 1024) {
71 track->PlayResX = 1280;
72 ass_msg(render_priv->library, MSGL_WARN,
73 "PlayResX undefined, setting to %d", track->PlayResX);
74 } else if (!track->PlayResX) {
75 track->PlayResX = track->PlayResY * 4 / 3;
76 ass_msg(render_priv->library, MSGL_WARN,
77 "PlayResX undefined, setting to %d", track->PlayResX);
82 ASS_Renderer *ass_renderer_init(ASS_Library *library)
84 int error;
85 FT_Library ft;
86 ASS_Renderer *priv = 0;
87 int vmajor, vminor, vpatch;
89 error = FT_Init_FreeType(&ft);
90 if (error) {
91 ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
92 goto ass_init_exit;
95 FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
96 ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d",
97 vmajor, vminor, vpatch);
98 ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d",
99 FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
101 priv = calloc(1, sizeof(ASS_Renderer));
102 if (!priv) {
103 FT_Done_FreeType(ft);
104 goto ass_init_exit;
107 priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
109 priv->library = library;
110 priv->ftlibrary = ft;
111 // images_root and related stuff is zero-filled in calloc
113 priv->cache.font_cache = ass_font_cache_init(library);
114 priv->cache.bitmap_cache = ass_bitmap_cache_init(library);
115 priv->cache.composite_cache = ass_composite_cache_init(library);
116 priv->cache.glyph_cache = ass_glyph_cache_init(library);
117 priv->cache.glyph_max = GLYPH_CACHE_MAX;
118 priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
120 priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
121 priv->text_info.max_lines = MAX_LINES_INITIAL;
122 priv->text_info.glyphs =
123 calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
124 priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
126 ass_init_exit:
127 if (priv)
128 ass_msg(library, MSGL_INFO, "Init");
129 else
130 ass_msg(library, MSGL_ERR, "Init failed");
132 return priv;
135 void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
136 int bitmap_max)
138 render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX;
139 render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max :
140 BITMAP_CACHE_MAX_SIZE;
143 static void free_list_clear(ASS_Renderer *render_priv)
145 if (render_priv->free_head) {
146 FreeList *item = render_priv->free_head;
147 while(item) {
148 FreeList *oi = item;
149 free(item->object);
150 item = item->next;
151 free(oi);
153 render_priv->free_head = NULL;
157 static void ass_free_images(ASS_Image *img);
159 void ass_renderer_done(ASS_Renderer *render_priv)
161 ass_font_cache_done(render_priv->cache.font_cache);
162 ass_bitmap_cache_done(render_priv->cache.bitmap_cache);
163 ass_composite_cache_done(render_priv->cache.composite_cache);
164 ass_glyph_cache_done(render_priv->cache.glyph_cache);
166 ass_free_images(render_priv->images_root);
167 ass_free_images(render_priv->prev_images_root);
169 if (render_priv->state.stroker) {
170 FT_Stroker_Done(render_priv->state.stroker);
171 render_priv->state.stroker = 0;
173 if (render_priv && render_priv->ftlibrary)
174 FT_Done_FreeType(render_priv->ftlibrary);
175 if (render_priv && render_priv->fontconfig_priv)
176 fontconfig_done(render_priv->fontconfig_priv);
177 if (render_priv && render_priv->synth_priv)
178 ass_synth_done(render_priv->synth_priv);
179 if (render_priv && render_priv->eimg)
180 free(render_priv->eimg);
181 free(render_priv->text_info.glyphs);
182 free(render_priv->text_info.lines);
184 free(render_priv->settings.default_font);
185 free(render_priv->settings.default_family);
187 free_list_clear(render_priv);
188 free(render_priv);
192 * \brief Create a new ASS_Image
193 * Parameters are the same as ASS_Image fields.
195 static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
196 int bitmap_h, int stride, int dst_x,
197 int dst_y, uint32_t color)
199 ASS_Image *img = calloc(1, sizeof(ASS_Image));
201 img->w = bitmap_w;
202 img->h = bitmap_h;
203 img->stride = stride;
204 img->bitmap = bitmap;
205 img->color = color;
206 img->dst_x = dst_x;
207 img->dst_y = dst_y;
209 return img;
212 static double x2scr_pos(ASS_Renderer *render_priv, double x);
213 static double y2scr_pos(ASS_Renderer *render_priv, double y);
216 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
218 * Inverse clipping with the following strategy:
219 * - find rectangle from (x0, y0) to (cx0, y1)
220 * - find rectangle from (cx0, y0) to (cx1, cy0)
221 * - find rectangle from (cx0, cy1) to (cx1, y1)
222 * - find rectangle from (cx1, y0) to (x1, y1)
223 * These rectangles can be invalid and in this case are discarded.
224 * Afterwards, they are clipped against the screen coordinates.
225 * In an additional pass, the rectangles need to be split up left/right for
226 * karaoke effects. This can result in a lot of bitmaps (6 to be exact).
228 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
229 Bitmap *bm, int dst_x, int dst_y,
230 uint32_t color, uint32_t color2, int brk,
231 ASS_Image **tail)
233 int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
234 Rect r[4];
235 ASS_Image *img;
237 dst_x += bm->left;
238 dst_y += bm->top;
240 // we still need to clip against screen boundaries
241 zx = x2scr_pos(render_priv, 0);
242 zy = y2scr_pos(render_priv, 0);
243 sx = x2scr_pos(render_priv, render_priv->track->PlayResX);
244 sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
246 x0 = 0;
247 y0 = 0;
248 x1 = bm->w;
249 y1 = bm->h;
250 cx0 = render_priv->state.clip_x0 - dst_x;
251 cy0 = render_priv->state.clip_y0 - dst_y;
252 cx1 = render_priv->state.clip_x1 - dst_x;
253 cy1 = render_priv->state.clip_y1 - dst_y;
255 // calculate rectangles and discard invalid ones while we're at it.
256 i = 0;
257 r[i].x0 = x0;
258 r[i].y0 = y0;
259 r[i].x1 = (cx0 > x1) ? x1 : cx0;
260 r[i].y1 = y1;
261 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
262 r[i].x0 = (cx0 < 0) ? x0 : cx0;
263 r[i].y0 = y0;
264 r[i].x1 = (cx1 > x1) ? x1 : cx1;
265 r[i].y1 = (cy0 > y1) ? y1 : cy0;
266 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
267 r[i].x0 = (cx0 < 0) ? x0 : cx0;
268 r[i].y0 = (cy1 < 0) ? y0 : cy1;
269 r[i].x1 = (cx1 > x1) ? x1 : cx1;
270 r[i].y1 = y1;
271 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
272 r[i].x0 = (cx1 < 0) ? x0 : cx1;
273 r[i].y0 = y0;
274 r[i].x1 = x1;
275 r[i].y1 = y1;
276 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
278 // clip each rectangle to screen coordinates
279 for (j = 0; j < i; j++) {
280 r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
281 r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
282 r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
283 r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
286 // draw the rectangles
287 for (j = 0; j < i; j++) {
288 int lbrk = brk;
289 // kick out rectangles that are invalid now
290 if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
291 continue;
292 // split up into left and right for karaoke, if needed
293 if (lbrk > r[j].x0) {
294 if (lbrk > r[j].x1) lbrk = r[j].x1;
295 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0,
296 lbrk - r[j].x0, r[j].y1 - r[j].y0,
297 bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color);
298 *tail = img;
299 tail = &img->next;
301 if (lbrk < r[j].x1) {
302 if (lbrk < r[j].x0) lbrk = r[j].x0;
303 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk,
304 r[j].x1 - lbrk, r[j].y1 - r[j].y0,
305 bm->w, dst_x + lbrk, dst_y + r[j].y0, color2);
306 *tail = img;
307 tail = &img->next;
311 return tail;
315 * \brief convert bitmap glyph into ASS_Image struct(s)
316 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
317 * \param dst_x bitmap x coordinate in video frame
318 * \param dst_y bitmap y coordinate in video frame
319 * \param color first color, RGBA
320 * \param color2 second color, RGBA
321 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
322 * \param tail pointer to the last image's next field, head of the generated list should be stored here
323 * \return pointer to the new list tail
324 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
326 static ASS_Image **
327 render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
328 uint32_t color, uint32_t color2, int brk, ASS_Image **tail)
330 // Inverse clipping in use?
331 if (render_priv->state.clip_mode)
332 return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
333 brk, tail);
335 // brk is relative to dst_x
336 // color = color left of brk
337 // color2 = color right of brk
338 int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
339 int clip_x0, clip_y0, clip_x1, clip_y1;
340 int tmp;
341 ASS_Image *img;
343 dst_x += bm->left;
344 dst_y += bm->top;
345 brk -= bm->left;
347 // clipping
348 clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width);
349 clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height);
350 clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width);
351 clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height);
352 b_x0 = 0;
353 b_y0 = 0;
354 b_x1 = bm->w;
355 b_y1 = bm->h;
357 tmp = dst_x - clip_x0;
358 if (tmp < 0) {
359 ass_msg(render_priv->library, MSGL_DBG2, "clip left");
360 b_x0 = -tmp;
362 tmp = dst_y - clip_y0;
363 if (tmp < 0) {
364 ass_msg(render_priv->library, MSGL_DBG2, "clip top");
365 b_y0 = -tmp;
367 tmp = clip_x1 - dst_x - bm->w;
368 if (tmp < 0) {
369 ass_msg(render_priv->library, MSGL_DBG2, "clip right");
370 b_x1 = bm->w + tmp;
372 tmp = clip_y1 - dst_y - bm->h;
373 if (tmp < 0) {
374 ass_msg(render_priv->library, MSGL_DBG2, "clip bottom");
375 b_y1 = bm->h + tmp;
378 if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
379 return tail;
381 if (brk > b_x0) { // draw left part
382 if (brk > b_x1)
383 brk = b_x1;
384 img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
385 brk - b_x0, b_y1 - b_y0, bm->w,
386 dst_x + b_x0, dst_y + b_y0, color);
387 *tail = img;
388 tail = &img->next;
390 if (brk < b_x1) { // draw right part
391 if (brk < b_x0)
392 brk = b_x0;
393 img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
394 b_x1 - brk, b_y1 - b_y0, bm->w,
395 dst_x + brk, dst_y + b_y0, color2);
396 *tail = img;
397 tail = &img->next;
399 return tail;
403 * \brief Replace the bitmap buffer in ASS_Image with a copy
404 * \param img ASS_Image to operate on
405 * \return pointer to old bitmap buffer
407 static unsigned char *clone_bitmap_buffer(ASS_Image *img)
409 unsigned char *old_bitmap = img->bitmap;
410 int size = img->stride * (img->h - 1) + img->w;
411 img->bitmap = malloc(size);
412 memcpy(img->bitmap, old_bitmap, size);
413 return old_bitmap;
417 * \brief Calculate overlapping area of two consecutive bitmaps and in case they
418 * overlap, blend them together
419 * Mainly useful for translucent glyphs and especially borders, to avoid the
420 * luminance adding up where they overlap (which looks ugly)
422 static void
423 render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
424 ASS_Image **tail)
426 int left, top, bottom, right;
427 int old_left, old_top, w, h, cur_left, cur_top;
428 int x, y, opos, cpos;
429 char m;
430 CompositeHashKey hk;
431 CompositeHashValue *hv;
432 CompositeHashValue chv;
433 int ax = (*last_tail)->dst_x;
434 int ay = (*last_tail)->dst_y;
435 int aw = (*last_tail)->w;
436 int as = (*last_tail)->stride;
437 int ah = (*last_tail)->h;
438 int bx = (*tail)->dst_x;
439 int by = (*tail)->dst_y;
440 int bw = (*tail)->w;
441 int bs = (*tail)->stride;
442 int bh = (*tail)->h;
443 unsigned char *a;
444 unsigned char *b;
446 if ((*last_tail)->bitmap == (*tail)->bitmap)
447 return;
449 if ((*last_tail)->color != (*tail)->color)
450 return;
452 // Calculate overlap coordinates
453 left = (ax > bx) ? ax : bx;
454 top = (ay > by) ? ay : by;
455 right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
456 bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
457 if ((right <= left) || (bottom <= top))
458 return;
459 old_left = left - ax;
460 old_top = top - ay;
461 w = right - left;
462 h = bottom - top;
463 cur_left = left - bx;
464 cur_top = top - by;
466 // Query cache
467 memset(&hk, 0, sizeof(hk));
468 hk.a = (*last_tail)->bitmap;
469 hk.b = (*tail)->bitmap;
470 hk.aw = aw;
471 hk.ah = ah;
472 hk.bw = bw;
473 hk.bh = bh;
474 hk.ax = ax;
475 hk.ay = ay;
476 hk.bx = bx;
477 hk.by = by;
478 hk.as = as;
479 hk.bs = bs;
480 hv = cache_find_composite(render_priv->cache.composite_cache, &hk);
481 if (hv) {
482 (*last_tail)->bitmap = hv->a;
483 (*tail)->bitmap = hv->b;
484 return;
486 // Allocate new bitmaps and copy over data
487 a = clone_bitmap_buffer(*last_tail);
488 b = clone_bitmap_buffer(*tail);
490 // Blend overlapping area
491 for (y = 0; y < h; y++)
492 for (x = 0; x < w; x++) {
493 opos = (old_top + y) * (as) + (old_left + x);
494 cpos = (cur_top + y) * (bs) + (cur_left + x);
495 m = FFMIN(a[opos] + b[cpos], 0xff);
496 (*last_tail)->bitmap[opos] = 0;
497 (*tail)->bitmap[cpos] = m;
500 // Insert bitmaps into the cache
501 chv.a = (*last_tail)->bitmap;
502 chv.b = (*tail)->bitmap;
503 cache_add_composite(render_priv->cache.composite_cache, &hk, &chv);
506 static void free_list_add(ASS_Renderer *render_priv, void *object)
508 if (!render_priv->free_head) {
509 render_priv->free_head = calloc(1, sizeof(FreeList));
510 render_priv->free_head->object = object;
511 render_priv->free_tail = render_priv->free_head;
512 } else {
513 FreeList *l = calloc(1, sizeof(FreeList));
514 l->object = object;
515 render_priv->free_tail->next = l;
516 render_priv->free_tail = render_priv->free_tail->next;
521 * Iterate through a list of bitmaps and blend with clip vector, if
522 * applicable. The blended bitmaps are added to a free list which is freed
523 * at the start of a new frame.
525 static void blend_vector_clip(ASS_Renderer *render_priv,
526 ASS_Image *head)
528 FT_Glyph glyph;
529 FT_BitmapGlyph clip_bm;
530 ASS_Image *cur;
531 ASS_Drawing *drawing = render_priv->state.clip_drawing;
532 int error;
534 if (!drawing)
535 return;
537 // Rasterize it
538 FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph);
539 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
540 if (error) {
541 ass_msg(render_priv->library, MSGL_V,
542 "Clip vector rasterization failed: %d. Skipping.", error);
543 goto blend_vector_exit;
545 clip_bm = (FT_BitmapGlyph) glyph;
546 clip_bm->top = -clip_bm->top;
548 assert(clip_bm->bitmap.pitch >= 0);
550 // Iterate through bitmaps and blend/clip them
551 for (cur = head; cur; cur = cur->next) {
552 int left, top, right, bottom, apos, bpos, y, x, w, h;
553 int ax, ay, aw, ah, as;
554 int bx, by, bw, bh, bs;
555 int aleft, atop, bleft, btop;
556 unsigned char *abuffer, *bbuffer, *nbuffer;
558 abuffer = cur->bitmap;
559 bbuffer = clip_bm->bitmap.buffer;
560 ax = cur->dst_x;
561 ay = cur->dst_y;
562 aw = cur->w;
563 ah = cur->h;
564 as = cur->stride;
565 bx = clip_bm->left;
566 by = clip_bm->top;
567 bw = clip_bm->bitmap.width;
568 bh = clip_bm->bitmap.rows;
569 bs = clip_bm->bitmap.pitch;
571 // Calculate overlap coordinates
572 left = (ax > bx) ? ax : bx;
573 top = (ay > by) ? ay : by;
574 right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
575 bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
576 aleft = left - ax;
577 atop = top - ay;
578 w = right - left;
579 h = bottom - top;
580 bleft = left - bx;
581 btop = top - by;
583 if (render_priv->state.clip_drawing_mode) {
584 // Inverse clip
585 if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
586 ay > by + bh) {
587 continue;
590 // Allocate new buffer and add to free list
591 nbuffer = malloc(as * ah);
592 free_list_add(render_priv, nbuffer);
594 // Blend together
595 memcpy(nbuffer, abuffer, as * (ah - 1) + aw);
596 for (y = 0; y < h; y++)
597 for (x = 0; x < w; x++) {
598 apos = (atop + y) * as + aleft + x;
599 bpos = (btop + y) * bs + bleft + x;
600 nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]);
602 } else {
603 // Regular clip
604 if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
605 ay > by + bh) {
606 cur->w = cur->h = 0;
607 continue;
610 // Allocate new buffer and add to free list
611 nbuffer = calloc(as, ah);
612 free_list_add(render_priv, nbuffer);
614 // Blend together
615 for (y = 0; y < h; y++)
616 for (x = 0; x < w; x++) {
617 apos = (atop + y) * as + aleft + x;
618 bpos = (btop + y) * bs + bleft + x;
619 nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8;
622 cur->bitmap = nbuffer;
625 // Free clip vector and its bitmap, we don't need it anymore
626 FT_Done_Glyph(glyph);
627 blend_vector_exit:
628 ass_drawing_free(render_priv->state.clip_drawing);
629 render_priv->state.clip_drawing = 0;
633 * \brief Convert TextInfo struct to ASS_Image list
634 * Splits glyphs in halves when needed (for \kf karaoke).
636 static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
637 int dst_y)
639 int pen_x, pen_y;
640 int i;
641 Bitmap *bm;
642 ASS_Image *head;
643 ASS_Image **tail = &head;
644 ASS_Image **last_tail = 0;
645 ASS_Image **here_tail = 0;
646 TextInfo *text_info = &render_priv->text_info;
648 for (i = 0; i < text_info->length; ++i) {
649 GlyphInfo *info = text_info->glyphs + i;
650 if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s
651 || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip)
652 continue;
654 pen_x =
655 dst_x + (info->pos.x >> 6) +
656 (int) (info->shadow_x * render_priv->border_scale);
657 pen_y =
658 dst_y + (info->pos.y >> 6) +
659 (int) (info->shadow_y * render_priv->border_scale);
660 bm = info->bm_s;
662 here_tail = tail;
663 tail =
664 render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
665 1000000, tail);
666 if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
667 render_overlap(render_priv, last_tail, here_tail);
669 last_tail = here_tail;
672 last_tail = 0;
673 for (i = 0; i < text_info->length; ++i) {
674 GlyphInfo *info = text_info->glyphs + i;
675 if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o
676 || info->skip)
677 continue;
679 pen_x = dst_x + (info->pos.x >> 6);
680 pen_y = dst_y + (info->pos.y >> 6);
681 bm = info->bm_o;
683 if ((info->effect_type == EF_KARAOKE_KO)
684 && (info->effect_timing <= (info->bbox.xMax >> 6))) {
685 // do nothing
686 } else {
687 here_tail = tail;
688 tail =
689 render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
690 0, 1000000, tail);
691 if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
692 render_overlap(render_priv, last_tail, here_tail);
694 last_tail = here_tail;
698 for (i = 0; i < text_info->length; ++i) {
699 GlyphInfo *info = text_info->glyphs + i;
700 if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm
701 || info->skip)
702 continue;
704 pen_x = dst_x + (info->pos.x >> 6);
705 pen_y = dst_y + (info->pos.y >> 6);
706 bm = info->bm;
708 if ((info->effect_type == EF_KARAOKE)
709 || (info->effect_type == EF_KARAOKE_KO)) {
710 if (info->effect_timing > (info->bbox.xMax >> 6))
711 tail =
712 render_glyph(render_priv, bm, pen_x, pen_y,
713 info->c[0], 0, 1000000, tail);
714 else
715 tail =
716 render_glyph(render_priv, bm, pen_x, pen_y,
717 info->c[1], 0, 1000000, tail);
718 } else if (info->effect_type == EF_KARAOKE_KF) {
719 tail =
720 render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
721 info->c[1], info->effect_timing, tail);
722 } else
723 tail =
724 render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
725 0, 1000000, tail);
728 *tail = 0;
729 blend_vector_clip(render_priv, head);
731 return head;
735 * \brief Mapping between script and screen coordinates
737 static double x2scr(ASS_Renderer *render_priv, double x)
739 return x * render_priv->orig_width_nocrop /
740 render_priv->track->PlayResX +
741 FFMAX(render_priv->settings.left_margin, 0);
743 static double x2scr_pos(ASS_Renderer *render_priv, double x)
745 return x * render_priv->orig_width / render_priv->track->PlayResX +
746 render_priv->settings.left_margin;
750 * \brief Mapping between script and screen coordinates
752 static double y2scr(ASS_Renderer *render_priv, double y)
754 return y * render_priv->orig_height_nocrop /
755 render_priv->track->PlayResY +
756 FFMAX(render_priv->settings.top_margin, 0);
758 static double y2scr_pos(ASS_Renderer *render_priv, double y)
760 return y * render_priv->orig_height / render_priv->track->PlayResY +
761 render_priv->settings.top_margin;
764 // the same for toptitles
765 static double y2scr_top(ASS_Renderer *render_priv, double y)
767 if (render_priv->settings.use_margins)
768 return y * render_priv->orig_height_nocrop /
769 render_priv->track->PlayResY;
770 else
771 return y * render_priv->orig_height_nocrop /
772 render_priv->track->PlayResY +
773 FFMAX(render_priv->settings.top_margin, 0);
776 // the same for subtitles
777 static double y2scr_sub(ASS_Renderer *render_priv, double y)
779 if (render_priv->settings.use_margins)
780 return y * render_priv->orig_height_nocrop /
781 render_priv->track->PlayResY +
782 FFMAX(render_priv->settings.top_margin,
783 0) + FFMAX(render_priv->settings.bottom_margin, 0);
784 else
785 return y * render_priv->orig_height_nocrop /
786 render_priv->track->PlayResY +
787 FFMAX(render_priv->settings.top_margin, 0);
790 static void compute_string_bbox(TextInfo *info, DBBox *bbox)
792 int i;
794 if (info->length > 0) {
795 bbox->xMin = 32000;
796 bbox->xMax = -32000;
797 bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y);
798 bbox->yMax = info->height - info->lines[0].asc +
799 d6_to_double(info->glyphs[0].pos.y);
801 for (i = 0; i < info->length; ++i) {
802 if (info->glyphs[i].skip) continue;
803 double s = d6_to_double(info->glyphs[i].pos.x);
804 double e = s + d6_to_double(info->glyphs[i].advance.x);
805 bbox->xMin = FFMIN(bbox->xMin, s);
806 bbox->xMax = FFMAX(bbox->xMax, e);
808 } else
809 bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
813 * \brief partially reset render_context to style values
814 * Works like {\r}: resets some style overrides
816 void reset_render_context(ASS_Renderer *render_priv)
818 render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
819 render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
820 render_priv->state.c[2] = render_priv->state.style->OutlineColour;
821 render_priv->state.c[3] = render_priv->state.style->BackColour;
822 render_priv->state.flags =
823 (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) |
824 (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0);
825 render_priv->state.font_size = render_priv->state.style->FontSize;
827 free(render_priv->state.family);
828 render_priv->state.family = NULL;
829 render_priv->state.family = strdup(render_priv->state.style->FontName);
830 render_priv->state.treat_family_as_pattern =
831 render_priv->state.style->treat_fontname_as_pattern;
832 render_priv->state.bold = render_priv->state.style->Bold;
833 render_priv->state.italic = render_priv->state.style->Italic;
834 update_font(render_priv);
836 change_border(render_priv, -1., -1.);
837 render_priv->state.scale_x = render_priv->state.style->ScaleX;
838 render_priv->state.scale_y = render_priv->state.style->ScaleY;
839 render_priv->state.hspacing = render_priv->state.style->Spacing;
840 render_priv->state.be = 0;
841 render_priv->state.blur = 0.0;
842 render_priv->state.shadow_x = render_priv->state.style->Shadow;
843 render_priv->state.shadow_y = render_priv->state.style->Shadow;
844 render_priv->state.frx = render_priv->state.fry = 0.;
845 render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.;
846 render_priv->state.fax = render_priv->state.fay = 0.;
847 render_priv->state.wrap_style = render_priv->track->WrapStyle;
849 // FIXME: does not reset unsupported attributes.
853 * \brief Start new event. Reset render_priv->state.
855 static void
856 init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
858 render_priv->state.event = event;
859 render_priv->state.style = render_priv->track->styles + event->Style;
861 reset_render_context(render_priv);
863 render_priv->state.evt_type = EVENT_NORMAL;
864 render_priv->state.alignment = render_priv->state.style->Alignment;
865 render_priv->state.pos_x = 0;
866 render_priv->state.pos_y = 0;
867 render_priv->state.org_x = 0;
868 render_priv->state.org_y = 0;
869 render_priv->state.have_origin = 0;
870 render_priv->state.clip_x0 = 0;
871 render_priv->state.clip_y0 = 0;
872 render_priv->state.clip_x1 = render_priv->track->PlayResX;
873 render_priv->state.clip_y1 = render_priv->track->PlayResY;
874 render_priv->state.clip_mode = 0;
875 render_priv->state.detect_collisions = 1;
876 render_priv->state.fade = 0;
877 render_priv->state.drawing_mode = 0;
878 render_priv->state.effect_type = EF_NONE;
879 render_priv->state.effect_timing = 0;
880 render_priv->state.effect_skip_timing = 0;
881 render_priv->state.drawing =
882 ass_drawing_new(render_priv->fontconfig_priv,
883 render_priv->state.font,
884 render_priv->settings.hinting,
885 render_priv->ftlibrary);
887 apply_transition_effects(render_priv, event);
890 static void free_render_context(ASS_Renderer *render_priv)
892 free(render_priv->state.family);
893 ass_drawing_free(render_priv->state.drawing);
895 render_priv->state.family = NULL;
896 render_priv->state.drawing = NULL;
899 // Calculate the cbox of a series of points
900 static void
901 get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
903 box->xMin = box->yMin = INT_MAX;
904 box->xMax = box->yMax = INT_MIN;
905 int i;
907 for (i = start; i < end; i++) {
908 box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
909 box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
910 box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
911 box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
916 * \brief Fix-up stroker result for huge borders by removing the contours from
917 * the outline that are harmful.
919 static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
920 int border_y)
922 int nc = glyph->outline.n_contours;
923 int begin, stop;
924 char modified = 0;
925 char *valid_cont;
926 int start = 0;
927 int end = -1;
928 FT_BBox *boxes = calloc(nc, sizeof(FT_BBox));
929 int i, j;
931 // Create a list of cboxes of the contours
932 for (i = 0; i < nc; i++) {
933 start = end + 1;
934 end = glyph->outline.contours[i];
935 get_contour_cbox(&boxes[i], glyph->outline.points, start, end);
938 // if a) contour's cbox is contained in another contours cbox
939 // b) contour's height or width is smaller than the border*2
940 // the contour can be safely removed.
941 valid_cont = calloc(1, nc);
942 for (i = 0; i < nc; i++) {
943 valid_cont[i] = 1;
944 for (j = 0; j < nc; j++) {
945 if (i == j)
946 continue;
947 if (boxes[i].xMin >= boxes[j].xMin &&
948 boxes[i].xMax <= boxes[j].xMax &&
949 boxes[i].yMin >= boxes[j].yMin &&
950 boxes[i].yMax <= boxes[j].yMax) {
951 int width = boxes[i].xMax - boxes[i].xMin;
952 int height = boxes[i].yMax - boxes[i].yMin;
953 if (width < border_x * 2 || height < border_y * 2) {
954 valid_cont[i] = 0;
955 modified = 1;
956 break;
962 // Zero-out contours that can be removed; much simpler than copying
963 if (modified) {
964 for (i = 0; i < nc; i++) {
965 if (valid_cont[i])
966 continue;
967 begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
968 stop = glyph->outline.contours[i];
969 for (j = begin; j <= stop; j++) {
970 glyph->outline.points[j].x = 0;
971 glyph->outline.points[j].y = 0;
972 glyph->outline.tags[j] = 0;
977 free(boxes);
978 free(valid_cont);
982 * Replace the outline of a glyph by a contour which makes up a simple
983 * opaque rectangle.
985 static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
986 FT_Glyph glyph, int sx, int sy)
988 int asc = 0, desc = 0;
989 int i;
990 int adv = d16_to_d6(glyph->advance.x);
991 double scale_y = render_priv->state.scale_y;
992 double scale_x = render_priv->state.scale_x
993 * render_priv->font_scale_x;
994 FT_OutlineGlyph og = (FT_OutlineGlyph) glyph;
995 FT_Outline *ol;
997 // to avoid gaps
998 sx = FFMAX(64, sx);
999 sy = FFMAX(64, sy);
1001 if (ch == -1) {
1002 asc = render_priv->state.drawing->asc;
1003 desc = render_priv->state.drawing->desc;
1004 } else {
1005 ass_font_get_asc_desc(render_priv->state.font, ch, &asc, &desc);
1006 asc *= scale_y;
1007 desc *= scale_y;
1010 // Emulate the WTFish behavior of VSFilter, i.e. double-scale
1011 // the sizes of the opaque box.
1012 adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale
1013 * scale_x);
1014 adv *= scale_x;
1015 sx *= scale_x;
1016 sy *= scale_y;
1017 desc *= scale_y;
1018 desc += asc * (scale_y - 1.0);
1020 FT_Vector points[4] = {
1021 { .x = -sx, .y = asc + sy },
1022 { .x = adv + sx, .y = asc + sy },
1023 { .x = adv + sx, .y = -desc - sy },
1024 { .x = -sx, .y = -desc - sy },
1027 FT_Outline_Done(render_priv->ftlibrary, &og->outline);
1028 FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline);
1030 ol = &og->outline;
1031 ol->n_points = ol->n_contours = 0;
1032 for (i = 0; i < 4; i++) {
1033 ol->points[ol->n_points] = points[i];
1034 ol->tags[ol->n_points++] = 1;
1036 ol->contours[ol->n_contours++] = ol->n_points - 1;
1040 * Stroke an outline glyph in x/y direction. Applies various fixups to get
1041 * around limitations of the FreeType stroker.
1043 static void stroke_outline_glyph(ASS_Renderer *render_priv,
1044 FT_OutlineGlyph *glyph, int sx, int sy)
1046 if (sx <= 0 && sy <= 0)
1047 return;
1049 fix_freetype_stroker(*glyph, sx, sy);
1051 // Borders are equal; use the regular stroker
1052 if (sx == sy && render_priv->state.stroker) {
1053 int error;
1054 error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph,
1055 render_priv->state.stroker, 0, 1);
1056 if (error)
1057 ass_msg(render_priv->library, MSGL_WARN,
1058 "FT_Glyph_Stroke error: %d", error);
1060 // "Stroke" with the outline emboldener in two passes.
1061 // The outlines look uglier, but the emboldening never adds any points
1062 } else {
1063 int i;
1064 FT_Outline *ol = &(*glyph)->outline;
1065 FT_Outline nol;
1066 FT_Outline_New(render_priv->ftlibrary, ol->n_points,
1067 ol->n_contours, &nol);
1068 FT_Outline_Copy(ol, &nol);
1070 FT_Outline_Embolden(ol, sx * 2);
1071 FT_Outline_Translate(ol, -sx, -sx);
1072 FT_Outline_Embolden(&nol, sy * 2);
1073 FT_Outline_Translate(&nol, -sy, -sy);
1075 for (i = 0; i < ol->n_points; i++)
1076 ol->points[i].y = nol.points[i].y;
1078 FT_Outline_Done(render_priv->ftlibrary, &nol);
1083 * \brief Get normal and outline (border) glyphs
1084 * \param symbol ucs4 char
1085 * \param info out: struct filled with extracted data
1086 * Tries to get both glyphs from cache.
1087 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
1088 * and add them to cache.
1089 * The glyphs are returned in info->glyph and info->outline_glyph
1091 static void
1092 get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
1093 ASS_Drawing *drawing)
1095 GlyphHashValue *val;
1096 GlyphHashKey key;
1097 memset(&key, 0, sizeof(key));
1099 if (drawing->hash) {
1100 key.scale_x = double_to_d16(render_priv->state.scale_x);
1101 key.scale_y = double_to_d16(render_priv->state.scale_y);
1102 key.outline.x = render_priv->state.border_x * 0xFFFF;
1103 key.outline.y = render_priv->state.border_y * 0xFFFF;
1104 key.border_style = render_priv->state.style->BorderStyle;
1105 key.drawing_hash = drawing->hash;
1106 } else {
1107 key.font = render_priv->state.font;
1108 key.size = render_priv->state.font_size;
1109 key.ch = symbol;
1110 key.bold = render_priv->state.bold;
1111 key.italic = render_priv->state.italic;
1112 key.scale_x = double_to_d16(render_priv->state.scale_x);
1113 key.scale_y = double_to_d16(render_priv->state.scale_y);
1114 key.outline.x = render_priv->state.border_x * 0xFFFF;
1115 key.outline.y = render_priv->state.border_y * 0xFFFF;
1116 key.flags = render_priv->state.flags;
1117 key.border_style = render_priv->state.style->BorderStyle;
1119 memset(info, 0, sizeof(GlyphInfo));
1121 val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
1122 if (val) {
1123 FT_Glyph_Copy(val->glyph, &info->glyph);
1124 if (val->outline_glyph)
1125 FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
1126 info->bbox = val->bbox_scaled;
1127 info->advance.x = val->advance.x;
1128 info->advance.y = val->advance.y;
1129 if (drawing->hash) {
1130 drawing->asc = val->asc;
1131 drawing->desc = val->desc;
1133 } else {
1134 GlyphHashValue v;
1135 if (drawing->hash) {
1136 if(!ass_drawing_parse(drawing, 0))
1137 return;
1138 FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph);
1139 } else {
1140 info->glyph =
1141 ass_font_get_glyph(render_priv->fontconfig_priv,
1142 render_priv->state.font, symbol,
1143 render_priv->settings.hinting,
1144 render_priv->state.flags);
1146 if (!info->glyph)
1147 return;
1148 info->advance.x = d16_to_d6(info->glyph->advance.x);
1149 info->advance.y = d16_to_d6(info->glyph->advance.y);
1150 FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
1152 if (render_priv->state.style->BorderStyle == 3 &&
1153 (render_priv->state.border_x > 0||
1154 render_priv->state.border_y > 0)) {
1155 FT_Glyph_Copy(info->glyph, &info->outline_glyph);
1156 draw_opaque_box(render_priv, symbol, info->outline_glyph,
1157 double_to_d6(render_priv->state.border_x *
1158 render_priv->border_scale),
1159 double_to_d6(render_priv->state.border_y *
1160 render_priv->border_scale));
1161 } else if (render_priv->state.border_x > 0 ||
1162 render_priv->state.border_y > 0) {
1164 FT_Glyph_Copy(info->glyph, &info->outline_glyph);
1165 stroke_outline_glyph(render_priv,
1166 (FT_OutlineGlyph *) &info->outline_glyph,
1167 double_to_d6(render_priv->state.border_x *
1168 render_priv->border_scale),
1169 double_to_d6(render_priv->state.border_y *
1170 render_priv->border_scale));
1173 memset(&v, 0, sizeof(v));
1174 FT_Glyph_Copy(info->glyph, &v.glyph);
1175 if (info->outline_glyph)
1176 FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
1177 v.advance = info->advance;
1178 v.bbox_scaled = info->bbox;
1179 if (drawing->hash) {
1180 v.asc = drawing->asc;
1181 v.desc = drawing->desc;
1183 cache_add_glyph(render_priv->cache.glyph_cache, &key, &v);
1187 static void transform_3d(FT_Vector shift, FT_Glyph *glyph,
1188 FT_Glyph *glyph2, double frx, double fry,
1189 double frz, double fax, double fay, double scale,
1190 int yshift);
1193 * \brief Get bitmaps for a glyph
1194 * \param info glyph info
1195 * Tries to get glyph bitmaps from bitmap cache.
1196 * If they can't be found, they are generated by rotating and rendering the glyph.
1197 * After that, bitmaps are added to the cache.
1198 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
1200 static void
1201 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
1203 BitmapHashValue *val;
1204 BitmapHashKey *key = &info->hash_key;
1206 val = cache_find_bitmap(render_priv->cache.bitmap_cache, key);
1208 if (val) {
1209 info->bm = val->bm;
1210 info->bm_o = val->bm_o;
1211 info->bm_s = val->bm_s;
1212 } else {
1213 FT_Vector shift;
1214 BitmapHashValue hash_val;
1215 int error;
1216 double fax_scaled, fay_scaled;
1217 info->bm = info->bm_o = info->bm_s = 0;
1218 if (info->glyph && info->symbol != '\n' && info->symbol != 0
1219 && !info->skip) {
1220 // calculating rotation shift vector (from rotation origin to the glyph basepoint)
1221 shift.x = info->hash_key.shift_x;
1222 shift.y = info->hash_key.shift_y;
1223 fax_scaled = info->fax * render_priv->font_scale_x *
1224 render_priv->state.scale_x;
1225 fay_scaled = info->fay * render_priv->state.scale_y;
1226 // apply rotation
1227 transform_3d(shift, &info->glyph, &info->outline_glyph,
1228 info->frx, info->fry, info->frz, fax_scaled,
1229 fay_scaled, render_priv->font_scale, info->asc);
1231 // subpixel shift
1232 if (info->glyph)
1233 FT_Outline_Translate(
1234 &((FT_OutlineGlyph) info->glyph)->outline,
1235 info->hash_key.advance.x,
1236 -info->hash_key.advance.y);
1237 if (info->outline_glyph)
1238 FT_Outline_Translate(
1239 &((FT_OutlineGlyph) info->outline_glyph)->outline,
1240 info->hash_key.advance.x,
1241 -info->hash_key.advance.y);
1243 // render glyph
1244 error = glyph_to_bitmap(render_priv->library,
1245 render_priv->synth_priv,
1246 info->glyph, info->outline_glyph,
1247 &info->bm, &info->bm_o,
1248 &info->bm_s, info->be,
1249 info->blur * render_priv->border_scale,
1250 info->hash_key.shadow_offset,
1251 info->hash_key.border_style);
1252 if (error)
1253 info->symbol = 0;
1255 // add bitmaps to cache
1256 hash_val.bm_o = info->bm_o;
1257 hash_val.bm = info->bm;
1258 hash_val.bm_s = info->bm_s;
1259 cache_add_bitmap(render_priv->cache.bitmap_cache,
1260 &(info->hash_key), &hash_val);
1263 // deallocate glyphs
1264 if (info->glyph)
1265 FT_Done_Glyph(info->glyph);
1266 if (info->outline_glyph)
1267 FT_Done_Glyph(info->outline_glyph);
1271 * This function goes through text_info and calculates text parameters.
1272 * The following text_info fields are filled:
1273 * height
1274 * lines[].height
1275 * lines[].asc
1276 * lines[].desc
1278 static void measure_text(ASS_Renderer *render_priv)
1280 TextInfo *text_info = &render_priv->text_info;
1281 int cur_line = 0;
1282 double max_asc = 0., max_desc = 0.;
1283 GlyphInfo *last = NULL;
1284 int i;
1285 int empty_line = 1;
1286 text_info->height = 0.;
1287 for (i = 0; i < text_info->length + 1; ++i) {
1288 if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
1289 if (empty_line && cur_line > 0 && last && i < text_info->length) {
1290 max_asc = d6_to_double(last->asc) / 2.0;
1291 max_desc = d6_to_double(last->desc) / 2.0;
1293 text_info->lines[cur_line].asc = max_asc;
1294 text_info->lines[cur_line].desc = max_desc;
1295 text_info->height += max_asc + max_desc;
1296 cur_line++;
1297 max_asc = max_desc = 0.;
1298 empty_line = 1;
1299 } else
1300 empty_line = 0;
1301 if (i < text_info->length) {
1302 GlyphInfo *cur = text_info->glyphs + i;
1303 if (d6_to_double(cur->asc) > max_asc)
1304 max_asc = d6_to_double(cur->asc);
1305 if (d6_to_double(cur->desc) > max_desc)
1306 max_desc = d6_to_double(cur->desc);
1307 if (cur->symbol != '\n' && cur->symbol != 0)
1308 last = cur;
1311 text_info->height +=
1312 (text_info->n_lines -
1313 1) * render_priv->settings.line_spacing;
1317 * Mark extra whitespace for later removal.
1319 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1320 && !x->linebreak)
1321 static void trim_whitespace(ASS_Renderer *render_priv)
1323 int i, j;
1324 GlyphInfo *cur;
1325 TextInfo *ti = &render_priv->text_info;
1327 // Mark trailing spaces
1328 i = ti->length - 1;
1329 cur = ti->glyphs + i;
1330 while (i && IS_WHITESPACE(cur)) {
1331 cur->skip++;
1332 cur = ti->glyphs + --i;
1335 // Mark leading whitespace
1336 i = 0;
1337 cur = ti->glyphs;
1338 while (i < ti->length && IS_WHITESPACE(cur)) {
1339 cur->skip++;
1340 cur = ti->glyphs + ++i;
1343 // Mark all extraneous whitespace inbetween
1344 for (i = 0; i < ti->length; ++i) {
1345 cur = ti->glyphs + i;
1346 if (cur->linebreak) {
1347 // Mark whitespace before
1348 j = i - 1;
1349 cur = ti->glyphs + j;
1350 while (j && IS_WHITESPACE(cur)) {
1351 cur->skip++;
1352 cur = ti->glyphs + --j;
1354 // A break itself can contain a whitespace, too
1355 cur = ti->glyphs + i;
1356 if (cur->symbol == ' ')
1357 cur->skip++;
1358 // Mark whitespace after
1359 j = i + 1;
1360 cur = ti->glyphs + j;
1361 while (j < ti->length && IS_WHITESPACE(cur)) {
1362 cur->skip++;
1363 cur = ti->glyphs + ++j;
1365 i = j - 1;
1369 #undef IS_WHITESPACE
1372 * \brief rearrange text between lines
1373 * \param max_text_width maximal text line width in pixels
1374 * The algo is similar to the one in libvo/sub.c:
1375 * 1. Place text, wrapping it when current line is full
1376 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1377 * the difference in lengths between this two lines.
1378 * The result may not be optimal, but usually is good enough.
1380 * FIXME: implement style 0 and 3 correctly, add support for style 1
1382 static void
1383 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
1385 int i;
1386 GlyphInfo *cur, *s1, *e1, *s2, *s3, *w;
1387 int last_space;
1388 int break_type;
1389 int exit;
1390 double pen_shift_x;
1391 double pen_shift_y;
1392 int cur_line;
1393 TextInfo *text_info = &render_priv->text_info;
1395 last_space = -1;
1396 text_info->n_lines = 1;
1397 break_type = 0;
1398 s1 = text_info->glyphs; // current line start
1399 for (i = 0; i < text_info->length; ++i) {
1400 int break_at;
1401 double s_offset, len;
1402 cur = text_info->glyphs + i;
1403 break_at = -1;
1404 s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
1405 len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset;
1407 if (cur->symbol == '\n') {
1408 break_type = 2;
1409 break_at = i;
1410 ass_msg(render_priv->library, MSGL_DBG2,
1411 "forced line break at %d", break_at);
1414 if ((len >= max_text_width)
1415 && (render_priv->state.wrap_style != 2)) {
1416 break_type = 1;
1417 break_at = last_space;
1418 if (break_at == -1)
1419 break_at = i - 1;
1420 if (break_at == -1)
1421 break_at = 0;
1422 ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i);
1423 ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
1424 break_at);
1427 if (break_at != -1) {
1428 // need to use one more line
1429 // marking break_at+1 as start of a new line
1430 int lead = break_at + 1; // the first symbol of the new line
1431 if (text_info->n_lines >= text_info->max_lines) {
1432 // Raise maximum number of lines
1433 text_info->max_lines *= 2;
1434 text_info->lines = realloc(text_info->lines,
1435 sizeof(LineInfo) *
1436 text_info->max_lines);
1438 if (lead < text_info->length)
1439 text_info->glyphs[lead].linebreak = break_type;
1440 last_space = -1;
1441 s1 = text_info->glyphs + lead;
1442 s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
1443 text_info->n_lines++;
1446 if (cur->symbol == ' ')
1447 last_space = i;
1449 // make sure the hard linebreak is not forgotten when
1450 // there was a new soft linebreak just inserted
1451 if (cur->symbol == '\n' && break_type == 1)
1452 i--;
1454 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1455 exit = 0;
1456 while (!exit && render_priv->state.wrap_style != 1) {
1457 exit = 1;
1458 w = s3 = text_info->glyphs;
1459 s1 = s2 = 0;
1460 for (i = 0; i <= text_info->length; ++i) {
1461 cur = text_info->glyphs + i;
1462 if ((i == text_info->length) || cur->linebreak) {
1463 s1 = s2;
1464 s2 = s3;
1465 s3 = cur;
1466 if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft'
1467 double l1, l2, l1_new, l2_new;
1469 w = s2;
1470 do {
1471 --w;
1472 } while ((w > s1) && (w->symbol == ' '));
1473 while ((w > s1) && (w->symbol != ' ')) {
1474 --w;
1476 e1 = w;
1477 while ((e1 > s1) && (e1->symbol == ' ')) {
1478 --e1;
1480 if (w->symbol == ' ')
1481 ++w;
1483 l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) -
1484 (s1->bbox.xMin + s1->pos.x));
1485 l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1486 (s2->bbox.xMin + s2->pos.x));
1487 l1_new = d6_to_double(
1488 (e1->bbox.xMax + e1->pos.x) -
1489 (s1->bbox.xMin + s1->pos.x));
1490 l2_new = d6_to_double(
1491 ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1492 (w->bbox.xMin + w->pos.x));
1494 if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
1495 w->linebreak = 1;
1496 s2->linebreak = 0;
1497 exit = 0;
1501 if (i == text_info->length)
1502 break;
1506 assert(text_info->n_lines >= 1);
1507 #undef DIFF
1509 measure_text(render_priv);
1510 trim_whitespace(render_priv);
1512 pen_shift_x = 0.;
1513 pen_shift_y = 0.;
1514 cur_line = 1;
1516 i = 0;
1517 cur = text_info->glyphs + i;
1518 while (i < text_info->length && cur->skip)
1519 cur = text_info->glyphs + ++i;
1520 pen_shift_x = d6_to_double(-cur->pos.x);
1522 for (i = 0; i < text_info->length; ++i) {
1523 cur = text_info->glyphs + i;
1524 if (cur->linebreak) {
1525 while (i < text_info->length && cur->skip && cur->symbol != '\n')
1526 cur = text_info->glyphs + ++i;
1527 double height =
1528 text_info->lines[cur_line - 1].desc +
1529 text_info->lines[cur_line].asc;
1530 cur_line++;
1531 pen_shift_x = d6_to_double(-cur->pos.x);
1532 pen_shift_y += height + render_priv->settings.line_spacing;
1533 ass_msg(render_priv->library, MSGL_DBG2,
1534 "shifting from %d to %d by (%f, %f)", i,
1535 text_info->length - 1, pen_shift_x, pen_shift_y);
1537 cur->pos.x += double_to_d6(pen_shift_x);
1538 cur->pos.y += double_to_d6(pen_shift_y);
1543 * \brief determine karaoke effects
1544 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
1545 * so they are done in a separate step.
1546 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
1547 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
1548 * This function:
1549 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
1550 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
1551 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
1553 static void process_karaoke_effects(ASS_Renderer *render_priv)
1555 GlyphInfo *cur, *cur2;
1556 GlyphInfo *s1, *e1; // start and end of the current word
1557 GlyphInfo *s2; // start of the next word
1558 int i;
1559 int timing; // current timing
1560 int tm_start, tm_end; // timings at start and end of the current word
1561 int tm_current;
1562 double dt;
1563 int x;
1564 int x_start, x_end;
1566 tm_current = render_priv->time - render_priv->state.event->Start;
1567 timing = 0;
1568 s1 = s2 = 0;
1569 for (i = 0; i <= render_priv->text_info.length; ++i) {
1570 cur = render_priv->text_info.glyphs + i;
1571 if ((i == render_priv->text_info.length)
1572 || (cur->effect_type != EF_NONE)) {
1573 s1 = s2;
1574 s2 = cur;
1575 if (s1) {
1576 e1 = s2 - 1;
1577 tm_start = timing + s1->effect_skip_timing;
1578 tm_end = tm_start + s1->effect_timing;
1579 timing = tm_end;
1580 x_start = 1000000;
1581 x_end = -1000000;
1582 for (cur2 = s1; cur2 <= e1; ++cur2) {
1583 x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
1584 x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
1587 dt = (tm_current - tm_start);
1588 if ((s1->effect_type == EF_KARAOKE)
1589 || (s1->effect_type == EF_KARAOKE_KO)) {
1590 if (dt > 0)
1591 x = x_end + 1;
1592 else
1593 x = x_start;
1594 } else if (s1->effect_type == EF_KARAOKE_KF) {
1595 dt /= (tm_end - tm_start);
1596 x = x_start + (x_end - x_start) * dt;
1597 } else {
1598 ass_msg(render_priv->library, MSGL_ERR,
1599 "Unknown effect type");
1600 continue;
1603 for (cur2 = s1; cur2 <= e1; ++cur2) {
1604 cur2->effect_type = s1->effect_type;
1605 cur2->effect_timing = x - d6_to_int(cur2->pos.x);
1613 * \brief Calculate base point for positioning and rotation
1614 * \param bbox text bbox
1615 * \param alignment alignment
1616 * \param bx, by out: base point coordinates
1618 static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
1620 const int halign = alignment & 3;
1621 const int valign = alignment & 12;
1622 if (bx)
1623 switch (halign) {
1624 case HALIGN_LEFT:
1625 *bx = bbox->xMin;
1626 break;
1627 case HALIGN_CENTER:
1628 *bx = (bbox->xMax + bbox->xMin) / 2.0;
1629 break;
1630 case HALIGN_RIGHT:
1631 *bx = bbox->xMax;
1632 break;
1634 if (by)
1635 switch (valign) {
1636 case VALIGN_TOP:
1637 *by = bbox->yMin;
1638 break;
1639 case VALIGN_CENTER:
1640 *by = (bbox->yMax + bbox->yMin) / 2.0;
1641 break;
1642 case VALIGN_SUB:
1643 *by = bbox->yMax;
1644 break;
1649 * \brief Apply transformation to outline points of a glyph
1650 * Applies rotations given by frx, fry and frz and projects the points back
1651 * onto the screen plane.
1653 static void
1654 transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry,
1655 double frz, double fax, double fay, double scale,
1656 int yshift)
1658 double sx = sin(frx);
1659 double sy = sin(fry);
1660 double sz = sin(frz);
1661 double cx = cos(frx);
1662 double cy = cos(fry);
1663 double cz = cos(frz);
1664 FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
1665 FT_Vector *p = outline->points;
1666 double x, y, z, xx, yy, zz;
1667 int i, dist;
1669 dist = 20000 * scale;
1670 for (i = 0; i < outline->n_points; i++) {
1671 x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
1672 y = (double) p[i].y + shift.y + (-fay * p[i].x);
1673 z = 0.;
1675 xx = x * cz + y * sz;
1676 yy = -(x * sz - y * cz);
1677 zz = z;
1679 x = xx;
1680 y = yy * cx + zz * sx;
1681 z = yy * sx - zz * cx;
1683 xx = x * cy + z * sy;
1684 yy = y;
1685 zz = x * sy - z * cy;
1687 zz = FFMAX(zz, 1000 - dist);
1689 x = (xx * dist) / (zz + dist);
1690 y = (yy * dist) / (zz + dist);
1691 p[i].x = x - shift.x + 0.5;
1692 p[i].y = y - shift.y + 0.5;
1697 * \brief Apply 3d transformation to several objects
1698 * \param shift FreeType vector
1699 * \param glyph FreeType glyph
1700 * \param glyph2 FreeType glyph
1701 * \param frx x-axis rotation angle
1702 * \param fry y-axis rotation angle
1703 * \param frz z-axis rotation angle
1704 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
1706 static void
1707 transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
1708 double frx, double fry, double frz, double fax, double fay,
1709 double scale, int yshift)
1711 frx = -frx;
1712 frz = -frz;
1713 if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
1714 if (glyph && *glyph)
1715 transform_3d_points(shift, *glyph, frx, fry, frz,
1716 fax, fay, scale, yshift);
1718 if (glyph2 && *glyph2)
1719 transform_3d_points(shift, *glyph2, frx, fry, frz,
1720 fax, fay, scale, yshift);
1726 * \brief Main ass rendering function, glues everything together
1727 * \param event event to render
1728 * \param event_images struct containing resulting images, will also be initialized
1729 * Process event, appending resulting ASS_Image's to images_root.
1731 static int
1732 ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
1733 EventImages *event_images)
1735 char *p;
1736 FT_UInt previous;
1737 FT_UInt num_glyphs;
1738 FT_Vector pen;
1739 unsigned code;
1740 DBBox bbox;
1741 int i, j;
1742 int MarginL, MarginR, MarginV;
1743 int last_break;
1744 int alignment, halign, valign;
1745 int kern = render_priv->track->Kerning;
1746 double device_x = 0;
1747 double device_y = 0;
1748 TextInfo *text_info = &render_priv->text_info;
1749 ASS_Drawing *drawing;
1751 if (event->Style >= render_priv->track->n_styles) {
1752 ass_msg(render_priv->library, MSGL_WARN, "No style found");
1753 return 1;
1755 if (!event->Text) {
1756 ass_msg(render_priv->library, MSGL_WARN, "Empty event");
1757 return 1;
1760 init_render_context(render_priv, event);
1762 drawing = render_priv->state.drawing;
1763 text_info->length = 0;
1764 pen.x = 0;
1765 pen.y = 0;
1766 previous = 0;
1767 num_glyphs = 0;
1768 p = event->Text;
1769 // Event parsing.
1770 while (1) {
1771 // get next char, executing style override
1772 // this affects render_context
1773 do {
1774 code = get_next_char(render_priv, &p);
1775 if (render_priv->state.drawing_mode && code)
1776 ass_drawing_add_char(drawing, (char) code);
1777 } while (code && render_priv->state.drawing_mode); // skip everything in drawing mode
1779 // Parse drawing
1780 if (drawing->i) {
1781 drawing->scale_x = render_priv->state.scale_x *
1782 render_priv->font_scale_x *
1783 render_priv->font_scale;
1784 drawing->scale_y = render_priv->state.scale_y *
1785 render_priv->font_scale;
1786 ass_drawing_hash(drawing);
1787 p--;
1788 code = -1;
1791 // face could have been changed in get_next_char
1792 if (!render_priv->state.font) {
1793 free_render_context(render_priv);
1794 return 1;
1797 if (code == 0)
1798 break;
1800 if (text_info->length >= text_info->max_glyphs) {
1801 // Raise maximum number of glyphs
1802 text_info->max_glyphs *= 2;
1803 text_info->glyphs =
1804 realloc(text_info->glyphs,
1805 sizeof(GlyphInfo) * text_info->max_glyphs);
1808 // Add kerning to pen
1809 if (kern && previous && code && !drawing->hash) {
1810 FT_Vector delta;
1811 delta =
1812 ass_font_get_kerning(render_priv->state.font, previous,
1813 code);
1814 pen.x += delta.x * render_priv->state.scale_x
1815 * render_priv->font_scale_x;
1816 pen.y += delta.y * render_priv->state.scale_y
1817 * render_priv->font_scale_x;
1820 ass_font_set_transform(render_priv->state.font,
1821 render_priv->state.scale_x *
1822 render_priv->font_scale_x,
1823 render_priv->state.scale_y, NULL);
1825 get_outline_glyph(render_priv, code,
1826 text_info->glyphs + text_info->length, drawing);
1828 // Add additional space after italic to non-italic style changes
1829 if (text_info->length &&
1830 text_info->glyphs[text_info->length - 1].hash_key.italic &&
1831 !render_priv->state.italic) {
1832 int back = text_info->length - 1;
1833 GlyphInfo *og = &text_info->glyphs[back];
1834 while (back && og->bbox.xMax - og->bbox.xMin == 0
1835 && og->hash_key.italic)
1836 og = &text_info->glyphs[--back];
1837 if (og->bbox.xMax > og->advance.x) {
1838 // The FreeType oblique slants by 6/16
1839 pen.x += og->bbox.yMax * 0.375;
1843 text_info->glyphs[text_info->length].pos.x = pen.x;
1844 text_info->glyphs[text_info->length].pos.y = pen.y;
1846 pen.x += text_info->glyphs[text_info->length].advance.x;
1847 pen.x += double_to_d6(render_priv->state.hspacing *
1848 render_priv->font_scale
1849 * render_priv->state.scale_x);
1850 pen.y += text_info->glyphs[text_info->length].advance.y;
1851 pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
1852 text_info->glyphs[text_info->length].advance.x;
1854 previous = code;
1856 text_info->glyphs[text_info->length].symbol = code;
1857 text_info->glyphs[text_info->length].linebreak = 0;
1858 for (i = 0; i < 4; ++i) {
1859 uint32_t clr = render_priv->state.c[i];
1860 change_alpha(&clr,
1861 mult_alpha(_a(clr), render_priv->state.fade), 1.);
1862 text_info->glyphs[text_info->length].c[i] = clr;
1864 text_info->glyphs[text_info->length].effect_type =
1865 render_priv->state.effect_type;
1866 text_info->glyphs[text_info->length].effect_timing =
1867 render_priv->state.effect_timing;
1868 text_info->glyphs[text_info->length].effect_skip_timing =
1869 render_priv->state.effect_skip_timing;
1870 text_info->glyphs[text_info->length].be = render_priv->state.be;
1871 text_info->glyphs[text_info->length].blur = render_priv->state.blur;
1872 text_info->glyphs[text_info->length].shadow_x =
1873 render_priv->state.shadow_x;
1874 text_info->glyphs[text_info->length].shadow_y =
1875 render_priv->state.shadow_y;
1876 text_info->glyphs[text_info->length].frx = render_priv->state.frx;
1877 text_info->glyphs[text_info->length].fry = render_priv->state.fry;
1878 text_info->glyphs[text_info->length].frz = render_priv->state.frz;
1879 text_info->glyphs[text_info->length].fax = render_priv->state.fax;
1880 text_info->glyphs[text_info->length].fay = render_priv->state.fay;
1881 if (drawing->hash) {
1882 text_info->glyphs[text_info->length].asc = drawing->asc;
1883 text_info->glyphs[text_info->length].desc = drawing->desc;
1884 } else {
1885 ass_font_get_asc_desc(render_priv->state.font, code,
1886 &text_info->glyphs[text_info->length].asc,
1887 &text_info->glyphs[text_info->length].desc);
1889 text_info->glyphs[text_info->length].asc *=
1890 render_priv->state.scale_y;
1891 text_info->glyphs[text_info->length].desc *=
1892 render_priv->state.scale_y;
1895 // fill bitmap_hash_key
1896 if (!drawing->hash) {
1897 text_info->glyphs[text_info->length].hash_key.font =
1898 render_priv->state.font;
1899 text_info->glyphs[text_info->length].hash_key.size =
1900 render_priv->state.font_size;
1901 text_info->glyphs[text_info->length].hash_key.bold =
1902 render_priv->state.bold;
1903 text_info->glyphs[text_info->length].hash_key.italic =
1904 render_priv->state.italic;
1905 } else
1906 text_info->glyphs[text_info->length].hash_key.drawing_hash =
1907 drawing->hash;
1908 text_info->glyphs[text_info->length].hash_key.ch = code;
1909 text_info->glyphs[text_info->length].hash_key.outline.x =
1910 double_to_d16(render_priv->state.border_x);
1911 text_info->glyphs[text_info->length].hash_key.outline.y =
1912 double_to_d16(render_priv->state.border_y);
1913 text_info->glyphs[text_info->length].hash_key.scale_x =
1914 double_to_d16(render_priv->state.scale_x);
1915 text_info->glyphs[text_info->length].hash_key.scale_y =
1916 double_to_d16(render_priv->state.scale_y);
1917 text_info->glyphs[text_info->length].hash_key.frx =
1918 rot_key(render_priv->state.frx);
1919 text_info->glyphs[text_info->length].hash_key.fry =
1920 rot_key(render_priv->state.fry);
1921 text_info->glyphs[text_info->length].hash_key.frz =
1922 rot_key(render_priv->state.frz);
1923 text_info->glyphs[text_info->length].hash_key.fax =
1924 double_to_d16(render_priv->state.fax);
1925 text_info->glyphs[text_info->length].hash_key.fay =
1926 double_to_d16(render_priv->state.fay);
1927 text_info->glyphs[text_info->length].hash_key.advance.x = pen.x;
1928 text_info->glyphs[text_info->length].hash_key.advance.y = pen.y;
1929 text_info->glyphs[text_info->length].hash_key.be =
1930 render_priv->state.be;
1931 text_info->glyphs[text_info->length].hash_key.blur =
1932 render_priv->state.blur;
1933 text_info->glyphs[text_info->length].hash_key.border_style =
1934 render_priv->state.style->BorderStyle;
1935 text_info->glyphs[text_info->length].hash_key.shadow_offset.x =
1936 double_to_d6(
1937 render_priv->state.shadow_x * render_priv->border_scale -
1938 (int) (render_priv->state.shadow_x *
1939 render_priv->border_scale));
1940 text_info->glyphs[text_info->length].hash_key.shadow_offset.y =
1941 double_to_d6(
1942 render_priv->state.shadow_y * render_priv->border_scale -
1943 (int) (render_priv->state.shadow_y *
1944 render_priv->border_scale));
1945 text_info->glyphs[text_info->length].hash_key.flags =
1946 render_priv->state.flags;
1948 text_info->length++;
1950 render_priv->state.effect_type = EF_NONE;
1951 render_priv->state.effect_timing = 0;
1952 render_priv->state.effect_skip_timing = 0;
1954 if (drawing->hash) {
1955 ass_drawing_free(drawing);
1956 drawing = render_priv->state.drawing =
1957 ass_drawing_new(render_priv->fontconfig_priv,
1958 render_priv->state.font,
1959 render_priv->settings.hinting,
1960 render_priv->ftlibrary);
1965 if (text_info->length == 0) {
1966 // no valid symbols in the event; this can be smth like {comment}
1967 free_render_context(render_priv);
1968 return 1;
1970 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
1971 process_karaoke_effects(render_priv);
1973 // alignments
1974 alignment = render_priv->state.alignment;
1975 halign = alignment & 3;
1976 valign = alignment & 12;
1978 MarginL =
1979 (event->MarginL) ? event->MarginL : render_priv->state.style->
1980 MarginL;
1981 MarginR =
1982 (event->MarginR) ? event->MarginR : render_priv->state.style->
1983 MarginR;
1984 MarginV =
1985 (event->MarginV) ? event->MarginV : render_priv->state.style->
1986 MarginV;
1988 if (render_priv->state.evt_type != EVENT_HSCROLL) {
1989 double max_text_width;
1991 // calculate max length of a line
1992 max_text_width =
1993 x2scr(render_priv,
1994 render_priv->track->PlayResX - MarginR) -
1995 x2scr(render_priv, MarginL);
1997 // rearrange text in several lines
1998 wrap_lines_smart(render_priv, max_text_width);
2000 // align text
2001 last_break = -1;
2002 for (i = 1; i < text_info->length + 1; ++i) { // (text_info->length + 1) is the end of the last line
2003 if ((i == text_info->length)
2004 || text_info->glyphs[i].linebreak) {
2005 double width, shift = 0;
2006 GlyphInfo *first_glyph =
2007 text_info->glyphs + last_break + 1;
2008 GlyphInfo *last_glyph = text_info->glyphs + i - 1;
2010 while (first_glyph < last_glyph && first_glyph->skip)
2011 first_glyph++;
2013 while ((last_glyph > first_glyph)
2014 && ((last_glyph->symbol == '\n')
2015 || (last_glyph->symbol == 0)
2016 || (last_glyph->skip)))
2017 last_glyph--;
2019 width = d6_to_double(
2020 last_glyph->pos.x + last_glyph->advance.x -
2021 first_glyph->pos.x);
2022 if (halign == HALIGN_LEFT) { // left aligned, no action
2023 shift = 0;
2024 } else if (halign == HALIGN_RIGHT) { // right aligned
2025 shift = max_text_width - width;
2026 } else if (halign == HALIGN_CENTER) { // centered
2027 shift = (max_text_width - width) / 2.0;
2029 for (j = last_break + 1; j < i; ++j) {
2030 text_info->glyphs[j].pos.x += double_to_d6(shift);
2032 last_break = i - 1;
2035 } else { // render_priv->state.evt_type == EVENT_HSCROLL
2036 measure_text(render_priv);
2039 // determing text bounding box
2040 compute_string_bbox(text_info, &bbox);
2042 // determine device coordinates for text
2044 // x coordinate for everything except positioned events
2045 if (render_priv->state.evt_type == EVENT_NORMAL ||
2046 render_priv->state.evt_type == EVENT_VSCROLL) {
2047 device_x = x2scr(render_priv, MarginL);
2048 } else if (render_priv->state.evt_type == EVENT_HSCROLL) {
2049 if (render_priv->state.scroll_direction == SCROLL_RL)
2050 device_x =
2051 x2scr(render_priv,
2052 render_priv->track->PlayResX -
2053 render_priv->state.scroll_shift);
2054 else if (render_priv->state.scroll_direction == SCROLL_LR)
2055 device_x =
2056 x2scr(render_priv,
2057 render_priv->state.scroll_shift) - (bbox.xMax -
2058 bbox.xMin);
2060 // y coordinate for everything except positioned events
2061 if (render_priv->state.evt_type == EVENT_NORMAL ||
2062 render_priv->state.evt_type == EVENT_HSCROLL) {
2063 if (valign == VALIGN_TOP) { // toptitle
2064 device_y =
2065 y2scr_top(render_priv,
2066 MarginV) + text_info->lines[0].asc;
2067 } else if (valign == VALIGN_CENTER) { // midtitle
2068 double scr_y =
2069 y2scr(render_priv, render_priv->track->PlayResY / 2.0);
2070 device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
2071 } else { // subtitle
2072 double scr_y;
2073 if (valign != VALIGN_SUB)
2074 ass_msg(render_priv->library, MSGL_V,
2075 "Invalid valign, supposing 0 (subtitle)");
2076 scr_y =
2077 y2scr_sub(render_priv,
2078 render_priv->track->PlayResY - MarginV);
2079 device_y = scr_y;
2080 device_y -= text_info->height;
2081 device_y += text_info->lines[0].asc;
2083 } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
2084 if (render_priv->state.scroll_direction == SCROLL_TB)
2085 device_y =
2086 y2scr(render_priv,
2087 render_priv->state.clip_y0 +
2088 render_priv->state.scroll_shift) - (bbox.yMax -
2089 bbox.yMin);
2090 else if (render_priv->state.scroll_direction == SCROLL_BT)
2091 device_y =
2092 y2scr(render_priv,
2093 render_priv->state.clip_y1 -
2094 render_priv->state.scroll_shift);
2096 // positioned events are totally different
2097 if (render_priv->state.evt_type == EVENT_POSITIONED) {
2098 double base_x = 0;
2099 double base_y = 0;
2100 ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f",
2101 render_priv->state.pos_x, render_priv->state.pos_y);
2102 get_base_point(&bbox, alignment, &base_x, &base_y);
2103 device_x =
2104 x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
2105 device_y =
2106 y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
2108 // fix clip coordinates (they depend on alignment)
2109 if (render_priv->state.evt_type == EVENT_NORMAL ||
2110 render_priv->state.evt_type == EVENT_HSCROLL ||
2111 render_priv->state.evt_type == EVENT_VSCROLL) {
2112 render_priv->state.clip_x0 =
2113 x2scr(render_priv, render_priv->state.clip_x0);
2114 render_priv->state.clip_x1 =
2115 x2scr(render_priv, render_priv->state.clip_x1);
2116 if (valign == VALIGN_TOP) {
2117 render_priv->state.clip_y0 =
2118 y2scr_top(render_priv, render_priv->state.clip_y0);
2119 render_priv->state.clip_y1 =
2120 y2scr_top(render_priv, render_priv->state.clip_y1);
2121 } else if (valign == VALIGN_CENTER) {
2122 render_priv->state.clip_y0 =
2123 y2scr(render_priv, render_priv->state.clip_y0);
2124 render_priv->state.clip_y1 =
2125 y2scr(render_priv, render_priv->state.clip_y1);
2126 } else if (valign == VALIGN_SUB) {
2127 render_priv->state.clip_y0 =
2128 y2scr_sub(render_priv, render_priv->state.clip_y0);
2129 render_priv->state.clip_y1 =
2130 y2scr_sub(render_priv, render_priv->state.clip_y1);
2132 } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
2133 render_priv->state.clip_x0 =
2134 x2scr_pos(render_priv, render_priv->state.clip_x0);
2135 render_priv->state.clip_x1 =
2136 x2scr_pos(render_priv, render_priv->state.clip_x1);
2137 render_priv->state.clip_y0 =
2138 y2scr_pos(render_priv, render_priv->state.clip_y0);
2139 render_priv->state.clip_y1 =
2140 y2scr_pos(render_priv, render_priv->state.clip_y1);
2142 // calculate rotation parameters
2144 DVector center;
2146 if (render_priv->state.have_origin) {
2147 center.x = x2scr(render_priv, render_priv->state.org_x);
2148 center.y = y2scr(render_priv, render_priv->state.org_y);
2149 } else {
2150 double bx = 0., by = 0.;
2151 get_base_point(&bbox, alignment, &bx, &by);
2152 center.x = device_x + bx;
2153 center.y = device_y + by;
2156 for (i = 0; i < text_info->length; ++i) {
2157 GlyphInfo *info = text_info->glyphs + i;
2159 if (info->hash_key.frx || info->hash_key.fry
2160 || info->hash_key.frz || info->hash_key.fax
2161 || info->hash_key.fay) {
2162 info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x);
2163 info->hash_key.shift_y =
2164 -(info->pos.y + double_to_d6(device_y - center.y));
2165 } else {
2166 info->hash_key.shift_x = 0;
2167 info->hash_key.shift_y = 0;
2172 // convert glyphs to bitmaps
2173 for (i = 0; i < text_info->length; ++i) {
2174 GlyphInfo *g = text_info->glyphs + i;
2175 g->hash_key.advance.x =
2176 double_to_d6(device_x - (int) device_x +
2177 d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
2178 g->hash_key.advance.y =
2179 double_to_d6(device_y - (int) device_y +
2180 d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
2181 get_bitmap_glyph(render_priv, text_info->glyphs + i);
2184 memset(event_images, 0, sizeof(*event_images));
2185 event_images->top = device_y - text_info->lines[0].asc;
2186 event_images->height = text_info->height;
2187 event_images->left = device_x + bbox.xMin + 0.5;
2188 event_images->width = bbox.xMax - bbox.xMin + 0.5;
2189 event_images->detect_collisions = render_priv->state.detect_collisions;
2190 event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
2191 event_images->event = event;
2192 event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y);
2194 free_render_context(render_priv);
2196 return 0;
2200 * \brief deallocate image list
2201 * \param img list pointer
2203 static void ass_free_images(ASS_Image *img)
2205 while (img) {
2206 ASS_Image *next = img->next;
2207 free(img);
2208 img = next;
2212 static void ass_reconfigure(ASS_Renderer *priv)
2214 priv->render_id++;
2215 priv->cache.glyph_cache =
2216 ass_glyph_cache_reset(priv->cache.glyph_cache);
2217 priv->cache.bitmap_cache =
2218 ass_bitmap_cache_reset(priv->cache.bitmap_cache);
2219 priv->cache.composite_cache =
2220 ass_composite_cache_reset(priv->cache.composite_cache);
2221 ass_free_images(priv->prev_images_root);
2222 priv->prev_images_root = 0;
2225 void ass_set_frame_size(ASS_Renderer *priv, int w, int h)
2227 if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
2228 priv->settings.frame_width = w;
2229 priv->settings.frame_height = h;
2230 if (priv->settings.aspect == 0.) {
2231 priv->settings.aspect = ((double) w) / h;
2232 priv->settings.storage_aspect = ((double) w) / h;
2234 ass_reconfigure(priv);
2238 void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r)
2240 if (priv->settings.left_margin != l ||
2241 priv->settings.right_margin != r ||
2242 priv->settings.top_margin != t
2243 || priv->settings.bottom_margin != b) {
2244 priv->settings.left_margin = l;
2245 priv->settings.right_margin = r;
2246 priv->settings.top_margin = t;
2247 priv->settings.bottom_margin = b;
2248 ass_reconfigure(priv);
2252 void ass_set_use_margins(ASS_Renderer *priv, int use)
2254 priv->settings.use_margins = use;
2257 void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar)
2259 if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) {
2260 priv->settings.aspect = dar;
2261 priv->settings.storage_aspect = sar;
2262 ass_reconfigure(priv);
2266 void ass_set_font_scale(ASS_Renderer *priv, double font_scale)
2268 if (priv->settings.font_size_coeff != font_scale) {
2269 priv->settings.font_size_coeff = font_scale;
2270 ass_reconfigure(priv);
2274 void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht)
2276 if (priv->settings.hinting != ht) {
2277 priv->settings.hinting = ht;
2278 ass_reconfigure(priv);
2282 void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing)
2284 priv->settings.line_spacing = line_spacing;
2287 void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
2288 const char *default_family, int fc, const char *config,
2289 int update)
2291 free(priv->settings.default_font);
2292 free(priv->settings.default_family);
2293 priv->settings.default_font = default_font ? strdup(default_font) : 0;
2294 priv->settings.default_family =
2295 default_family ? strdup(default_family) : 0;
2297 if (priv->fontconfig_priv)
2298 fontconfig_done(priv->fontconfig_priv);
2299 priv->fontconfig_priv =
2300 fontconfig_init(priv->library, priv->ftlibrary, default_family,
2301 default_font, fc, config, update);
2304 int ass_fonts_update(ASS_Renderer *render_priv)
2306 return fontconfig_update(render_priv->fontconfig_priv);
2310 * \brief Start a new frame
2312 static int
2313 ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
2314 long long now)
2316 ASS_Settings *settings_priv = &render_priv->settings;
2317 CacheStore *cache = &render_priv->cache;
2319 if (!render_priv->settings.frame_width
2320 && !render_priv->settings.frame_height)
2321 return 1; // library not initialized
2323 if (render_priv->library != track->library)
2324 return 1;
2326 free_list_clear(render_priv);
2328 if (track->n_events == 0)
2329 return 1; // nothing to do
2331 render_priv->width = settings_priv->frame_width;
2332 render_priv->height = settings_priv->frame_height;
2333 render_priv->orig_width =
2334 settings_priv->frame_width - settings_priv->left_margin -
2335 settings_priv->right_margin;
2336 render_priv->orig_height =
2337 settings_priv->frame_height - settings_priv->top_margin -
2338 settings_priv->bottom_margin;
2339 render_priv->orig_width_nocrop =
2340 settings_priv->frame_width - FFMAX(settings_priv->left_margin,
2341 0) -
2342 FFMAX(settings_priv->right_margin, 0);
2343 render_priv->orig_height_nocrop =
2344 settings_priv->frame_height - FFMAX(settings_priv->top_margin,
2345 0) -
2346 FFMAX(settings_priv->bottom_margin, 0);
2347 render_priv->track = track;
2348 render_priv->time = now;
2350 ass_lazy_track_init(render_priv);
2352 render_priv->font_scale = settings_priv->font_size_coeff *
2353 render_priv->orig_height / render_priv->track->PlayResY;
2354 if (render_priv->track->ScaledBorderAndShadow)
2355 render_priv->border_scale =
2356 ((double) render_priv->orig_height) /
2357 render_priv->track->PlayResY;
2358 else
2359 render_priv->border_scale = 1.;
2361 // PAR correction
2362 render_priv->font_scale_x = render_priv->settings.aspect /
2363 render_priv->settings.storage_aspect;
2365 render_priv->prev_images_root = render_priv->images_root;
2366 render_priv->images_root = 0;
2368 if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) {
2369 ass_msg(render_priv->library, MSGL_V,
2370 "Hitting hard bitmap cache limit (was: %ld bytes), "
2371 "resetting.", (long) cache->bitmap_cache->cache_size);
2372 cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache);
2373 cache->composite_cache = ass_composite_cache_reset(
2374 cache->composite_cache);
2375 ass_free_images(render_priv->prev_images_root);
2376 render_priv->prev_images_root = 0;
2379 if (cache->glyph_cache->count > cache->glyph_max) {
2380 ass_msg(render_priv->library, MSGL_V,
2381 "Hitting hard glyph cache limit (was: %ld glyphs), resetting.",
2382 (long) cache->glyph_cache->count);
2383 cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache);
2386 return 0;
2389 static int cmp_event_layer(const void *p1, const void *p2)
2391 ASS_Event *e1 = ((EventImages *) p1)->event;
2392 ASS_Event *e2 = ((EventImages *) p2)->event;
2393 if (e1->Layer < e2->Layer)
2394 return -1;
2395 if (e1->Layer > e2->Layer)
2396 return 1;
2397 if (e1->ReadOrder < e2->ReadOrder)
2398 return -1;
2399 if (e1->ReadOrder > e2->ReadOrder)
2400 return 1;
2401 return 0;
2404 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
2405 ASS_Event *event)
2407 if (!event->render_priv)
2408 event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
2409 if (render_priv->render_id != event->render_priv->render_id) {
2410 memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
2411 event->render_priv->render_id = render_priv->render_id;
2414 return event->render_priv;
2417 static int overlap(Segment *s1, Segment *s2)
2419 if (s1->a >= s2->b || s2->a >= s1->b ||
2420 s1->ha >= s2->hb || s2->ha >= s1->hb)
2421 return 0;
2422 return 1;
2425 static int cmp_segment(const void *p1, const void *p2)
2427 return ((Segment *) p1)->a - ((Segment *) p2)->a;
2430 static void
2431 shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
2433 ASS_Image *cur = ei->imgs;
2434 while (cur) {
2435 cur->dst_y += shift;
2436 // clip top and bottom
2437 if (cur->dst_y < 0) {
2438 int clip = -cur->dst_y;
2439 cur->h -= clip;
2440 cur->bitmap += clip * cur->stride;
2441 cur->dst_y = 0;
2443 if (cur->dst_y + cur->h >= render_priv->height) {
2444 int clip = cur->dst_y + cur->h - render_priv->height;
2445 cur->h -= clip;
2447 if (cur->h <= 0) {
2448 cur->h = 0;
2449 cur->dst_y = 0;
2451 cur = cur->next;
2453 ei->top += shift;
2456 // dir: 1 - move down
2457 // -1 - move up
2458 static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
2460 int i;
2461 int shift = 0;
2463 if (dir == 1) // move down
2464 for (i = 0; i < *cnt; ++i) {
2465 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2466 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2467 continue;
2468 shift = fixed[i].b - s->a;
2469 } else // dir == -1, move up
2470 for (i = *cnt - 1; i >= 0; --i) {
2471 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2472 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2473 continue;
2474 shift = fixed[i].a - s->b;
2477 fixed[*cnt].a = s->a + shift;
2478 fixed[*cnt].b = s->b + shift;
2479 fixed[*cnt].ha = s->ha;
2480 fixed[*cnt].hb = s->hb;
2481 (*cnt)++;
2482 qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
2484 return shift;
2487 static void
2488 fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
2490 Segment *used = malloc(cnt * sizeof(*used));
2491 int cnt_used = 0;
2492 int i, j;
2494 // fill used[] with fixed events
2495 for (i = 0; i < cnt; ++i) {
2496 ASS_RenderPriv *priv;
2497 if (!imgs[i].detect_collisions)
2498 continue;
2499 priv = get_render_priv(render_priv, imgs[i].event);
2500 if (priv->height > 0) { // it's a fixed event
2501 Segment s;
2502 s.a = priv->top;
2503 s.b = priv->top + priv->height;
2504 s.ha = priv->left;
2505 s.hb = priv->left + priv->width;
2506 if (priv->height != imgs[i].height) { // no, it's not
2507 ass_msg(render_priv->library, MSGL_WARN,
2508 "Warning! Event height has changed");
2509 priv->top = 0;
2510 priv->height = 0;
2511 priv->left = 0;
2512 priv->width = 0;
2514 for (j = 0; j < cnt_used; ++j)
2515 if (overlap(&s, used + j)) { // no, it's not
2516 priv->top = 0;
2517 priv->height = 0;
2518 priv->left = 0;
2519 priv->width = 0;
2521 if (priv->height > 0) { // still a fixed event
2522 used[cnt_used].a = priv->top;
2523 used[cnt_used].b = priv->top + priv->height;
2524 used[cnt_used].ha = priv->left;
2525 used[cnt_used].hb = priv->left + priv->width;
2526 cnt_used++;
2527 shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
2531 qsort(used, cnt_used, sizeof(Segment), cmp_segment);
2533 // try to fit other events in free spaces
2534 for (i = 0; i < cnt; ++i) {
2535 ASS_RenderPriv *priv;
2536 if (!imgs[i].detect_collisions)
2537 continue;
2538 priv = get_render_priv(render_priv, imgs[i].event);
2539 if (priv->height == 0) { // not a fixed event
2540 int shift;
2541 Segment s;
2542 s.a = imgs[i].top;
2543 s.b = imgs[i].top + imgs[i].height;
2544 s.ha = imgs[i].left;
2545 s.hb = imgs[i].left + imgs[i].width;
2546 shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
2547 if (shift)
2548 shift_event(render_priv, imgs + i, shift);
2549 // make it fixed
2550 priv->top = imgs[i].top;
2551 priv->height = imgs[i].height;
2552 priv->left = imgs[i].left;
2553 priv->width = imgs[i].width;
2558 free(used);
2562 * \brief compare two images
2563 * \param i1 first image
2564 * \param i2 second image
2565 * \return 0 if identical, 1 if different positions, 2 if different content
2567 static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
2569 if (i1->w != i2->w)
2570 return 2;
2571 if (i1->h != i2->h)
2572 return 2;
2573 if (i1->stride != i2->stride)
2574 return 2;
2575 if (i1->color != i2->color)
2576 return 2;
2577 if (i1->bitmap != i2->bitmap)
2578 return 2;
2579 if (i1->dst_x != i2->dst_x)
2580 return 1;
2581 if (i1->dst_y != i2->dst_y)
2582 return 1;
2583 return 0;
2587 * \brief compare current and previous image list
2588 * \param priv library handle
2589 * \return 0 if identical, 1 if different positions, 2 if different content
2591 static int ass_detect_change(ASS_Renderer *priv)
2593 ASS_Image *img, *img2;
2594 int diff;
2596 img = priv->prev_images_root;
2597 img2 = priv->images_root;
2598 diff = 0;
2599 while (img && diff < 2) {
2600 ASS_Image *next, *next2;
2601 next = img->next;
2602 if (img2) {
2603 int d = ass_image_compare(img, img2);
2604 if (d > diff)
2605 diff = d;
2606 next2 = img2->next;
2607 } else {
2608 // previous list is shorter
2609 diff = 2;
2610 break;
2612 img = next;
2613 img2 = next2;
2616 // is the previous list longer?
2617 if (img2)
2618 diff = 2;
2620 return diff;
2624 * \brief render a frame
2625 * \param priv library handle
2626 * \param track track
2627 * \param now current video timestamp (ms)
2628 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
2629 * 0 if identical, 1 if different positions, 2 if different content.
2630 * Can be NULL, in that case no detection is performed.
2632 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
2633 long long now, int *detect_change)
2635 int i, cnt, rc;
2636 EventImages *last;
2637 ASS_Image **tail;
2639 // init frame
2640 rc = ass_start_frame(priv, track, now);
2641 if (rc != 0)
2642 return 0;
2644 // render events separately
2645 cnt = 0;
2646 for (i = 0; i < track->n_events; ++i) {
2647 ASS_Event *event = track->events + i;
2648 if ((event->Start <= now)
2649 && (now < (event->Start + event->Duration))) {
2650 if (cnt >= priv->eimg_size) {
2651 priv->eimg_size += 100;
2652 priv->eimg =
2653 realloc(priv->eimg,
2654 priv->eimg_size * sizeof(EventImages));
2656 rc = ass_render_event(priv, event, priv->eimg + cnt);
2657 if (!rc)
2658 ++cnt;
2662 // sort by layer
2663 qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
2665 // call fix_collisions for each group of events with the same layer
2666 last = priv->eimg;
2667 for (i = 1; i < cnt; ++i)
2668 if (last->event->Layer != priv->eimg[i].event->Layer) {
2669 fix_collisions(priv, last, priv->eimg + i - last);
2670 last = priv->eimg + i;
2672 if (cnt > 0)
2673 fix_collisions(priv, last, priv->eimg + cnt - last);
2675 // concat lists
2676 tail = &priv->images_root;
2677 for (i = 0; i < cnt; ++i) {
2678 ASS_Image *cur = priv->eimg[i].imgs;
2679 while (cur) {
2680 *tail = cur;
2681 tail = &cur->next;
2682 cur = cur->next;
2686 if (detect_change)
2687 *detect_change = ass_detect_change(priv);
2689 // free the previous image list
2690 ass_free_images(priv->prev_images_root);
2691 priv->prev_images_root = 0;
2693 return priv->images_root;