whitespace cosmetics: Remove all trailing whitespace.
[mplayer.git] / libass / ass_render.c
index 6b8ee70..f13f766 100644 (file)
 
 #define MAX_GLYPHS 3000
 #define MAX_LINES 300
-#define BE_RADIUS 1.5
 #define BLUR_MAX_RADIUS 50.0
+#define MAX_BE 100
+#define ROUND(x) ((int) ((x) + .5))
+#define SUBPIXEL_MASK 56       // d6 bitmask for subpixel accuracy adjustment
 
 static int last_render_id = 0;
 
@@ -82,7 +84,6 @@ struct ass_renderer_s {
        ass_settings_t settings;
        int render_id;
        ass_synth_priv_t* synth_priv;
-       ass_synth_priv_t* synth_priv_blur;
 
        ass_image_t* images_root; // rendering result is stored here
        ass_image_t* prev_images_root;
@@ -116,9 +117,9 @@ typedef struct glyph_info_s {
 //     int height;
        int be; // blur edges
        double blur; // gaussian blur
-       int shadow;
+       double shadow;
        double frx, fry, frz; // rotation
-       
+
        bitmap_hash_key_t hash_key;
 } glyph_info_t;
 
@@ -140,11 +141,11 @@ typedef struct text_info_s {
 typedef struct render_context_s {
        ass_event_t* event;
        ass_style_t* style;
-       
+
        ass_font_t* font;
        char* font_path;
        double font_size;
-       
+
        FT_Stroker stroker;
        int alignment; // alignment overrides go here; if zero, style value will be used
        double frx, fry, frz;
@@ -165,7 +166,7 @@ typedef struct render_context_s {
        uint32_t fade; // alpha from \fad
        char be; // blur edges
        double blur; // gaussian blur
-       int shadow;
+       double shadow;
        int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags
 
        effect_t effect_type;
@@ -183,7 +184,8 @@ typedef struct render_context_s {
        char* family;
        unsigned bold;
        unsigned italic;
-       
+       int treat_family_as_pattern;
+
 } render_context_t;
 
 // frame-global data
@@ -224,9 +226,15 @@ static void ass_lazy_track_init(void)
        } else {
                double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) /
                        frame_context.orig_height / frame_context.width;
-               if (!track->PlayResY) {
+               if (!track->PlayResY && track->PlayResX == 1280) {
+                       track->PlayResY = 1024;
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+               } else if (!track->PlayResY) {
                        track->PlayResY = track->PlayResX / orig_aspect + .5;
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+               } else if (!track->PlayResX && track->PlayResY == 1024) {
+                       track->PlayResX = 1280;
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
                } else if (!track->PlayResX) {
                        track->PlayResX = track->PlayResY * orig_aspect + .5;
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
@@ -240,13 +248,13 @@ ass_renderer_t* ass_renderer_init(ass_library_t* library)
        FT_Library ft;
        ass_renderer_t* priv = 0;
        int vmajor, vminor, vpatch;
-       
+
        memset(&render_context, 0, sizeof(render_context));
        memset(&frame_context, 0, sizeof(frame_context));
        memset(&text_info, 0, sizeof(text_info));
 
        error = FT_Init_FreeType( &ft );
-       if ( error ) { 
+       if ( error ) {
                mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
                goto ass_init_exit;
        }
@@ -263,19 +271,19 @@ ass_renderer_t* ass_renderer_init(ass_library_t* library)
                goto ass_init_exit;
        }
 
-       priv->synth_priv = ass_synth_init(BE_RADIUS);
-       priv->synth_priv_blur = ass_synth_init(BLUR_MAX_RADIUS);
+       priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
 
        priv->library = library;
        priv->ftlibrary = ft;
        // images_root and related stuff is zero-filled in calloc
-       
+
        ass_font_cache_init();
        ass_bitmap_cache_init();
+       ass_composite_cache_init();
        ass_glyph_cache_init();
 
        text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
-       
+
 ass_init_exit:
        if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
        else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
@@ -287,6 +295,7 @@ void ass_renderer_done(ass_renderer_t* priv)
 {
        ass_font_cache_done();
        ass_bitmap_cache_done();
+       ass_composite_cache_done();
        ass_glyph_cache_done();
        if (render_context.stroker) {
                FT_Stroker_Done(render_context.stroker);
@@ -307,7 +316,7 @@ void ass_renderer_done(ass_renderer_t* priv)
 static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color)
 {
        ass_image_t* img = calloc(1, sizeof(ass_image_t));
-       
+
        img->w = bitmap_w;
        img->h = bitmap_h;
        img->stride = stride;
@@ -344,7 +353,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        dst_x += bm->left;
        dst_y += bm->top;
        brk -= bm->left;
-       
+
        // clipping
        clip_x0 = render_context.clip_x0;
        clip_y0 = render_context.clip_y0;
@@ -354,7 +363,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        b_y0 = 0;
        b_x1 = bm->w;
        b_y1 = bm->h;
-       
+
        tmp = dst_x - clip_x0;
        if (tmp < 0) {
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n");
@@ -375,13 +384,13 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n");
                b_y1 = bm->h + tmp;
        }
-       
+
        if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
                return tail;
 
        if (brk > b_x0) { // draw left part
                if (brk > b_x1) brk = b_x1;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, 
+               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
                        brk - b_x0, b_y1 - b_y0, bm->w,
                        dst_x + b_x0, dst_y + b_y0, color);
                *tail = img;
@@ -389,7 +398,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        }
        if (brk < b_x1) { // draw right part
                if (brk < b_x0) brk = b_x0;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, 
+               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
                        b_x1 - brk, b_y1 - b_y0, bm->w,
                        dst_x + brk, dst_y + b_y0, color2);
                *tail = img;
@@ -399,6 +408,99 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
 }
 
 /**
+ * \brief Calculate overlapping area of two consecutive bitmaps and in case they
+ * overlap, composite 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_image_t** last_tail, ass_image_t** tail, bitmap_hash_key_t *last_hash, bitmap_hash_key_t* hash) {
+       int left, top, bottom, right;
+       int old_left, old_top, w, h, cur_left, cur_top;
+       int x, y, opos, cpos;
+       char m;
+       composite_hash_key_t hk;
+       composite_hash_val_t *hv;
+       composite_hash_key_t *nhk;
+       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
+       memset(&hk, 0, sizeof(hk));
+       memcpy(&hk.a, last_hash, sizeof(*last_hash));
+       memcpy(&hk.b, hash, sizeof(*hash));
+       hk.aw = aw;
+       hk.ah = ah;
+       hk.bw = bw;
+       hk.bh = bh;
+       hk.ax = ax;
+       hk.ay = ay;
+       hk.bx = bx;
+       hk.by = by;
+       hv = cache_find_composite(&hk);
+       if (hv) {
+               (*last_tail)->bitmap = hv->a;
+               (*tail)->bitmap = hv->b;
+               return;
+       }
+
+       // Allocate new bitmaps and copy over data
+       a = (*last_tail)->bitmap;
+       b = (*tail)->bitmap;
+       (*last_tail)->bitmap = malloc(as*ah);
+       (*tail)->bitmap = malloc(bs*bh);
+       memcpy((*last_tail)->bitmap, a, as*ah);
+       memcpy((*tail)->bitmap, b, bs*bh);
+
+       // Composite 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 = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
+                       (*last_tail)->bitmap[opos] = 0;
+                       (*tail)->bitmap[cpos] = m;
+               }
+
+       // Insert bitmaps into the cache
+       nhk = calloc(1, sizeof(*nhk));
+       memcpy(nhk, &hk, sizeof(*nhk));
+       hv = calloc(1, sizeof(*hv));
+       hv->a = (*last_tail)->bitmap;
+       hv->b = (*tail)->bitmap;
+       cache_add_composite(nhk, hv);
+}
+
+/**
  * \brief Convert text_info_t struct to ass_image_t list
  * Splits glyphs in halves when needed (for \kf karaoke).
  */
@@ -409,19 +511,28 @@ static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
        bitmap_t* bm;
        ass_image_t* head;
        ass_image_t** tail = &head;
+       ass_image_t** last_tail = 0;
+       ass_image_t** here_tail = 0;
+       bitmap_hash_key_t* last_hash = 0;
 
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0))
                        continue;
 
-               pen_x = dst_x + info->pos.x + info->shadow;
-               pen_y = dst_y + info->pos.y + info->shadow;
+               pen_x = dst_x + info->pos.x + ROUND(info->shadow * frame_context.border_scale);
+               pen_y = dst_y + info->pos.y + ROUND(info->shadow * frame_context.border_scale);
                bm = info->bm_s;
 
+               here_tail = tail;
                tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail);
+               if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
+                       render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
+               last_tail = here_tail;
+               last_hash = &info->hash_key;
        }
 
+       last_tail = 0;
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o)
@@ -430,11 +541,17 @@ static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
                pen_x = dst_x + info->pos.x;
                pen_y = dst_y + info->pos.y;
                bm = info->bm_o;
