From c80f332798238731e1ddf1b541748f3d5c8030f3 Mon Sep 17 00:00:00 2001 From: "Dr.Smile" Date: Mon, 20 May 2019 00:48:26 +0300 Subject: Consolidate and quantize all transformations This commit defers all outline transformations until rasterization stage. Combined transformation is then quantized and used as bitmap key. That should improve performance of slow animations. Also caching of initial and stroked outlines and bitmaps is now separate in preparation to proper error estimation for stroker stage. Note that Z-clipping for perspective transformations is now done differently compared to VSFilter. That clipping is mostly safety feature to protect from overflows and divisions by zero and is almost never triggered in real-world subtitles. --- libass/ass_bitmap.c | 33 +- libass/ass_bitmap.h | 12 - libass/ass_cache.c | 118 +++---- libass/ass_cache.h | 30 +- libass/ass_cache_template.h | 62 +--- libass/ass_outline.c | 44 +-- libass/ass_outline.h | 4 +- libass/ass_render.c | 839 +++++++++++++++++++++++++------------------- libass/ass_render.h | 14 +- 9 files changed, 578 insertions(+), 578 deletions(-) (limited to 'libass') diff --git a/libass/ass_bitmap.c b/libass/ass_bitmap.c index cc40ed6..3e3a5a0 100644 --- a/libass/ass_bitmap.c +++ b/libass/ass_bitmap.c @@ -200,6 +200,9 @@ Bitmap *outline_to_bitmap(ASS_Renderer *render_priv, return NULL; } + if (rst->bbox.x_min > rst->bbox.x_max || rst->bbox.y_min > rst->bbox.y_max) + return NULL; + if (bord < 0 || bord > INT_MAX / 2) return NULL; if (rst->bbox.x_max > INT_MAX - 63 || rst->bbox.y_max > INT_MAX - 63) @@ -434,36 +437,6 @@ int be_padding(int be) return FFMAX(128 - be, 0); } -bool outline_to_bitmap2(ASS_Renderer *render_priv, ASS_Outline *outline, - ASS_Outline *border1, ASS_Outline *border2, - Bitmap **bm_g, Bitmap **bm_o) -{ - assert(bm_g && bm_o); - *bm_g = *bm_o = NULL; - - if (outline && !outline->n_points) - outline = NULL; - if (border1 && !border1->n_points) - border1 = NULL; - if (border2 && !border2->n_points) - border2 = NULL; - - if (outline) { - *bm_g = outline_to_bitmap(render_priv, outline, NULL, 1); - if (!*bm_g) - return false; - } - - if (border1 || border2) { - *bm_o = outline_to_bitmap(render_priv, border1, border2, 1); - if (!*bm_o) { - return false; - } - } - - return true; -} - /** * \brief Add two bitmaps together at a given position * Uses additive blending, clipped to [0,255]. Pure C implementation. diff --git a/libass/ass_bitmap.h b/libass/ass_bitmap.h index e919cd5..ca7ea32 100644 --- a/libass/ass_bitmap.h +++ b/libass/ass_bitmap.h @@ -108,18 +108,6 @@ Bitmap *outline_to_bitmap(ASS_Renderer *render_priv, void ass_synth_blur(const BitmapEngine *engine, int opaque_box, int be, double blur_radius, Bitmap *bm_g, Bitmap *bm_o); -/** - * \brief perform glyph rendering - * \param outline original glyph - * \param border1 inside "border" outline, produced by stroker - * \param border2 outside "border" outline, produced by stroker - * \param bm_g out: pointer to the bitmap of original glyph is returned here - * \param bm_o out: pointer to the bitmap of border glyph is returned here - */ -bool outline_to_bitmap2(ASS_Renderer *render_priv, ASS_Outline *outline, - ASS_Outline *border1, ASS_Outline *border2, - Bitmap **bm_g, Bitmap **bm_o); - int be_padding(int be); void be_blur_pre(uint8_t *buf, intptr_t w, intptr_t h, intptr_t stride); diff --git a/libass/ass_cache.c b/libass/ass_cache.c index 3f2cb19..b92e6d9 100644 --- a/libass/ass_cache.c +++ b/libass/ass_cache.c @@ -92,66 +92,22 @@ const CacheDesc font_cache_desc = { // bitmap cache -static uint32_t bitmap_hash(void *key, uint32_t hval) -{ - BitmapHashKey *k = key; - switch (k->type) { - case BITMAP_OUTLINE: - return outline_bitmap_hash(&k->u, hval); - case BITMAP_CLIP: - return clip_bitmap_hash(&k->u, hval); - default: - return hval; - } -} - -static bool bitmap_compare(void *a, void *b) -{ - BitmapHashKey *ak = a; - BitmapHashKey *bk = b; - if (ak->type != bk->type) - return false; - switch (ak->type) { - case BITMAP_OUTLINE: - return outline_bitmap_compare(&ak->u, &bk->u); - case BITMAP_CLIP: - return clip_bitmap_compare(&ak->u, &bk->u); - default: - return false; - } -} - static bool bitmap_key_move(void *dst, void *src) { - BitmapHashKey *d = dst, *s = src; - if (!dst) { - if (s->type == BITMAP_OUTLINE) - ass_cache_dec_ref(s->u.outline.outline); - return true; - } - memcpy(dst, src, sizeof(BitmapHashKey)); - if (s->type != BITMAP_CLIP) - return true; - d->u.clip.text = strdup(s->u.clip.text); - return d->u.clip.text; + BitmapHashKey *k = src; + if (dst) + memcpy(dst, src, sizeof(BitmapHashKey)); + else + ass_cache_dec_ref(k->outline); + return true; } static void bitmap_destruct(void *key, void *value) { BitmapHashValue *v = value; BitmapHashKey *k = key; - if (v->bm) - ass_free_bitmap(v->bm); - if (v->bm_o) - ass_free_bitmap(v->bm_o); - switch (k->type) { - case BITMAP_OUTLINE: - ass_cache_dec_ref(k->u.outline.outline); - break; - case BITMAP_CLIP: - free(k->u.clip.text); - break; - } + ass_free_bitmap(v->bm); + ass_cache_dec_ref(k->outline); } size_t ass_bitmap_construct(void *key, void *value, void *priv); @@ -173,7 +129,8 @@ static uint32_t composite_hash(void *key, uint32_t hval) CompositeHashKey *k = key; hval = filter_hash(&k->filter, hval); for (size_t i = 0; i < k->bitmap_count; i++) { - hval = fnv_32a_buf(&k->bitmaps[i].image, sizeof(k->bitmaps[i].image), hval); + hval = fnv_32a_buf(&k->bitmaps[i].image, sizeof(k->bitmaps[i].image), hval); + hval = fnv_32a_buf(&k->bitmaps[i].image_o, sizeof(k->bitmaps[i].image_o), hval); hval = fnv_32a_buf(&k->bitmaps[i].x, sizeof(k->bitmaps[i].x), hval); hval = fnv_32a_buf(&k->bitmaps[i].y, sizeof(k->bitmaps[i].y), hval); } @@ -187,7 +144,8 @@ static bool composite_compare(void *a, void *b) if (ak->bitmap_count != bk->bitmap_count) return false; for (size_t i = 0; i < ak->bitmap_count; i++) { - if (ak->bitmaps[i].image != bk->bitmaps[i].image || + if (ak->bitmaps[i].image != bk->bitmaps[i].image || + ak->bitmaps[i].image_o != bk->bitmaps[i].image_o || ak->bitmaps[i].x != bk->bitmaps[i].x || ak->bitmaps[i].y != bk->bitmaps[i].y) return false; @@ -202,8 +160,10 @@ static bool composite_key_move(void *dst, void *src) return true; } CompositeHashKey *k = src; - for (size_t i = 0; i < k->bitmap_count; i++) + for (size_t i = 0; i < k->bitmap_count; i++) { ass_cache_dec_ref(k->bitmaps[i].image); + ass_cache_dec_ref(k->bitmaps[i].image_o); + } free(k->bitmaps); return true; } @@ -212,14 +172,13 @@ static void composite_destruct(void *key, void *value) { CompositeHashValue *v = value; 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); - for (size_t i = 0; i < k->bitmap_count; i++) + ass_free_bitmap(v->bm); + ass_free_bitmap(v->bm_o); + ass_free_bitmap(v->bm_s); + for (size_t i = 0; i < k->bitmap_count; i++) { ass_cache_dec_ref(k->bitmaps[i].image); + ass_cache_dec_ref(k->bitmaps[i].image_o); + } free(k->bitmaps); } @@ -245,9 +204,10 @@ static uint32_t outline_hash(void *key, uint32_t hval) return glyph_hash(&k->u, hval); case OUTLINE_DRAWING: return drawing_hash(&k->u, hval); - default: - // unused, added to disable warning - return outline_common_hash(&k->u, hval); + case OUTLINE_BORDER: + return border_hash(&k->u, hval); + default: // OUTLINE_BOX + return hval; } } @@ -262,9 +222,10 @@ static bool outline_compare(void *a, void *b) return glyph_compare(&ak->u, &bk->u); case OUTLINE_DRAWING: return drawing_compare(&ak->u, &bk->u); - default: - // unused, added to disable warning - return outline_common_compare(&ak->u, &bk->u); + case OUTLINE_BORDER: + return border_compare(&ak->u, &bk->u); + default: // OUTLINE_BOX + return true; } } @@ -277,19 +238,21 @@ static bool outline_key_move(void *dst, void *src) return true; } memcpy(dst, src, sizeof(OutlineHashKey)); - if (s->type != OUTLINE_DRAWING) - return true; - d->u.drawing.text = strdup(s->u.drawing.text); - return d->u.drawing.text; + if (s->type == OUTLINE_DRAWING) { + d->u.drawing.text = strdup(s->u.drawing.text); + return d->u.drawing.text; + } + if (s->type == OUTLINE_BORDER) + ass_cache_inc_ref(s->u.border.outline); + return true; } static void outline_destruct(void *key, void *value) { OutlineHashValue *v = value; OutlineHashKey *k = key; - outline_free(&v->outline); - outline_free(&v->border[0]); - outline_free(&v->border[1]); + outline_free(&v->outline[0]); + outline_free(&v->outline[1]); switch (k->type) { case OUTLINE_GLYPH: ass_cache_dec_ref(k->u.glyph.font); @@ -297,6 +260,11 @@ static void outline_destruct(void *key, void *value) case OUTLINE_DRAWING: free(k->u.drawing.text); break; + case OUTLINE_BORDER: + ass_cache_dec_ref(k->u.border.outline); + break; + default: // OUTLINE_BOX + break; } } diff --git a/libass/ass_cache.h b/libass/ass_cache.h index 453cefa..93e5e11 100644 --- a/libass/ass_cache.h +++ b/libass/ass_cache.h @@ -30,9 +30,7 @@ typedef struct cache Cache; // cache values typedef struct { - bool valid; - Bitmap *bm; // the actual bitmaps - Bitmap *bm_o; + Bitmap *bm; // the actual bitmap } BitmapHashValue; typedef struct { @@ -43,11 +41,10 @@ typedef struct { typedef struct { bool valid; - ASS_Outline outline; - ASS_Outline border[2]; - ASS_Rect bbox_scaled; // bbox after scaling, but before rotation - int advance; // 26.6, advance distance to the next outline in line - int asc, desc; // ascender/descender + ASS_Outline outline[2]; + ASS_Rect cbox; // bounding box of all control points + int advance; // 26.6, advance distance to the next outline in line + int asc, desc; // ascender/descender } OutlineHashValue; typedef struct { @@ -71,27 +68,18 @@ typedef struct outline_hash_key { enum { OUTLINE_GLYPH, OUTLINE_DRAWING, + OUTLINE_BORDER, + OUTLINE_BOX, } type; union { GlyphHashKey glyph; DrawingHashKey drawing; - OutlineCommonKey common; + BorderHashKey border; } u; } OutlineHashKey; -typedef struct bitmap_hash_key { - enum { - BITMAP_OUTLINE, - BITMAP_CLIP, - } type; - union { - OutlineBitmapHashKey outline; - ClipMaskHashKey clip; - } u; -} BitmapHashKey; - typedef struct { - BitmapHashValue *image; + BitmapHashValue *image, *image_o; int x, y; } BitmapRef; diff --git a/libass/ass_cache_template.h b/libass/ass_cache_template.h index 2bb0228..0c4ffdc 100644 --- a/libass/ass_cache_template.h +++ b/libass/ass_cache_template.h @@ -51,26 +51,14 @@ // describes an outline bitmap -START(outline_bitmap, outline_bitmap_hash_key) +START(bitmap, bitmap_hash_key) GENERIC(OutlineHashValue *, outline) - GENERIC(int, frx) // signed 10.22 - GENERIC(int, fry) // signed 10.22 - GENERIC(int, frz) // signed 10.22 - GENERIC(int, fax) // signed 16.16 - GENERIC(int, fay) // signed 16.16 - // shift vector that was added to glyph before applying rotation - // = 0, if frx = fry = frx = 0 - // = (glyph base point) - (rotation origin), otherwise - GENERIC(int, shift_x) - GENERIC(int, shift_y) - VECTOR(advance) // subpixel shift vector -END(OutlineBitmapHashKey) - -// describe a clip mask bitmap -START(clip_bitmap, clip_bitmap_hash_key) - GENERIC(int, scale) - STRING(text) -END(ClipMaskHashKey) + // quantized transform matrix + VECTOR(offset) + VECTOR(matrix_x) + VECTOR(matrix_y) + VECTOR(matrix_z) +END(BitmapHashKey) START(glyph_metrics, glyph_metrics_hash_key) GENERIC(ASS_Font *, font) @@ -79,25 +67,8 @@ START(glyph_metrics, glyph_metrics_hash_key) GENERIC(int, glyph_index) END(GlyphMetricsHashKey) -// common outline data -START(outline_common, outline_common_hash_key) - GENERIC(unsigned, scale_x) // 16.16 - GENERIC(unsigned, scale_y) // 16.16 - VECTOR(outline) // border width, 26.6 - GENERIC(unsigned, border_style) - GENERIC(int, scale_fix) // 16.16 - GENERIC(int, advance) // 26.6 -END(OutlineCommonKey) - // describes an outline glyph START(glyph, glyph_hash_key) - GENERIC(unsigned, scale_x) // 16.16 - GENERIC(unsigned, scale_y) // 16.16 - VECTOR(outline) // border width, 26.6 - GENERIC(unsigned, border_style) - GENERIC(int, scale_fix) // 16.16 - GENERIC(int, advance) // 26.6 - GENERIC(ASS_Font *, font) GENERIC(double, size) // font size GENERIC(int, face_index) @@ -109,18 +80,19 @@ END(GlyphHashKey) // describes an outline drawing START(drawing, drawing_hash_key) - GENERIC(unsigned, scale_x) // 16.16 - GENERIC(unsigned, scale_y) // 16.16 - VECTOR(outline) // border width, 26.6 - GENERIC(unsigned, border_style) - GENERIC(int, scale_fix) // 16.16 - GENERIC(int, advance) // 26.6 - - GENERIC(int, pbo) - GENERIC(int, scale) STRING(text) END(DrawingHashKey) +// describes an offset outline +START(border, border_hash_key) + GENERIC(OutlineHashValue *, outline) + // outline is scaled by 2^scale_ord_x|y before stroking + // to keep stoker error in allowable range + GENERIC(int, scale_ord_x) + GENERIC(int, scale_ord_y) + VECTOR(border) // border size in STROKER_ACCURACY units +END(BorderHashKey) + // describes post-combining effects START(filter, filter_desc) GENERIC(int, flags) diff --git a/libass/ass_outline.c b/libass/ass_outline.c index 1ed94b8..109e09a 100644 --- a/libass/ass_outline.c +++ b/libass/ass_outline.c @@ -287,43 +287,15 @@ bool outline_close_contour(ASS_Outline *outline) } -void outline_translate(const ASS_Outline *outline, int32_t dx, int32_t dy) -{ - for (size_t i = 0; i < outline->n_points; i++) { - outline->points[i].x += dx; - outline->points[i].y += dy; - } -} - -void outline_adjust(const ASS_Outline *outline, double scale_x, int32_t dx, int32_t dy) -{ - int32_t mul = lrint(scale_x * 0x10000); - if (mul == 0x10000) { - outline_translate(outline, dx, dy); - return; - } - for (size_t i = 0; i < outline->n_points; i++) { - int32_t x = (int64_t) outline->points[i].x * mul >> 16; - outline->points[i].x = x + dx; - outline->points[i].y += dy; - } -} - -void outline_get_cbox(const ASS_Outline *outline, ASS_Rect *cbox) +/* + * \brief Update bounding box of control points. + */ +void outline_update_cbox(const ASS_Outline *outline, ASS_Rect *cbox) { - if (!outline->n_points) { - cbox->x_min = cbox->x_max = 0; - cbox->y_min = cbox->y_max = 0; - return; - } - cbox->x_min = cbox->x_max = outline->points[0].x; - cbox->y_min = cbox->y_max = outline->points[0].y; - for (size_t i = 1; i < outline->n_points; i++) { - cbox->x_min = FFMIN(cbox->x_min, outline->points[i].x); - cbox->x_max = FFMAX(cbox->x_max, outline->points[i].x); - cbox->y_min = FFMIN(cbox->y_min, outline->points[i].y); - cbox->y_max = FFMAX(cbox->y_max, outline->points[i].y); - } + for (size_t i = 0; i < outline->n_points; i++) + rectangle_update(cbox, + outline->points[i].x, outline->points[i].y, + outline->points[i].x, outline->points[i].y); } diff --git a/libass/ass_outline.h b/libass/ass_outline.h index 85faea9..0a45589 100644 --- a/libass/ass_outline.h +++ b/libass/ass_outline.h @@ -96,9 +96,7 @@ bool outline_add_point(ASS_Outline *outline, ASS_Vector pt, char segment); bool outline_add_segment(ASS_Outline *outline, char segment); bool outline_close_contour(ASS_Outline *outline); -void outline_translate(const ASS_Outline *outline, int32_t dx, int32_t dy); -void outline_adjust(const ASS_Outline *outline, double scale_x, int32_t dx, int32_t dy); -void outline_get_cbox(const ASS_Outline *outline, ASS_Rect *cbox); +void outline_update_cbox(const ASS_Outline *outline, ASS_Rect *cbox); bool outline_stroke(ASS_Outline *result, ASS_Outline *result1, const ASS_Outline *path, int xbord, int ybord, int eps); diff --git a/libass/ass_render.c b/libass/ass_render.c index 8e5014e..4badaad 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -34,7 +34,10 @@ #define MAX_BITMAPS_INITIAL 16 #define MAX_SUB_BITMAPS_INITIAL 64 #define SUBPIXEL_MASK 63 -#define SUBPIXEL_ACCURACY 7 +#define STROKER_PRECISION 16 // stroker error in integer units, unrelated to final accuracy +#define RASTERIZER_PRECISION 16 // rasterizer spline approximation error in 1/64 pixel units +#define POSITION_PRECISION 8.0 // rough estimate of transform error in 1/64 pixel units +#define MAX_PERSP_SCALE 16.0 ASS_Renderer *ass_renderer_init(ASS_Library *library) @@ -75,7 +78,8 @@ ASS_Renderer *ass_renderer_init(ASS_Library *library) priv->engine = &ass_bitmap_engine_c; #endif - if (!rasterizer_init(&priv->rasterizer, priv->engine->tile_order, 16)) { + if (!rasterizer_init(&priv->rasterizer, priv->engine->tile_order, + RASTERIZER_PRECISION)) { FT_Done_FreeType(ft); goto ass_init_exit; } @@ -442,6 +446,197 @@ render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y, return tail; } +static bool quantize_transform(double m[3][3], BitmapHashKey *key) +{ + // Full transform: + // x_out = (m_xx * x + m_xy * y + m_xz) / z, + // y_out = (m_yx * x + m_yy * y + m_yz) / z, + // z = m_zx * x + m_zy * y + m_zz. + + const double max_val = 1000000; + + const ASS_Rect *bbox = &key->outline->cbox; + double x0 = (bbox->x_min + bbox->x_max) / 2.0; + double y0 = (bbox->y_min + bbox->y_max) / 2.0; + double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64; + double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64; + + // Change input coordinates' origin to (x0, y0), + // after that transformation x:[-dx, dx], y:[-dy, dy], + // max|x| = dx and max|y| = dy. + for (int i = 0; i < 3; i++) + m[i][2] += m[i][0] * x0 + m[i][1] * y0; + + if (m[2][2] <= 0) + return false; + + double w = 1 / m[2][2]; + // Transformed center of bounding box + double center[2] = { m[0][2] * w, m[1][2] * w }; + // Change output coordinates' origin to center, + // m_xz and m_yz is skipped as it becomes 0 and no longer needed. + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + m[i][j] -= m[2][j] * center[i]; + + int32_t qr[2]; + for (int i = 0; i < 2; i++) { + center[i] /= POSITION_PRECISION; + if (!(fabs(center[i]) < max_val)) + return false; + qr[i] = lrint(center[i]); + } + + // Minimal bounding box z coordinate + double z0 = m[2][2] - fabs(m[2][0]) * dx - fabs(m[2][1]) * dy; + // z0 clamped to z_center / MAX_PERSP_SCALE to mitigate problems with small z + w = 1.0 / POSITION_PRECISION / FFMAX(z0, m[2][2] / MAX_PERSP_SCALE); + double mul[2] = { dx * w, dy * w }; // 1 / q_x, 1 / q_y + + // z0 = m_zz - |m_zx| * dx - |m_zy| * dy, + // m_zz = z0 + |m_zx| * dx + |m_zy| * dy, + // z = m_zx * x + m_zy * y + m_zz + // = m_zx * (x + sign(m_zx) * dx) + m_zy * (y + sign(m_zy) * dy) + z0. + + // D(f)--absolute value of error in quantity f + // as function of error in matrix coefficients, i. e. D(m_??). + // Error in constant is zero, i. e. D(dx) = D(dy) = D(z0) = 0. + // In the following calculation errors are considered small + // and second- and higher-order terms are dropped. + // That approximation is valid as long as glyph dimensions are larger than couple of pixels. + // Therefore standard relations for derivatives can be used for D(?): + // D(A * B) <= D(A) * max|B| + max|A| * D(B), + // D(1 / C) <= D(C) * max|1 / C^2|. + + // D(x_out) = D((m_xx * x + m_xy * y) / z) + // <= D(m_xx * x + m_xy * y) * max|1 / z| + max|m_xx * x + m_xy * y| * D(1 / z) + // <= (D(m_xx) * dx + D(m_xy) * dy) / z0 + (|m_xx| * dx + |m_xy| * dy) * D(z) / z0^2, + // D(y_out) = D((m_yx * x + m_yy * y) / z) + // <= D(m_yx * x + m_yy * y) * max|1 / z| + max|m_yx * x + m_yy * y| * D(1 / z) + // <= (D(m_yx) * dx + D(m_yy) * dy) / z0 + (|m_yx| * dx + |m_yy| * dy) * D(z) / z0^2, + // |m_xx| * dx + |m_xy| * dy = x_lim, + // |m_yx| * dx + |m_yy| * dy = y_lim, + // D(z) <= 2 * (D(m_zx) * dx + D(m_zy) * dy), + // D(x_out) <= (D(m_xx) * dx + D(m_xy) * dy) / z0 + // + 2 * (D(m_zx) * dx + D(m_zy) * dy) * x_lim / z0^2, + // D(y_out) <= (D(m_yx) * dx + D(m_yy) * dy) / z0 + // + 2 * (D(m_zx) * dx + D(m_zy) * dy) * y_lim / z0^2. + + // To estimate acceptable error in matrix coefficient + // set error in all other coefficients to zero and solve system + // D(x_out) <= ACCURACY & D(y_out) <= ACCURACY for desired D(m_??). + // ACCURACY here is some part of total error, i. e. ACCURACY ~ POSITION_PRECISION. + // Note that POSITION_PRECISION isn't total error, it's convenient constant. + // True error can be up to several POSITION_PRECISION. + + // Quantization steps (ACCURACY ~ POSITION_PRECISION): + // D(m_xx), D(m_yx) ~ q_x = POSITION_PRECISION * z0 / dx, + // D(m_xy), D(m_yy) ~ q_y = POSITION_PRECISION * z0 / dy, + // qm_xx = round(m_xx / q_x), qm_xy = round(m_xy / q_y), + // qm_yx = round(m_yx / q_x), qm_yy = round(m_yy / q_y). + + int32_t qm[3][2]; + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) { + double val = m[i][j] * mul[j]; + if (!(fabs(val) < max_val)) + return false; + qm[i][j] = lrint(val); + } + + // x_lim = |m_xx| * dx + |m_xy| * dy + // ~= |qm_xx| * q_x * dx + |qm_xy| * q_y * dy + // = (|qm_xx| + |qm_xy|) * POSITION_PRECISION * z0, + // y_lim = |m_yx| * dx + |m_yy| * dy + // ~= |qm_yx| * q_x * dx + |qm_yy| * q_y * dy + // = (|qm_yx| + |qm_yy|) * POSITION_PRECISION * z0, + // max(x_lim, y_lim) / z0 ~= w + // = max(|qm_xx| + |qm_xy|, |qm_yx| + |qm_yy|) * POSITION_PRECISION. + + // Quantization steps (ACCURACY ~ 2 * POSITION_PRECISION): + // D(m_zx) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dx ~= q_zx = q_x / w, + // D(m_zy) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dy ~= q_zy = q_y / w, + // qm_zx = round(m_zx / q_zx), qm_zy = round(m_zy / q_zy). + + int32_t qmx = abs(qm[0][0]) + abs(qm[0][1]); + int32_t qmy = abs(qm[1][0]) + abs(qm[1][1]); + w = POSITION_PRECISION * FFMAX(qmx, qmy); + mul[0] *= w; + mul[1] *= w; + + for (int j = 0; j < 2; j++) { + double val = m[2][j] * mul[j]; + if (!(fabs(val) < max_val)) + return false; + qm[2][j] = lrint(val); + } + + key->offset.x = qr[0]; key->offset.y = qr[1]; + key->matrix_x.x = qm[0][0]; key->matrix_x.y = qm[0][1]; + key->matrix_y.x = qm[1][0]; key->matrix_y.y = qm[1][1]; + key->matrix_z.x = qm[2][0]; key->matrix_z.y = qm[2][1]; + return true; +} + +static void restore_transform(double m[3][3], const BitmapHashKey *key) +{ + const ASS_Rect *bbox = &key->outline->cbox; + double x0 = (bbox->x_min + bbox->x_max) / 2.0; + double y0 = (bbox->y_min + bbox->y_max) / 2.0; + double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64; + double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64; + + // Arbitrary scale has chosen so that z0 = 1 + double q_x = POSITION_PRECISION / dx; + double q_y = POSITION_PRECISION / dy; + m[0][0] = key->matrix_x.x * q_x; + m[0][1] = key->matrix_x.y * q_y; + m[1][0] = key->matrix_y.x * q_x; + m[1][1] = key->matrix_y.y * q_y; + + int32_t qmx = abs(key->matrix_x.x) + abs(key->matrix_x.y); + int32_t qmy = abs(key->matrix_y.x) + abs(key->matrix_y.y); + double scale_z = 1.0 / POSITION_PRECISION / FFMAX(qmx, qmy); + m[2][0] = key->matrix_z.x * q_x * scale_z; // qm_zx * q_zx + m[2][1] = key->matrix_z.y * q_y * scale_z; // qm_zy * q_zy + + m[0][2] = m[1][2] = 0; + m[2][2] = 1 + fabs(m[2][0]) * dx + fabs(m[2][1]) * dy; + m[2][2] = FFMIN(m[2][2], MAX_PERSP_SCALE); + + double center[2] = { + key->offset.x * POSITION_PRECISION, + key->offset.y * POSITION_PRECISION, + }; + for (int i = 0; i < 2; i++) + for (int j = 0; j < 3; j++) + m[i][j] += m[2][j] * center[i]; + + for (int i = 0; i < 3; i++) + m[i][2] -= m[i][0] * x0 + m[i][1] * y0; +} + +static BitmapHashValue *get_bitmap(ASS_Renderer *render_priv, OutlineHashValue *outline, + const ASS_Transform *trans, const double mat[3][3]) +{ + if (!outline) + return NULL; + + double m[3][3]; + for (int i = 0; i < 3; i++) { + m[i][0] = mat[i][0] * trans->scale.x; + m[i][1] = mat[i][1] * trans->scale.y; + m[i][2] = mat[i][0] * trans->offset.x + mat[i][1] * trans->offset.y + mat[i][2]; + } + + BitmapHashKey k; + k.outline = outline; + if (quantize_transform(m, &k)) + return ass_cache_get(render_priv->cache.bitmap_cache, &k, render_priv); + ass_cache_dec_ref(outline); + return NULL; +} + // Calculate bitmap memory footprint static inline size_t bitmap_size(Bitmap *bm) { @@ -459,13 +654,30 @@ static void blend_vector_clip(ASS_Renderer *render_priv, if (!render_priv->state.clip_drawing_text) return; - // Get mask from cache - BitmapHashKey key; - key.type = BITMAP_CLIP; - key.u.clip.scale = render_priv->state.clip_drawing_scale; - key.u.clip.text = render_priv->state.clip_drawing_text; + OutlineHashKey key; + key.type = OUTLINE_DRAWING; + key.u.drawing.text = render_priv->state.clip_drawing_text; + OutlineHashValue *outline = + ass_cache_get(render_priv->cache.outline_cache, &key, render_priv); + if (!outline || !outline->valid) { + ass_cache_dec_ref(outline); + return; + } + + ASS_Transform trans; + double w = render_priv->font_scale / (1 << (render_priv->state.clip_drawing_scale - 1)); + trans.scale.x = render_priv->font_scale_x * w; + trans.scale.y = w; + + trans.offset.x = int_to_d6(render_priv->settings.left_margin); + trans.offset.y = int_to_d6(render_priv->settings.top_margin); - BitmapHashValue *val = ass_cache_get(render_priv->cache.bitmap_cache, &key, render_priv); + double m[3][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + BitmapHashValue *val = get_bitmap(render_priv, outline, &trans, m); if (!val) return; @@ -880,104 +1092,6 @@ static void free_render_context(ASS_Renderer *render_priv) text_info->length = 0; } -/* - * Replace the outline of a glyph by a contour which makes up a simple - * opaque rectangle. - */ -static void draw_opaque_box(ASS_Renderer *render_priv, - double scale_x, double scale_y, - int asc, int desc, ASS_Outline *ol, - int adv, int sx, int sy) -{ - // to avoid gaps - sx = FFMAX(64, sx); - sy = FFMAX(64, sy); - - // Emulate the WTFish behavior of VSFilter, i.e. double-scale - // the sizes of the opaque box. - adv *= scale_x; - sx *= scale_x; - sy *= scale_y; - desc *= scale_y; - desc += asc * (scale_y - 1.0); - - ASS_Vector points[4] = { - { .x = -sx, .y = -asc - sy }, - { .x = adv + sx, .y = -asc - sy }, - { .x = adv + sx, .y = desc + sy }, - { .x = -sx, .y = desc + sy }, - }; - - const char segments[4] = { - OUTLINE_LINE_SEGMENT, - OUTLINE_LINE_SEGMENT, - OUTLINE_LINE_SEGMENT, - OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END - }; - - ol->n_points = ol->n_segments = 0; - if (!outline_alloc(ol, 4, 4)) - return; - for (int i = 0; i < 4; i++) { - ol->points[ol->n_points++] = points[i]; - ol->segments[ol->n_segments++] = segments[i]; - } -} - -/** - * \brief Prepare glyph hash - */ -static void -fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key, - GlyphInfo *info) -{ - OutlineCommonKey *common = &outline_key->u.common; - common->scale_x = double_to_d16(info->scale_x); - common->scale_y = double_to_d16(info->scale_y); - common->outline.x = double_to_d6(info->border_x * priv->border_scale); - common->outline.y = double_to_d6(info->border_y * priv->border_scale); - common->border_style = info->border_style; - // following fields only matter for opaque box borders (see draw_opaque_box), - // so for normal borders, maximize cache utility by ignoring them - if (info->border_style == 3) { - common->scale_fix = double_to_d16(info->scale_fix); - common->advance = info->hspacing_scaled; - if (priv->settings.shaper != ASS_SHAPING_SIMPLE && !info->drawing_text) - common->advance += info->advance.x; - } else { - common->scale_fix = 0; - common->advance = 0; - } - - if (info->drawing_text) { - outline_key->type = OUTLINE_DRAWING; - DrawingHashKey *key = &outline_key->u.drawing; - key->text = info->drawing_text; - key->pbo = info->drawing_pbo; - key->scale = info->drawing_scale; - } else { - outline_key->type = OUTLINE_GLYPH; - GlyphHashKey *key = &outline_key->u.glyph; - key->font = info->font; - key->size = info->font_size; - key->face_index = info->face_index; - key->glyph_index = info->glyph_index; - key->bold = info->bold; - key->italic = info->italic; - key->flags = info->flags; - } -} - -/** - * \brief Prepare combined-bitmap hash - */ -static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info) -{ - hk->filter = info->filter; - hk->bitmap_count = info->bitmap_count; - hk->bitmaps = info->bitmaps; -} - /** * \brief Get normal and outline (border) glyphs * \param info out: struct filled with extracted data @@ -988,27 +1102,121 @@ static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info) static void get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info) { - memset(&info->hash_key, 0, sizeof(info->hash_key)); + OutlineHashValue *val; + ASS_DVector scale, offset = {0}; + int32_t asc, desc; OutlineHashKey key; - fill_glyph_hash(priv, &key, info); - OutlineHashValue *val = ass_cache_get(priv->cache.outline_cache, &key, priv); - if (!val || !val->valid) { - ass_cache_dec_ref(val); - return; + if (info->drawing_text) { + key.type = OUTLINE_DRAWING; + key.u.drawing.text = info->drawing_text; + val = ass_cache_get(priv->cache.outline_cache, &key, priv); + if (!val || !val->valid) { + ass_cache_dec_ref(val); + return; + } + + double w = priv->font_scale / (1 << (info->drawing_scale - 1)); + scale.x = info->scale_x * w; + scale.y = info->scale_y * w; + desc = 64 * info->drawing_pbo; + asc = val->asc - desc; + + offset.y = -asc * scale.y; + } else { + key.type = OUTLINE_GLYPH; + GlyphHashKey *k = &key.u.glyph; + k->font = info->font; + k->size = info->font_size; + k->face_index = info->face_index; + k->glyph_index = info->glyph_index; + k->bold = info->bold; + k->italic = info->italic; + k->flags = info->flags; + + val = ass_cache_get(priv->cache.outline_cache, &key, priv); + if (!val || !val->valid) { + ass_cache_dec_ref(val); + return; + } + + scale.x = info->scale_x; + scale.y = info->scale_y; + asc = val->asc; + desc = val->desc; } - info->hash_key.u.outline.outline = val; - info->outline = &val->outline; - info->border[0] = &val->border[0]; - info->border[1] = &val->border[1]; - info->bbox = val->bbox_scaled; + info->outline = val; + info->outline_transform.scale = scale; + info->outline_transform.offset = offset; + + info->bbox.x_min = lrint(val->cbox.x_min * scale.x + offset.x); + info->bbox.y_min = lrint(val->cbox.y_min * scale.y + offset.y); + info->bbox.x_max = lrint(val->cbox.x_max * scale.x + offset.x); + info->bbox.y_max = lrint(val->cbox.y_max * scale.y + offset.y); + if (info->drawing_text || priv->settings.shaper == ASS_SHAPING_SIMPLE) { - info->cluster_advance.x = info->advance.x = val->advance; + info->cluster_advance.x = info->advance.x = lrint(val->advance * scale.x); info->cluster_advance.y = info->advance.y = 0; } - info->asc = val->asc; - info->desc = val->desc; + info->asc = lrint(asc * scale.y); + info->desc = lrint(desc * scale.y); + + if (info->border_style == 3) { + key.type = OUTLINE_BOX; + val = ass_cache_get(priv->cache.outline_cache, &key, priv); + if (!val || !val->valid) { + ass_cache_dec_ref(val); + return; + } + + double w = 64 * priv->border_scale; + ASS_DVector bord = { info->border_x * w, info->border_y * w }; + double width = info->hspacing_scaled + info->advance.x; + double height = info->asc + info->desc; + + ASS_DVector orig_scale; + orig_scale.x = info->scale_x * info->scale_fix; + orig_scale.y = info->scale_y * info->scale_fix; + + // Emulate the WTFish behavior of VSFilter, i.e. double-scale + // the sizes of the opaque box. + bord.x *= orig_scale.x; + bord.y *= orig_scale.y; + width *= orig_scale.x; + height *= orig_scale.y; + + // to avoid gaps + bord.x = FFMAX(64, bord.x); + bord.y = FFMAX(64, bord.y); + + scale.x = (width + 2 * bord.x) / 64; + scale.y = (height + 2 * bord.y) / 64; + offset.x = -bord.x; + offset.y = -bord.y - info->asc; + } else { + key.type = OUTLINE_BORDER; + BorderHashKey *k = &key.u.border; + k->outline = val; + + // XXX: account for full transformation instead of only scale + scale.x = frexp(scale.x, &k->scale_ord_x); + scale.y = frexp(scale.y, &k->scale_ord_y); + + double w = priv->border_scale * (64 / STROKER_PRECISION); + k->border.x = lrint(w * info->border_x / scale.x); + k->border.y = lrint(w * info->border_y / scale.y); + + val = ass_cache_get(priv->cache.outline_cache, &key, priv); + if (!val || !val->valid) { + ass_cache_dec_ref(val); + return; + } + } + + info->border = val; + info->border_transform.scale = scale; + info->border_transform.offset = offset; } size_t ass_outline_construct(void *key, void *value, void *priv) @@ -1018,101 +1226,114 @@ size_t ass_outline_construct(void *key, void *value, void *priv) OutlineHashValue *v = value; memset(v, 0, sizeof(*v)); - OutlineCommonKey *common = &outline_key->u.common; - double scale_x = d16_to_double(common->scale_x); - double scale_y = d16_to_double(common->scale_y); - - ASS_DVector scale, offset = {0}; - if (outline_key->type == OUTLINE_DRAWING) { - DrawingHashKey *k = &outline_key->u.drawing; - - ASS_Rect bbox; - if (!ass_drawing_parse(&v->outline, &bbox, k->text, render_priv->library)) - return 1; - - double w = render_priv->font_scale / (1 << (k->scale - 1)); - scale.x = scale_x * w; - scale.y = scale_y * w; + switch (outline_key->type) { + case OUTLINE_GLYPH: + { + GlyphHashKey *k = &outline_key->u.glyph; + ass_face_set_size(k->font->faces[k->face_index], k->size); + FT_Glyph glyph = + ass_font_get_glyph(k->font, k->face_index, k->glyph_index, + render_priv->settings.hinting, k->flags); + if (glyph != NULL) { + FT_Outline *src = &((FT_OutlineGlyph) glyph)->outline; + if (!outline_convert(&v->outline[0], src)) + return 1; + v->advance = d16_to_d6(glyph->advance.x); + FT_Done_Glyph(glyph); + ass_font_get_asc_desc(k->font, k->face_index, + &v->asc, &v->desc); + } + break; + } + case OUTLINE_DRAWING: + { + ASS_Rect bbox; + const char *text = outline_key->u.drawing.text; + if (!ass_drawing_parse(&v->outline[0], &bbox, text, render_priv->library)) + return 1; - v->advance = (bbox.x_max - bbox.x_min) * scale.x; + v->advance = bbox.x_max - bbox.x_min; + v->asc = bbox.y_max - bbox.y_min; + v->desc = 0; + break; + } + case OUTLINE_BORDER: + { + BorderHashKey *k = &outline_key->u.border; + if (!k->border.x && !k->border.y) + break; + if (!k->outline->outline[0].n_points) + break; - double pbo = (double) k->pbo / (1 << (k->scale - 1)); - v->desc = double_to_d6(pbo * scale_y * render_priv->font_scale); - v->asc = (bbox.y_max - bbox.y_min) * scale.y - v->desc; - offset.y = -v->asc; - } else { - GlyphHashKey *k = &outline_key->u.glyph; - ass_face_set_size(k->font->faces[k->face_index], k->size); - FT_Glyph glyph = - ass_font_get_glyph(k->font, k->face_index, k->glyph_index, - render_priv->settings.hinting, k->flags); - if (glyph != NULL) { - FT_Outline *src = &((FT_OutlineGlyph) glyph)->outline; - if (!outline_convert(&v->outline, src)) + ASS_Outline src; + if (!outline_copy(&src, &k->outline->outline[0])) return 1; - if (render_priv->settings.shaper == ASS_SHAPING_SIMPLE) - v->advance = d16_to_d6(glyph->advance.x * scale_x); - FT_Done_Glyph(glyph); - ass_font_get_asc_desc(k->font, k->face_index, - &v->asc, &v->desc); - v->asc *= scale_y; - v->desc *= scale_y; - } - scale.x = scale_x; - scale.y = scale_y; - } - v->valid = true; + for (size_t i = 0; i < src.n_points; i++) { + // that's equivalent to src.points[i].x << k->scale_ord_x, + // but works even for negative coordinate and/or shift amount + src.points[i].x = src.points[i].x * ((int64_t) 1 << (32 + k->scale_ord_x)) >> 32; + src.points[i].y = src.points[i].y * ((int64_t) 1 << (32 + k->scale_ord_y)) >> 32; + } - ASS_Outline *ol = &v->outline; - for (size_t i = 0; i < ol->n_points; i++) { - ol->points[i].x = lrint(ol->points[i].x * scale.x + offset.x); - ol->points[i].y = lrint(ol->points[i].y * scale.y + offset.y); - } - outline_get_cbox(&v->outline, &v->bbox_scaled); - - if (common->border_style == 3) { - int advance = common->advance; - if (render_priv->settings.shaper == ASS_SHAPING_SIMPLE || - outline_key->type == OUTLINE_DRAWING) - advance += v->advance; - - double scale_fix = d16_to_double(common->scale_fix); - draw_opaque_box(priv, scale_x * scale_fix, scale_y * scale_fix, - v->asc, v->desc, &v->border[0], advance, - common->outline.x, common->outline.y); - - } else if (v->outline.n_points && common->scale_x && common->scale_y) { - const int eps = 16; - if (common->outline.x >= eps || common->outline.y >= eps) { - outline_alloc(&v->border[0], 2 * v->outline.n_points, 2 * v->outline.n_segments); - outline_alloc(&v->border[1], 2 * v->outline.n_points, 2 * v->outline.n_segments); - if (!v->border[0].max_points || !v->border[1].max_points || - !outline_stroke(&v->border[0], &v->border[1], - &v->outline, common->outline.x, common->outline.y, eps)) { + outline_alloc(&v->outline[0], 2 * src.n_points, 2 * src.n_segments); + outline_alloc(&v->outline[1], 2 * src.n_points, 2 * src.n_segments); + if (!v->outline[0].max_points || !v->outline[1].max_points || + !outline_stroke(&v->outline[0], &v->outline[1], &src, + k->border.x * STROKER_PRECISION, + k->border.y * STROKER_PRECISION, + STROKER_PRECISION)) { ass_msg(render_priv->library, MSGL_WARN, "Cannot stroke outline"); - outline_free(&v->border[0]); - outline_free(&v->border[1]); + outline_free(&v->outline[0]); + outline_free(&v->outline[1]); + outline_free(&src); + return 1; } + outline_free(&src); + break; + } + case OUTLINE_BOX: + { + ASS_Outline *ol = &v->outline[0]; + if (!outline_alloc(ol, 4, 4)) + return 1; + ol->points[0].x = ol->points[3].x = 0; + ol->points[1].x = ol->points[2].x = 64; + ol->points[0].y = ol->points[1].y = 0; + ol->points[2].y = ol->points[3].y = 64; + ol->segments[0] = OUTLINE_LINE_SEGMENT; + ol->segments[1] = OUTLINE_LINE_SEGMENT; + ol->segments[2] = OUTLINE_LINE_SEGMENT; + ol->segments[3] = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END; + ol->n_points = ol->n_segments = 4; + break; } + default: + return 1; } + + rectangle_reset(&v->cbox); + outline_update_cbox(&v->outline[0], &v->cbox); + outline_update_cbox(&v->outline[1], &v->cbox); + if (v->cbox.x_min > v->cbox.x_max || v->cbox.y_min > v->cbox.y_max) + v->cbox.x_min = v->cbox.y_min = v->cbox.x_max = v->cbox.y_max = 0; + v->valid = true; return 1; } /** - * \brief Calculate transform matrix for transform_3d() + * \brief Calculate outline transformation matrix */ -static void -calc_transform_matrix(ASS_Vector shift, - double frx, double fry, double frz, - double fax, double fay, double scale, - int yshift, double m[3][3]) +static void calc_transform_matrix(ASS_Renderer *render_priv, + GlyphInfo *info, double m[3][3]) { - double sx = -sin(frx), cx = cos(frx); - double sy = sin(fry), cy = cos(fry); - double sz = -sin(frz), cz = cos(frz); + double sx = -sin(info->frx), cx = cos(info->frx); + double sy = sin(info->fry), cy = cos(info->fry); + double sz = -sin(info->frz), cz = cos(info->frz); - double x1[3] = { 1, fax, shift.x + fax * yshift }; - double y1[3] = { fay, 1, shift.y }; + double fax = info->fax * info->scale_x / info->scale_y; + double fay = info->fay * info->scale_y / info->scale_x; + double x1[3] = { 1, fax, info->shift.x + info->asc * fax }; + double y1[3] = { fay, 1, info->shift.y }; double x2[3], y2[3]; for (int i = 0; i < 3; i++) { @@ -1132,48 +1353,17 @@ calc_transform_matrix(ASS_Vector shift, z4[i] = x2[i] * sy + z3[i] * cy; } - double dist = 20000 * scale; + double dist = 20000 * render_priv->blur_scale; + z4[2] += dist; + + double scale_x = dist * render_priv->font_scale_x; + double offs_x = info->bitmap_advance.x - info->shift.x * render_priv->font_scale_x; + double offs_y = info->bitmap_advance.y - info->shift.y; for (int i = 0; i < 3; i++) { - m[0][i] = x4[i] * dist; - m[1][i] = y3[i] * dist; + m[0][i] = z4[i] * offs_x + x4[i] * scale_x; + m[1][i] = z4[i] * offs_y + y3[i] * dist; m[2][i] = z4[i]; } - m[2][2] += dist; -} - -/** - * \brief Apply 3d transformation to several objects - * \param shift FreeType vector - * \param glyph FreeType glyph - * \param glyph2 FreeType glyph - * \param frx x-axis rotation angle - * \param fry y-axis rotation angle - * \param frz z-axis rotation angle - * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it. - */ -static void -transform_3d(ASS_Vector shift, ASS_Outline *outline, int n_outlines, - double frx, double fry, double frz, double fax, double fay, - double scale, int yshift) -{ - if (frx == 0 && fry == 0 && frz == 0 && fax == 0 && fay == 0) - return; - - double m[3][3]; - calc_transform_matrix(shift, frx, fry, frz, fax, fay, scale, yshift, m); - - for (int i = 0; i < n_outlines; i++) { - ASS_Vector *p = outline[i].points; - for (size_t j = 0; j < outline[i].n_points; ++j) { - double v[3]; - for (int k = 0; k < 3; k++) - v[k] = m[k][0] * p[j].x + m[k][1] * p[j].y + m[k][2]; - - double w = 1 / FFMAX(v[2], 1000); - p[j].x = lrint(v[0] * w) - shift.x; - p[j].y = lrint(v[1] * w) - shift.y; - } - } } /** @@ -1182,97 +1372,56 @@ transform_3d(ASS_Vector shift, ASS_Outline *outline, int n_outlines, * Tries to get glyph bitmaps from bitmap cache. * If they can't be found, they are generated by rotating and rendering the glyph. * After that, bitmaps are added to the cache. - * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow). + * They are returned in info->image (glyph), info->image_o (outline). */ static void get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info) { if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip) { - ass_cache_dec_ref(info->hash_key.u.outline.outline); + ass_cache_dec_ref(info->outline); + ass_cache_dec_ref(info->border); return; } - BitmapHashValue *val = ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key, render_priv); - if (!val || !val->valid) - info->symbol = 0; - info->image = val; + double m[3][3]; + calc_transform_matrix(render_priv, info, m); + + info->image = get_bitmap(render_priv, info->outline, &info->outline_transform, m); + info->image_o = get_bitmap(render_priv, info->border, &info->border_transform, m); } size_t ass_bitmap_construct(void *key, void *value, void *priv) { ASS_Renderer *render_priv = priv; - BitmapHashKey *bitmap_key = key; + BitmapHashKey *k = key; BitmapHashValue *v = value; - v->bm = v->bm_o = NULL; - v->valid = false; - - const size_t hdr = sizeof(BitmapHashKey) + sizeof(BitmapHashValue); - - if (bitmap_key->type == BITMAP_CLIP) { - ASS_Rect cbox; - ASS_Outline outline; - if (!ass_drawing_parse(&outline, &cbox, bitmap_key->u.clip.text, render_priv->library)) { - ass_msg(render_priv->library, MSGL_WARN, - "Clip vector parsing failed. Skipping."); - return hdr; - } - - ASS_DVector scale; - double w = render_priv->font_scale / (1 << (bitmap_key->u.clip.scale - 1)); - scale.x = render_priv->font_scale_x * w; - scale.y = w; + v->bm = NULL; - ASS_DVector offset; - offset.x = int_to_d6(render_priv->settings.left_margin); - offset.y = int_to_d6(render_priv->settings.top_margin); + double m[3][3]; + restore_transform(m, k); - ASS_Outline *ol = &outline; - for (size_t i = 0; i < ol->n_points; i++) { - ol->points[i].x = lrint(ol->points[i].x * scale.x + offset.x); - ol->points[i].y = lrint(ol->points[i].y * scale.y + offset.y); - } + ASS_Outline outline[2]; + outline_copy(&outline[0], &k->outline->outline[0]); + outline_copy(&outline[1], &k->outline->outline[1]); - v->bm = outline_to_bitmap(render_priv, &outline, NULL, 1); - outline_free(&outline); - v->valid = !!v->bm; + for (int i = 0; i < 2; i++) { + ASS_Vector *p = outline[i].points; + for (size_t j = 0; j < outline[i].n_points; ++j) { + double v[3]; + for (int k = 0; k < 3; k++) + v[k] = m[k][0] * p[j].x + m[k][1] * p[j].y + m[k][2]; - return bitmap_size(v->bm) + hdr; + double w = 1 / FFMAX(v[2], 0.1); + p[j].x = lrint(v[0] * w); + p[j].y = lrint(v[1] * w); + } } - OutlineBitmapHashKey *k = &bitmap_key->u.outline; - if (!k->outline->valid) - return hdr; - - const int n_outlines = 3; - ASS_Outline outline[n_outlines]; - outline_copy(&outline[0], &k->outline->outline); - outline_copy(&outline[1], &k->outline->border[0]); - outline_copy(&outline[2], &k->outline->border[1]); - - // calculating rotation shift vector (from rotation origin to the glyph basepoint) - ASS_Vector shift = { k->shift_x, k->shift_y }; - double scale_x = render_priv->font_scale_x; - double fax_scaled = d16_to_double(k->fax); - double fay_scaled = d16_to_double(k->fay); - - // apply rotation - // use blur_scale because, like blurs, VSFilter forgets to scale this - transform_3d(shift, outline, n_outlines, - d22_to_double(k->frx), d22_to_double(k->fry), d22_to_double(k->frz), - fax_scaled, fay_scaled, render_priv->blur_scale, k->outline->asc); - - // PAR correction scaling + subpixel shift - for (int i = 0; i < n_outlines; i++) - outline_adjust(&outline[i], scale_x, k->advance.x, k->advance.y); + v->bm = outline_to_bitmap(render_priv, &outline[0], &outline[1], 1); + outline_free(&outline[0]); + outline_free(&outline[1]); - // render glyph - v->valid = outline_to_bitmap2(render_priv, - &outline[0], &outline[1], &outline[2], - &v->bm, &v->bm_o); - for (int i = 0; i < n_outlines; i++) - outline_free(&outline[i]); - - return bitmap_size(v->bm) + bitmap_size(v->bm_o) + hdr; + return bitmap_size(v->bm) + sizeof(BitmapHashKey) + sizeof(BitmapHashValue); } /** @@ -1575,20 +1724,6 @@ static void get_base_point(ASS_DRect *bbox, int alignment, double *bx, double *b } } -/** - * Prepare bitmap hash key of a glyph - */ -static void -fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info, - OutlineBitmapHashKey *hash_key) -{ - hash_key->frx = rot_key(info->frx); - hash_key->fry = rot_key(info->fry); - hash_key->frz = rot_key(info->frz); - hash_key->fax = double_to_d16(info->fax * info->scale_x / info->scale_y); - hash_key->fay = double_to_d16(info->fay * info->scale_y / info->scale_x); -} - /** * \brief Adjust the glyph's font size and scale factors to ensure smooth * scaling and handle pathological font sizes. The main problem here is @@ -1860,10 +1995,6 @@ static void preliminary_layout(ASS_Renderer *render_priv) cluster_pen.x += info->advance.x; cluster_pen.y += info->advance.y; - // fill bitmap hash - info->hash_key.type = BITMAP_OUTLINE; - fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline); - info = info->next; } info = render_priv->text_info.glyphs + i; @@ -2006,15 +2137,8 @@ static void calculate_rotation_params(ASS_Renderer *render_priv, ASS_DRect *bbox for (int i = 0; i < text_info->length; i++) { GlyphInfo *info = text_info->glyphs + i; while (info) { - OutlineBitmapHashKey *key = &info->hash_key.u.outline; - - if (key->frx || key->fry || key->frz || key->fax || key->fay) { - key->shift_x = info->pos.x + double_to_d6(device_x - center.x); - key->shift_y = info->pos.y + double_to_d6(device_y - center.y); - } else { - key->shift_x = 0; - key->shift_y = 0; - } + info->shift.x = info->pos.x + double_to_d6(device_x - center.x); + info->shift.y = info->pos.y + double_to_d6(device_y - center.y); info = info->next; } } @@ -2037,17 +2161,17 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, GlyphInfo *info = text_info->glyphs + i; if (info->linebreak) linebreak = 1; if (info->skip) { - for (; info; info = info->next) - ass_cache_dec_ref(info->hash_key.u.outline.outline); + for (; info; info = info->next) { + ass_cache_dec_ref(info->outline); + ass_cache_dec_ref(info->border); + } continue; } for (; info; info = info->next) { - OutlineBitmapHashKey *key = &info->hash_key.u.outline; - info->pos.x = double_to_d6(device_x + d6_to_double(info->pos.x) * render_priv->font_scale_x); info->pos.y = double_to_d6(device_y) + info->pos.y; - key->advance.x = info->pos.x & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY); - key->advance.y = info->pos.y & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY); + info->bitmap_advance.x = info->pos.x & SUBPIXEL_MASK; + info->bitmap_advance.y = info->pos.y & SUBPIXEL_MASK; int x = info->pos.x >> 6, y = info->pos.y >> 6; get_bitmap_glyph(render_priv, info); @@ -2058,6 +2182,7 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, size_t new_size = 2 * text_info->max_bitmaps; if (!ASS_REALLOC_ARRAY(text_info->combined_bitmaps, new_size)) { ass_cache_dec_ref(info->image); + ass_cache_dec_ref(info->image_o); continue; } text_info->max_bitmaps = new_size; @@ -2072,14 +2197,14 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, current_info->filter.flags = 0; if (info->border_style == 3) - current_info->filter.flags |= FILTER_BORDER_STYLE_3; + current_info->filter.flags |= FILTER_BORDER_STYLE_3 | FILTER_DRAW_SHADOW; if (info->border_x || info->border_y) - current_info->filter.flags |= FILTER_NONZERO_BORDER; + current_info->filter.flags |= FILTER_NONZERO_BORDER | FILTER_DRAW_SHADOW; if (info->shadow_x || info->shadow_y) current_info->filter.flags |= FILTER_NONZERO_SHADOW; // VSFilter compatibility: invisible fill and no border? // In this case no shadow is supposed to be rendered. - if (info->border[0] || info->border[1] || (info->c[0] & 0xFF) != 0xFF) + if ((info->c[0] & 0xFF) != 0xFF) current_info->filter.flags |= FILTER_DRAW_SHADOW; current_info->filter.be = info->be; @@ -2095,6 +2220,7 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, current_info->bitmaps = malloc(MAX_SUB_BITMAPS_INITIAL * sizeof(BitmapRef)); if (!current_info->bitmaps) { ass_cache_dec_ref(info->image); + ass_cache_dec_ref(info->image_o); continue; } current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL; @@ -2105,6 +2231,7 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, if (!info->image || !current_info) { ass_cache_dec_ref(info->image); + ass_cache_dec_ref(info->image_o); continue; } @@ -2112,11 +2239,13 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, size_t new_size = 2 * current_info->max_bitmap_count; if (!ASS_REALLOC_ARRAY(current_info->bitmaps, new_size)) { ass_cache_dec_ref(info->image); + ass_cache_dec_ref(info->image_o); continue; } current_info->max_bitmap_count = new_size; } - current_info->bitmaps[current_info->bitmap_count].image = info->image; + current_info->bitmaps[current_info->bitmap_count].image = info->image; + current_info->bitmaps[current_info->bitmap_count].image_o = info->image_o; current_info->bitmaps[current_info->bitmap_count].x = x; current_info->bitmaps[current_info->bitmap_count].y = y; current_info->bitmap_count++; @@ -2134,7 +2263,9 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv, } CompositeHashKey key; - fill_composite_hash(&key, info); + key.filter = info->filter; + key.bitmap_count = info->bitmap_count; + key.bitmaps = info->bitmaps; CompositeHashValue *val = ass_cache_get(render_priv->cache.composite_cache, &key, render_priv); if (!val) continue; @@ -2171,13 +2302,13 @@ size_t ass_composite_construct(void *key, void *value, void *priv) BitmapRef *last = NULL, *last_o = NULL; for (int i = 0; i < k->bitmap_count; i++) { BitmapRef *ref = &k->bitmaps[i]; - if (ref->image->bm) { + if (ref->image && ref->image->bm) { rectangle_combine(&rect, ref->image->bm, ref->x, ref->y); last = ref; n_bm++; } - if (ref->image->bm_o) { - rectangle_combine(&rect_o, ref->image->bm_o, ref->x, ref->y); + if (ref->image_o && ref->image_o->bm) { + rectangle_combine(&rect_o, ref->image_o->bm, ref->x, ref->y); last_o = ref; n_bm_o++; } @@ -2199,6 +2330,8 @@ size_t ass_composite_construct(void *key, void *value, void *priv) dst->left = rect.x_min - bord; dst->top = rect.y_min - bord; for (int i = 0; i < k->bitmap_count; i++) { + if (!k->bitmaps[i].image) + continue; Bitmap *src = k->bitmaps[i].image->bm; if (!src) continue; @@ -2214,7 +2347,7 @@ size_t ass_composite_construct(void *key, void *value, void *priv) } } if (!bord && n_bm_o == 1) { - v->bm_o = copy_bitmap(render_priv->engine, last_o->image->bm_o); + v->bm_o = copy_bitmap(render_priv->engine, last_o->image_o->bm); if (v->bm_o) { v->bm_o->left += last_o->x; v->bm_o->top += last_o->y; @@ -2228,7 +2361,9 @@ size_t ass_composite_construct(void *key, void *value, void *priv) dst->left = rect_o.x_min - bord; dst->top = rect_o.y_min - bord; for (int i = 0; i < k->bitmap_count; i++) { - Bitmap *src = k->bitmaps[i].image->bm_o; + if (!k->bitmaps[i].image_o) + continue; + Bitmap *src = k->bitmaps[i].image_o->bm; if (!src) continue; int x = k->bitmaps[i].x + src->left - dst->left; diff --git a/libass/ass_render.h b/libass/ass_render.h index 539d619..f8d84f9 100644 --- a/libass/ass_render.h +++ b/libass/ass_render.h @@ -113,6 +113,10 @@ typedef struct { CompositeHashValue *image; } CombinedBitmapInfo; +typedef struct { + ASS_DVector scale, offset; +} ASS_Transform; + // describes a glyph // GlyphInfo and TextInfo are used for text centering and word-wrapping operations typedef struct glyph_info { @@ -130,8 +134,9 @@ typedef struct glyph_info { char *drawing_text; int drawing_scale; int drawing_pbo; - ASS_Outline *outline; - ASS_Outline *border[2]; + OutlineHashValue *outline, *border; + ASS_Transform outline_transform; + ASS_Transform border_transform; ASS_Rect bbox; ASS_Vector pos; ASS_Vector offset; @@ -166,8 +171,9 @@ typedef struct glyph_info { int shape_run_id; - BitmapHashKey hash_key; - BitmapHashValue *image; + ASS_Vector shift; + ASS_Vector bitmap_advance; + BitmapHashValue *image, *image_o; // next glyph in this cluster struct glyph_info *next; -- cgit v1.2.3