2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #include "ass_render.h"
25 #include "ass_parse.h"
27 #define MAX_GLYPHS_INITIAL 1024
28 #define MAX_LINES_INITIAL 64
29 #define SUBPIXEL_MASK 63
30 #define SUBPIXEL_ACCURACY 7
32 static void ass_lazy_track_init(ASS_Renderer
*render_priv
)
34 ASS_Track
*track
= render_priv
->track
;
36 if (track
->PlayResX
&& track
->PlayResY
)
38 if (!track
->PlayResX
&& !track
->PlayResY
) {
39 ass_msg(render_priv
->library
, MSGL_WARN
,
40 "Neither PlayResX nor PlayResY defined. Assuming 384x288");
41 track
->PlayResX
= 384;
42 track
->PlayResY
= 288;
44 if (!track
->PlayResY
&& track
->PlayResX
== 1280) {
45 track
->PlayResY
= 1024;
46 ass_msg(render_priv
->library
, MSGL_WARN
,
47 "PlayResY undefined, setting to %d", track
->PlayResY
);
48 } else if (!track
->PlayResY
) {
49 track
->PlayResY
= track
->PlayResX
* 3 / 4;
50 ass_msg(render_priv
->library
, MSGL_WARN
,
51 "PlayResY undefined, setting to %d", track
->PlayResY
);
52 } else if (!track
->PlayResX
&& track
->PlayResY
== 1024) {
53 track
->PlayResX
= 1280;
54 ass_msg(render_priv
->library
, MSGL_WARN
,
55 "PlayResX undefined, setting to %d", track
->PlayResX
);
56 } else if (!track
->PlayResX
) {
57 track
->PlayResX
= track
->PlayResY
* 4 / 3;
58 ass_msg(render_priv
->library
, MSGL_WARN
,
59 "PlayResX undefined, setting to %d", track
->PlayResX
);
64 ASS_Renderer
*ass_renderer_init(ASS_Library
*library
)
68 ASS_Renderer
*priv
= 0;
69 int vmajor
, vminor
, vpatch
;
71 error
= FT_Init_FreeType(&ft
);
73 ass_msg(library
, MSGL_FATAL
, "%s failed", "FT_Init_FreeType");
77 FT_Library_Version(ft
, &vmajor
, &vminor
, &vpatch
);
78 ass_msg(library
, MSGL_V
, "FreeType library version: %d.%d.%d",
79 vmajor
, vminor
, vpatch
);
80 ass_msg(library
, MSGL_V
, "FreeType headers version: %d.%d.%d",
81 FREETYPE_MAJOR
, FREETYPE_MINOR
, FREETYPE_PATCH
);
83 priv
= calloc(1, sizeof(ASS_Renderer
));
89 priv
->synth_priv
= ass_synth_init(BLUR_MAX_RADIUS
);
91 priv
->library
= library
;
93 // images_root and related stuff is zero-filled in calloc
95 priv
->cache
.font_cache
= ass_font_cache_init(library
);
96 priv
->cache
.bitmap_cache
= ass_bitmap_cache_init(library
);
97 priv
->cache
.composite_cache
= ass_composite_cache_init(library
);
98 priv
->cache
.glyph_cache
= ass_glyph_cache_init(library
);
99 priv
->cache
.glyph_max
= GLYPH_CACHE_MAX
;
100 priv
->cache
.bitmap_max_size
= BITMAP_CACHE_MAX_SIZE
;
102 priv
->text_info
.max_glyphs
= MAX_GLYPHS_INITIAL
;
103 priv
->text_info
.max_lines
= MAX_LINES_INITIAL
;
104 priv
->text_info
.glyphs
= calloc(MAX_GLYPHS_INITIAL
, sizeof(GlyphInfo
));
105 priv
->text_info
.lines
= calloc(MAX_LINES_INITIAL
, sizeof(LineInfo
));
107 priv
->settings
.font_size_coeff
= 1.;
111 ass_msg(library
, MSGL_V
, "Init");
113 ass_msg(library
, MSGL_ERR
, "Init failed");
118 static void free_list_clear(ASS_Renderer
*render_priv
)
120 if (render_priv
->free_head
) {
121 FreeList
*item
= render_priv
->free_head
;
128 render_priv
->free_head
= NULL
;
132 void ass_renderer_done(ASS_Renderer
*render_priv
)
134 ass_font_cache_done(render_priv
->cache
.font_cache
);
135 ass_bitmap_cache_done(render_priv
->cache
.bitmap_cache
);
136 ass_composite_cache_done(render_priv
->cache
.composite_cache
);
137 ass_glyph_cache_done(render_priv
->cache
.glyph_cache
);
139 ass_free_images(render_priv
->images_root
);
140 ass_free_images(render_priv
->prev_images_root
);
142 if (render_priv
->state
.stroker
) {
143 FT_Stroker_Done(render_priv
->state
.stroker
);
144 render_priv
->state
.stroker
= 0;
146 if (render_priv
->ftlibrary
)
147 FT_Done_FreeType(render_priv
->ftlibrary
);
148 if (render_priv
->fontconfig_priv
)
149 fontconfig_done(render_priv
->fontconfig_priv
);
150 if (render_priv
->synth_priv
)
151 ass_synth_done(render_priv
->synth_priv
);
152 free(render_priv
->eimg
);
153 free(render_priv
->text_info
.glyphs
);
154 free(render_priv
->text_info
.lines
);
156 free(render_priv
->settings
.default_font
);
157 free(render_priv
->settings
.default_family
);
159 free_list_clear(render_priv
);
164 * \brief Create a new ASS_Image
165 * Parameters are the same as ASS_Image fields.
167 static ASS_Image
*my_draw_bitmap(unsigned char *bitmap
, int bitmap_w
,
168 int bitmap_h
, int stride
, int dst_x
,
169 int dst_y
, uint32_t color
)
171 ASS_Image
*img
= malloc(sizeof(ASS_Image
));
176 img
->stride
= stride
;
177 img
->bitmap
= bitmap
;
187 * \brief Mapping between script and screen coordinates
189 static double x2scr(ASS_Renderer
*render_priv
, double x
)
191 return x
* render_priv
->orig_width_nocrop
/ render_priv
->font_scale_x
/
192 render_priv
->track
->PlayResX
+
193 FFMAX(render_priv
->settings
.left_margin
, 0);
195 static double x2scr_pos(ASS_Renderer
*render_priv
, double x
)
197 return x
* render_priv
->orig_width
/ render_priv
->font_scale_x
/ render_priv
->track
->PlayResX
+
198 render_priv
->settings
.left_margin
;
200 static double x2scr_scaled(ASS_Renderer
*render_priv
, double x
)
202 return x
* render_priv
->orig_width_nocrop
/
203 render_priv
->track
->PlayResX
+
204 FFMAX(render_priv
->settings
.left_margin
, 0);
206 static double x2scr_pos_scaled(ASS_Renderer
*render_priv
, double x
)
208 return x
* render_priv
->orig_width
/ render_priv
->track
->PlayResX
+
209 render_priv
->settings
.left_margin
;
212 * \brief Mapping between script and screen coordinates
214 static double y2scr(ASS_Renderer
*render_priv
, double y
)
216 return y
* render_priv
->orig_height_nocrop
/
217 render_priv
->track
->PlayResY
+
218 FFMAX(render_priv
->settings
.top_margin
, 0);
220 static double y2scr_pos(ASS_Renderer
*render_priv
, double y
)
222 return y
* render_priv
->orig_height
/ render_priv
->track
->PlayResY
+
223 render_priv
->settings
.top_margin
;
226 // the same for toptitles
227 static double y2scr_top(ASS_Renderer
*render_priv
, double y
)
229 if (render_priv
->settings
.use_margins
)
230 return y
* render_priv
->orig_height_nocrop
/
231 render_priv
->track
->PlayResY
;
233 return y
* render_priv
->orig_height_nocrop
/
234 render_priv
->track
->PlayResY
+
235 FFMAX(render_priv
->settings
.top_margin
, 0);
237 // the same for subtitles
238 static double y2scr_sub(ASS_Renderer
*render_priv
, double y
)
240 if (render_priv
->settings
.use_margins
)
241 return y
* render_priv
->orig_height_nocrop
/
242 render_priv
->track
->PlayResY
+
243 FFMAX(render_priv
->settings
.top_margin
, 0)
244 + FFMAX(render_priv
->settings
.bottom_margin
, 0);
246 return y
* render_priv
->orig_height_nocrop
/
247 render_priv
->track
->PlayResY
+
248 FFMAX(render_priv
->settings
.top_margin
, 0);
252 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
254 * Inverse clipping with the following strategy:
255 * - find rectangle from (x0, y0) to (cx0, y1)
256 * - find rectangle from (cx0, y0) to (cx1, cy0)
257 * - find rectangle from (cx0, cy1) to (cx1, y1)
258 * - find rectangle from (cx1, y0) to (x1, y1)
259 * These rectangles can be invalid and in this case are discarded.
260 * Afterwards, they are clipped against the screen coordinates.
261 * In an additional pass, the rectangles need to be split up left/right for
262 * karaoke effects. This can result in a lot of bitmaps (6 to be exact).
264 static ASS_Image
**render_glyph_i(ASS_Renderer
*render_priv
,
265 Bitmap
*bm
, int dst_x
, int dst_y
,
266 uint32_t color
, uint32_t color2
, int brk
,
269 int i
, j
, x0
, y0
, x1
, y1
, cx0
, cy0
, cx1
, cy1
, sx
, sy
, zx
, zy
;
276 // we still need to clip against screen boundaries
277 zx
= x2scr_pos_scaled(render_priv
, 0);
278 zy
= y2scr_pos(render_priv
, 0);
279 sx
= x2scr_pos_scaled(render_priv
, render_priv
->track
->PlayResX
);
280 sy
= y2scr_pos(render_priv
, render_priv
->track
->PlayResY
);
286 cx0
= render_priv
->state
.clip_x0
- dst_x
;
287 cy0
= render_priv
->state
.clip_y0
- dst_y
;
288 cx1
= render_priv
->state
.clip_x1
- dst_x
;
289 cy1
= render_priv
->state
.clip_y1
- dst_y
;
291 // calculate rectangles and discard invalid ones while we're at it.
295 r
[i
].x1
= (cx0
> x1
) ? x1
: cx0
;
297 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
298 r
[i
].x0
= (cx0
< 0) ? x0
: cx0
;
300 r
[i
].x1
= (cx1
> x1
) ? x1
: cx1
;
301 r
[i
].y1
= (cy0
> y1
) ? y1
: cy0
;
302 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
303 r
[i
].x0
= (cx0
< 0) ? x0
: cx0
;
304 r
[i
].y0
= (cy1
< 0) ? y0
: cy1
;
305 r
[i
].x1
= (cx1
> x1
) ? x1
: cx1
;
307 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
308 r
[i
].x0
= (cx1
< 0) ? x0
: cx1
;
312 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
314 // clip each rectangle to screen coordinates
315 for (j
= 0; j
< i
; j
++) {
316 r
[j
].x0
= (r
[j
].x0
+ dst_x
< zx
) ? zx
- dst_x
: r
[j
].x0
;
317 r
[j
].y0
= (r
[j
].y0
+ dst_y
< zy
) ? zy
- dst_y
: r
[j
].y0
;
318 r
[j
].x1
= (r
[j
].x1
+ dst_x
> sx
) ? sx
- dst_x
: r
[j
].x1
;
319 r
[j
].y1
= (r
[j
].y1
+ dst_y
> sy
) ? sy
- dst_y
: r
[j
].y1
;
322 // draw the rectangles
323 for (j
= 0; j
< i
; j
++) {
325 // kick out rectangles that are invalid now
326 if (r
[j
].x1
<= r
[j
].x0
|| r
[j
].y1
<= r
[j
].y0
)
328 // split up into left and right for karaoke, if needed
329 if (lbrk
> r
[j
].x0
) {
330 if (lbrk
> r
[j
].x1
) lbrk
= r
[j
].x1
;
331 img
= my_draw_bitmap(bm
->buffer
+ r
[j
].y0
* bm
->w
+ r
[j
].x0
,
332 lbrk
- r
[j
].x0
, r
[j
].y1
- r
[j
].y0
,
333 bm
->w
, dst_x
+ r
[j
].x0
, dst_y
+ r
[j
].y0
, color
);
338 if (lbrk
< r
[j
].x1
) {
339 if (lbrk
< r
[j
].x0
) lbrk
= r
[j
].x0
;
340 img
= my_draw_bitmap(bm
->buffer
+ r
[j
].y0
* bm
->w
+ lbrk
,
341 r
[j
].x1
- lbrk
, r
[j
].y1
- r
[j
].y0
,
342 bm
->w
, dst_x
+ lbrk
, dst_y
+ r
[j
].y0
, color2
);
353 * \brief convert bitmap glyph into ASS_Image struct(s)
354 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
355 * \param dst_x bitmap x coordinate in video frame
356 * \param dst_y bitmap y coordinate in video frame
357 * \param color first color, RGBA
358 * \param color2 second color, RGBA
359 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
360 * \param tail pointer to the last image's next field, head of the generated list should be stored here
361 * \return pointer to the new list tail
362 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
365 render_glyph(ASS_Renderer
*render_priv
, Bitmap
*bm
, int dst_x
, int dst_y
,
366 uint32_t color
, uint32_t color2
, int brk
, ASS_Image
**tail
)
368 // Inverse clipping in use?
369 if (render_priv
->state
.clip_mode
)
370 return render_glyph_i(render_priv
, bm
, dst_x
, dst_y
, color
, color2
,
373 // brk is relative to dst_x
374 // color = color left of brk
375 // color2 = color right of brk
376 int b_x0
, b_y0
, b_x1
, b_y1
; // visible part of the bitmap
377 int clip_x0
, clip_y0
, clip_x1
, clip_y1
;
386 clip_x0
= FFMINMAX(render_priv
->state
.clip_x0
, 0, render_priv
->width
);
387 clip_y0
= FFMINMAX(render_priv
->state
.clip_y0
, 0, render_priv
->height
);
388 clip_x1
= FFMINMAX(render_priv
->state
.clip_x1
, 0, render_priv
->width
);
389 clip_y1
= FFMINMAX(render_priv
->state
.clip_y1
, 0, render_priv
->height
);
395 tmp
= dst_x
- clip_x0
;
397 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip left");
400 tmp
= dst_y
- clip_y0
;
402 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip top");
405 tmp
= clip_x1
- dst_x
- bm
->w
;
407 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip right");
410 tmp
= clip_y1
- dst_y
- bm
->h
;
412 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip bottom");
416 if ((b_y0
>= b_y1
) || (b_x0
>= b_x1
))
419 if (brk
> b_x0
) { // draw left part
422 img
= my_draw_bitmap(bm
->buffer
+ bm
->w
* b_y0
+ b_x0
,
423 brk
- b_x0
, b_y1
- b_y0
, bm
->w
,
424 dst_x
+ b_x0
, dst_y
+ b_y0
, color
);
425 if (!img
) return tail
;
429 if (brk
< b_x1
) { // draw right part
432 img
= my_draw_bitmap(bm
->buffer
+ bm
->w
* b_y0
+ brk
,
433 b_x1
- brk
, b_y1
- b_y0
, bm
->w
,
434 dst_x
+ brk
, dst_y
+ b_y0
, color2
);
435 if (!img
) return tail
;
443 * \brief Replace the bitmap buffer in ASS_Image with a copy
444 * \param img ASS_Image to operate on
445 * \return pointer to old bitmap buffer
447 static unsigned char *clone_bitmap_buffer(ASS_Image
*img
)
449 unsigned char *old_bitmap
= img
->bitmap
;
450 int size
= img
->stride
* (img
->h
- 1) + img
->w
;
451 img
->bitmap
= malloc(size
);
452 memcpy(img
->bitmap
, old_bitmap
, size
);
457 * \brief Calculate overlapping area of two consecutive bitmaps and in case they
458 * overlap, blend them together
459 * Mainly useful for translucent glyphs and especially borders, to avoid the
460 * luminance adding up where they overlap (which looks ugly)
463 render_overlap(ASS_Renderer
*render_priv
, ASS_Image
**last_tail
,
466 int left
, top
, bottom
, right
;
467 int old_left
, old_top
, w
, h
, cur_left
, cur_top
;
468 int x
, y
, opos
, cpos
;
471 CompositeHashValue
*hv
;
472 CompositeHashValue chv
;
473 int ax
= (*last_tail
)->dst_x
;
474 int ay
= (*last_tail
)->dst_y
;
475 int aw
= (*last_tail
)->w
;
476 int as
= (*last_tail
)->stride
;
477 int ah
= (*last_tail
)->h
;
478 int bx
= (*tail
)->dst_x
;
479 int by
= (*tail
)->dst_y
;
481 int bs
= (*tail
)->stride
;
486 if ((*last_tail
)->bitmap
== (*tail
)->bitmap
)
489 if ((*last_tail
)->color
!= (*tail
)->color
)
492 // Calculate overlap coordinates
493 left
= (ax
> bx
) ? ax
: bx
;
494 top
= (ay
> by
) ? ay
: by
;
495 right
= ((ax
+ aw
) < (bx
+ bw
)) ? (ax
+ aw
) : (bx
+ bw
);
496 bottom
= ((ay
+ ah
) < (by
+ bh
)) ? (ay
+ ah
) : (by
+ bh
);
497 if ((right
<= left
) || (bottom
<= top
))
499 old_left
= left
- ax
;
503 cur_left
= left
- bx
;
507 hk
.a
= (*last_tail
)->bitmap
;
508 hk
.b
= (*tail
)->bitmap
;
519 hv
= cache_find_composite(render_priv
->cache
.composite_cache
, &hk
);
521 (*last_tail
)->bitmap
= hv
->a
;
522 (*tail
)->bitmap
= hv
->b
;
525 // Allocate new bitmaps and copy over data
526 a
= clone_bitmap_buffer(*last_tail
);
527 b
= clone_bitmap_buffer(*tail
);
529 // Blend overlapping area
530 for (y
= 0; y
< h
; y
++)
531 for (x
= 0; x
< w
; x
++) {
532 opos
= (old_top
+ y
) * (as
) + (old_left
+ x
);
533 cpos
= (cur_top
+ y
) * (bs
) + (cur_left
+ x
);
534 m
= FFMIN(a
[opos
] + b
[cpos
], 0xff);
535 (*last_tail
)->bitmap
[opos
] = 0;
536 (*tail
)->bitmap
[cpos
] = m
;
539 // Insert bitmaps into the cache
540 chv
.a
= (*last_tail
)->bitmap
;
541 chv
.b
= (*tail
)->bitmap
;
542 cache_add_composite(render_priv
->cache
.composite_cache
, &hk
, &chv
);
545 static void free_list_add(ASS_Renderer
*render_priv
, void *object
)
547 if (!render_priv
->free_head
) {
548 render_priv
->free_head
= calloc(1, sizeof(FreeList
));
549 render_priv
->free_head
->object
= object
;
550 render_priv
->free_tail
= render_priv
->free_head
;
552 FreeList
*l
= calloc(1, sizeof(FreeList
));
554 render_priv
->free_tail
->next
= l
;
555 render_priv
->free_tail
= render_priv
->free_tail
->next
;
560 * Iterate through a list of bitmaps and blend with clip vector, if
561 * applicable. The blended bitmaps are added to a free list which is freed
562 * at the start of a new frame.
564 static void blend_vector_clip(ASS_Renderer
*render_priv
,
568 FT_BitmapGlyph clip_bm
;
570 ASS_Drawing
*drawing
= render_priv
->state
.clip_drawing
;
578 // Try to get mask from cache
579 ass_drawing_hash(drawing
);
580 memset(&key
, 0, sizeof(key
));
582 key
.drawing_hash
= drawing
->hash
;
583 val
= cache_find_glyph(render_priv
->cache
.glyph_cache
, &key
);
586 clip_bm
= (FT_BitmapGlyph
) val
->glyph
;
590 // Not found in cache, parse and rasterize it
591 glyph
= (FT_Glyph
) *ass_drawing_parse(drawing
, 1);
593 ass_msg(render_priv
->library
, MSGL_WARN
,
594 "Clip vector parsing failed. Skipping.");
595 goto blend_vector_error
;
598 // We need to translate the clip according to screen borders
599 if (render_priv
->settings
.left_margin
!= 0 ||
600 render_priv
->settings
.top_margin
!= 0) {
602 .x
= int_to_d6(render_priv
->settings
.left_margin
),
603 .y
= -int_to_d6(render_priv
->settings
.top_margin
),
605 FT_Outline_Translate(&drawing
->glyph
->outline
,
609 // Check glyph bounding box size
610 if (check_glyph_area(render_priv
->library
, glyph
)) {
611 FT_Done_Glyph(glyph
);
613 goto blend_vector_error
;
616 ass_msg(render_priv
->library
, MSGL_DBG2
,
617 "Parsed vector clip: scales (%f, %f) string [%s]\n",
618 drawing
->scale_x
, drawing
->scale_y
, drawing
->text
);
620 error
= FT_Glyph_To_Bitmap(&glyph
, FT_RENDER_MODE_NORMAL
, 0, 1);
622 ass_msg(render_priv
->library
, MSGL_WARN
,
623 "Clip vector rasterization failed: %d. Skipping.", error
);
624 FT_Done_Glyph(glyph
);
629 clip_bm
= (FT_BitmapGlyph
) glyph
;
632 memset(&v
, 0, sizeof(v
));
634 cache_add_glyph(render_priv
->cache
.glyph_cache
, &key
, &v
);
637 if (!clip_bm
) goto blend_vector_exit
;
639 // Iterate through bitmaps and blend/clip them
640 for (cur
= head
; cur
; cur
= cur
->next
) {
641 int left
, top
, right
, bottom
, apos
, bpos
, y
, x
, w
, h
;
642 int ax
, ay
, aw
, ah
, as
;
643 int bx
, by
, bw
, bh
, bs
;
644 int aleft
, atop
, bleft
, btop
;
645 unsigned char *abuffer
, *bbuffer
, *nbuffer
;
647 abuffer
= cur
->bitmap
;
648 bbuffer
= clip_bm
->bitmap
.buffer
;
656 bw
= clip_bm
->bitmap
.width
;
657 bh
= clip_bm
->bitmap
.rows
;
658 bs
= clip_bm
->bitmap
.pitch
;
660 // Calculate overlap coordinates
661 left
= (ax
> bx
) ? ax
: bx
;
662 top
= (ay
> by
) ? ay
: by
;
663 right
= ((ax
+ aw
) < (bx
+ bw
)) ? (ax
+ aw
) : (bx
+ bw
);
664 bottom
= ((ay
+ ah
) < (by
+ bh
)) ? (ay
+ ah
) : (by
+ bh
);
672 if (render_priv
->state
.clip_drawing_mode
) {
674 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
679 // Allocate new buffer and add to free list
680 nbuffer
= malloc(as
* ah
);
681 if (!nbuffer
) goto blend_vector_exit
;
682 free_list_add(render_priv
, nbuffer
);
685 memcpy(nbuffer
, abuffer
, as
* (ah
- 1) + aw
);
686 for (y
= 0; y
< h
; y
++)
687 for (x
= 0; x
< w
; x
++) {
688 apos
= (atop
+ y
) * as
+ aleft
+ x
;
689 bpos
= (btop
+ y
) * bs
+ bleft
+ x
;
690 nbuffer
[apos
] = FFMAX(0, abuffer
[apos
] - bbuffer
[bpos
]);
694 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
700 // Allocate new buffer and add to free list
701 nbuffer
= calloc(as
, ah
);
702 if (!nbuffer
) goto blend_vector_exit
;
703 free_list_add(render_priv
, nbuffer
);
706 for (y
= 0; y
< h
; y
++)
707 for (x
= 0; x
< w
; x
++) {
708 apos
= (atop
+ y
) * as
+ aleft
+ x
;
709 bpos
= (btop
+ y
) * bs
+ bleft
+ x
;
710 nbuffer
[apos
] = (abuffer
[apos
] * bbuffer
[bpos
] + 255) >> 8;
713 cur
->bitmap
= nbuffer
;
717 ass_drawing_free(render_priv
->state
.clip_drawing
);
718 render_priv
->state
.clip_drawing
= 0;
722 * \brief Convert TextInfo struct to ASS_Image list
723 * Splits glyphs in halves when needed (for \kf karaoke).
725 static ASS_Image
*render_text(ASS_Renderer
*render_priv
, int dst_x
, int dst_y
)
731 ASS_Image
**tail
= &head
;
732 ASS_Image
**last_tail
= 0;
733 ASS_Image
**here_tail
= 0;
734 TextInfo
*text_info
= &render_priv
->text_info
;
736 for (i
= 0; i
< text_info
->length
; ++i
) {
737 GlyphInfo
*info
= text_info
->glyphs
+ i
;
738 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm_s
739 || (info
->shadow_x
== 0 && info
->shadow_y
== 0) || info
->skip
)
743 dst_x
+ (info
->pos
.x
>> 6) +
744 (int) (info
->shadow_x
* render_priv
->border_scale
);
746 dst_y
+ (info
->pos
.y
>> 6) +
747 (int) (info
->shadow_y
* render_priv
->border_scale
);
752 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[3], 0,
754 if (last_tail
&& tail
!= here_tail
&& ((info
->c
[3] & 0xff) > 0))
755 render_overlap(render_priv
, last_tail
, here_tail
);
757 last_tail
= here_tail
;
761 for (i
= 0; i
< text_info
->length
; ++i
) {
762 GlyphInfo
*info
= text_info
->glyphs
+ i
;
763 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm_o
767 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
768 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
771 if ((info
->effect_type
== EF_KARAOKE_KO
)
772 && (info
->effect_timing
<= (info
->bbox
.xMax
>> 6))) {
777 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[2],
779 if (last_tail
&& tail
!= here_tail
&& ((info
->c
[2] & 0xff) > 0))
780 render_overlap(render_priv
, last_tail
, here_tail
);
782 last_tail
= here_tail
;
786 for (i
= 0; i
< text_info
->length
; ++i
) {
787 GlyphInfo
*info
= text_info
->glyphs
+ i
;
788 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm
792 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
793 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
796 if ((info
->effect_type
== EF_KARAOKE
)
797 || (info
->effect_type
== EF_KARAOKE_KO
)) {
798 if (info
->effect_timing
> (info
->bbox
.xMax
>> 6))
800 render_glyph(render_priv
, bm
, pen_x
, pen_y
,
801 info
->c
[0], 0, 1000000, tail
);
804 render_glyph(render_priv
, bm
, pen_x
, pen_y
,
805 info
->c
[1], 0, 1000000, tail
);
806 } else if (info
->effect_type
== EF_KARAOKE_KF
) {
808 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
809 info
->c
[1], info
->effect_timing
, tail
);
812 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
817 blend_vector_clip(render_priv
, head
);
822 static void compute_string_bbox(TextInfo
*info
, DBBox
*bbox
)
826 if (info
->length
> 0) {
829 bbox
->yMin
= -1 * info
->lines
[0].asc
+ d6_to_double(info
->glyphs
[0].pos
.y
);
830 bbox
->yMax
= info
->height
- info
->lines
[0].asc
+
831 d6_to_double(info
->glyphs
[0].pos
.y
);
833 for (i
= 0; i
< info
->length
; ++i
) {
834 if (info
->glyphs
[i
].skip
) continue;
835 double s
= d6_to_double(info
->glyphs
[i
].pos
.x
);
836 double e
= s
+ d6_to_double(info
->glyphs
[i
].advance
.x
);
837 bbox
->xMin
= FFMIN(bbox
->xMin
, s
);
838 bbox
->xMax
= FFMAX(bbox
->xMax
, e
);
841 bbox
->xMin
= bbox
->xMax
= bbox
->yMin
= bbox
->yMax
= 0.;
845 * \brief partially reset render_context to style values
846 * Works like {\r}: resets some style overrides
848 void reset_render_context(ASS_Renderer
*render_priv
)
850 render_priv
->state
.c
[0] = render_priv
->state
.style
->PrimaryColour
;
851 render_priv
->state
.c
[1] = render_priv
->state
.style
->SecondaryColour
;
852 render_priv
->state
.c
[2] = render_priv
->state
.style
->OutlineColour
;
853 render_priv
->state
.c
[3] = render_priv
->state
.style
->BackColour
;
854 render_priv
->state
.flags
=
855 (render_priv
->state
.style
->Underline
? DECO_UNDERLINE
: 0) |
856 (render_priv
->state
.style
->StrikeOut
? DECO_STRIKETHROUGH
: 0);
857 render_priv
->state
.font_size
= render_priv
->state
.style
->FontSize
;
859 free(render_priv
->state
.family
);
860 render_priv
->state
.family
= NULL
;
861 render_priv
->state
.family
= strdup(render_priv
->state
.style
->FontName
);
862 render_priv
->state
.treat_family_as_pattern
=
863 render_priv
->state
.style
->treat_fontname_as_pattern
;
864 render_priv
->state
.bold
= render_priv
->state
.style
->Bold
;
865 render_priv
->state
.italic
= render_priv
->state
.style
->Italic
;
866 update_font(render_priv
);
868 change_border(render_priv
, -1., -1.);
869 render_priv
->state
.scale_x
= render_priv
->state
.style
->ScaleX
;
870 render_priv
->state
.scale_y
= render_priv
->state
.style
->ScaleY
;
871 render_priv
->state
.hspacing
= render_priv
->state
.style
->Spacing
;
872 render_priv
->state
.be
= 0;
873 render_priv
->state
.blur
= 0.0;
874 render_priv
->state
.shadow_x
= render_priv
->state
.style
->Shadow
;
875 render_priv
->state
.shadow_y
= render_priv
->state
.style
->Shadow
;
876 render_priv
->state
.frx
= render_priv
->state
.fry
= 0.;
877 render_priv
->state
.frz
= M_PI
* render_priv
->state
.style
->Angle
/ 180.;
878 render_priv
->state
.fax
= render_priv
->state
.fay
= 0.;
879 render_priv
->state
.wrap_style
= render_priv
->track
->WrapStyle
;
883 * \brief Start new event. Reset render_priv->state.
886 init_render_context(ASS_Renderer
*render_priv
, ASS_Event
*event
)
888 render_priv
->state
.event
= event
;
889 render_priv
->state
.style
= render_priv
->track
->styles
+ event
->Style
;
891 reset_render_context(render_priv
);
893 render_priv
->state
.evt_type
= EVENT_NORMAL
;
894 render_priv
->state
.alignment
= render_priv
->state
.style
->Alignment
;
895 render_priv
->state
.pos_x
= 0;
896 render_priv
->state
.pos_y
= 0;
897 render_priv
->state
.org_x
= 0;
898 render_priv
->state
.org_y
= 0;
899 render_priv
->state
.have_origin
= 0;
900 render_priv
->state
.clip_x0
= 0;
901 render_priv
->state
.clip_y0
= 0;
902 render_priv
->state
.clip_x1
= render_priv
->track
->PlayResX
;
903 render_priv
->state
.clip_y1
= render_priv
->track
->PlayResY
;
904 render_priv
->state
.clip_mode
= 0;
905 render_priv
->state
.detect_collisions
= 1;
906 render_priv
->state
.fade
= 0;
907 render_priv
->state
.drawing_mode
= 0;
908 render_priv
->state
.effect_type
= EF_NONE
;
909 render_priv
->state
.effect_timing
= 0;
910 render_priv
->state
.effect_skip_timing
= 0;
911 ass_drawing_free(render_priv
->state
.drawing
);
912 render_priv
->state
.drawing
= ass_drawing_new(render_priv
->fontconfig_priv
,
913 render_priv
->state
.font
,
914 render_priv
->ftlibrary
);
916 apply_transition_effects(render_priv
, event
);
919 static void free_render_context(ASS_Renderer
*render_priv
)
921 free(render_priv
->state
.family
);
922 ass_drawing_free(render_priv
->state
.drawing
);
924 render_priv
->state
.family
= NULL
;
925 render_priv
->state
.drawing
= NULL
;
929 * Replace the outline of a glyph by a contour which makes up a simple
932 static void draw_opaque_box(ASS_Renderer
*render_priv
, uint32_t ch
,
933 FT_Glyph glyph
, int sx
, int sy
)
935 int asc
= 0, desc
= 0;
937 int adv
= d16_to_d6(glyph
->advance
.x
);
938 double scale_y
= render_priv
->state
.scale_y
;
939 double scale_x
= render_priv
->state
.scale_x
;
940 FT_OutlineGlyph og
= (FT_OutlineGlyph
) glyph
;
948 asc
= render_priv
->state
.drawing
->asc
;
949 desc
= render_priv
->state
.drawing
->desc
;
951 ass_font_get_asc_desc(render_priv
->state
.font
, ch
, &asc
, &desc
);
956 // Emulate the WTFish behavior of VSFilter, i.e. double-scale
957 // the sizes of the opaque box.
958 adv
+= double_to_d6(render_priv
->state
.hspacing
* render_priv
->font_scale
964 desc
+= asc
* (scale_y
- 1.0);
966 FT_Vector points
[4] = {
967 { .x
= -sx
, .y
= asc
+ sy
},
968 { .x
= adv
+ sx
, .y
= asc
+ sy
},
969 { .x
= adv
+ sx
, .y
= -desc
- sy
},
970 { .x
= -sx
, .y
= -desc
- sy
},
973 FT_Outline_Done(render_priv
->ftlibrary
, &og
->outline
);
974 FT_Outline_New(render_priv
->ftlibrary
, 4, 1, &og
->outline
);
977 ol
->n_points
= ol
->n_contours
= 0;
978 for (i
= 0; i
< 4; i
++) {
979 ol
->points
[ol
->n_points
] = points
[i
];
980 ol
->tags
[ol
->n_points
++] = 1;
982 ol
->contours
[ol
->n_contours
++] = ol
->n_points
- 1;
986 * Stroke an outline glyph in x/y direction. Applies various fixups to get
987 * around limitations of the FreeType stroker.
989 static void stroke_outline_glyph(ASS_Renderer
*render_priv
,
990 FT_OutlineGlyph
*glyph
, int sx
, int sy
)
992 if (sx
<= 0 && sy
<= 0)
995 fix_freetype_stroker(*glyph
, sx
, sy
);
997 // Borders are equal; use the regular stroker
998 if (sx
== sy
&& render_priv
->state
.stroker
) {
1000 error
= FT_Glyph_StrokeBorder((FT_Glyph
*) glyph
,
1001 render_priv
->state
.stroker
, 0, 1);
1003 ass_msg(render_priv
->library
, MSGL_WARN
,
1004 "FT_Glyph_Stroke error: %d", error
);
1006 // "Stroke" with the outline emboldener in two passes.
1007 // The outlines look uglier, but the emboldening never adds any points
1010 FT_Outline
*ol
= &(*glyph
)->outline
;
1012 FT_Outline_New(render_priv
->ftlibrary
, ol
->n_points
,
1013 ol
->n_contours
, &nol
);
1014 FT_Outline_Copy(ol
, &nol
);
1016 FT_Outline_Embolden(ol
, sx
* 2);
1017 FT_Outline_Translate(ol
, -sx
, -sx
);
1018 FT_Outline_Embolden(&nol
, sy
* 2);
1019 FT_Outline_Translate(&nol
, -sy
, -sy
);
1021 for (i
= 0; i
< ol
->n_points
; i
++)
1022 ol
->points
[i
].y
= nol
.points
[i
].y
;
1024 FT_Outline_Done(render_priv
->ftlibrary
, &nol
);
1029 * \brief Prepare glyph hash
1032 fill_glyph_hash(ASS_Renderer
*priv
, GlyphHashKey
*key
,
1033 ASS_Drawing
*drawing
, uint32_t ch
)
1035 if (drawing
->hash
) {
1036 key
->scale_x
= double_to_d16(priv
->state
.scale_x
);
1037 key
->scale_y
= double_to_d16(priv
->state
.scale_y
);
1038 key
->outline
.x
= priv
->state
.border_x
* 0xFFFF;
1039 key
->outline
.y
= priv
->state
.border_y
* 0xFFFF;
1040 key
->border_style
= priv
->state
.style
->BorderStyle
;
1041 key
->drawing_hash
= drawing
->hash
;
1042 // not very clean, but works
1043 key
->size
= drawing
->scale
;
1046 key
->font
= priv
->state
.font
;
1047 key
->size
= priv
->state
.font_size
;
1049 key
->bold
= priv
->state
.bold
;
1050 key
->italic
= priv
->state
.italic
;
1051 key
->scale_x
= double_to_d16(priv
->state
.scale_x
);
1052 key
->scale_y
= double_to_d16(priv
->state
.scale_y
);
1053 key
->outline
.x
= priv
->state
.border_x
* 0xFFFF;
1054 key
->outline
.y
= priv
->state
.border_y
* 0xFFFF;
1055 key
->flags
= priv
->state
.flags
;
1056 key
->border_style
= priv
->state
.style
->BorderStyle
;
1061 * \brief Get normal and outline (border) glyphs
1062 * \param symbol ucs4 char
1063 * \param info out: struct filled with extracted data
1064 * Tries to get both glyphs from cache.
1065 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
1066 * and add them to cache.
1067 * The glyphs are returned in info->glyph and info->outline_glyph
1070 get_outline_glyph(ASS_Renderer
*render_priv
, int symbol
, GlyphInfo
*info
,
1071 ASS_Drawing
*drawing
)
1073 GlyphHashValue
*val
;
1076 memset(&key
, 0, sizeof(key
));
1077 memset(info
, 0, sizeof(GlyphInfo
));
1079 fill_glyph_hash(render_priv
, &key
, drawing
, symbol
);
1080 val
= cache_find_glyph(render_priv
->cache
.glyph_cache
, &key
);
1082 info
->glyph
= val
->glyph
;
1083 info
->outline_glyph
= val
->outline_glyph
;
1084 info
->bbox
= val
->bbox_scaled
;
1085 info
->advance
.x
= val
->advance
.x
;
1086 info
->advance
.y
= val
->advance
.y
;
1087 if (drawing
->hash
) {
1088 drawing
->asc
= val
->asc
;
1089 drawing
->desc
= val
->desc
;
1093 if (drawing
->hash
) {
1094 if(!ass_drawing_parse(drawing
, 0))
1096 info
->glyph
= (FT_Glyph
) drawing
->glyph
;
1099 ass_font_get_glyph(render_priv
->fontconfig_priv
,
1100 render_priv
->state
.font
, symbol
,
1101 render_priv
->settings
.hinting
,
1102 render_priv
->state
.flags
);
1107 info
->advance
.x
= d16_to_d6(info
->glyph
->advance
.x
);
1108 info
->advance
.y
= d16_to_d6(info
->glyph
->advance
.y
);
1109 FT_Glyph_Get_CBox(info
->glyph
, FT_GLYPH_BBOX_SUBPIXELS
, &info
->bbox
);
1111 if (render_priv
->state
.style
->BorderStyle
== 3 &&
1112 (render_priv
->state
.border_x
> 0||
1113 render_priv
->state
.border_y
> 0)) {
1114 FT_Glyph_Copy(info
->glyph
, &info
->outline_glyph
);
1115 draw_opaque_box(render_priv
, symbol
, info
->outline_glyph
,
1116 double_to_d6(render_priv
->state
.border_x
*
1117 render_priv
->border_scale
),
1118 double_to_d6(render_priv
->state
.border_y
*
1119 render_priv
->border_scale
));
1120 } else if ((render_priv
->state
.border_x
> 0
1121 || render_priv
->state
.border_y
> 0)
1122 && key
.scale_x
&& key
.scale_y
) {
1124 FT_Glyph_Copy(info
->glyph
, &info
->outline_glyph
);
1125 stroke_outline_glyph(render_priv
,
1126 (FT_OutlineGlyph
*) &info
->outline_glyph
,
1127 double_to_d6(render_priv
->state
.border_x
*
1128 render_priv
->border_scale
),
1129 double_to_d6(render_priv
->state
.border_y
*
1130 render_priv
->border_scale
));
1133 memset(&v
, 0, sizeof(v
));
1134 v
.glyph
= info
->glyph
;
1135 v
.outline_glyph
= info
->outline_glyph
;
1136 v
.advance
= info
->advance
;
1137 v
.bbox_scaled
= info
->bbox
;
1138 if (drawing
->hash
) {
1139 v
.asc
= drawing
->asc
;
1140 v
.desc
= drawing
->desc
;
1142 cache_add_glyph(render_priv
->cache
.glyph_cache
, &key
, &v
);
1147 * \brief Apply transformation to outline points of a glyph
1148 * Applies rotations given by frx, fry and frz and projects the points back
1149 * onto the screen plane.
1152 transform_3d_points(FT_Vector shift
, FT_Glyph glyph
, double frx
, double fry
,
1153 double frz
, double fax
, double fay
, double scale
,
1156 double sx
= sin(frx
);
1157 double sy
= sin(fry
);
1158 double sz
= sin(frz
);
1159 double cx
= cos(frx
);
1160 double cy
= cos(fry
);
1161 double cz
= cos(frz
);
1162 FT_Outline
*outline
= &((FT_OutlineGlyph
) glyph
)->outline
;
1163 FT_Vector
*p
= outline
->points
;
1164 double x
, y
, z
, xx
, yy
, zz
;
1167 dist
= 20000 * scale
;
1168 for (i
= 0; i
< outline
->n_points
; i
++) {
1169 x
= (double) p
[i
].x
+ shift
.x
+ (fax
* (yshift
- p
[i
].y
));
1170 y
= (double) p
[i
].y
+ shift
.y
+ (-fay
* p
[i
].x
);
1173 xx
= x
* cz
+ y
* sz
;
1174 yy
= -(x
* sz
- y
* cz
);
1178 y
= yy
* cx
+ zz
* sx
;
1179 z
= yy
* sx
- zz
* cx
;
1181 xx
= x
* cy
+ z
* sy
;
1183 zz
= x
* sy
- z
* cy
;
1185 zz
= FFMAX(zz
, 1000 - dist
);
1187 x
= (xx
* dist
) / (zz
+ dist
);
1188 y
= (yy
* dist
) / (zz
+ dist
);
1189 p
[i
].x
= x
- shift
.x
+ 0.5;
1190 p
[i
].y
= y
- shift
.y
+ 0.5;
1195 * \brief Apply 3d transformation to several objects
1196 * \param shift FreeType vector
1197 * \param glyph FreeType glyph
1198 * \param glyph2 FreeType glyph
1199 * \param frx x-axis rotation angle
1200 * \param fry y-axis rotation angle
1201 * \param frz z-axis rotation angle
1202 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
1205 transform_3d(FT_Vector shift
, FT_Glyph
*glyph
, FT_Glyph
*glyph2
,
1206 double frx
, double fry
, double frz
, double fax
, double fay
,
1207 double scale
, int yshift
)
1211 if (frx
!= 0. || fry
!= 0. || frz
!= 0. || fax
!= 0. || fay
!= 0.) {
1212 if (glyph
&& *glyph
)
1213 transform_3d_points(shift
, *glyph
, frx
, fry
, frz
,
1214 fax
, fay
, scale
, yshift
);
1216 if (glyph2
&& *glyph2
)
1217 transform_3d_points(shift
, *glyph2
, frx
, fry
, frz
,
1218 fax
, fay
, scale
, yshift
);
1223 * \brief Get bitmaps for a glyph
1224 * \param info glyph info
1225 * Tries to get glyph bitmaps from bitmap cache.
1226 * If they can't be found, they are generated by rotating and rendering the glyph.
1227 * After that, bitmaps are added to the cache.
1228 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
1231 get_bitmap_glyph(ASS_Renderer
*render_priv
, GlyphInfo
*info
)
1233 BitmapHashValue
*val
;
1234 BitmapHashKey
*key
= &info
->hash_key
;
1236 val
= cache_find_bitmap(render_priv
->cache
.bitmap_cache
, key
);
1240 info
->bm_o
= val
->bm_o
;
1241 info
->bm_s
= val
->bm_s
;
1244 BitmapHashValue hash_val
;
1246 double fax_scaled
, fay_scaled
;
1247 info
->bm
= info
->bm_o
= info
->bm_s
= 0;
1248 if (info
->glyph
&& info
->symbol
!= '\n' && info
->symbol
!= 0
1252 double scale_x
= render_priv
->font_scale_x
;
1254 FT_Glyph_Copy(info
->glyph
, &glyph
);
1255 FT_Glyph_Copy(info
->outline_glyph
, &outline
);
1256 // calculating rotation shift vector (from rotation origin to the glyph basepoint)
1257 shift
.x
= key
->shift_x
;
1258 shift
.y
= key
->shift_y
;
1259 fax_scaled
= info
->fax
*
1260 render_priv
->state
.scale_x
;
1261 fay_scaled
= info
->fay
* render_priv
->state
.scale_y
;
1263 transform_3d(shift
, &glyph
, &outline
,
1264 info
->frx
, info
->fry
, info
->frz
, fax_scaled
,
1265 fay_scaled
, render_priv
->font_scale
, info
->asc
);
1267 // PAR correction scaling
1268 FT_Matrix m
= { double_to_d16(scale_x
), 0,
1269 0, double_to_d16(1.0) };
1273 FT_Outline
*outl
= &((FT_OutlineGlyph
) glyph
)->outline
;
1275 FT_Outline_Transform(outl
, &m
);
1276 FT_Outline_Translate(outl
, key
->advance
.x
, -key
->advance
.y
);
1279 FT_Outline
*outl
= &((FT_OutlineGlyph
) outline
)->outline
;
1281 FT_Outline_Transform(outl
, &m
);
1282 FT_Outline_Translate(outl
, key
->advance
.x
, -key
->advance
.y
);
1285 error
= glyph_to_bitmap(render_priv
->library
,
1286 render_priv
->synth_priv
,
1288 &info
->bm
, &info
->bm_o
,
1289 &info
->bm_s
, info
->be
,
1290 info
->blur
* render_priv
->border_scale
,
1291 key
->shadow_offset
, key
->border_style
);
1295 // add bitmaps to cache
1296 hash_val
.bm_o
= info
->bm_o
;
1297 hash_val
.bm
= info
->bm
;
1298 hash_val
.bm_s
= info
->bm_s
;
1299 cache_add_bitmap(render_priv
->cache
.bitmap_cache
, key
, &hash_val
);
1301 FT_Done_Glyph(glyph
);
1302 FT_Done_Glyph(outline
);
1308 * This function goes through text_info and calculates text parameters.
1309 * The following text_info fields are filled:
1315 static void measure_text(ASS_Renderer
*render_priv
)
1317 TextInfo
*text_info
= &render_priv
->text_info
;
1319 double max_asc
= 0., max_desc
= 0.;
1320 GlyphInfo
*last
= NULL
;
1323 text_info
->height
= 0.;
1324 for (i
= 0; i
< text_info
->length
+ 1; ++i
) {
1325 if ((i
== text_info
->length
) || text_info
->glyphs
[i
].linebreak
) {
1326 if (empty_line
&& cur_line
> 0 && last
&& i
< text_info
->length
) {
1327 max_asc
= d6_to_double(last
->asc
) / 2.0;
1328 max_desc
= d6_to_double(last
->desc
) / 2.0;
1330 text_info
->lines
[cur_line
].asc
= max_asc
;
1331 text_info
->lines
[cur_line
].desc
= max_desc
;
1332 text_info
->height
+= max_asc
+ max_desc
;
1334 max_asc
= max_desc
= 0.;
1338 if (i
< text_info
->length
) {
1339 GlyphInfo
*cur
= text_info
->glyphs
+ i
;
1340 if (d6_to_double(cur
->asc
) > max_asc
)
1341 max_asc
= d6_to_double(cur
->asc
);
1342 if (d6_to_double(cur
->desc
) > max_desc
)
1343 max_desc
= d6_to_double(cur
->desc
);
1344 if (cur
->symbol
!= '\n' && cur
->symbol
!= 0)
1348 text_info
->height
+=
1349 (text_info
->n_lines
-
1350 1) * render_priv
->settings
.line_spacing
;
1354 * Mark extra whitespace for later removal.
1356 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1358 static void trim_whitespace(ASS_Renderer
*render_priv
)
1362 TextInfo
*ti
= &render_priv
->text_info
;
1364 // Mark trailing spaces
1366 cur
= ti
->glyphs
+ i
;
1367 while (i
&& IS_WHITESPACE(cur
)) {
1369 cur
= ti
->glyphs
+ --i
;
1372 // Mark leading whitespace
1375 while (i
< ti
->length
&& IS_WHITESPACE(cur
)) {
1377 cur
= ti
->glyphs
+ ++i
;
1380 // Mark all extraneous whitespace inbetween
1381 for (i
= 0; i
< ti
->length
; ++i
) {
1382 cur
= ti
->glyphs
+ i
;
1383 if (cur
->linebreak
) {
1384 // Mark whitespace before
1386 cur
= ti
->glyphs
+ j
;
1387 while (j
&& IS_WHITESPACE(cur
)) {
1389 cur
= ti
->glyphs
+ --j
;
1391 // A break itself can contain a whitespace, too
1392 cur
= ti
->glyphs
+ i
;
1393 if (cur
->symbol
== ' ')
1395 // Mark whitespace after
1397 cur
= ti
->glyphs
+ j
;
1398 while (j
< ti
->length
&& IS_WHITESPACE(cur
)) {
1400 cur
= ti
->glyphs
+ ++j
;
1406 #undef IS_WHITESPACE
1409 * \brief rearrange text between lines
1410 * \param max_text_width maximal text line width in pixels
1411 * The algo is similar to the one in libvo/sub.c:
1412 * 1. Place text, wrapping it when current line is full
1413 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1414 * the difference in lengths between this two lines.
1415 * The result may not be optimal, but usually is good enough.
1417 * FIXME: implement style 0 and 3 correctly
1420 wrap_lines_smart(ASS_Renderer
*render_priv
, double max_text_width
)
1423 GlyphInfo
*cur
, *s1
, *e1
, *s2
, *s3
, *w
;
1430 TextInfo
*text_info
= &render_priv
->text_info
;
1433 text_info
->n_lines
= 1;
1435 s1
= text_info
->glyphs
; // current line start
1436 for (i
= 0; i
< text_info
->length
; ++i
) {
1438 double s_offset
, len
;
1439 cur
= text_info
->glyphs
+ i
;
1441 s_offset
= d6_to_double(s1
->bbox
.xMin
+ s1
->pos
.x
);
1442 len
= d6_to_double(cur
->bbox
.xMax
+ cur
->pos
.x
) - s_offset
;
1444 if (cur
->symbol
== '\n') {
1447 ass_msg(render_priv
->library
, MSGL_DBG2
,
1448 "forced line break at %d", break_at
);
1451 if ((len
>= max_text_width
)
1452 && (render_priv
->state
.wrap_style
!= 2)) {
1454 break_at
= last_space
;
1456 ass_msg(render_priv
->library
, MSGL_DBG2
, "line break at %d",
1460 if (break_at
!= -1) {
1461 // need to use one more line
1462 // marking break_at+1 as start of a new line
1463 int lead
= break_at
+ 1; // the first symbol of the new line
1464 if (text_info
->n_lines
>= text_info
->max_lines
) {
1465 // Raise maximum number of lines
1466 text_info
->max_lines
*= 2;
1467 text_info
->lines
= realloc(text_info
->lines
,
1469 text_info
->max_lines
);
1471 if (lead
< text_info
->length
)
1472 text_info
->glyphs
[lead
].linebreak
= break_type
;
1474 s1
= text_info
->glyphs
+ lead
;
1475 s_offset
= d6_to_double(s1
->bbox
.xMin
+ s1
->pos
.x
);
1476 text_info
->n_lines
++;
1479 if (cur
->symbol
== ' ')
1482 // make sure the hard linebreak is not forgotten when
1483 // there was a new soft linebreak just inserted
1484 if (cur
->symbol
== '\n' && break_type
== 1)
1487 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1489 while (!exit
&& render_priv
->state
.wrap_style
!= 1) {
1491 w
= s3
= text_info
->glyphs
;
1493 for (i
= 0; i
<= text_info
->length
; ++i
) {
1494 cur
= text_info
->glyphs
+ i
;
1495 if ((i
== text_info
->length
) || cur
->linebreak
) {
1499 if (s1
&& (s2
->linebreak
== 1)) { // have at least 2 lines, and linebreak is 'soft'
1500 double l1
, l2
, l1_new
, l2_new
;
1505 } while ((w
> s1
) && (w
->symbol
== ' '));
1506 while ((w
> s1
) && (w
->symbol
!= ' ')) {
1510 while ((e1
> s1
) && (e1
->symbol
== ' ')) {
1513 if (w
->symbol
== ' ')
1516 l1
= d6_to_double(((s2
- 1)->bbox
.xMax
+ (s2
- 1)->pos
.x
) -
1517 (s1
->bbox
.xMin
+ s1
->pos
.x
));
1518 l2
= d6_to_double(((s3
- 1)->bbox
.xMax
+ (s3
- 1)->pos
.x
) -
1519 (s2
->bbox
.xMin
+ s2
->pos
.x
));
1520 l1_new
= d6_to_double(
1521 (e1
->bbox
.xMax
+ e1
->pos
.x
) -
1522 (s1
->bbox
.xMin
+ s1
->pos
.x
));
1523 l2_new
= d6_to_double(
1524 ((s3
- 1)->bbox
.xMax
+ (s3
- 1)->pos
.x
) -
1525 (w
->bbox
.xMin
+ w
->pos
.x
));
1527 if (DIFF(l1_new
, l2_new
) < DIFF(l1
, l2
)) {
1534 if (i
== text_info
->length
)
1539 assert(text_info
->n_lines
>= 1);
1542 measure_text(render_priv
);
1543 trim_whitespace(render_priv
);
1550 cur
= text_info
->glyphs
+ i
;
1551 while (i
< text_info
->length
&& cur
->skip
)
1552 cur
= text_info
->glyphs
+ ++i
;
1553 pen_shift_x
= d6_to_double(-cur
->pos
.x
);
1555 for (i
= 0; i
< text_info
->length
; ++i
) {
1556 cur
= text_info
->glyphs
+ i
;
1557 if (cur
->linebreak
) {
1558 while (i
< text_info
->length
&& cur
->skip
&& cur
->symbol
!= '\n')
1559 cur
= text_info
->glyphs
+ ++i
;
1561 text_info
->lines
[cur_line
- 1].desc
+
1562 text_info
->lines
[cur_line
].asc
;
1564 pen_shift_x
= d6_to_double(-cur
->pos
.x
);
1565 pen_shift_y
+= height
+ render_priv
->settings
.line_spacing
;
1566 ass_msg(render_priv
->library
, MSGL_DBG2
,
1567 "shifting from %d to %d by (%f, %f)", i
,
1568 text_info
->length
- 1, pen_shift_x
, pen_shift_y
);
1570 cur
->pos
.x
+= double_to_d6(pen_shift_x
);
1571 cur
->pos
.y
+= double_to_d6(pen_shift_y
);
1576 * \brief determine karaoke effects
1577 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
1578 * so they are done in a separate step.
1579 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
1580 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
1582 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
1583 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
1584 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
1586 static void process_karaoke_effects(ASS_Renderer
*render_priv
)
1588 GlyphInfo
*cur
, *cur2
;
1589 GlyphInfo
*s1
, *e1
; // start and end of the current word
1590 GlyphInfo
*s2
; // start of the next word
1592 int timing
; // current timing
1593 int tm_start
, tm_end
; // timings at start and end of the current word
1599 tm_current
= render_priv
->time
- render_priv
->state
.event
->Start
;
1602 for (i
= 0; i
<= render_priv
->text_info
.length
; ++i
) {
1603 cur
= render_priv
->text_info
.glyphs
+ i
;
1604 if ((i
== render_priv
->text_info
.length
)
1605 || (cur
->effect_type
!= EF_NONE
)) {
1610 tm_start
= timing
+ s1
->effect_skip_timing
;
1611 tm_end
= tm_start
+ s1
->effect_timing
;
1615 for (cur2
= s1
; cur2
<= e1
; ++cur2
) {
1616 x_start
= FFMIN(x_start
, d6_to_int(cur2
->bbox
.xMin
+ cur2
->pos
.x
));
1617 x_end
= FFMAX(x_end
, d6_to_int(cur2
->bbox
.xMax
+ cur2
->pos
.x
));
1620 dt
= (tm_current
- tm_start
);
1621 if ((s1
->effect_type
== EF_KARAOKE
)
1622 || (s1
->effect_type
== EF_KARAOKE_KO
)) {
1627 } else if (s1
->effect_type
== EF_KARAOKE_KF
) {
1628 dt
/= (tm_end
- tm_start
);
1629 x
= x_start
+ (x_end
- x_start
) * dt
;
1631 ass_msg(render_priv
->library
, MSGL_ERR
,
1632 "Unknown effect type");
1636 for (cur2
= s1
; cur2
<= e1
; ++cur2
) {
1637 cur2
->effect_type
= s1
->effect_type
;
1638 cur2
->effect_timing
= x
- d6_to_int(cur2
->pos
.x
);
1646 * \brief Calculate base point for positioning and rotation
1647 * \param bbox text bbox
1648 * \param alignment alignment
1649 * \param bx, by out: base point coordinates
1651 static void get_base_point(DBBox
*bbox
, int alignment
, double *bx
, double *by
)
1653 const int halign
= alignment
& 3;
1654 const int valign
= alignment
& 12;
1661 *bx
= (bbox
->xMax
+ bbox
->xMin
) / 2.0;
1673 *by
= (bbox
->yMax
+ bbox
->yMin
) / 2.0;
1682 * Prepare bitmap hash key of a glyph
1685 fill_bitmap_hash(ASS_Renderer
*priv
, BitmapHashKey
*hash_key
,
1686 ASS_Drawing
*drawing
, FT_Vector pen
, uint32_t code
)
1688 if (!drawing
->hash
) {
1689 hash_key
->font
= priv
->state
.font
;
1690 hash_key
->size
= priv
->state
.font_size
;
1691 hash_key
->bold
= priv
->state
.bold
;
1692 hash_key
->italic
= priv
->state
.italic
;
1694 hash_key
->drawing_hash
= drawing
->hash
;
1695 hash_key
->size
= drawing
->scale
;
1697 hash_key
->ch
= code
;
1698 hash_key
->outline
.x
= double_to_d16(priv
->state
.border_x
);
1699 hash_key
->outline
.y
= double_to_d16(priv
->state
.border_y
);
1700 hash_key
->scale_x
= double_to_d16(priv
->state
.scale_x
);
1701 hash_key
->scale_y
= double_to_d16(priv
->state
.scale_y
);
1702 hash_key
->frx
= rot_key(priv
->state
.frx
);
1703 hash_key
->fry
= rot_key(priv
->state
.fry
);
1704 hash_key
->frz
= rot_key(priv
->state
.frz
);
1705 hash_key
->fax
= double_to_d16(priv
->state
.fax
);
1706 hash_key
->fay
= double_to_d16(priv
->state
.fay
);
1707 hash_key
->be
= priv
->state
.be
;
1708 hash_key
->blur
= priv
->state
.blur
;
1709 hash_key
->border_style
= priv
->state
.style
->BorderStyle
;
1710 hash_key
->shadow_offset
.x
= double_to_d6(
1711 priv
->state
.shadow_x
* priv
->border_scale
-
1712 (int) (priv
->state
.shadow_x
* priv
->border_scale
));
1713 hash_key
->shadow_offset
.y
= double_to_d6(
1714 priv
->state
.shadow_y
* priv
->border_scale
-
1715 (int) (priv
->state
.shadow_y
* priv
->border_scale
));
1716 hash_key
->flags
= priv
->state
.flags
;
1720 * \brief Main ass rendering function, glues everything together
1721 * \param event event to render
1722 * \param event_images struct containing resulting images, will also be initialized
1723 * Process event, appending resulting ASS_Image's to images_root.
1726 ass_render_event(ASS_Renderer
*render_priv
, ASS_Event
*event
,
1727 EventImages
*event_images
)
1736 int MarginL
, MarginR
, MarginV
;
1738 int alignment
, halign
, valign
;
1739 int kern
= render_priv
->track
->Kerning
;
1740 double device_x
= 0;
1741 double device_y
= 0;
1742 TextInfo
*text_info
= &render_priv
->text_info
;
1743 GlyphInfo
*glyphs
= render_priv
->text_info
.glyphs
;
1744 ASS_Drawing
*drawing
;
1746 if (event
->Style
>= render_priv
->track
->n_styles
) {
1747 ass_msg(render_priv
->library
, MSGL_WARN
, "No style found");
1751 ass_msg(render_priv
->library
, MSGL_WARN
, "Empty event");
1755 init_render_context(render_priv
, event
);
1757 drawing
= render_priv
->state
.drawing
;
1758 text_info
->length
= 0;
1766 // get next char, executing style override
1767 // this affects render_context
1769 code
= get_next_char(render_priv
, &p
);
1770 if (render_priv
->state
.drawing_mode
&& code
)
1771 ass_drawing_add_char(drawing
, (char) code
);
1772 } while (code
&& render_priv
->state
.drawing_mode
); // skip everything in drawing mode
1776 drawing
->scale_x
= render_priv
->state
.scale_x
*
1777 render_priv
->font_scale
;
1778 drawing
->scale_y
= render_priv
->state
.scale_y
*
1779 render_priv
->font_scale
;
1780 ass_drawing_hash(drawing
);
1785 // face could have been changed in get_next_char
1786 if (!render_priv
->state
.font
) {
1787 free_render_context(render_priv
);
1794 if (text_info
->length
>= text_info
->max_glyphs
) {
1795 // Raise maximum number of glyphs
1796 text_info
->max_glyphs
*= 2;
1797 text_info
->glyphs
= glyphs
=
1798 realloc(text_info
->glyphs
,
1799 sizeof(GlyphInfo
) * text_info
->max_glyphs
);
1802 // Add kerning to pen
1803 if (kern
&& previous
&& code
&& !drawing
->hash
) {
1806 ass_font_get_kerning(render_priv
->state
.font
, previous
,
1808 pen
.x
+= delta
.x
* render_priv
->state
.scale_x
;
1809 pen
.y
+= delta
.y
* render_priv
->state
.scale_y
;
1812 ass_font_set_transform(render_priv
->state
.font
,
1813 render_priv
->state
.scale_x
,
1814 render_priv
->state
.scale_y
, NULL
);
1816 get_outline_glyph(render_priv
, code
,
1817 glyphs
+ text_info
->length
, drawing
);
1819 // Add additional space after italic to non-italic style changes
1820 if (text_info
->length
&&
1821 glyphs
[text_info
->length
- 1].hash_key
.italic
&&
1822 !render_priv
->state
.italic
) {
1823 int back
= text_info
->length
- 1;
1824 GlyphInfo
*og
= &glyphs
[back
];
1825 while (back
&& og
->bbox
.xMax
- og
->bbox
.xMin
== 0
1826 && og
->hash_key
.italic
)
1827 og
= &glyphs
[--back
];
1828 if (og
->bbox
.xMax
> og
->advance
.x
) {
1829 // The FreeType oblique slants by 6/16
1830 pen
.x
+= og
->bbox
.yMax
* 0.375;
1834 glyphs
[text_info
->length
].pos
.x
= pen
.x
;
1835 glyphs
[text_info
->length
].pos
.y
= pen
.y
;
1837 pen
.x
+= glyphs
[text_info
->length
].advance
.x
;
1838 pen
.x
+= double_to_d6(render_priv
->state
.hspacing
*
1839 render_priv
->font_scale
1840 * render_priv
->state
.scale_x
);
1841 pen
.y
+= glyphs
[text_info
->length
].advance
.y
;
1842 pen
.y
+= (render_priv
->state
.fay
* render_priv
->state
.scale_y
) *
1843 glyphs
[text_info
->length
].advance
.x
;
1847 glyphs
[text_info
->length
].symbol
= code
;
1848 glyphs
[text_info
->length
].linebreak
= 0;
1849 for (i
= 0; i
< 4; ++i
) {
1850 uint32_t clr
= render_priv
->state
.c
[i
];
1852 mult_alpha(_a(clr
), render_priv
->state
.fade
), 1.);
1853 glyphs
[text_info
->length
].c
[i
] = clr
;
1855 glyphs
[text_info
->length
].effect_type
= render_priv
->state
.effect_type
;
1856 glyphs
[text_info
->length
].effect_timing
=
1857 render_priv
->state
.effect_timing
;
1858 glyphs
[text_info
->length
].effect_skip_timing
=
1859 render_priv
->state
.effect_skip_timing
;
1860 glyphs
[text_info
->length
].be
= render_priv
->state
.be
;
1861 glyphs
[text_info
->length
].blur
= render_priv
->state
.blur
;
1862 glyphs
[text_info
->length
].shadow_x
= render_priv
->state
.shadow_x
;
1863 glyphs
[text_info
->length
].shadow_y
= render_priv
->state
.shadow_y
;
1864 glyphs
[text_info
->length
].frx
= render_priv
->state
.frx
;
1865 glyphs
[text_info
->length
].fry
= render_priv
->state
.fry
;
1866 glyphs
[text_info
->length
].frz
= render_priv
->state
.frz
;
1867 glyphs
[text_info
->length
].fax
= render_priv
->state
.fax
;
1868 glyphs
[text_info
->length
].fay
= render_priv
->state
.fay
;
1869 if (drawing
->hash
) {
1870 glyphs
[text_info
->length
].asc
= drawing
->asc
;
1871 glyphs
[text_info
->length
].desc
= drawing
->desc
;
1873 ass_font_get_asc_desc(render_priv
->state
.font
, code
,
1874 &glyphs
[text_info
->length
].asc
,
1875 &glyphs
[text_info
->length
].desc
);
1877 glyphs
[text_info
->length
].asc
*= render_priv
->state
.scale_y
;
1878 glyphs
[text_info
->length
].desc
*= render_priv
->state
.scale_y
;
1882 fill_bitmap_hash(render_priv
, &glyphs
[text_info
->length
].hash_key
,
1883 drawing
, pen
, code
);
1885 text_info
->length
++;
1887 render_priv
->state
.effect_type
= EF_NONE
;
1888 render_priv
->state
.effect_timing
= 0;
1889 render_priv
->state
.effect_skip_timing
= 0;
1891 if (drawing
->hash
) {
1892 ass_drawing_free(drawing
);
1893 drawing
= render_priv
->state
.drawing
=
1894 ass_drawing_new(render_priv
->fontconfig_priv
,
1895 render_priv
->state
.font
,
1896 render_priv
->ftlibrary
);
1901 if (text_info
->length
== 0) {
1902 // no valid symbols in the event; this can be smth like {comment}
1903 free_render_context(render_priv
);
1907 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
1908 process_karaoke_effects(render_priv
);
1911 alignment
= render_priv
->state
.alignment
;
1912 halign
= alignment
& 3;
1913 valign
= alignment
& 12;
1916 (event
->MarginL
) ? event
->MarginL
: render_priv
->state
.style
->MarginL
;
1918 (event
->MarginR
) ? event
->MarginR
: render_priv
->state
.style
->MarginR
;
1920 (event
->MarginV
) ? event
->MarginV
: render_priv
->state
.style
->MarginV
;
1922 if (render_priv
->state
.evt_type
!= EVENT_HSCROLL
) {
1923 double max_text_width
;
1925 // calculate max length of a line
1928 render_priv
->track
->PlayResX
- MarginR
) -
1929 x2scr(render_priv
, MarginL
);
1931 // rearrange text in several lines
1932 wrap_lines_smart(render_priv
, max_text_width
);
1936 for (i
= 1; i
< text_info
->length
+ 1; ++i
) { // (text_info->length + 1) is the end of the last line
1937 if ((i
== text_info
->length
)
1938 || glyphs
[i
].linebreak
) {
1939 double width
, shift
= 0;
1940 GlyphInfo
*first_glyph
=
1941 glyphs
+ last_break
+ 1;
1942 GlyphInfo
*last_glyph
= glyphs
+ i
- 1;
1944 while (first_glyph
< last_glyph
&& first_glyph
->skip
)
1947 while ((last_glyph
> first_glyph
)
1948 && ((last_glyph
->symbol
== '\n')
1949 || (last_glyph
->symbol
== 0)
1950 || (last_glyph
->skip
)))
1953 width
= d6_to_double(
1954 last_glyph
->pos
.x
+ last_glyph
->advance
.x
-
1955 first_glyph
->pos
.x
);
1956 if (halign
== HALIGN_LEFT
) { // left aligned, no action
1958 } else if (halign
== HALIGN_RIGHT
) { // right aligned
1959 shift
= max_text_width
- width
;
1960 } else if (halign
== HALIGN_CENTER
) { // centered
1961 shift
= (max_text_width
- width
) / 2.0;
1963 for (j
= last_break
+ 1; j
< i
; ++j
) {
1964 glyphs
[j
].pos
.x
+= double_to_d6(shift
);
1969 } else { // render_priv->state.evt_type == EVENT_HSCROLL
1970 measure_text(render_priv
);
1973 // determing text bounding box
1974 compute_string_bbox(text_info
, &bbox
);
1976 // determine device coordinates for text
1978 // x coordinate for everything except positioned events
1979 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
1980 render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
1981 device_x
= x2scr(render_priv
, MarginL
);
1982 } else if (render_priv
->state
.evt_type
== EVENT_HSCROLL
) {
1983 if (render_priv
->state
.scroll_direction
== SCROLL_RL
)
1986 render_priv
->track
->PlayResX
-
1987 render_priv
->state
.scroll_shift
);
1988 else if (render_priv
->state
.scroll_direction
== SCROLL_LR
)
1991 render_priv
->state
.scroll_shift
) - (bbox
.xMax
-
1995 // y coordinate for everything except positioned events
1996 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
1997 render_priv
->state
.evt_type
== EVENT_HSCROLL
) {
1998 if (valign
== VALIGN_TOP
) { // toptitle
2000 y2scr_top(render_priv
,
2001 MarginV
) + text_info
->lines
[0].asc
;
2002 } else if (valign
== VALIGN_CENTER
) { // midtitle
2004 y2scr(render_priv
, render_priv
->track
->PlayResY
/ 2.0);
2005 device_y
= scr_y
- (bbox
.yMax
+ bbox
.yMin
) / 2.0;
2006 } else { // subtitle
2008 if (valign
!= VALIGN_SUB
)
2009 ass_msg(render_priv
->library
, MSGL_V
,
2010 "Invalid valign, assuming 0 (subtitle)");
2012 y2scr_sub(render_priv
,
2013 render_priv
->track
->PlayResY
- MarginV
);
2015 device_y
-= text_info
->height
;
2016 device_y
+= text_info
->lines
[0].asc
;
2018 } else if (render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
2019 if (render_priv
->state
.scroll_direction
== SCROLL_TB
)
2022 render_priv
->state
.clip_y0
+
2023 render_priv
->state
.scroll_shift
) - (bbox
.yMax
-
2025 else if (render_priv
->state
.scroll_direction
== SCROLL_BT
)
2028 render_priv
->state
.clip_y1
-
2029 render_priv
->state
.scroll_shift
);
2032 // positioned events are totally different
2033 if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
2036 ass_msg(render_priv
->library
, MSGL_DBG2
, "positioned event at %f, %f",
2037 render_priv
->state
.pos_x
, render_priv
->state
.pos_y
);
2038 get_base_point(&bbox
, alignment
, &base_x
, &base_y
);
2040 x2scr_pos(render_priv
, render_priv
->state
.pos_x
) - base_x
;
2042 y2scr_pos(render_priv
, render_priv
->state
.pos_y
) - base_y
;
2045 // fix clip coordinates (they depend on alignment)
2046 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
2047 render_priv
->state
.evt_type
== EVENT_HSCROLL
||
2048 render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
2049 render_priv
->state
.clip_x0
=
2050 x2scr_scaled(render_priv
, render_priv
->state
.clip_x0
);
2051 render_priv
->state
.clip_x1
=
2052 x2scr_scaled(render_priv
, render_priv
->state
.clip_x1
);
2053 if (valign
== VALIGN_TOP
) {
2054 render_priv
->state
.clip_y0
=
2055 y2scr_top(render_priv
, render_priv
->state
.clip_y0
);
2056 render_priv
->state
.clip_y1
=
2057 y2scr_top(render_priv
, render_priv
->state
.clip_y1
);
2058 } else if (valign
== VALIGN_CENTER
) {
2059 render_priv
->state
.clip_y0
=
2060 y2scr(render_priv
, render_priv
->state
.clip_y0
);
2061 render_priv
->state
.clip_y1
=
2062 y2scr(render_priv
, render_priv
->state
.clip_y1
);
2063 } else if (valign
== VALIGN_SUB
) {
2064 render_priv
->state
.clip_y0
=
2065 y2scr_sub(render_priv
, render_priv
->state
.clip_y0
);
2066 render_priv
->state
.clip_y1
=
2067 y2scr_sub(render_priv
, render_priv
->state
.clip_y1
);
2069 } else if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
2070 render_priv
->state
.clip_x0
=
2071 x2scr_pos_scaled(render_priv
, render_priv
->state
.clip_x0
);
2072 render_priv
->state
.clip_x1
=
2073 x2scr_pos_scaled(render_priv
, render_priv
->state
.clip_x1
);
2074 render_priv
->state
.clip_y0
=
2075 y2scr_pos(render_priv
, render_priv
->state
.clip_y0
);
2076 render_priv
->state
.clip_y1
=
2077 y2scr_pos(render_priv
, render_priv
->state
.clip_y1
);
2080 // calculate rotation parameters
2084 if (render_priv
->state
.have_origin
) {
2085 center
.x
= x2scr(render_priv
, render_priv
->state
.org_x
);
2086 center
.y
= y2scr(render_priv
, render_priv
->state
.org_y
);
2088 double bx
= 0., by
= 0.;
2089 get_base_point(&bbox
, alignment
, &bx
, &by
);
2090 center
.x
= device_x
+ bx
;
2091 center
.y
= device_y
+ by
;
2094 for (i
= 0; i
< text_info
->length
; ++i
) {
2095 GlyphInfo
*info
= glyphs
+ i
;
2097 if (info
->hash_key
.frx
|| info
->hash_key
.fry
2098 || info
->hash_key
.frz
|| info
->hash_key
.fax
2099 || info
->hash_key
.fay
) {
2100 info
->hash_key
.shift_x
= info
->pos
.x
+ double_to_d6(device_x
- center
.x
);
2101 info
->hash_key
.shift_y
=
2102 -(info
->pos
.y
+ double_to_d6(device_y
- center
.y
));
2104 info
->hash_key
.shift_x
= 0;
2105 info
->hash_key
.shift_y
= 0;
2110 // convert glyphs to bitmaps
2111 device_x
*= render_priv
->font_scale_x
;
2112 for (i
= 0; i
< text_info
->length
; ++i
) {
2113 GlyphInfo
*g
= glyphs
+ i
;
2114 g
->pos
.x
*= render_priv
->font_scale_x
;
2115 g
->hash_key
.advance
.x
=
2116 double_to_d6(device_x
- (int) device_x
+
2117 d6_to_double(g
->pos
.x
& SUBPIXEL_MASK
)) & ~SUBPIXEL_ACCURACY
;
2118 g
->hash_key
.advance
.y
=
2119 double_to_d6(device_y
- (int) device_y
+
2120 d6_to_double(g
->pos
.y
& SUBPIXEL_MASK
)) & ~SUBPIXEL_ACCURACY
;
2121 get_bitmap_glyph(render_priv
, glyphs
+ i
);
2124 memset(event_images
, 0, sizeof(*event_images
));
2125 event_images
->top
= device_y
- text_info
->lines
[0].asc
;
2126 event_images
->height
= text_info
->height
;
2127 event_images
->left
=
2128 (device_x
+ bbox
.xMin
* render_priv
->font_scale_x
) + 0.5;
2129 event_images
->width
=
2130 (bbox
.xMax
- bbox
.xMin
) * render_priv
->font_scale_x
+ 0.5;
2131 event_images
->detect_collisions
= render_priv
->state
.detect_collisions
;
2132 event_images
->shift_direction
= (valign
== VALIGN_TOP
) ? 1 : -1;
2133 event_images
->event
= event
;
2134 event_images
->imgs
= render_text(render_priv
, (int) device_x
, (int) device_y
);
2136 free_render_context(render_priv
);
2142 * \brief deallocate image list
2143 * \param img list pointer
2145 void ass_free_images(ASS_Image
*img
)
2148 ASS_Image
*next
= img
->next
;
2155 * \brief Check cache limits and reset cache if they are exceeded
2157 static void check_cache_limits(ASS_Renderer
*priv
, CacheStore
*cache
)
2159 if (cache
->bitmap_cache
->cache_size
> cache
->bitmap_max_size
) {
2160 ass_msg(priv
->library
, MSGL_V
,
2161 "Hitting hard bitmap cache limit (was: %ld bytes), "
2162 "resetting.", (long) cache
->bitmap_cache
->cache_size
);
2163 cache
->bitmap_cache
= ass_bitmap_cache_reset(cache
->bitmap_cache
);
2164 cache
->composite_cache
= ass_composite_cache_reset(
2165 cache
->composite_cache
);
2166 ass_free_images(priv
->prev_images_root
);
2167 priv
->prev_images_root
= 0;
2170 if (cache
->glyph_cache
->count
> cache
->glyph_max
2171 || cache
->glyph_cache
->cache_size
> cache
->bitmap_max_size
) {
2172 ass_msg(priv
->library
, MSGL_V
,
2173 "Hitting hard glyph cache limit (was: %d glyphs, %ld bytes), "
2175 cache
->glyph_cache
->count
, (long) cache
->glyph_cache
->cache_size
);
2176 cache
->glyph_cache
= ass_glyph_cache_reset(cache
->glyph_cache
);
2181 * \brief Start a new frame
2184 ass_start_frame(ASS_Renderer
*render_priv
, ASS_Track
*track
,
2187 ASS_Settings
*settings_priv
= &render_priv
->settings
;
2189 if (!render_priv
->settings
.frame_width
2190 && !render_priv
->settings
.frame_height
)
2191 return 1; // library not initialized
2193 if (render_priv
->library
!= track
->library
)
2196 if (!render_priv
->fontconfig_priv
)
2199 free_list_clear(render_priv
);
2201 if (track
->n_events
== 0)
2202 return 1; // nothing to do
2204 render_priv
->track
= track
;
2205 render_priv
->time
= now
;
2207 ass_lazy_track_init(render_priv
);
2209 render_priv
->font_scale
= settings_priv
->font_size_coeff
*
2210 render_priv
->orig_height
/ render_priv
->track
->PlayResY
;
2211 if (render_priv
->track
->ScaledBorderAndShadow
)
2212 render_priv
->border_scale
=
2213 ((double) render_priv
->orig_height
) /
2214 render_priv
->track
->PlayResY
;
2216 render_priv
->border_scale
= 1.;
2219 render_priv
->font_scale_x
= render_priv
->settings
.aspect
/
2220 render_priv
->settings
.storage_aspect
;
2222 render_priv
->prev_images_root
= render_priv
->images_root
;
2223 render_priv
->images_root
= 0;
2225 check_cache_limits(render_priv
, &render_priv
->cache
);
2230 static int cmp_event_layer(const void *p1
, const void *p2
)
2232 ASS_Event
*e1
= ((EventImages
*) p1
)->event
;
2233 ASS_Event
*e2
= ((EventImages
*) p2
)->event
;
2234 if (e1
->Layer
< e2
->Layer
)
2236 if (e1
->Layer
> e2
->Layer
)
2238 if (e1
->ReadOrder
< e2
->ReadOrder
)
2240 if (e1
->ReadOrder
> e2
->ReadOrder
)
2245 static ASS_RenderPriv
*get_render_priv(ASS_Renderer
*render_priv
,
2248 if (!event
->render_priv
)
2249 event
->render_priv
= calloc(1, sizeof(ASS_RenderPriv
));
2250 if (render_priv
->render_id
!= event
->render_priv
->render_id
) {
2251 memset(event
->render_priv
, 0, sizeof(ASS_RenderPriv
));
2252 event
->render_priv
->render_id
= render_priv
->render_id
;
2255 return event
->render_priv
;
2258 static int overlap(Segment
*s1
, Segment
*s2
)
2260 if (s1
->a
>= s2
->b
|| s2
->a
>= s1
->b
||
2261 s1
->ha
>= s2
->hb
|| s2
->ha
>= s1
->hb
)
2266 static int cmp_segment(const void *p1
, const void *p2
)
2268 return ((Segment
*) p1
)->a
- ((Segment
*) p2
)->a
;
2272 shift_event(ASS_Renderer
*render_priv
, EventImages
*ei
, int shift
)
2274 ASS_Image
*cur
= ei
->imgs
;
2276 cur
->dst_y
+= shift
;
2277 // clip top and bottom
2278 if (cur
->dst_y
< 0) {
2279 int clip
= -cur
->dst_y
;
2281 cur
->bitmap
+= clip
* cur
->stride
;
2284 if (cur
->dst_y
+ cur
->h
>= render_priv
->height
) {
2285 int clip
= cur
->dst_y
+ cur
->h
- render_priv
->height
;
2297 // dir: 1 - move down
2299 static int fit_segment(Segment
*s
, Segment
*fixed
, int *cnt
, int dir
)
2304 if (dir
== 1) // move down
2305 for (i
= 0; i
< *cnt
; ++i
) {
2306 if (s
->b
+ shift
<= fixed
[i
].a
|| s
->a
+ shift
>= fixed
[i
].b
||
2307 s
->hb
<= fixed
[i
].ha
|| s
->ha
>= fixed
[i
].hb
)
2309 shift
= fixed
[i
].b
- s
->a
;
2310 } else // dir == -1, move up
2311 for (i
= *cnt
- 1; i
>= 0; --i
) {
2312 if (s
->b
+ shift
<= fixed
[i
].a
|| s
->a
+ shift
>= fixed
[i
].b
||
2313 s
->hb
<= fixed
[i
].ha
|| s
->ha
>= fixed
[i
].hb
)
2315 shift
= fixed
[i
].a
- s
->b
;
2318 fixed
[*cnt
].a
= s
->a
+ shift
;
2319 fixed
[*cnt
].b
= s
->b
+ shift
;
2320 fixed
[*cnt
].ha
= s
->ha
;
2321 fixed
[*cnt
].hb
= s
->hb
;
2323 qsort(fixed
, *cnt
, sizeof(Segment
), cmp_segment
);
2329 fix_collisions(ASS_Renderer
*render_priv
, EventImages
*imgs
, int cnt
)
2331 Segment
*used
= malloc(cnt
* sizeof(*used
));
2335 // fill used[] with fixed events
2336 for (i
= 0; i
< cnt
; ++i
) {
2337 ASS_RenderPriv
*priv
;
2338 if (!imgs
[i
].detect_collisions
)
2340 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2341 if (priv
->height
> 0) { // it's a fixed event
2344 s
.b
= priv
->top
+ priv
->height
;
2346 s
.hb
= priv
->left
+ priv
->width
;
2347 if (priv
->height
!= imgs
[i
].height
) { // no, it's not
2348 ass_msg(render_priv
->library
, MSGL_WARN
,
2349 "Event height has changed");
2355 for (j
= 0; j
< cnt_used
; ++j
)
2356 if (overlap(&s
, used
+ j
)) { // no, it's not
2362 if (priv
->height
> 0) { // still a fixed event
2363 used
[cnt_used
].a
= priv
->top
;
2364 used
[cnt_used
].b
= priv
->top
+ priv
->height
;
2365 used
[cnt_used
].ha
= priv
->left
;
2366 used
[cnt_used
].hb
= priv
->left
+ priv
->width
;
2368 shift_event(render_priv
, imgs
+ i
, priv
->top
- imgs
[i
].top
);
2372 qsort(used
, cnt_used
, sizeof(Segment
), cmp_segment
);
2374 // try to fit other events in free spaces
2375 for (i
= 0; i
< cnt
; ++i
) {
2376 ASS_RenderPriv
*priv
;
2377 if (!imgs
[i
].detect_collisions
)
2379 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2380 if (priv
->height
== 0) { // not a fixed event
2384 s
.b
= imgs
[i
].top
+ imgs
[i
].height
;
2385 s
.ha
= imgs
[i
].left
;
2386 s
.hb
= imgs
[i
].left
+ imgs
[i
].width
;
2387 shift
= fit_segment(&s
, used
, &cnt_used
, imgs
[i
].shift_direction
);
2389 shift_event(render_priv
, imgs
+ i
, shift
);
2391 priv
->top
= imgs
[i
].top
;
2392 priv
->height
= imgs
[i
].height
;
2393 priv
->left
= imgs
[i
].left
;
2394 priv
->width
= imgs
[i
].width
;
2403 * \brief compare two images
2404 * \param i1 first image
2405 * \param i2 second image
2406 * \return 0 if identical, 1 if different positions, 2 if different content
2408 static int ass_image_compare(ASS_Image
*i1
, ASS_Image
*i2
)
2414 if (i1
->stride
!= i2
->stride
)
2416 if (i1
->color
!= i2
->color
)
2418 if (i1
->bitmap
!= i2
->bitmap
)
2420 if (i1
->dst_x
!= i2
->dst_x
)
2422 if (i1
->dst_y
!= i2
->dst_y
)
2428 * \brief compare current and previous image list
2429 * \param priv library handle
2430 * \return 0 if identical, 1 if different positions, 2 if different content
2432 static int ass_detect_change(ASS_Renderer
*priv
)
2434 ASS_Image
*img
, *img2
;
2437 img
= priv
->prev_images_root
;
2438 img2
= priv
->images_root
;
2440 while (img
&& diff
< 2) {
2441 ASS_Image
*next
, *next2
;
2444 int d
= ass_image_compare(img
, img2
);
2449 // previous list is shorter
2457 // is the previous list longer?
2465 * \brief render a frame
2466 * \param priv library handle
2467 * \param track track
2468 * \param now current video timestamp (ms)
2469 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
2470 * 0 if identical, 1 if different positions, 2 if different content.
2471 * Can be NULL, in that case no detection is performed.
2473 ASS_Image
*ass_render_frame(ASS_Renderer
*priv
, ASS_Track
*track
,
2474 long long now
, int *detect_change
)
2481 rc
= ass_start_frame(priv
, track
, now
);
2485 // render events separately
2487 for (i
= 0; i
< track
->n_events
; ++i
) {
2488 ASS_Event
*event
= track
->events
+ i
;
2489 if ((event
->Start
<= now
)
2490 && (now
< (event
->Start
+ event
->Duration
))) {
2491 if (cnt
>= priv
->eimg_size
) {
2492 priv
->eimg_size
+= 100;
2495 priv
->eimg_size
* sizeof(EventImages
));
2497 rc
= ass_render_event(priv
, event
, priv
->eimg
+ cnt
);
2504 qsort(priv
->eimg
, cnt
, sizeof(EventImages
), cmp_event_layer
);
2506 // call fix_collisions for each group of events with the same layer
2508 for (i
= 1; i
< cnt
; ++i
)
2509 if (last
->event
->Layer
!= priv
->eimg
[i
].event
->Layer
) {
2510 fix_collisions(priv
, last
, priv
->eimg
+ i
- last
);
2511 last
= priv
->eimg
+ i
;
2514 fix_collisions(priv
, last
, priv
->eimg
+ cnt
- last
);
2517 tail
= &priv
->images_root
;
2518 for (i
= 0; i
< cnt
; ++i
) {
2519 ASS_Image
*cur
= priv
->eimg
[i
].imgs
;
2528 *detect_change
= ass_detect_change(priv
);
2530 // free the previous image list
2531 ass_free_images(priv
->prev_images_root
);
2532 priv
->prev_images_root
= 0;
2534 return priv
->images_root
;