-               
+
                if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) {
                        // do nothing
-               } else
+               } else {
+                       here_tail = tail;
                        tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail);
+                       if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
+                               render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
+                       last_tail = here_tail;
+                       last_hash = &info->hash_key;
+               }
        }
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
@@ -467,17 +584,22 @@ static int x2scr(double x) {
        return x*frame_context.orig_width_nocrop / frame_context.track->PlayResX +
                FFMAX(global_settings->left_margin, 0);
 }
-static int x2scr_pos(double x) {
+static double x2scr_pos(double x) {
        return x*frame_context.orig_width / frame_context.track->PlayResX +
                global_settings->left_margin;
 }
 /**
  * \brief Mapping between script and screen coordinates
  */
-static int y2scr(double y) {
+static double y2scr(double y) {
        return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
                FFMAX(global_settings->top_margin, 0);
 }
+static double y2scr_pos(double y) {
+       return y * frame_context.orig_height / frame_context.track->PlayResY +
+               global_settings->top_margin;
+}
+
 // the same for toptitles
 static int y2scr_top(double y) {
        if (global_settings->use_margins)
@@ -500,7 +622,7 @@ static int y2scr_sub(double y) {
 static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) {
        FT_BBox bbox;
        int i;
-       
+
        if (text_info.length > 0) {
                bbox.xMin = 32000;
                bbox.xMax = -32000;
@@ -556,6 +678,7 @@ static void update_font(void)
        ass_renderer_t* priv = frame_context.ass_priv;
        ass_font_desc_t desc;
        desc.family = strdup(render_context.family);
+       desc.treat_family_as_pattern = render_context.treat_family_as_pattern;
 
        val = render_context.bold;
        // 0 = normal, 1 = bold, >1 = exact weight
@@ -570,7 +693,7 @@ static void update_font(void)
 
        render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc);
        free(desc.family);
-       
+
        if (render_context.font)
                change_font_size(render_context.font_size);
 }
@@ -656,7 +779,7 @@ static uint32_t mult_alpha(uint32_t a, uint32_t b)
  * \brief Calculate alpha value by piecewise linear function
  * Used for \fad, \fade implementation.
  */
-static unsigned interpolate_alpha(long long now, 
+static unsigned interpolate_alpha(long long now,
                long long t1, long long t2, long long t3, long long t4,
                unsigned a1, unsigned a2, unsigned a3)
 {
@@ -689,7 +812,7 @@ static void reset_render_context(void);
 static char* parse_tag(char* p, double pwr) {
 #define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
 #define skip(x) if (*p == (x)) ++p; else { return p; }
-       
+
        skip_to('\\');
        skip('\\');
        if ((*p == '}') || (*p == 0))
@@ -780,29 +903,29 @@ static char* parse_tag(char* p, double pwr) {
                        val = -1.; // reset to default
                change_border(val);
        } else if (mystrcmp(&p, "move")) {
-               int x1, x2, y1, y2;
+               double x1, x2, y1, y2;
                long long t1, t2, delta_t, t;
                double x, y;
                double k;
                skip('(');
-               mystrtoi(&p, &x1);
+               mystrtod(&p, &x1);
                skip(',');
-               mystrtoi(&p, &y1);
+               mystrtod(&p, &y1);
                skip(',');
-               mystrtoi(&p, &x2);
+               mystrtod(&p, &x2);
                skip(',');
-               mystrtoi(&p, &y2);
+               mystrtod(&p, &y2);
                if (*p == ',') {
                        skip(',');
                        mystrtoll(&p, &t1);
                        skip(',');
                        mystrtoll(&p, &t2);
-                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%d, %d) -> (%d, %d), (%" PRId64 " .. %" PRId64 ")\n", 
+                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %" PRId64 ")\n",
                                x1, y1, x2, y2, (int64_t)t1, (int64_t)t2);
                } else {
                        t1 = 0;
                        t2 = render_context.event->Duration;
-                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%d, %d) -> (%d, %d)\n", x1, y1, x2, y2);
+                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2);
                }
                skip(')');
                delta_t = t2 - t1;
@@ -888,13 +1011,13 @@ static char* parse_tag(char* p, double pwr) {
                else
                        render_context.alignment = render_context.style->Alignment;
        } else if (mystrcmp(&p, "pos")) {
-               int v1, v2;
+               double v1, v2;
                skip('(');
-               mystrtoi(&p, &v1);
+               mystrtod(&p, &v1);
                skip(',');
-               mystrtoi(&p, &v2);
+               mystrtod(&p, &v2);
                skip(')');
-               mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%d, %d)\n", v1, v2);
+               mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%f, %f)\n", v1, v2);
                if (render_context.evt_type != EVENT_POSITIONED) {
                        render_context.evt_type = EVENT_POSITIONED;
                        render_context.detect_collisions = 0;
@@ -944,10 +1067,12 @@ static char* parse_tag(char* p, double pwr) {
                skip(')');
                mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2);
                //                              render_context.evt_type = EVENT_POSITIONED;
-               render_context.org_x = v1;
-               render_context.org_y = v2;
-               render_context.have_origin = 1;
-               render_context.detect_collisions = 0;
+               if (!render_context.have_origin) {
+                       render_context.org_x = v1;
+                       render_context.org_y = v2;
+                       render_context.have_origin = 1;
+                       render_context.detect_collisions = 0;
+               }
        } else if (mystrcmp(&p, "t")) {
                double v[3];
                int v1, v2;
@@ -987,7 +1112,7 @@ static char* parse_tag(char* p, double pwr) {
                        k = pow(((double)(t - t1)) / delta_t, v3);
                }
                while (*p == '\\')
-                       p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's 
+                       p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's
                skip_to(')'); // in case there is some unknown tag or a comment
                skip(')');
        } else if (mystrcmp(&p, "clip")) {
@@ -1044,9 +1169,9 @@ static char* parse_tag(char* p, double pwr) {
        } else if (mystrcmp(&p, "be")) {
                int val;
                if (mystrtoi(&p, &val)) {
-                       // Clamp to 10, since high values need excessive CPU
+                       // Clamp to a safe upper limit, since high values need excessive CPU
                        val = (val < 0) ? 0 : val;
-                       val = (val > 10) ? 10 : val;
+                       val = (val > MAX_BE) ? MAX_BE : val;
                        render_context.be = val;
                } else
                        render_context.be = 0;
@@ -1169,7 +1294,7 @@ static void apply_transition_effects(ass_event_t* event)
        while (cnt < 4 && (p = strchr(p, ';'))) {
                v[cnt++] = atoi(++p);
        }
-       
+
        if (strncmp(event->Effect, "Banner;", 7) == 0) {
                int delay;
                if (cnt < 1) {
@@ -1237,6 +1362,7 @@ static void reset_render_context(void)
        if (render_context.family)
                free(render_context.family);
        render_context.family = strdup(render_context.style->FontName);
+       render_context.treat_family_as_pattern = render_context.style->treat_fontname_as_pattern;
        render_context.bold = render_context.style->Bold;
        render_context.italic = render_context.style->Italic;
        update_font();
@@ -1281,7 +1407,7 @@ static void init_render_context(ass_event_t* event)
        render_context.effect_type = EF_NONE;
        render_context.effect_timing = 0;
        render_context.effect_skip_timing = 0;
-       
+
        apply_transition_effects(event);
 }
 
@@ -1304,6 +1430,7 @@ static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance
        int error;
        glyph_hash_val_t* val;
        glyph_hash_key_t key;
+       memset(&key, 0, sizeof(key));
        key.font = render_context.font;
        key.size = render_context.font_size;
        key.ch = symbol;
@@ -1365,10 +1492,10 @@ static void get_bitmap_glyph(glyph_info_t* info)
 {
        bitmap_hash_val_t* val;
        bitmap_hash_key_t* key = &info->hash_key;
-       
+
        val = cache_find_bitmap(key);
 /*     val = 0; */
-       
+
        if (val) {
                info->bm = val->bm;
                info->bm_o = val->bm_o;
@@ -1387,10 +1514,9 @@ static void get_bitmap_glyph(glyph_info_t* info)
 
                        // render glyph
                        error = glyph_to_bitmap(ass_renderer->synth_priv,
-                                       ass_renderer->synth_priv_blur,
                                        info->glyph, info->outline_glyph,
                                        &info->bm, &info->bm_o,
-                                       &info->bm_s, info->be, info->blur);
+                                       &info->bm_s, info->be, info->blur * frame_context.border_scale);
                        if (error)
                                info->symbol = 0;
 
@@ -1476,8 +1602,8 @@ static void wrap_lines_smart(int max_text_width)
                        break_at = i;
                        mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at);
                }
-               
-               if (len >= max_text_width) {
+
+               if ((len >= max_text_width) && (frame_context.track->WrapStyle != 2)) {
                        break_type = 1;
                        break_at = last_space;
                        if (break_at == -1)
@@ -1493,7 +1619,7 @@ static void wrap_lines_smart(int max_text_width)
                        // marking break_at+1 as start of a new line
                        int lead = break_at + 1; // the first symbol of the new line
                        if (text_info.n_lines >= MAX_LINES) {
-                               // to many lines ! 
+                               // to many lines !
                                // no more linebreaks
                                for (j = lead; j < text_info.length; ++j)
                                        text_info.glyphs[j].linebreak = 0;
@@ -1506,7 +1632,7 @@ static void wrap_lines_smart(int max_text_width)
                        s_offset = s1->bbox.xMin + s1->pos.x;
                        text_info.n_lines ++;
                }
-               
+
                if (cur->symbol == ' ')
                        last_space = i;
 
@@ -1552,11 +1678,11 @@ static void wrap_lines_smart(int max_text_width)
                        if (i == text_info.length)
                                break;
                }
-               
+
        }
        assert(text_info.n_lines >= 1);
 #undef DIFF
-       
+
        measure_text();
 
        pen_shift_x = 0;
@@ -1580,7 +1706,7 @@ static void wrap_lines_smart(int max_text_width)
  * \brief determine karaoke effects
  * Karaoke effects cannot be calculated during parse stage (get_next_char()),
  * so they are done in a separate step.
- * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's 
+ * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
  * (the first glyph of the karaoke word)'s effect_type and effect_timing.
  * This function:
  * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
@@ -1680,75 +1806,46 @@ static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by)
 }
 
 /**
- * \brief Multiply 4-vector by 4-matrix
- * \param a 4-vector
- * \param m 4-matrix]
- * \param b out: 4-vector
- * Calculates a * m and stores result in b
- */
-static inline void transform_point_3d(double *a, double *m, double *b)
-{
-       b[0] = a[0] * m[0] + a[1] * m[4] + a[2] * m[8] +  a[3] * m[12];
-       b[1] = a[0] * m[1] + a[1] * m[5] + a[2] * m[9] +  a[3] * m[13];
-       b[2] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10] + a[3] * m[14];
-       b[3] = a[0] * m[3] + a[1] * m[7] + a[2] * m[11] + a[3] * m[15];
-}
-
-/**
- * \brief Apply 3d transformation to a vector
- * \param v FreeType vector (2d)
- * \param m 4-matrix
- * Transforms v by m, projects the result back to the screen plane
- * Result is returned in v.
+ * \brief Apply transformation to outline points of a glyph
+ * Applies rotations given by frx, fry and frz and projects the points back
+ * onto the screen plane.
  */
