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.
27 #include FT_FREETYPE_H
30 #include FT_SYNTHESIS_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
)
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;
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
)
86 ASS_Renderer
*priv
= 0;
87 int vmajor
, vminor
, vpatch
;
89 error
= FT_Init_FreeType(&ft
);
91 ass_msg(library
, MSGL_FATAL
, "%s failed", "FT_Init_FreeType");
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
));
103 FT_Done_FreeType(ft
);
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
));
128 ass_msg(library
, MSGL_INFO
, "Init");
130 ass_msg(library
, MSGL_ERR
, "Init failed");
135 void ass_set_cache_limits(ASS_Renderer
*render_priv
, int glyph_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
;
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
);
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
));
203 img
->stride
= stride
;
204 img
->bitmap
= bitmap
;
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
,
233 int i
, j
, x0
, y0
, x1
, y1
, cx0
, cy0
, cx1
, cy1
, sx
, sy
, zx
, zy
;
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
);
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.
259 r
[i
].x1
= (cx0
> x1
) ? x1
: cx0
;
261 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
262 r
[i
].x0
= (cx0
< 0) ? x0
: cx0
;
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
;
271 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
272 r
[i
].x0
= (cx1
< 0) ? x0
: cx1
;
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
++) {
289 // kick out rectangles that are invalid now
290 if (r
[j
].x1
<= r
[j
].x0
|| r
[j
].y1
<= r
[j
].y0
)
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
);
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
);
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.
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
,
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
;
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
);
357 tmp
= dst_x
- clip_x0
;
359 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip left");
362 tmp
= dst_y
- clip_y0
;
364 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip top");
367 tmp
= clip_x1
- dst_x
- bm
->w
;
369 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip right");
372 tmp
= clip_y1
- dst_y
- bm
->h
;
374 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip bottom");
378 if ((b_y0
>= b_y1
) || (b_x0
>= b_x1
))
381 if (brk
> b_x0
) { // draw left part
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
);
390 if (brk
< b_x1
) { // draw right part
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
);
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
);
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)
423 render_overlap(ASS_Renderer
*render_priv
, ASS_Image
**last_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
;
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
;
441 int bs
= (*tail
)->stride
;
446 if ((*last_tail
)->bitmap
== (*tail
)->bitmap
)
449 if ((*last_tail
)->color
!= (*tail
)->color
)
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
))
459 old_left
= left
- ax
;
463 cur_left
= left
- bx
;
467 memset(&hk
, 0, sizeof(hk
));
468 hk
.a
= (*last_tail
)->bitmap
;
469 hk
.b
= (*tail
)->bitmap
;
480 hv
= cache_find_composite(render_priv
->cache
.composite_cache
, &hk
);
482 (*last_tail
)->bitmap
= hv
->a
;
483 (*tail
)->bitmap
= hv
->b
;
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
;
513 FreeList
*l
= calloc(1, sizeof(FreeList
));
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
,
529 FT_BitmapGlyph clip_bm
;
531 ASS_Drawing
*drawing
= render_priv
->state
.clip_drawing
;
538 FT_Glyph_Copy((FT_Glyph
) drawing
->glyph
, &glyph
);
539 error
= FT_Glyph_To_Bitmap(&glyph
, FT_RENDER_MODE_NORMAL
, 0, 1);
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
;
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
);
583 if (render_priv
->state
.clip_drawing_mode
) {
585 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
590 // Allocate new buffer and add to free list
591 nbuffer
= malloc(as
* ah
);
592 free_list_add(render_priv
, nbuffer
);
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
]);
604 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
610 // Allocate new buffer and add to free list
611 nbuffer
= calloc(as
, ah
);
612 free_list_add(render_priv
, nbuffer
);
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
);
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
,
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
)
655 dst_x
+ (info
->pos
.x
>> 6) +
656 (int) (info
->shadow_x
* render_priv
->border_scale
);
658 dst_y
+ (info
->pos
.y
>> 6) +
659 (int) (info
->shadow_y
* render_priv
->border_scale
);
664 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[3], 0,
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
;
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
679 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
680 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
683 if ((info
->effect_type
== EF_KARAOKE_KO
)
684 && (info
->effect_timing
<= (info
->bbox
.xMax
>> 6))) {
689 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[2],
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
704 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
705 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
708 if ((info
->effect_type
== EF_KARAOKE
)
709 || (info
->effect_type
== EF_KARAOKE_KO
)) {
710 if (info
->effect_timing
> (info
->bbox
.xMax
>> 6))
712 render_glyph(render_priv
, bm
, pen_x
, pen_y
,
713 info
->c
[0], 0, 1000000, 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
) {
720 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
721 info
->c
[1], info
->effect_timing
, tail
);
724 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
729 blend_vector_clip(render_priv
, 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
;
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);
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
)
794 if (info
->length
> 0) {
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
);
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.
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
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
;
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
,
922 int nc
= glyph
->outline
.n_contours
;
928 FT_BBox
*boxes
= calloc(nc
, sizeof(FT_BBox
));
931 // Create a list of cboxes of the contours
932 for (i
= 0; i
< nc
; i
++) {
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
++) {
944 for (j
= 0; j
< nc
; j
++) {
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) {
962 // Zero-out contours that can be removed; much simpler than copying
964 for (i
= 0; i
< nc
; i
++) {
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;
982 * Replace the outline of a glyph by a contour which makes up a simple
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;
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
;
1002 asc
= render_priv
->state
.drawing
->asc
;
1003 desc
= render_priv
->state
.drawing
->desc
;
1005 ass_font_get_asc_desc(render_priv
->state
.font
, ch
, &asc
, &desc
);
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
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
);
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)
1049 fix_freetype_stroker(*glyph
, sx
, sy
);
1051 // Borders are equal; use the regular stroker
1052 if (sx
== sy
&& render_priv
->state
.stroker
) {
1054 error
= FT_Glyph_StrokeBorder((FT_Glyph
*) glyph
,
1055 render_priv
->state
.stroker
, 0, 1);
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
1064 FT_Outline
*ol
= &(*glyph
)->outline
;
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
1092 get_outline_glyph(ASS_Renderer
*render_priv
, int symbol
, GlyphInfo
*info
,
1093 ASS_Drawing
*drawing
)
1095 GlyphHashValue
*val
;
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
;
1107 key
.font
= render_priv
->state
.font
;
1108 key
.size
= render_priv
->state
.font_size
;
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
);
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
;
1135 if (drawing
->hash
) {
1136 if(!ass_drawing_parse(drawing
, 0))
1138 FT_Glyph_Copy((FT_Glyph
) drawing
->glyph
, &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
);
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
,
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).
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
);
1210 info
->bm_o
= val
->bm_o
;
1211 info
->bm_s
= val
->bm_s
;
1214 BitmapHashValue hash_val
;
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
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
;
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
);
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
);
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
);
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
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:
1278 static void measure_text(ASS_Renderer
*render_priv
)
1280 TextInfo
*text_info
= &render_priv
->text_info
;
1282 double max_asc
= 0., max_desc
= 0.;
1283 GlyphInfo
*last
= NULL
;
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
;
1297 max_asc
= max_desc
= 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)
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') \
1321 static void trim_whitespace(ASS_Renderer
*render_priv
)
1325 TextInfo
*ti
= &render_priv
->text_info
;
1327 // Mark trailing spaces
1329 cur
= ti
->glyphs
+ i
;
1330 while (i
&& IS_WHITESPACE(cur
)) {
1332 cur
= ti
->glyphs
+ --i
;
1335 // Mark leading whitespace
1338 while (i
< ti
->length
&& IS_WHITESPACE(cur
)) {
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
1349 cur
= ti
->glyphs
+ j
;
1350 while (j
&& IS_WHITESPACE(cur
)) {
1352 cur
= ti
->glyphs
+ --j
;
1354 // A break itself can contain a whitespace, too
1355 cur
= ti
->glyphs
+ i
;
1356 if (cur
->symbol
== ' ')
1358 // Mark whitespace after
1360 cur
= ti
->glyphs
+ j
;
1361 while (j
< ti
->length
&& IS_WHITESPACE(cur
)) {
1363 cur
= ti
->glyphs
+ ++j
;
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
1383 wrap_lines_smart(ASS_Renderer
*render_priv
, double max_text_width
)
1386 GlyphInfo
*cur
, *s1
, *e1
, *s2
, *s3
, *w
;
1393 TextInfo
*text_info
= &render_priv
->text_info
;
1396 text_info
->n_lines
= 1;
1398 s1
= text_info
->glyphs
; // current line start
1399 for (i
= 0; i
< text_info
->length
; ++i
) {
1401 double s_offset
, len
;
1402 cur
= text_info
->glyphs
+ i
;
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') {
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)) {
1417 break_at
= last_space
;
1422 ass_msg(render_priv
->library
, MSGL_DBG2
, "overfill at %d", i
);
1423 ass_msg(render_priv
->library
, MSGL_DBG2
, "line break at %d",
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
,
1436 text_info
->max_lines
);
1438 if (lead
< text_info
->length
)
1439 text_info
->glyphs
[lead
].linebreak
= break_type
;
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
== ' ')
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)
1454 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1456 while (!exit
&& render_priv
->state
.wrap_style
!= 1) {
1458 w
= s3
= text_info
->glyphs
;
1460 for (i
= 0; i
<= text_info
->length
; ++i
) {
1461 cur
= text_info
->glyphs
+ i
;
1462 if ((i
== text_info
->length
) || cur
->linebreak
) {
1466 if (s1
&& (s2
->linebreak
== 1)) { // have at least 2 lines, and linebreak is 'soft'
1467 double l1
, l2
, l1_new
, l2_new
;
1472 } while ((w
> s1
) && (w
->symbol
== ' '));
1473 while ((w
> s1
) && (w
->symbol
!= ' ')) {
1477 while ((e1
> s1
) && (e1
->symbol
== ' ')) {
1480 if (w
->symbol
== ' ')
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
)) {
1501 if (i
== text_info
->length
)
1506 assert(text_info
->n_lines
>= 1);
1509 measure_text(render_priv
);
1510 trim_whitespace(render_priv
);
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
;
1528 text_info
->lines
[cur_line
- 1].desc
+
1529 text_info
->lines
[cur_line
].asc
;
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.
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
1559 int timing
; // current timing
1560 int tm_start
, tm_end
; // timings at start and end of the current word
1566 tm_current
= render_priv
->time
- render_priv
->state
.event
->Start
;
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
)) {
1577 tm_start
= timing
+ s1
->effect_skip_timing
;
1578 tm_end
= tm_start
+ s1
->effect_timing
;
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
)) {
1594 } else if (s1
->effect_type
== EF_KARAOKE_KF
) {
1595 dt
/= (tm_end
- tm_start
);
1596 x
= x_start
+ (x_end
- x_start
) * dt
;
1598 ass_msg(render_priv
->library
, MSGL_ERR
,
1599 "Unknown effect type");
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;
1628 *bx
= (bbox
->xMax
+ bbox
->xMin
) / 2.0;
1640 *by
= (bbox
->yMax
+ bbox
->yMin
) / 2.0;
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.
1654 transform_3d_points(FT_Vector shift
, FT_Glyph glyph
, double frx
, double fry
,
1655 double frz
, double fax
, double fay
, double scale
,
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
;
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
);
1675 xx
= x
* cz
+ y
* sz
;
1676 yy
= -(x
* sz
- y
* cz
);
1680 y
= yy
* cx
+ zz
* sx
;
1681 z
= yy
* sx
- zz
* cx
;
1683 xx
= x
* cy
+ z
* sy
;
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.
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
)
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.
1732 ass_render_event(ASS_Renderer
*render_priv
, ASS_Event
*event
,
1733 EventImages
*event_images
)
1742 int MarginL
, MarginR
, MarginV
;
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");
1756 ass_msg(render_priv
->library
, MSGL_WARN
, "Empty event");
1760 init_render_context(render_priv
, event
);
1762 drawing
= render_priv
->state
.drawing
;
1763 text_info
->length
= 0;
1771 // get next char, executing style override
1772 // this affects render_context
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
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
);
1791 // face could have been changed in get_next_char
1792 if (!render_priv
->state
.font
) {
1793 free_render_context(render_priv
);
1800 if (text_info
->length
>= text_info
->max_glyphs
) {
1801 // Raise maximum number of glyphs
1802 text_info
->max_glyphs
*= 2;
1804 realloc(text_info
->glyphs
,
1805 sizeof(GlyphInfo
) * text_info
->max_glyphs
);
1808 // Add kerning to pen
1809 if (kern
&& previous
&& code
&& !drawing
->hash
) {
1812 ass_font_get_kerning(render_priv
->state
.font
, previous
,
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
;
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
];
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
;
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
;
1906 text_info
->glyphs
[text_info
->length
].hash_key
.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
=
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
=
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
);
1970 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
1971 process_karaoke_effects(render_priv
);
1974 alignment
= render_priv
->state
.alignment
;
1975 halign
= alignment
& 3;
1976 valign
= alignment
& 12;
1979 (event
->MarginL
) ? event
->MarginL
: render_priv
->state
.style
->
1982 (event
->MarginR
) ? event
->MarginR
: render_priv
->state
.style
->
1985 (event
->MarginV
) ? event
->MarginV
: render_priv
->state
.style
->
1988 if (render_priv
->state
.evt_type
!= EVENT_HSCROLL
) {
1989 double max_text_width
;
1991 // calculate max length of a line
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
);
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
)
2013 while ((last_glyph
> first_glyph
)
2014 && ((last_glyph
->symbol
== '\n')
2015 || (last_glyph
->symbol
== 0)
2016 || (last_glyph
->skip
)))
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
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
);
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
)
2052 render_priv
->track
->PlayResX
-
2053 render_priv
->state
.scroll_shift
);
2054 else if (render_priv
->state
.scroll_direction
== SCROLL_LR
)
2057 render_priv
->state
.scroll_shift
) - (bbox
.xMax
-
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
2065 y2scr_top(render_priv
,
2066 MarginV
) + text_info
->lines
[0].asc
;
2067 } else if (valign
== VALIGN_CENTER
) { // midtitle
2069 y2scr(render_priv
, render_priv
->track
->PlayResY
/ 2.0);
2070 device_y
= scr_y
- (bbox
.yMax
+ bbox
.yMin
) / 2.0;
2071 } else { // subtitle
2073 if (valign
!= VALIGN_SUB
)
2074 ass_msg(render_priv
->library
, MSGL_V
,
2075 "Invalid valign, supposing 0 (subtitle)");
2077 y2scr_sub(render_priv
,
2078 render_priv
->track
->PlayResY
- MarginV
);
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
)
2087 render_priv
->state
.clip_y0
+
2088 render_priv
->state
.scroll_shift
) - (bbox
.yMax
-
2090 else if (render_priv
->state
.scroll_direction
== SCROLL_BT
)
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
) {
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
);
2104 x2scr_pos(render_priv
, render_priv
->state
.pos_x
) - base_x
;
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
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
);
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
));
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
);
2200 * \brief deallocate image list
2201 * \param img list pointer
2203 static void ass_free_images(ASS_Image
*img
)
2206 ASS_Image
*next
= img
->next
;
2212 static void ass_reconfigure(ASS_Renderer
*priv
)
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
,
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
2313 ass_start_frame(ASS_Renderer
*render_priv
, ASS_Track
*track
,
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
)
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
,
2342 FFMAX(settings_priv
->right_margin
, 0);
2343 render_priv
->orig_height_nocrop
=
2344 settings_priv
->frame_height
- FFMAX(settings_priv
->top_margin
,
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
;
2359 render_priv
->border_scale
= 1.;
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
);
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
)
2395 if (e1
->Layer
> e2
->Layer
)
2397 if (e1
->ReadOrder
< e2
->ReadOrder
)
2399 if (e1
->ReadOrder
> e2
->ReadOrder
)
2404 static ASS_RenderPriv
*get_render_priv(ASS_Renderer
*render_priv
,
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
)
2425 static int cmp_segment(const void *p1
, const void *p2
)
2427 return ((Segment
*) p1
)->a
- ((Segment
*) p2
)->a
;
2431 shift_event(ASS_Renderer
*render_priv
, EventImages
*ei
, int shift
)
2433 ASS_Image
*cur
= ei
->imgs
;
2435 cur
->dst_y
+= shift
;
2436 // clip top and bottom
2437 if (cur
->dst_y
< 0) {
2438 int clip
= -cur
->dst_y
;
2440 cur
->bitmap
+= clip
* cur
->stride
;
2443 if (cur
->dst_y
+ cur
->h
>= render_priv
->height
) {
2444 int clip
= cur
->dst_y
+ cur
->h
- render_priv
->height
;
2456 // dir: 1 - move down
2458 static int fit_segment(Segment
*s
, Segment
*fixed
, int *cnt
, int dir
)
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
)
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
)
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
;
2482 qsort(fixed
, *cnt
, sizeof(Segment
), cmp_segment
);
2488 fix_collisions(ASS_Renderer
*render_priv
, EventImages
*imgs
, int cnt
)
2490 Segment
*used
= malloc(cnt
* sizeof(*used
));
2494 // fill used[] with fixed events
2495 for (i
= 0; i
< cnt
; ++i
) {
2496 ASS_RenderPriv
*priv
;
2497 if (!imgs
[i
].detect_collisions
)
2499 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2500 if (priv
->height
> 0) { // it's a fixed event
2503 s
.b
= priv
->top
+ priv
->height
;
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");
2514 for (j
= 0; j
< cnt_used
; ++j
)
2515 if (overlap(&s
, used
+ j
)) { // no, it's not
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
;
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
)
2538 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2539 if (priv
->height
== 0) { // not a fixed event
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
);
2548 shift_event(render_priv
, imgs
+ i
, shift
);
2550 priv
->top
= imgs
[i
].top
;
2551 priv
->height
= imgs
[i
].height
;
2552 priv
->left
= imgs
[i
].left
;
2553 priv
->width
= imgs
[i
].width
;
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
)
2573 if (i1
->stride
!= i2
->stride
)
2575 if (i1
->color
!= i2
->color
)
2577 if (i1
->bitmap
!= i2
->bitmap
)
2579 if (i1
->dst_x
!= i2
->dst_x
)
2581 if (i1
->dst_y
!= i2
->dst_y
)
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
;
2596 img
= priv
->prev_images_root
;
2597 img2
= priv
->images_root
;
2599 while (img
&& diff
< 2) {
2600 ASS_Image
*next
, *next2
;
2603 int d
= ass_image_compare(img
, img2
);
2608 // previous list is shorter
2616 // is the previous list longer?
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
)
2640 rc
= ass_start_frame(priv
, track
, now
);
2644 // render events separately
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;
2654 priv
->eimg_size
* sizeof(EventImages
));
2656 rc
= ass_render_event(priv
, event
, priv
->eimg
+ cnt
);
2663 qsort(priv
->eimg
, cnt
, sizeof(EventImages
), cmp_event_layer
);
2665 // call fix_collisions for each group of events with the same layer
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
;
2673 fix_collisions(priv
, last
, priv
->eimg
+ cnt
- last
);
2676 tail
= &priv
->images_root
;
2677 for (i
= 0; i
< cnt
; ++i
) {
2678 ASS_Image
*cur
= priv
->eimg
[i
].imgs
;
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
;