]> granicus.if.org Git - libass/commitdiff
Combine bitmaps before applying blur and shadow
author11rcombs <rodger.combs@gmail.com>
Sun, 26 Jan 2014 01:06:12 +0000 (19:06 -0600)
committer11rcombs <rodger.combs@gmail.com>
Sun, 26 Jan 2014 01:09:15 +0000 (19:09 -0600)
libass/ass_bitmap.c
libass/ass_bitmap.h
libass/ass_cache.c
libass/ass_cache.h
libass/ass_cache_template.h
libass/ass_parse.c
libass/ass_render.c
libass/ass_render.h
libass/ass_shaper.c
libass/ass_utils.c
libass/ass_utils.h

index dcc2f2b21457682407772211d0b7f8adea29f0e2..ad00df2af7e4e0c7df35036473d1f5d8e51738a7 100644 (file)
 
 #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;
+    }
 }
index 53be7af11588c18e9b3809b29ebb73282c6df1b8..b51c1bfec967c7ea210811564218f6a3737d578e 100644 (file)
 
 #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 */
index 8234e5f006647f839084830e9c3da0a2da37ad46..6baa92489d864e98573d81c6798938e93710a9f4 100644 (file)
@@ -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));
 }
index 7375f04343428cf0c063f7c19d6914d7eaa28857..677b705d4492e174201443216ba3d8dd2ad244b1 100644 (file)
@@ -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 {
index 3d8185f81e99e892c4855145f99b413a69af3876..8f7f2af2f405f9716532f5435d3d313d14e7ff48 100644 (file)
@@ -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
index c3292c9260ba7531f09607b6c2c342bf3e1dd342..fe40e69456849c7e31fa7b961593e605b3977d8e 100644 (file)
@@ -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;
             }
         }
     }
index 12f570157b54af6f322d2d3ebe974f564837d8db..e6e60521ffffb07c2909a22e561d0018de22c8ae 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <assert.h>
 #include <math.h>
+#include <string.h>
 
 #include "ass_render.h"
 #include "ass_parse.h"
 
 #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(&current_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(
+                            &current_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(
+                            &current_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);
 }
 
 /**
index a41586b5b5826af8ee11faa25c0e434467247acf..ab0b7046f4f66f1544e83817e6d3f4dc2bcb86de 100644 (file)
@@ -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);
index 944603ee171d78cade5ea4f3ec19fdc2c4325977..6aca5f5ef6396b457e5c16272604e5c38f50fe5a 100644 (file)
@@ -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;
     }
index 3c2c40a3e7cbe87c430529367d052c365e95b7d1..2a95ddc17ef2d38679ec484174b5a61f43f58c7b 100644 (file)
@@ -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
index b797c8c93e76e32037277cdbfbd8c0d34e48f022..39a9da07ddaabfba2b521888b5b967c3c8ef78c6 100644 (file)
@@ -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);