-static inline void transform_vector_3d(FT_Vector* v, double *m) {
-       const double camera = 2500 * frame_context.border_scale; // camera distance
-       const double cutoff_z = 10.;
-       double a[4], b[4];
-       a[0] = d6_to_double(v->x);
-       a[1] = d6_to_double(v->y);
-       a[2] = 0.;
-       a[3] = 1.;
-       transform_point_3d(a, m, b);
-       /* Apply perspective projection with the following matrix:
-          2500     0     0     0
-             0  2500     0     0
-             0     0     0     0
-             0     0     8     2500
-          where 2500 is camera distance, 8 - z-axis scale.
-          Camera is always located in (org_x, org_y, -2500). This means
-          that different subtitle events can be displayed at the same time
-          using different cameras. */
-       b[0] *= camera;
-       b[1] *= camera;
-       b[3] = 8 * b[2] + camera;
-       if (b[3] < cutoff_z)
-               b[3] = cutoff_z;
-       v->x = double_to_d6(b[0] / b[3]);
-       v->y = double_to_d6(b[1] / b[3]);
-}
-
-/**
- * \brief Apply 3d transformation to a glyph
- * \param glyph FreeType glyph
- * \param m 4-matrix
- * Transforms glyph by m, projects the result back to the screen plane
- * Result is returned in glyph.
- */
-static inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift) {
-       int i;
-       FT_Outline* outline = &((FT_OutlineGlyph)glyph)->outline;
+static void transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, double frz) {
+       double sx = sin(frx);
+       double sy = sin(fry);
+       double sz = sin(frz);
+       double cx = cos(frx);
+       double cy = cos(fry);
+       double cz = cos(frz);
+       FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
        FT_Vector* p = outline->points;
+       double x, y, z, xx, yy, zz;
+       int i;
 
        for (i=0; i<outline->n_points; i++) {
-               p[i].x += shift.x;
-               p[i].y += shift.y;
-               transform_vector_3d(p + i, m);
-               p[i].x -= shift.x;
-               p[i].y -= shift.y;
-       }
+               x = p[i].x + shift.x;
+               y = p[i].y + shift.y;
+               z = 0.;
+
+               xx = x*cz + y*sz;
+               yy = -(x*sz - y*cz);
+               zz = z;
 
-       //transform_vector_3d(&glyph->advance, m);
+               x = xx;
+               y = yy*cx + zz*sx;
+               z = yy*sx - zz*cx;
+
+               xx = x*cy + z*sy;
+               yy = y;
+               zz = x*sy - z*cy;
+
+               zz = FFMAX(zz, -19000);
+
+               x = (xx * 20000) / (zz + 20000);
+               y = (yy * 20000) / (zz + 20000);
+               p[i].x = x - shift.x + 0.5;
+               p[i].y = y - shift.y + 0.5;
+       }
 }
 
 /**
@@ -1763,37 +1860,28 @@ static inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift
  */
 static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
 {
-       fry = - fry; // FreeType's y axis goes in the opposite direction
+       frx = - frx;
+       frz = - frz;
        if (frx != 0. || fry != 0. || frz != 0.) {
-               double m[16];
-               double sx = sin(frx);
-               double sy = sin(fry);
-               double sz = sin(frz);
-               double cx = cos(frx);
-               double cy = cos(fry);
-               double cz = cos(frz);
-               m[0] = cy * cz;            m[1] = cy*sz;              m[2]  = -sy;    m[3] = 0.0;
-               m[4] = -cx*sz + sx*sy*cz;  m[5] = cx*cz + sx*sy*sz;   m[6]  = sx*cy;  m[7] = 0.0;
-               m[8] = sx*sz + cx*sy*cz;   m[9] = -sx*cz + cx*sy*sz;  m[10] = cx*cy;  m[11] = 0.0;
-               m[12] = 0.0;               m[13] = 0.0;               m[14] = 0.0;    m[15] = 1.0;
-
                if (glyph && *glyph)
-                       transform_glyph_3d(*glyph, m, shift);
+                       transform_3d_points(shift, *glyph, frx, fry, frz);
 
                if (glyph2 && *glyph2)
-                       transform_glyph_3d(*glyph2, m, shift);
+                       transform_3d_points(shift, *glyph2, frx, fry, frz);
        }
 }
 
