From: 11rcombs Date: Sun, 26 Jan 2014 01:06:12 +0000 (-0600) Subject: Combine bitmaps before applying blur and shadow X-Git-Tag: 0.11.0~28 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5dd56af2f97419ce5a72d1dab2e9c19b34c1dd57;p=libass Combine bitmaps before applying blur and shadow --- diff --git a/libass/ass_bitmap.c b/libass/ass_bitmap.c index dcc2f2b..ad00df2 100644 --- a/libass/ass_bitmap.c +++ b/libass/ass_bitmap.c @@ -27,24 +27,11 @@ #include "ass_utils.h" #include "ass_bitmap.h" - -struct ass_synth_priv { - int tmp_w, tmp_h; - unsigned *tmp; - - int g_r; - int g_w; - - double *g0; - unsigned *g; - unsigned *gt2; - - double radius; -}; +#include "ass_render.h" static const unsigned base = 256; -static int generate_tables(ASS_SynthPriv *priv, double radius) +int generate_tables(ASS_SynthPriv *priv, double radius) { double A = log(1.0 / base) / (radius * radius * 2); int mx, i; @@ -103,7 +90,7 @@ static int generate_tables(ASS_SynthPriv *priv, double radius) return 0; } -static void resize_tmp(ASS_SynthPriv *priv, int w, int h) +void resize_tmp(ASS_SynthPriv *priv, int w, int h) { if (priv->tmp_w >= w && priv->tmp_h >= h) return; @@ -135,12 +122,17 @@ void ass_synth_done(ASS_SynthPriv *priv) free(priv); } -static Bitmap *alloc_bitmap(int w, int h) +Bitmap *alloc_bitmap(int w, int h) { Bitmap *bm; - unsigned s = w; // XXX: alignment + + uintptr_t alignment_offset = (w > 31) ? 31 : ((w > 15) ? 15 : 0); + unsigned s = (w + alignment_offset) & ~alignment_offset; bm = malloc(sizeof(Bitmap)); - bm->buffer = calloc(s, h); + bm->buffer_ptr = malloc(s * h + alignment_offset + 32); + bm->buffer = (unsigned char*) + (((uintptr_t)bm->buffer_ptr + alignment_offset) & ~alignment_offset); + memset(bm->buffer, 0, s * h + 32); bm->w = w; bm->h = h; bm->stride = s; @@ -151,11 +143,11 @@ static Bitmap *alloc_bitmap(int w, int h) void ass_free_bitmap(Bitmap *bm) { if (bm) - free(bm->buffer); + free(bm->buffer_ptr); free(bm); } -static Bitmap *copy_bitmap(const Bitmap *src) +Bitmap *copy_bitmap(const Bitmap *src) { Bitmap *dst = alloc_bitmap(src->w, src->h); dst->left = src->left; @@ -220,7 +212,7 @@ Bitmap *outline_to_bitmap(ASS_Library *library, FT_Library ftlib, * The glyph bitmap is subtracted from outline bitmap. This way looks much * better in some cases. */ -static void fix_outline(Bitmap *bm_g, Bitmap *bm_o) +void fix_outline(Bitmap *bm_g, Bitmap *bm_o) { int x, y; const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left; @@ -253,7 +245,7 @@ static void fix_outline(Bitmap *bm_g, Bitmap *bm_o) * \brief Shift a bitmap by the fraction of a pixel in x and y direction * expressed in 26.6 fixed point */ -static void shift_bitmap(Bitmap *bm, int shift_x, int shift_y) +void shift_bitmap(Bitmap *bm, int shift_x, int shift_y) { int x, y, b; int w = bm->w; @@ -305,9 +297,9 @@ static void shift_bitmap(Bitmap *bm, int shift_x, int shift_y) /* * Gaussian blur. An fast pure C implementation from MPlayer. */ -static void ass_gauss_blur(unsigned char *buffer, unsigned *tmp2, - int width, int height, int stride, - unsigned *m2, int r, int mwidth) +void ass_gauss_blur(unsigned char *buffer, unsigned *tmp2, + int width, int height, int stride, + unsigned *m2, int r, int mwidth) { int x, y; @@ -427,36 +419,72 @@ static void ass_gauss_blur(unsigned char *buffer, unsigned *tmp2, /** * \brief Blur with [[1,2,1]. [2,4,2], [1,2,1]] kernel * This blur is the same as the one employed by vsfilter. + * Pure C implementation. */ -static void be_blur(Bitmap *bm) +void be_blur_c(uint8_t *buf, intptr_t w, + intptr_t h, intptr_t stride, + uint16_t *tmp) { - int w = bm->w; - int h = bm->h; - int s = bm->stride; - unsigned char *buf = bm->buffer; - unsigned int x, y; - unsigned int old_sum, new_sum; - - for (y = 0; y < h; y++) { - old_sum = 2 * buf[y * s]; - for (x = 0; x < w - 1; x++) { - new_sum = buf[y * s + x] + buf[y * s + x + 1]; - buf[y * s + x] = (old_sum + new_sum) >> 2; - old_sum = new_sum; + unsigned short *col_pix_buf = tmp; + unsigned short *col_sum_buf = tmp + w * sizeof(unsigned short); + unsigned x, y, old_pix, old_sum, new_sum, temp1, temp2; + unsigned char *src, *dst; + memset(col_pix_buf, 0, w * sizeof(unsigned short)); + memset(col_sum_buf, 0, w * sizeof(unsigned short)); + { + y = 0; + src=buf+y*stride; + + x = 2; + old_pix = src[x-1]; + old_sum = old_pix + src[x-2]; + for ( ; x < w; x++) { + temp1 = src[x]; + temp2 = old_pix + temp1; + old_pix = temp1; + temp1 = old_sum + temp2; + old_sum = temp2; + col_pix_buf[x] = temp1; + } + } + new_sum = 2 * buf[y * stride + w - 1]; + buf[y * stride + w - 1] = (old_sum + new_sum) >> 2; + { + x = 2; + old_pix = src[x-1]; + old_sum = old_pix + src[x-2]; + for ( ; x < w; x++) { + temp1 = src[x]; + temp2 = old_pix + temp1; + old_pix = temp1; + temp1 = old_sum + temp2; + old_sum = temp2; + + temp2 = col_pix_buf[x] + temp1; + col_pix_buf[x] = temp1; + col_sum_buf[x] = temp2; } - new_sum = 2 * buf[y * s + w - 1]; - buf[y * s + w - 1] = (old_sum + new_sum) >> 2; } - for (x = 0; x < w; x++) { - old_sum = 2 * buf[x]; - for (y = 0; y < h - 1; y++) { - new_sum = buf[y * s + x] + buf[(y + 1) * s + x]; - buf[y * s + x] = (old_sum + new_sum) >> 2; - old_sum = new_sum; + for (y = 2; y < h; y++) { + src=buf+y*stride; + dst=buf+(y-1)*stride; + + x = 2; + old_pix = src[x-1]; + old_sum = old_pix + src[x-2]; + for ( ; x < w; x++) { + temp1 = src[x]; + temp2 = old_pix + temp1; + old_pix = temp1; + temp1 = old_sum + temp2; + old_sum = temp2; + + temp2 = col_pix_buf[x] + temp1; + col_pix_buf[x] = temp1; + dst[x-1] = (col_sum_buf[x] + temp2) >> 4; + col_sum_buf[x] = temp2; } - new_sum = 2 * buf[(h - 1) * s + x]; - buf[(h - 1) * s + x] = (old_sum + new_sum) >> 2; } } @@ -489,46 +517,69 @@ int outline_to_bitmap3(ASS_Library *library, ASS_SynthPriv *priv_blur, } } - // Apply box blur (multiple passes, if requested) - while (be--) { - if (*bm_o) - be_blur(*bm_o); - if (!*bm_o || border_style == 3) - be_blur(*bm_g); - } + return 0; +} - // Apply gaussian blur - if (blur_radius > 0.0) { - if (*bm_o) - resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h); - if (!*bm_o || border_style == 3) - resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h); - generate_tables(priv_blur, blur_radius); - if (*bm_o) - ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp, - (*bm_o)->w, (*bm_o)->h, (*bm_o)->stride, - priv_blur->gt2, priv_blur->g_r, priv_blur->g_w); - if (!*bm_o || border_style == 3) - ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp, - (*bm_g)->w, (*bm_g)->h, (*bm_g)->stride, - priv_blur->gt2, priv_blur->g_r, priv_blur->g_w); +/** + * \brief Add two bitmaps together at a given position + * Uses additive blending, clipped to [0,255]. Pure C implementation. + */ +void add_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t height, intptr_t width) +{ + unsigned out; + uint8_t* end = dst + dst_stride * height; + while (dst < end) { + for (unsigned j = 0; j < width; ++j) { + out = dst[j] + src[j]; + dst[j] = FFMIN(out, 255); + } + dst += dst_stride; + src += src_stride; } +} - // Create shadow and fix outline as needed - if (*bm_o && border_style != 3) { - *bm_s = copy_bitmap(*bm_o); - fix_outline(*bm_g, *bm_o); - } else if (*bm_o && border_visible) { - *bm_s = copy_bitmap(*bm_o); - } else if (*bm_o) { - *bm_s = *bm_o; - *bm_o = 0; - } else - *bm_s = copy_bitmap(*bm_g); - - assert(bm_s); +void sub_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t height, intptr_t width) +{ + unsigned out; + uint8_t* end = dst + dst_stride * height; + while (dst < end) { + for (unsigned j = 0; j < width; ++j) { + out = dst[j] - src[j]; + dst[j] = FFMAX(out, 0); + } + dst += dst_stride; + src += src_stride; + } +} - shift_bitmap(*bm_s, shadow_offset.x, shadow_offset.y); +void restride_bitmap_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t width, intptr_t height) +{ + uint8_t* end = dst + dst_stride * height; + while (dst < end) { + memcpy(dst, src, width); + dst += dst_stride; + src += src_stride; + } +} - return 0; +void mul_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src1, intptr_t src1_stride, + uint8_t *src2, intptr_t src2_stride, + intptr_t w, intptr_t h) +{ + uint8_t* end = src1 + src1_stride * h; + while (src1 < end) { + for (unsigned x = 0; x < w; ++x) { + dst[x] = (src1[x] * src2[x] + 255) >> 8; + } + dst += dst_stride; + src1 += src1_stride; + src2 += src2_stride; + } } diff --git a/libass/ass_bitmap.h b/libass/ass_bitmap.h index 53be7af..b51c1bf 100644 --- a/libass/ass_bitmap.h +++ b/libass/ass_bitmap.h @@ -24,7 +24,19 @@ #include "ass.h" -typedef struct ass_synth_priv ASS_SynthPriv; +typedef struct ass_synth_priv { + int tmp_w, tmp_h; + unsigned *tmp; + + int g_r; + int g_w; + + double *g0; + unsigned *g; + unsigned *gt2; + + double radius; +} ASS_SynthPriv; ASS_SynthPriv *ass_synth_init(double); void ass_synth_done(ASS_SynthPriv *priv); @@ -33,11 +45,14 @@ typedef struct { int left, top; int w, h; // width, height int stride; - unsigned char *buffer; // w x h buffer + unsigned char *buffer; // h * stride buffer + unsigned char *buffer_ptr; // unaligned pointer (for free()) } Bitmap; Bitmap *outline_to_bitmap(ASS_Library *library, FT_Library ftlib, FT_Outline *outline, int bord); + +Bitmap *alloc_bitmap(int w, int h); /** * \brief perform glyph rendering * \param glyph original glyph @@ -55,5 +70,29 @@ int outline_to_bitmap3(ASS_Library *library, ASS_SynthPriv *priv_blur, int border_style, int border_visible); void ass_free_bitmap(Bitmap *bm); +void ass_gauss_blur(unsigned char *buffer, unsigned *tmp2, + int width, int height, int stride, + unsigned *m2, int r, int mwidth); +void be_blur_c(uint8_t *buf, intptr_t w, + intptr_t h, intptr_t stride, + uint16_t *tmp); +void add_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t height, intptr_t width); +void sub_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t height, intptr_t width); +void restride_bitmap_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t width, intptr_t height); +void mul_bitmaps_c(uint8_t *dst, intptr_t dst_stride, + uint8_t *src1, intptr_t src1_stride, + uint8_t *src2, intptr_t src2_stride, + intptr_t w, intptr_t h); +void shift_bitmap(Bitmap *bm, int shift_x, int shift_y); +void fix_outline(Bitmap *bm_g, Bitmap *bm_o); +void resize_tmp(ASS_SynthPriv *priv, int w, int h); +int generate_tables(ASS_SynthPriv *priv, double radius); +Bitmap *copy_bitmap(const Bitmap *src); #endif /* LIBASS_BITMAP_H */ diff --git a/libass/ass_cache.c b/libass/ass_cache.c index 8234e5f..6baa924 100644 --- a/libass/ass_cache.c +++ b/libass/ass_cache.c @@ -125,12 +125,28 @@ static unsigned bitmap_compare (void *a, void *b, size_t key_size) static void composite_destruct(void *key, void *value) { CompositeHashValue *v = value; - free(v->a); - free(v->b); + CompositeHashKey *k = key; + if (v->bm) + ass_free_bitmap(v->bm); + if (v->bm_o) + ass_free_bitmap(v->bm_o); + if (v->bm_s) + ass_free_bitmap(v->bm_s); + free(k->str); free(key); free(value); } +static size_t composite_size(void *value, size_t value_size) +{ + CompositeHashValue *val = value; + if (val->bm_o) + return val->bm_o->w * val->bm_o->h * 3; + else if (val->bm) + return val->bm->w * val->bm->h * 3; + return 0; +} + // outline cache static unsigned outline_hash(void *key, size_t key_size) @@ -349,6 +365,6 @@ Cache *ass_bitmap_cache_create(void) Cache *ass_composite_cache_create(void) { return ass_cache_create(composite_hash, composite_compare, - composite_destruct, (ItemSize)NULL, sizeof(CompositeHashKey), + composite_destruct, composite_size, sizeof(CompositeHashKey), sizeof(CompositeHashValue)); } diff --git a/libass/ass_cache.h b/libass/ass_cache.h index 7375f04..677b705 100644 --- a/libass/ass_cache.h +++ b/libass/ass_cache.h @@ -35,8 +35,10 @@ typedef struct { } BitmapHashValue; typedef struct { - unsigned char *a; - unsigned char *b; + Bitmap *bm; + Bitmap *bm_o; + Bitmap *bm_s; + FT_Vector pos; } CompositeHashValue; typedef struct { diff --git a/libass/ass_cache_template.h b/libass/ass_cache_template.h index 3d8185f..8f7f2af 100644 --- a/libass/ass_cache_template.h +++ b/libass/ass_cache_template.h @@ -123,21 +123,36 @@ END(DrawingHashKey) // Cache for composited bitmaps START(composite, composite_hash_key) - GENERIC(int, aw) - GENERIC(int, ah) - GENERIC(int, bw) - GENERIC(int, bh) - GENERIC(int, ax) - GENERIC(int, ay) - GENERIC(int, bx) - GENERIC(int, by) - GENERIC(int, as) - GENERIC(int, bs) - GENERIC(unsigned char *, a) - GENERIC(unsigned char *, b) + GENERIC(unsigned, w) + GENERIC(unsigned, h) + GENERIC(unsigned, o_w) + GENERIC(unsigned, o_h) + GENERIC(int, is_drawing) + GENERIC(unsigned, chars) + GENERIC(int, be) + GENERIC(double, blur) + GENERIC(int, border_style) + GENERIC(int, has_border) + GENERIC(double, border_x) + GENERIC(double, border_y) + GENERIC(double, shadow_x) + GENERIC(double, shadow_y) + GENERIC(double, frx) + GENERIC(double, fry) + GENERIC(double, frz) + GENERIC(double, fax) + GENERIC(double, fay) + GENERIC(double, scale_x) + GENERIC(double, scale_y) + GENERIC(double, hspacing) + GENERIC(unsigned, italic) + GENERIC(unsigned, bold) + GENERIC(int, flags) + GENERIC(unsigned, has_outline) + FTVECTOR(advance) + STRING(str) END(CompositeHashKey) - #undef START #undef GENERIC #undef STRING diff --git a/libass/ass_parse.c b/libass/ass_parse.c index c3292c9..fe40e69 100644 --- a/libass/ass_parse.c +++ b/libass/ass_parse.c @@ -983,6 +983,7 @@ void process_karaoke_effects(ASS_Renderer *render_priv) cur2->effect_type = s1->effect_type; cur2->effect_timing = x - d6_to_int(cur2->pos.x); } + s1->effect = 1; } } } diff --git a/libass/ass_render.c b/libass/ass_render.c index 12f5701..e6e6052 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -20,6 +20,7 @@ #include #include +#include #include "ass_render.h" #include "ass_parse.h" @@ -27,9 +28,12 @@ #define MAX_GLYPHS_INITIAL 1024 #define MAX_LINES_INITIAL 64 +#define MAX_BITMAPS_INITIAL 16 +#define MAX_STR_LENGTH_INITIAL 64 #define SUBPIXEL_MASK 63 #define SUBPIXEL_ACCURACY 7 + ASS_Renderer *ass_renderer_init(ASS_Library *library) { int error; @@ -59,15 +63,25 @@ ASS_Renderer *ass_renderer_init(ASS_Library *library) priv->ftlibrary = ft; // images_root and related stuff is zero-filled in calloc + priv->add_bitmaps_func = add_bitmaps_c; + priv->sub_bitmaps_func = sub_bitmaps_c; + priv->mul_bitmaps_func = mul_bitmaps_c; + priv->be_blur_func = be_blur_c; + priv->restride_bitmap_func = restride_bitmap_c; + priv->cache.font_cache = ass_font_cache_create(); priv->cache.bitmap_cache = ass_bitmap_cache_create(); priv->cache.composite_cache = ass_composite_cache_create(); priv->cache.outline_cache = ass_outline_cache_create(); priv->cache.glyph_max = GLYPH_CACHE_MAX; priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; + priv->cache.composite_max_size = COMPOSITE_CACHE_MAX_SIZE; + priv->text_info.max_bitmaps = MAX_BITMAPS_INITIAL; priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; priv->text_info.max_lines = MAX_LINES_INITIAL; + priv->text_info.n_bitmaps = 0; + priv->text_info.combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo)); priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); @@ -129,6 +143,8 @@ void ass_renderer_done(ASS_Renderer *render_priv) free(render_priv->text_info.glyphs); free(render_priv->text_info.lines); + free(render_priv->text_info.combined_bitmaps); + free(render_priv->settings.default_font); free(render_priv->settings.default_family); @@ -415,109 +431,6 @@ render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y, return tail; } -/** - * \brief Replace the bitmap buffer in ASS_Image with a copy - * \param img ASS_Image to operate on - * \return pointer to old bitmap buffer - */ -static unsigned char *clone_bitmap_buffer(ASS_Image *img) -{ - unsigned char *old_bitmap = img->bitmap; - int size = img->stride * (img->h - 1) + img->w; - img->bitmap = malloc(size); - memcpy(img->bitmap, old_bitmap, size); - return old_bitmap; -} - -/** - * \brief Calculate overlapping area of two consecutive bitmaps and in case they - * overlap, blend them together - * Mainly useful for translucent glyphs and especially borders, to avoid the - * luminance adding up where they overlap (which looks ugly) - */ -static void -render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail, - ASS_Image **tail) -{ - int left, top, bottom, right; - int old_left, old_top, w, h, cur_left, cur_top; - int x, y, opos, cpos; - char m; - CompositeHashKey hk; - CompositeHashValue *hv; - CompositeHashValue chv; - int ax = (*last_tail)->dst_x; - int ay = (*last_tail)->dst_y; - int aw = (*last_tail)->w; - int as = (*last_tail)->stride; - int ah = (*last_tail)->h; - int bx = (*tail)->dst_x; - int by = (*tail)->dst_y; - int bw = (*tail)->w; - int bs = (*tail)->stride; - int bh = (*tail)->h; - unsigned char *a; - unsigned char *b; - - if ((*last_tail)->bitmap == (*tail)->bitmap) - return; - - if ((*last_tail)->color != (*tail)->color) - return; - - // Calculate overlap coordinates - left = (ax > bx) ? ax : bx; - top = (ay > by) ? ay : by; - right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); - bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); - if ((right <= left) || (bottom <= top)) - return; - old_left = left - ax; - old_top = top - ay; - w = right - left; - h = bottom - top; - cur_left = left - bx; - cur_top = top - by; - - // Query cache - hk.a = (*last_tail)->bitmap; - hk.b = (*tail)->bitmap; - hk.aw = aw; - hk.ah = ah; - hk.bw = bw; - hk.bh = bh; - hk.ax = ax; - hk.ay = ay; - hk.bx = bx; - hk.by = by; - hk.as = as; - hk.bs = bs; - hv = ass_cache_get(render_priv->cache.composite_cache, &hk); - if (hv) { - (*last_tail)->bitmap = hv->a; - (*tail)->bitmap = hv->b; - return; - } - // Allocate new bitmaps and copy over data - a = clone_bitmap_buffer(*last_tail); - b = clone_bitmap_buffer(*tail); - - // Blend overlapping area - for (y = 0; y < h; y++) - for (x = 0; x < w; x++) { - opos = (old_top + y) * (as) + (old_left + x); - cpos = (cur_top + y) * (bs) + (cur_left + x); - m = FFMIN(a[opos] + b[cpos], 0xff); - (*last_tail)->bitmap[opos] = 0; - (*tail)->bitmap[cpos] = m; - } - - // Insert bitmaps into the cache - chv.a = (*last_tail)->bitmap; - chv.b = (*tail)->bitmap; - ass_cache_put(render_priv->cache.composite_cache, &hk, &chv); -} - static void free_list_add(ASS_Renderer *render_priv, void *object) { if (!render_priv->free_head) { @@ -594,7 +507,7 @@ blend_vector_error: // Iterate through bitmaps and blend/clip them for (cur = head; cur; cur = cur->next) { - int left, top, right, bottom, apos, bpos, y, x, w, h; + int left, top, right, bottom, w, h; int ax, ay, aw, ah, as; int bx, by, bw, bh, bs; int aleft, atop, bleft, btop; @@ -628,43 +541,48 @@ blend_vector_error: if (render_priv->state.clip_drawing_mode) { // Inverse clip if (ax + aw < bx || ay + ah < by || ax > bx + bw || - ay > by + bh) { + ay > by + bh || !h || !w) { continue; } // Allocate new buffer and add to free list - nbuffer = malloc(as * ah); + nbuffer = malloc(as * ah + 0x1F); if (!nbuffer) goto blend_vector_exit; free_list_add(render_priv, nbuffer); + nbuffer = (unsigned char*)(((uintptr_t)nbuffer + 0x1F) & ~0x1F); // Blend together - memcpy(nbuffer, abuffer, as * (ah - 1) + aw); - for (y = 0; y < h; y++) - for (x = 0; x < w; x++) { - apos = (atop + y) * as + aleft + x; - bpos = (btop + y) * bs + bleft + x; - nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); - } + memcpy(nbuffer, abuffer, ((ah - 1) * as) + aw); + render_priv->sub_bitmaps_func(nbuffer + atop * as + aleft, as, + bbuffer + btop * bs + bleft, bs, + h, w); } else { // Regular clip if (ax + aw < bx || ay + ah < by || ax > bx + bw || - ay > by + bh) { - cur->w = cur->h = 0; + ay > by + bh || !h || !w) { + cur->w = cur->h = cur->stride = 0; continue; } // Allocate new buffer and add to free list - nbuffer = calloc(as, ah); + uintptr_t alignment_offset = (w > 15) ? 15 : ((w > 7) ? 7 : 0); + unsigned ns = (w + alignment_offset) & ~alignment_offset; + nbuffer = malloc(ns * h + alignment_offset); if (!nbuffer) goto blend_vector_exit; free_list_add(render_priv, nbuffer); + nbuffer = (unsigned char*) + (((uintptr_t)nbuffer + alignment_offset) & ~alignment_offset); // Blend together - for (y = 0; y < h; y++) - for (x = 0; x < w; x++) { - apos = (atop + y) * as + aleft + x; - bpos = (btop + y) * bs + bleft + x; - nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; - } + render_priv->mul_bitmaps_func(nbuffer, ns, + abuffer + atop * as + aleft, as, + bbuffer + btop * bs + bleft, bs, + w, h); + cur->dst_x += aleft; + cur->dst_y += atop; + cur->w = w; + cur->h = h; + cur->stride = ns; } cur->bitmap = nbuffer; } @@ -674,8 +592,10 @@ blend_vector_exit: render_priv->state.clip_drawing = 0; } -#define IS_SKIP_SYMBOL(x) ((x) == 0 || (x) == '\n' || (x) == '\r') - +static inline int is_skip_symbol(uint32_t x) +{ + return (x == 0 || x == '\n' || x == '\r'); +} /** * \brief Convert TextInfo struct to ASS_Image list * Splits glyphs in halves when needed (for \kf karaoke). @@ -687,118 +607,102 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x, int dst_y) Bitmap *bm; ASS_Image *head; ASS_Image **tail = &head; - ASS_Image **last_tail = 0; - ASS_Image **here_tail = 0; TextInfo *text_info = &render_priv->text_info; - for (i = 0; i < text_info->length; ++i) { - GlyphInfo *info = text_info->glyphs + i; - if (IS_SKIP_SYMBOL(info->symbol) || !info->bm_s - || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip) + for (i = 0; i < text_info->n_bitmaps; ++i) { + CombinedBitmapInfo *info = &text_info->combined_bitmaps[i]; + if (!info->bm_s || (info->shadow_x == 0 && info->shadow_y == 0)) continue; - while (info) { - if (!info->bm_s) { - info = info->next; - continue; - } - - pen_x = - dst_x + (info->pos.x >> 6) + - (int) (info->shadow_x * render_priv->border_scale); - pen_y = - dst_y + (info->pos.y >> 6) + - (int) (info->shadow_y * render_priv->border_scale); - bm = info->bm_s; - - here_tail = tail; - tail = - render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0, - 1000000, tail, IMAGE_TYPE_SHADOW); - - if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) - render_overlap(render_priv, last_tail, here_tail); - last_tail = here_tail; + pen_x = + dst_x + info->pos.x + + (int) (info->shadow_x * render_priv->border_scale); + pen_y = + dst_y + info->pos.y + + (int) (info->shadow_y * render_priv->border_scale); + bm = info->bm_s; - info = info->next; - } + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0, + 1000000, tail, IMAGE_TYPE_SHADOW); } - last_tail = 0; - for (i = 0; i < text_info->length; ++i) { - GlyphInfo *info = text_info->glyphs + i; - if (IS_SKIP_SYMBOL(info->symbol) || !info->bm_o - || info->skip) + for (i = 0; i < text_info->n_bitmaps; ++i) { + CombinedBitmapInfo *info = &text_info->combined_bitmaps[i]; + if (!info->bm_o) continue; - while (info) { - if (!info->bm_o) { - info = info->next; - continue; - } - - pen_x = dst_x + (info->pos.x >> 6); - pen_y = dst_y + (info->pos.y >> 6); - bm = info->bm_o; - - if ((info->effect_type == EF_KARAOKE_KO) - && (info->effect_timing <= (info->bbox.xMax >> 6))) { - // do nothing - } else { - here_tail = tail; - tail = - render_glyph(render_priv, bm, pen_x, pen_y, info->c[2], - 0, 1000000, tail, IMAGE_TYPE_OUTLINE); - if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) - render_overlap(render_priv, last_tail, here_tail); + pen_x = dst_x + info->pos.x; + pen_y = dst_y + info->pos.y; + bm = info->bm_o; - last_tail = here_tail; - } - info = info->next; + if ((info->effect_type == EF_KARAOKE_KO) + && (info->effect_timing <= info->first_pos_x)) { + // do nothing + } else { + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[2], + 0, 1000000, tail, IMAGE_TYPE_OUTLINE); } } - for (i = 0; i < text_info->length; ++i) { - GlyphInfo *info = text_info->glyphs + i; - if (IS_SKIP_SYMBOL(info->symbol) || !info->bm - || info->skip) + for (i = 0; i < text_info->n_bitmaps; ++i) { + CombinedBitmapInfo *info = &text_info->combined_bitmaps[i]; + if (!info->bm) continue; - while (info) { - if (!info->bm) { - info = info->next; - continue; - } + pen_x = dst_x + info->pos.x; + pen_y = dst_y + info->pos.y; + bm = info->bm; - pen_x = dst_x + (info->pos.x >> 6); - pen_y = dst_y + (info->pos.y >> 6); - bm = info->bm; - - if ((info->effect_type == EF_KARAOKE) - || (info->effect_type == EF_KARAOKE_KO)) { - if (info->effect_timing > (info->bbox.xMax >> 6)) - tail = - render_glyph(render_priv, bm, pen_x, pen_y, - info->c[0], 0, 1000000, tail, IMAGE_TYPE_CHARACTER); - else - tail = - render_glyph(render_priv, bm, pen_x, pen_y, - info->c[1], 0, 1000000, tail, IMAGE_TYPE_CHARACTER); - } else if (info->effect_type == EF_KARAOKE_KF) { + if ((info->effect_type == EF_KARAOKE) + || (info->effect_type == EF_KARAOKE_KO)) { + if (info->effect_timing > info->first_pos_x) tail = - render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], - info->c[1], info->effect_timing, tail, IMAGE_TYPE_CHARACTER); - } else + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[0], 0, 1000000, tail, IMAGE_TYPE_CHARACTER); + else tail = - render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], - 0, 1000000, tail, IMAGE_TYPE_CHARACTER); - info = info->next; - } + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[1], 0, 1000000, tail, IMAGE_TYPE_CHARACTER); + } else if (info->effect_type == EF_KARAOKE_KF) { + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + info->c[1], info->effect_timing, tail, IMAGE_TYPE_CHARACTER); + } else + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + 0, 1000000, tail, IMAGE_TYPE_CHARACTER); } *tail = 0; blend_vector_clip(render_priv, head); + for (ASS_Image* cur = head; cur; cur = cur->next) { + unsigned w = cur->w, + h = cur->h, + s = cur->stride; + if(w + 31 < (unsigned)cur->stride){ // Larger value? Play with this. + // Allocate new buffer and add to free list + uintptr_t alignment_offset = (w > 31) ? 31 : ((w > 15) ? 15 : 0); + unsigned ns = (w + alignment_offset) & ~alignment_offset; + uint8_t* nbuffer = malloc(ns * cur->h + alignment_offset); + if (!nbuffer) continue; + free_list_add(render_priv, nbuffer); + nbuffer = (unsigned char*) + (((uintptr_t)nbuffer + alignment_offset) & ~alignment_offset); + + // Copy + render_priv->restride_bitmap_func(nbuffer, ns, + cur->bitmap, s, + w, h); + cur->w = w; + cur->h = h; + cur->stride = ns; + cur->bitmap = nbuffer; + } + } + return head; } @@ -1062,6 +966,41 @@ fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key, } } +/** + * \brief Prepare combined-bitmap hash + */ +static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info) +{ + hk->w = info->w; + hk->h = info->h; + hk->o_w = info->o_w; + hk->o_h = info->o_h; + hk->be = info->be; + hk->blur = info->blur; + hk->border_style = info->border_style; + hk->has_outline = info->has_outline; + hk->is_drawing = info->is_drawing; + hk->str = info->str; + hk->chars = info->chars; + hk->shadow_x = info->shadow_x; + hk->shadow_y = info->shadow_y; + hk->flags = info->flags; + hk->bold = info->bold; + hk->italic = info->italic; + hk->hspacing = info->hspacing; + hk->scale_x = info->scale_x; + hk->scale_y = info->scale_y; + hk->has_border = info->has_border; + hk->border_x = info->border_x; + hk->border_y = info->border_y; + hk->frx = info->frx; + hk->fry = info->fry; + hk->frz = info->frz; + hk->fax = info->fax; + hk->fay = info->fay; + hk->advance = info->advance; +} + /** * \brief Get normal and outline (border) glyphs * \param info out: struct filled with extracted data @@ -1164,8 +1103,6 @@ get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info) } info->asc = val->asc; info->desc = val->desc; - - ass_drawing_free(info->drawing); } /** @@ -1326,12 +1263,6 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info) info->bm = val->bm; info->bm_o = val->bm_o; - info->bm_s = val->bm_s; - - // VSFilter compatibility: invisible fill and no border? - // In this case no shadow is supposed to be rendered. - if (!info->border && (info->c[0] & 0xFF) == 0xFF) - info->bm_s = 0; } /** @@ -1594,7 +1525,6 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) pen_shift_x = d6_to_double(-cur->pos.x); pen_shift_y += height + render_priv->settings.line_spacing; } - cur->bm_run_id += run_offset; cur->pos.x += double_to_d6(pen_shift_x); cur->pos.y += double_to_d6(pen_shift_y); } @@ -1695,6 +1625,50 @@ fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph) glyph->font_size = ft_size; } + /** + * \brief Checks whether a glyph should start a new bitmap run + * \param info Pointer to new GlyphInfo to check + * \param current_info Pointer to CombinedBitmapInfo for current run (may be NULL) + * \return 1 if a new run should be started + */ +static int is_new_bm_run(GlyphInfo *info, GlyphInfo *last) +{ + if (!last || info->linebreak || info->effect || + info->drawing || last->drawing) { + return 1; + } + // FIXME: Don't break on glyph substitutions + if (strcmp(last->font->desc.family, info->font->desc.family) || + last->font->desc.vertical != info->font->desc.vertical || + last->face_index != info->face_index || + last->font_size != info->font_size || + last->c[0] != info->c[0] || + last->c[1] != info->c[1] || + last->c[2] != info->c[2] || + last->c[3] != info->c[3] || + last->be != info->be || + last->blur != info->blur || + last->shadow_x != info->shadow_x || + last->shadow_y != info->shadow_y || + last->frx != info->frx || + last->fry != info->fry || + last->frz != info->frz || + last->fax != info->fax || + last->fay != info->fay || + last->scale_x != info->scale_x || + last->scale_y != info->scale_y || + last->border_style != info->border_style || + last->border_x != info->border_x || + last->border_y != info->border_y || + last->hspacing != info->hspacing || + last->italic != info->italic || + last->bold != info->bold || + last->flags != info->flags){ + return 1; + } + return 0; +} + /** * \brief Main ass rendering function, glues everything together * \param event event to render @@ -2173,6 +2147,10 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, // convert glyphs to bitmaps int left = render_priv->settings.left_margin; device_x = (device_x - left) * render_priv->font_scale_x + left; + unsigned nb_bitmaps = 0; + CombinedBitmapInfo *combined_info = text_info->combined_bitmaps; + CombinedBitmapInfo *current_info = NULL; + GlyphInfo *last_info = NULL; for (i = 0; i < text_info->length; ++i) { GlyphInfo *info = glyphs + i; while (info) { @@ -2185,10 +2163,232 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, double_to_d6(device_y - (int) device_y + d6_to_double(info->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; get_bitmap_glyph(render_priv, info); + + int bm_x = info->pos.x >> 6, + bm_y = info->pos.y >> 6, + bm_o_x = bm_x, bm_o_y = bm_y, min_bm_x = bm_x, min_bm_y = bm_y; + + if(info->bm){ + bm_x += info->bm->left; + bm_y += info->bm->top; + min_bm_x = bm_x; + min_bm_y = bm_y; + } + + if(info->bm_o){ + bm_o_x += info->bm_o->left; + bm_o_y += info->bm_o->top; + min_bm_x = FFMIN(min_bm_x, bm_o_x); + min_bm_y = FFMIN(min_bm_y, bm_o_y); + } + + if(is_new_bm_run(info, last_info)){ + ++nb_bitmaps; + if (nb_bitmaps >= text_info->max_bitmaps) { + // Raise maximum number of bitmaps + text_info->max_bitmaps *= 2; + text_info->combined_bitmaps = combined_info = + realloc(combined_info, + sizeof(CombinedBitmapInfo) * text_info->max_bitmaps); + } + + current_info = &combined_info[nb_bitmaps - 1]; + + current_info->pos.x = min_bm_x; + current_info->pos.y = min_bm_y; + + current_info->first_pos_x = info->bbox.xMax >> 6; + + memcpy(¤t_info->c, &info->c, sizeof(info->c)); + current_info->effect_type = info->effect_type; + current_info->effect_timing = info->effect_timing; + current_info->be = info->be; + current_info->blur = info->blur; + current_info->shadow_x = info->shadow_x; + current_info->shadow_y = info->shadow_y; + current_info->frx = info->frx; + current_info->fry = info->fry; + current_info->frz = info->frz; + current_info->fax = info->fax; + current_info->fay = info->fay; + current_info->scale_x = info->scale_x; + current_info->scale_y = info->scale_y; + current_info->border_style = info->border_style; + current_info->border_x = info->border_x; + current_info->border_y = info->border_y; + current_info->hspacing = info->hspacing; + current_info->italic = info->italic; + current_info->bold = info->bold; + current_info->flags = info->flags; + + current_info->advance = info->hash_key.u.outline.advance; + + current_info->has_border = !!info->border; + + current_info->has_outline = 0; + current_info->cached = 0; + current_info->is_drawing = 0; + + current_info->bm = current_info->bm_o = current_info->bm_s = NULL; + + current_info->max_str_length = MAX_STR_LENGTH_INITIAL; + current_info->str_length = 0; + current_info->str = malloc(MAX_STR_LENGTH_INITIAL); + current_info->chars = 0; + + current_info->w = current_info->h = current_info->o_w = current_info->o_h = 0; + + } + + if(info->drawing){ + free(current_info->str); + current_info->str = strdup(info->drawing->text); + current_info->is_drawing = 1; + ass_drawing_free(info->drawing); + }else{ + current_info->str_length += + ass_utf8_put_char( + current_info->str + current_info->str_length, + info->symbol); + current_info->chars++; + if(current_info->str_length > current_info->max_str_length - 5){ + current_info->max_str_length *= 2; + current_info->str = realloc(current_info->str, + current_info->max_str_length); + } + } + + current_info->has_outline = current_info->has_outline || !!info->bm_o; + + if(min_bm_y < current_info->pos.y){ + current_info->h += current_info->pos.y - min_bm_y; + current_info->o_h += current_info->pos.y - min_bm_y; + current_info->pos.y = min_bm_y; + } + + if(min_bm_x < current_info->pos.x){ + current_info->w += current_info->pos.x - min_bm_x; + current_info->o_w += current_info->pos.x - min_bm_x; + current_info->pos.x = min_bm_x; + } + + if(info->bm){ + current_info->w = + FFMAX(current_info->w, info->bm->w + bm_x - current_info->pos.x); + current_info->h = + FFMAX(current_info->h, info->bm->h + bm_y - current_info->pos.y); + } + + if(info->bm_o){ + current_info->o_w = + FFMAX(current_info->o_w, info->bm_o->w + bm_o_x - current_info->pos.x); + current_info->o_h = + FFMAX(current_info->o_h, info->bm_o->h + bm_o_y - current_info->pos.y); + } + + info->bm_run_id = nb_bitmaps - 1; + + last_info = info; + info = info->next; + } + } + + CompositeHashKey hk; + CompositeHashValue *hv; + for (i = 0; i < nb_bitmaps; ++i) { + CombinedBitmapInfo *info = &combined_info[i]; + + fill_composite_hash(&hk, info); + + hv = ass_cache_get(render_priv->cache.composite_cache, &hk); + + if(hv){ + info->bm = hv->bm; + info->bm_o = hv->bm_o; + info->bm_s = hv->bm_s; + info->cached = 1; + free(info->str); + }else{ + if(info->chars != 1 && !info->is_drawing){ + info->bm = alloc_bitmap(info->w, info->h); + if(info->has_outline){ + info->bm_o = alloc_bitmap(info->o_w, info->o_h); + } + } + } + } + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = glyphs + i; + while (info) { + current_info = &combined_info[info->bm_run_id]; + if(!current_info->cached && !is_skip_symbol(info->symbol)){ + if(current_info->chars == 1 || current_info->is_drawing){ + int offset_x = (info->pos.x >> 6) - current_info->pos.x; + int offset_y = (info->pos.y >> 6) - current_info->pos.y; + if(info->bm){ + current_info->bm = copy_bitmap(info->bm); + current_info->bm->left += offset_x; + current_info->bm->top += offset_y; + } + if(info->bm_o){ + current_info->bm_o = copy_bitmap(info->bm_o); + current_info->bm_o->left += offset_x; + current_info->bm_o->top += offset_y; + } + }else{ + unsigned offset_x, offset_y; + if(info->bm && info->bm->w && info->bm->h){ + offset_x = (info->pos.x >> 6) - current_info->pos.x + info->bm->left; + offset_y = (info->pos.y >> 6) - current_info->pos.y + info->bm->top; + render_priv->add_bitmaps_func( + ¤t_info->bm->buffer[offset_y * current_info->bm->stride + offset_x], + current_info->bm->stride, + info->bm->buffer, + info->bm->stride, + info->bm->h, + info->bm->w + ); + } + if(info->bm_o && info->bm_o->w && info->bm_o->h){ + offset_x = (info->pos.x >> 6) - current_info->pos.x + info->bm_o->left; + offset_y = (info->pos.y >> 6) - current_info->pos.y + info->bm_o->top; + render_priv->add_bitmaps_func( + ¤t_info->bm_o->buffer[offset_y * current_info->bm_o->stride + offset_x], + current_info->bm_o->stride, + info->bm_o->buffer, + info->bm_o->stride, + info->bm_o->h, + info->bm_o->w + ); + } + } + } info = info->next; } } + for (i = 0; i < nb_bitmaps; ++i) { + if(!combined_info[i].cached){ + CompositeHashValue chv; + CombinedBitmapInfo *info = &combined_info[i]; + if(info->bm || info->bm_o){ + apply_blur(info, render_priv); + make_shadow_bitmap(info); + } + + fill_composite_hash(&hk, info); + + chv.bm = info->bm; + chv.bm_o = info->bm_o; + chv.bm_s = info->bm_s; + + ass_cache_put(render_priv->cache.composite_cache, &hk, &chv); + } + } + + text_info->n_bitmaps = nb_bitmaps; + memset(event_images, 0, sizeof(*event_images)); event_images->top = device_y - text_info->lines[0].asc; event_images->height = text_info->height; @@ -2226,18 +2426,115 @@ void ass_free_images(ASS_Image *img) static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache) { if (ass_cache_empty(cache->bitmap_cache, cache->bitmap_max_size)) { - ass_cache_empty(cache->composite_cache, 0); ass_free_images(priv->prev_images_root); priv->prev_images_root = 0; priv->cache_cleared = 1; } if (ass_cache_empty(cache->outline_cache, cache->glyph_max)) { ass_cache_empty(cache->bitmap_cache, 0); - ass_cache_empty(cache->composite_cache, 0); ass_free_images(priv->prev_images_root); priv->prev_images_root = 0; priv->cache_cleared = 1; } + if (ass_cache_empty(cache->composite_cache, cache->composite_max_size)) { + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; + priv->cache_cleared = 1; + } +} + +void apply_blur(CombinedBitmapInfo *info, ASS_Renderer *render_priv) +{ + int be = info->be; + double blur_radius = info->blur * render_priv->blur_scale * 2; + ASS_SynthPriv *priv_blur = render_priv->synth_priv; + Bitmap *bm_g = info->bm; + Bitmap *bm_o = info->bm_o; + int border_style = info->border_style; + + if(blur_radius > 0.0 || be){ + if (bm_o) + resize_tmp(priv_blur, bm_o->w, bm_o->h); + if (!bm_o || border_style == 3) + resize_tmp(priv_blur, bm_g->w, bm_g->h); + } + + // Apply box blur (multiple passes, if requested) + if (be) { + uint16_t* tmp = (uint16_t*)(((uintptr_t)priv_blur->tmp + 0x0F) & ~0x0F); + if (bm_o) { + unsigned passes = be; + unsigned w = bm_o->w; + unsigned h = bm_o->h; + unsigned stride = bm_o->stride; + unsigned char *buf = bm_o->buffer; + if(w && h){ + while(passes--){ + memset(tmp, 0, stride * 2); + if(w < 16){ + be_blur_c(buf, w, h, stride, tmp); + }else{ + render_priv->be_blur_func(buf, w, h, stride, tmp); + } + } + } + } + if (!bm_o || border_style == 3) { + unsigned passes = be; + unsigned w = bm_g->w; + unsigned h = bm_g->h; + unsigned stride = bm_g->stride; + unsigned char *buf = bm_g->buffer; + if(w && h){ + while(passes--){ + memset(tmp, 0, stride * 2); + render_priv->be_blur_func(buf, w, h, stride, tmp); + } + } + } + } + + // Apply gaussian blur + if (blur_radius > 0.0) { + generate_tables(priv_blur, blur_radius); + if (bm_o) + ass_gauss_blur(bm_o->buffer, priv_blur->tmp, + bm_o->w, bm_o->h, bm_o->stride, + priv_blur->gt2, priv_blur->g_r, + priv_blur->g_w); + if (!bm_o || border_style == 3) + ass_gauss_blur(bm_g->buffer, priv_blur->tmp, + bm_g->w, bm_g->h, bm_g->stride, + priv_blur->gt2, priv_blur->g_r, + priv_blur->g_w); + } +} + +void make_shadow_bitmap(CombinedBitmapInfo *info) +{ + + // VSFilter compatibility: invisible fill and no border? + // In this case no shadow is supposed to be rendered. + if (!info->has_border && (info->c[0] & 0xFF) == 0xFF) { + return; + } + + int border_style = info->border_style; + // Create shadow and fix outline as needed + if (info->bm_o && border_style != 3) { + info->bm_s = copy_bitmap(info->bm_o); + fix_outline(info->bm, info->bm_o); + } else if (info->bm_o && (info->border_x || info->border_y)) { + info->bm_s = copy_bitmap(info->bm_o); + } else if (info->bm_o) { + info->bm_s = info->bm_o; + info->bm_o = 0; + } else + info->bm_s = copy_bitmap(info->bm); + + assert(info->bm_s); + + shift_bitmap(info->bm_s, info->shadow_x, info->shadow_y); } /** diff --git a/libass/ass_render.h b/libass/ass_render.h index a41586b..ab0b704 100644 --- a/libass/ass_render.h +++ b/libass/ass_render.h @@ -41,9 +41,11 @@ typedef struct ass_shaper ASS_Shaper; #include "ass_fontconfig.h" #include "ass_library.h" #include "ass_drawing.h" +#include "ass_bitmap.h" -#define GLYPH_CACHE_MAX 1000 -#define BITMAP_CACHE_MAX_SIZE 30 * 1048576 +#define GLYPH_CACHE_MAX 10000 +#define BITMAP_CACHE_MAX_SIZE 500 * 1048576 +#define COMPOSITE_CACHE_MAX_SIZE 500 * 1048576 #define PARSED_FADE (1<<0) #define PARSED_A (1<<1) @@ -103,6 +105,49 @@ typedef enum { EF_KARAOKE_KO } Effect; +// describes a combined bitmap +typedef struct { + Bitmap *bm; // glyphs bitmap + unsigned w; + unsigned h; + Bitmap *bm_o; // outline bitmap + unsigned o_w; + unsigned o_h; + Bitmap *bm_s; // shadow bitmap + FT_Vector pos; + uint32_t c[4]; // colors + FT_Vector advance; // 26.6 + Effect effect_type; + int effect_timing; // time duration of current karaoke word + // after process_karaoke_effects: distance in pixels from the glyph origin. + // part of the glyph to the left of it is displayed in a different color. + int be; // blur edges + double blur; // gaussian blur + double shadow_x; + double shadow_y; + double frx, fry, frz; // rotation + double fax, fay; // text shearing + double scale_x, scale_y; + int border_style; + int has_border; + double border_x, border_y; + double hspacing; + unsigned italic; + unsigned bold; + int flags; + + unsigned has_outline; + unsigned is_drawing; + + int max_str_length; + int str_length; + unsigned chars; + char *str; + int cached; + FT_Vector pos_orig; + int first_pos_x; +} CombinedBitmapInfo; + // describes a glyph // GlyphInfo and TextInfo are used for text centering and word-wrapping operations typedef struct glyph_info { @@ -130,6 +175,7 @@ typedef struct glyph_info { uint32_t c[4]; // colors FT_Vector advance; // 26.6 FT_Vector cluster_advance; + char effect; // the first (leading) glyph of some effect ? Effect effect_type; int effect_timing; // time duration of current karaoke word // after process_karaoke_effects: distance in pixels from the glyph origin. @@ -170,9 +216,12 @@ typedef struct { int length; LineInfo *lines; int n_lines; + CombinedBitmapInfo *combined_bitmaps; + unsigned n_bitmaps; double height; int max_glyphs; int max_lines; + unsigned max_bitmaps; } TextInfo; // Renderer state. @@ -251,8 +300,23 @@ typedef struct { Cache *composite_cache; size_t glyph_max; size_t bitmap_max_size; + size_t composite_max_size; } CacheStore; +typedef void (*BitmapBlendFunc)(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t height, intptr_t width); +typedef void (*BitmapMulFunc)(uint8_t *dst, intptr_t dst_stride, + uint8_t *src1, intptr_t src1_stride, + uint8_t *src2, intptr_t src2_stride, + intptr_t width, intptr_t height); +typedef void (*BEBlurFunc)(uint8_t *buf, intptr_t w, + intptr_t h, intptr_t stride, + uint16_t *tmp); +typedef void (*RestrideBitmapFunc)(uint8_t *dst, intptr_t dst_stride, + uint8_t *src, intptr_t src_stride, + intptr_t width, intptr_t height); + struct ass_renderer { ASS_Library *library; FT_Library ftlibrary; @@ -288,6 +352,12 @@ struct ass_renderer { TextInfo text_info; CacheStore cache; + BitmapBlendFunc add_bitmaps_func; + BitmapBlendFunc sub_bitmaps_func; + BitmapMulFunc mul_bitmaps_func; + BEBlurFunc be_blur_func; + RestrideBitmapFunc restride_bitmap_func; + FreeList *free_head; FreeList *free_tail; }; @@ -311,6 +381,8 @@ typedef struct { void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style); void ass_free_images(ASS_Image *img); +void make_shadow_bitmap(CombinedBitmapInfo* info); +void apply_blur(CombinedBitmapInfo* info, ASS_Renderer *render_priv); // XXX: this is actually in ass.c, includes should be fixed later on void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track); diff --git a/libass/ass_shaper.c b/libass/ass_shaper.c index 944603e..6aca5f5 100644 --- a/libass/ass_shaper.c +++ b/libass/ass_shaper.c @@ -726,14 +726,37 @@ void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv, // set size and get glyph index ass_font_get_index(render_priv->fontconfig_priv, info->font, info->symbol, &info->face_index, &info->glyph_index); - // shape runs share the same font face and size + // shape runs break on: xbord, ybord, xshad, yshad, + // all four colors, all four alphas, be, blur, fn, fs, + // fscx, fscy, fsp, bold, italic, underline, strikeout, + // frx, fry, frz, fax, fay, karaoke start, karaoke type, + // and on every line break if (i > 0 && (last->font != info->font || + last->face_index != info->face_index || + last->script != info->script || last->font_size != info->font_size || + last->c[0] != info->c[0] || + last->c[1] != info->c[1] || + last->c[2] != info->c[2] || + last->c[3] != info->c[3] || + last->be != info->be || + last->blur != info->blur || + last->shadow_x != info->shadow_x || + last->shadow_y != info->shadow_y || + last->frx != info->frx || + last->fry != info->fry || + last->frz != info->frz || + last->fax != info->fax || + last->fay != info->fay || last->scale_x != info->scale_x || last->scale_y != info->scale_y || + last->border_style != info->border_style || + last->border_x != info->border_x || + last->border_y != info->border_y || last->hspacing != info->hspacing || - last->face_index != info->face_index || - last->script != info->script)) + last->italic != info->italic || + last->bold != info->bold || + last->flags != info->flags)) shape_run++; info->shape_run_id = shape_run; } diff --git a/libass/ass_utils.c b/libass/ass_utils.c index 3c2c40a..2a95ddc 100644 --- a/libass/ass_utils.c +++ b/libass/ass_utils.c @@ -204,6 +204,38 @@ unsigned ass_utf8_get_char(char **str) return c; } +/** + * Original version from http://www.cprogramming.com/tutorial/utf8.c + * \brief Converts a single UTF-32 code point to UTF-8 + * \param dest Buffer to write to. Writes a NULL terminator. + * \param ch 32-bit character code to convert + * \return number of bytes written + * converts a single character and ASSUMES YOU HAVE ENOUGH SPACE + */ +unsigned ass_utf8_put_char(char *dest, uint32_t ch) +{ + char *orig_dest = dest; + + if (ch < 0x80) { + *dest++ = (char)ch; + } else if (ch < 0x800) { + *dest++ = (ch >> 6) | 0xC0; + *dest++ = (ch & 0x3F) | 0x80; + } else if (ch < 0x10000) { + *dest++ = (ch >> 12) | 0xE0; + *dest++ = ((ch >> 6) & 0x3F) | 0x80; + *dest++ = (ch & 0x3F) | 0x80; + } else if (ch < 0x110000) { + *dest++ = (ch >> 18) | 0xF0; + *dest++ = ((ch >> 12) & 0x3F) | 0x80; + *dest++ = ((ch >> 6) & 0x3F) | 0x80; + *dest++ = (ch & 0x3F) | 0x80; + } + + *dest = '\0'; + return dest - orig_dest; +} + /** * \brief find style by name * \param track track diff --git a/libass/ass_utils.h b/libass/ass_utils.h index b797c8c..39a9da0 100644 --- a/libass/ass_utils.h +++ b/libass/ass_utils.h @@ -51,6 +51,7 @@ int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex); char parse_bool(char *str); int parse_ycbcr_matrix(char *str); unsigned ass_utf8_get_char(char **str); +unsigned ass_utf8_put_char(char *dest, uint32_t ch); void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...); int lookup_style(ASS_Track *track, char *name); ASS_Style *lookup_style_strict(ASS_Track *track, char *name);