+
 /**
  * \brief Main ass rendering function, glues everything together
  * \param event event to render
+ * \param event_images struct containing resulting images, will also be initialized
  * Process event, appending resulting ass_image_t's to images_root.
  */
 static int ass_render_event(ass_event_t* event, event_images_t* event_images)
 {
        char* p;
-       FT_UInt previous; 
+       FT_UInt previous;
        FT_UInt num_glyphs;
        FT_Vector pen;
        unsigned code;
@@ -1829,7 +1917,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                do {
                        code = get_next_char(&p);
                } while (code && render_context.drawing_mode); // skip everything in drawing mode
-               
+
                // face could have been changed in get_next_char
                if (!render_context.font) {
                        free_render_context();
@@ -1840,7 +1928,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        break;
 
                if (text_info.length >= MAX_GLYPHS) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, 
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached,
                                        (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text);
                        break;
                }
@@ -1852,8 +1940,13 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        pen.y += delta.y * render_context.scale_y;
                }
 
-               shift.x = pen.x & 63;
-               shift.y = pen.y & 63;
+               shift.x = pen.x & SUBPIXEL_MASK;
+               shift.y = pen.y & SUBPIXEL_MASK;
+
+               if (render_context.evt_type == EVENT_POSITIONED) {
+                       shift.x += double_to_d6(x2scr_pos(render_context.pos_x)) & SUBPIXEL_MASK;
+                       shift.y -= double_to_d6(y2scr_pos(render_context.pos_y)) & SUBPIXEL_MASK;
+               }
 
                ass_font_set_transform(render_context.font,
                                       render_context.scale_x * frame_context.font_scale_x,
@@ -1861,14 +1954,14 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                                       &shift );
 
                get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
-               
+
                text_info.glyphs[text_info.length].pos.x = pen.x >> 6;
                text_info.glyphs[text_info.length].pos.y = pen.y >> 6;
-               
+
                pen.x += text_info.glyphs[text_info.length].advance.x;
                pen.x += double_to_d6(render_context.hspacing);
                pen.y += text_info.glyphs[text_info.length].advance.y;
-               
+
                previous = code;
 
                text_info.glyphs[text_info.length].symbol = code;
@@ -1915,23 +2008,23 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                render_context.effect_timing = 0;
                render_context.effect_skip_timing = 0;
        }
-       
+
        if (text_info.length == 0) {
                // no valid symbols in the event; this can be smth like {comment}
                free_render_context();
                return 1;
        }
-       
+
        // depends on glyph x coordinates being monotonous, so it should be done before line wrap
        process_karaoke_effects();
-       
+
        // alignments
        alignment = render_context.alignment;
        halign = alignment & 3;
        valign = alignment & 12;
 
-       MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; 
-       MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; 
+       MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL;
+       MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR;
        MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV;
 
        if (render_context.evt_type != EVENT_HSCROLL) {
@@ -1971,12 +2064,12 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        } else { // render_context.evt_type == EVENT_HSCROLL
                measure_text();
        }
-       
+
        // determing text bounding box
        compute_string_bbox(&text_info, &bbox);
-       
+
        // determine device coordinates for text
-       
+
        // x coordinate for everything except positioned events
        if (render_context.evt_type == EVENT_NORMAL ||
            render_context.evt_type == EVENT_VSCROLL) {
@@ -2019,15 +2112,15 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %f, %f\n", render_context.pos_x, render_context.pos_y);
                get_base_point(bbox, alignment, &base_x, &base_y);
                device_x = x2scr_pos(render_context.pos_x) - base_x;
-               device_y = y2scr(render_context.pos_y) - base_y;
+               device_y = y2scr_pos(render_context.pos_y) - base_y;
        }
-       
+
        // fix clip coordinates (they depend on alignment)
-       render_context.clip_x0 = x2scr(render_context.clip_x0);
-       render_context.clip_x1 = x2scr(render_context.clip_x1);
        if (render_context.evt_type == EVENT_NORMAL ||
            render_context.evt_type == EVENT_HSCROLL ||
            render_context.evt_type == EVENT_VSCROLL) {
+               render_context.clip_x0 = x2scr(render_context.clip_x0);
+               render_context.clip_x1 = x2scr(render_context.clip_x1);
                if (valign == VALIGN_TOP) {
                        render_context.clip_y0 = y2scr_top(render_context.clip_y0);
                        render_context.clip_y1 = y2scr_top(render_context.clip_y1);
@@ -2039,14 +2132,16 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        render_context.clip_y1 = y2scr_sub(render_context.clip_y1);
                }
        } else if (render_context.evt_type == EVENT_POSITIONED) {
-               render_context.clip_y0 = y2scr(render_context.clip_y0);
-               render_context.clip_y1 = y2scr(render_context.clip_y1);
+               render_context.clip_x0 = x2scr_pos(render_context.clip_x0);
+               render_context.clip_x1 = x2scr_pos(render_context.clip_x1);
+               render_context.clip_y0 = y2scr_pos(render_context.clip_y0);
+               render_context.clip_y1 = y2scr_pos(render_context.clip_y1);
        }
 
        // calculate rotation parameters
        {
                FT_Vector center;
-               
+
                if (render_context.have_origin) {
                        center.x = x2scr(render_context.org_x);
                        center.y = y2scr(render_context.org_y);
@@ -2074,6 +2169,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        for (i = 0; i < text_info.length; ++i)
                get_bitmap_glyph(text_info.glyphs + i);
 
+       memset(event_images, 0, sizeof(*event_images));
        event_images->top = device_y - d6_to_int(text_info.lines[0].asc);
        event_images->height = d6_to_int(text_info.height);
        event_images->detect_collisions = render_context.detect_collisions;
@@ -2082,7 +2178,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        event_images->imgs = render_text(&text_info, device_x, device_y);
 
        free_render_context();
-       
+
        return 0;
 }
 
@@ -2104,6 +2200,7 @@ static void ass_reconfigure(ass_renderer_t* priv)
        priv->render_id = ++last_render_id;
        ass_glyph_cache_reset();
        ass_bitmap_cache_reset();
+       ass_composite_cache_reset();
        ass_free_images(priv->prev_images_root);
        priv->prev_images_root = 0;
 }
@@ -2207,7 +2304,7 @@ static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long n
 
        if (track->n_events == 0)
                return 1; // nothing to do
-       
+
        frame_context.ass_priv = priv;
        frame_context.width = global_settings->frame_width;
        frame_context.height = global_settings->frame_height;
@@ -2223,10 +2320,13 @@ static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long n
        frame_context.time = now;
 
        ass_lazy_track_init();
-       
+
        frame_context.font_scale = global_settings->font_size_coeff *
                                   frame_context.orig_height / frame_context.track->PlayResY;
-       frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
+       if (frame_context.track->ScaledBorderAndShadow)
+               frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
+       else
+               frame_context.border_scale = 1.;
 
        frame_context.font_scale_x = 1.;
 
@@ -2330,7 +2430,7 @@ static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir)
        fixed[*cnt].b = s->b + shift;
        (*cnt)++;
        qsort(fixed, *cnt, sizeof(segment_t), cmp_segment);
-       
+
        return shift;
 }
 
@@ -2385,7 +2485,7 @@ static void fix_collisions(event_images_t* imgs, int cnt)
                        priv->top = imgs[i].top;
                        priv->height = imgs[i].height;
                }
-               
+
        }
 }
 
@@ -2458,7 +2558,7 @@ ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long lon
        int i, cnt, rc;
        event_images_t* last;
        ass_image_t** tail;
-       
+
        // init frame
        rc = ass_start_frame(priv, track, now);
        if (rc != 0)
@@ -2504,7 +2604,7 @@ ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long lon
 
        if (detect_change)
                *detect_change = ass_detect_change(priv);
-       
+
        // free the previous image list
        ass_free_images(priv->prev_images_root);
        priv->prev_images_root = 0;