diff --git a/aegisub/libass/ass.h b/aegisub/libass/ass.h index 0a03ec2a0..b92960a15 100644 --- a/aegisub/libass/ass.h +++ b/aegisub/libass/ass.h @@ -25,125 +25,239 @@ #include #include "ass_types.h" -/// Libass renderer object. Contents are private. +#define LIBASS_VERSION 0x00907000 + +/* Libass renderer object. Contents are private. */ typedef struct ass_renderer_s ass_renderer_t; -/// a linked list of images produced by ass renderer +/* A linked list of images produced by ass renderer. */ typedef struct ass_image_s { - int w, h; // bitmap width/height - int stride; // bitmap stride + int w, h; // Bitmap width/height + int stride; // Bitmap stride unsigned char *bitmap; // 1bpp stride*h alpha buffer - uint32_t color; // RGBA - int dst_x, dst_y; // bitmap placement inside the video frame + uint32_t color; // Bitmap color and alpha, RGBA + int dst_x, dst_y; // Bitmap placement inside the video frame - struct ass_image_s *next; // linked list + struct ass_image_s *next; // Next image, or NULL } ass_image_t; -/// Hinting type -typedef enum { ASS_HINTING_NONE = 0, +/* Hinting type. */ +typedef enum { + ASS_HINTING_NONE = 0, ASS_HINTING_LIGHT, ASS_HINTING_NORMAL, ASS_HINTING_NATIVE } ass_hinting_t; /** - * \brief initialize the library + * \brief Initialize the library. * \return library handle or NULL if failed */ ass_library_t *ass_library_init(void); /** - * \brief finalize the library + * \brief Finalize the library * \param priv library handle */ void ass_library_done(ass_library_t *); /** - * \brief set private font directory + * \brief Set private font directory. * It is used for saving embedded fonts and also in font lookup. + * + * \param priv library handle + * \param fonts_dir private directory for font extraction */ void ass_set_fonts_dir(ass_library_t *priv, const char *fonts_dir); +/** + * \brief Whether fonts should be extracted from track data. + * \param priv library handle + * \param extract whether to extract fonts + */ void ass_set_extract_fonts(ass_library_t *priv, int extract); +/** + * \brief Register style overrides with a library instance. + * The overrides should have the form [Style.]Param=Value, e.g. + * SomeStyle.Font=Arial + * ScaledBorderAndShadow=yes + * + * \param priv library handle + * \param list NULL-terminated list of strings + */ void ass_set_style_overrides(ass_library_t *priv, char **list); +/** + * \brief Explicitly process style overrides for a track. + * \param track track handle + */ void ass_process_force_style(ass_track_t *track); +/** + * \brief Register a callback for debug/info messages. + * If a callback is registered, it is called for every message emitted by + * libass. The callback receives a format string and a list of arguments, + * to be used for the printf family of functions. Additionally, a log level + * from 0 (FATAL errors) to 7 (verbose DEBUG) is passed. Usually, level 5 + * should be used by applications. + * If no callback is set, all messages level < 5 are printed to stderr, + * prefixed with [ass]. + * + * \param priv library handle + * \param msg_cb pointer to callback function + * \param data additional data, will be passed to callback + */ void ass_set_message_cb(ass_library_t *priv, void (*msg_cb)(int, char *, va_list *, void *), void *data); /** - * \brief initialize the renderer + * \brief Initialize the renderer. * \param priv library handle * \return renderer handle or NULL if failed */ ass_renderer_t *ass_renderer_init(ass_library_t *); /** - * \brief finalize the renderer + * \brief Finalize the renderer. * \param priv renderer handle */ void ass_renderer_done(ass_renderer_t *priv); +/** + * \brief Set the frame size in pixels, including margins. + * \param priv renderer handle + * \param w width + * \param h height + */ void ass_set_frame_size(ass_renderer_t *priv, int w, int h); + +/** + * \brief Set frame margins. These values may be negative if pan-and-scan + * is used. + * \param priv renderer handle + * \param t top margin + * \param b bottom margin + * \param l left margin + * \param r right margin + */ void ass_set_margins(ass_renderer_t *priv, int t, int b, int l, int r); + +/** + * \brief Whether margins should be used for placing regular events. + * \param priv renderer handle + * \param use whether to use the margins + */ void ass_set_use_margins(ass_renderer_t *priv, int use); + +/** + * \brief Set aspect ratio parameters. + * \param priv renderer handle + * \param ar physical aspect ratio + * \param par pixel ratio, e.g. width / height of the video + */ void ass_set_aspect_ratio(ass_renderer_t *priv, double ar, double par); + +/** + * \brief Set a fixed font scaling factor. + * \param priv renderer handle + * \param font_scale scaling factor, default is 1.0 + */ void ass_set_font_scale(ass_renderer_t *priv, double font_scale); + +/** + * \brief Set font hinting method. + * \param priv renderer handle + * \param ht hinting method + */ void ass_set_hinting(ass_renderer_t *priv, ass_hinting_t ht); + +/** + * \brief Set line spacing. Will not be scaled with frame size. + * \param priv renderer handle + * \param line_spacing line spacing in pixels + */ void ass_set_line_spacing(ass_renderer_t *priv, double line_spacing); /** - * \brief set font lookup defaults - * \param fc bool, use fontconfig? - * \param config path to fontconfig configuration file, or NULL. Only matters - * if fontconfig is used + * \brief Set font lookup defaults. + * \param fc whether to use fontconfig + * \param config path to fontconfig configuration file, or NULL. Only relevant + * if fontconfig is used. + * \param update whether fontconfig cache should be built/updated now. Only + * relevant if fontconfig is used. */ -int ass_set_fonts(ass_renderer_t *priv, const char *default_font, - const char *default_family, int fc, const char *config); +void ass_set_fonts(ass_renderer_t *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update); /** - * \brief render a frame, producing a list of ass_image_t - * \param priv library + * \brief Update/build font cache. This needs to be called if it was + * disabled when ass_set_fonts was set. + * + * \param priv renderer handle + * \return success + */ +int ass_fonts_update(ass_renderer_t *priv); + +/** + * \brief Set hard cache limits. Do not set, or set to zero, for reasonable + * defaults. + * + * \param priv renderer handle + * \param glyph_max maximum number of cached glyphs + * \param bitmap_max_size maximum bitmap cache size (in MB) + */ +void ass_set_cache_limits(ass_renderer_t *priv, int glyph_max, + int bitmap_max_size); + +/** + * \brief Render a frame, producing a list of ass_image_t. + * \param priv renderer handle * \param track subtitle track * \param now video timestamp in milliseconds + * \param detect_change will be set to 1 if a change occured compared + * to the last invocation */ ass_image_t *ass_render_frame(ass_renderer_t *priv, ass_track_t *track, long long now, int *detect_change); -// The following functions operate on track objects and do not need an ass_renderer // +/* + * The following functions operate on track objects and do not need + * an ass_renderer + */ /** - * \brief allocate a new empty track object + * \brief Allocate a new empty track object. + * \param library handle * \return pointer to empty track */ ass_track_t *ass_new_track(ass_library_t *); /** - * \brief deallocate track and all its child objects (styles and events) + * \brief Deallocate track and all its child objects (styles and events). * \param track track to deallocate */ void ass_free_track(ass_track_t *track); /** - * \brief allocate new style + * \brief Allocate new style. * \param track track * \return newly allocated style id */ int ass_alloc_style(ass_track_t *track); /** - * \brief allocate new event + * \brief Allocate new event. * \param track track * \return newly allocated event id */ int ass_alloc_event(ass_track_t *track); /** - * \brief delete a style + * \brief Delete a style. * \param track track * \param sid style id * Deallocates style data. Does not modify track->n_styles. @@ -151,7 +265,7 @@ int ass_alloc_event(ass_track_t *track); void ass_free_style(ass_track_t *track, int sid); /** - * \brief delete an event + * \brief Delete an event. * \param track track * \param eid event id * Deallocates event data. Does not modify track->n_events. @@ -167,7 +281,7 @@ void ass_free_event(ass_track_t *track, int eid); void ass_process_data(ass_track_t *track, char *data, int size); /** - * \brief Parse Codec Private section of subtitle stream + * \brief Parse Codec Private section of subtitle stream. * \param track target track * \param data string to parse * \param size length of data @@ -175,19 +289,22 @@ void ass_process_data(ass_track_t *track, char *data, int size); void ass_process_codec_private(ass_track_t *track, char *data, int size); /** - * \brief Parse a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary). + * \brief Parse a chunk of subtitle stream data. In Matroska, + * this contains exactly 1 event (or a commentary). * \param track track * \param data string to parse * \param size length of data * \param timecode starting time of the event (milliseconds) * \param duration duration of the event (milliseconds) -*/ + */ void ass_process_chunk(ass_track_t *track, char *data, int size, long long timecode, long long duration); /** * \brief Read subtitles from file. + * \param library library handle * \param fname file name + * \param codepage encoding (iconv format) * \return newly allocated track */ ass_track_t *ass_read_file(ass_library_t *library, char *fname, @@ -195,22 +312,25 @@ ass_track_t *ass_read_file(ass_library_t *library, char *fname, /** * \brief Read subtitles from memory. - * \param library libass library object + * \param library library handle * \param buf pointer to subtitles text * \param bufsize size of buffer - * \param codepage recode buffer contents from given codepage + * \param codepage encoding (iconv format) * \return newly allocated track */ ass_track_t *ass_read_memory(ass_library_t *library, char *buf, size_t bufsize, char *codepage); /** - * \brief read styles from file into already initialized track + * \brief Read styles from file into already initialized track. + * \param fname file name + * \param codepage encoding (iconv format) * \return 0 on success */ int ass_read_styles(ass_track_t *track, char *fname, char *codepage); /** * \brief Add a memory font. + * \param library library handle * \param name attachment name * \param data binary font data * \param data_size data size @@ -219,18 +339,20 @@ void ass_add_font(ass_library_t *library, char *name, char *data, int data_size); /** - * \brief Remove all fonts stored in ass_library object + * \brief Remove all fonts stored in an ass_library object. + * \param library library handle */ void ass_clear_fonts(ass_library_t *library); /** - * \brief Calculates timeshift from now to the start of some other subtitle event, depending on movement parameter + * \brief Calculates timeshift from now to the start of some other subtitle + * event, depending on movement parameter. * \param track subtitle track - * \param now current time, ms + * \param now current time in milliseconds * \param movement how many events to skip from the one currently displayed * +2 means "the one after the next", -1 means "previous" - * \return timeshift, ms + * \return timeshift in milliseconds */ long long ass_step_sub(ass_track_t *track, long long now, int movement); -#endif /* LIBASS_ASS_H */ +#endif /* LIBASS_ASS_H */ diff --git a/aegisub/libass/ass_cache.c b/aegisub/libass/ass_cache.c index 4c7a3fa53..524a1c83e 100644 --- a/aegisub/libass/ass_cache.c +++ b/aegisub/libass/ass_cache.c @@ -223,6 +223,12 @@ static void bitmap_hash_dtor(void *key, size_t key_size, void *value, void *cache_add_bitmap(hashmap_t *bitmap_cache, bitmap_hash_key_t *key, bitmap_hash_val_t *val) { + // Note: this is only an approximation + if (val->bm_o) + bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3; + else + bitmap_cache->cache_size += val->bm->w * val->bm->h * 3; + return hashmap_insert(bitmap_cache, key, val); } diff --git a/aegisub/libass/ass_cache.h b/aegisub/libass/ass_cache.h index 83c994386..e31d8cf68 100644 --- a/aegisub/libass/ass_cache.h +++ b/aegisub/libass/ass_cache.h @@ -45,6 +45,7 @@ typedef struct hashmap_s { hashmap_item_dtor_t item_dtor; // a destructor for hashmap key/value pairs hashmap_key_compare_t key_compare; hashmap_hash_t hash; + size_t cache_size; // stats int hit_count; int miss_count; diff --git a/aegisub/libass/ass_drawing.c b/aegisub/libass/ass_drawing.c index f75bbd9ad..0b270c1a1 100644 --- a/aegisub/libass/ass_drawing.c +++ b/aegisub/libass/ass_drawing.c @@ -106,7 +106,7 @@ static void drawing_prepare(ass_drawing_t *drawing) * \brief Finish a drawing. This only sets the horizontal advance according * to the glyph's bbox at the moment. */ -static void drawing_finish(ass_drawing_t *drawing) +static void drawing_finish(ass_drawing_t *drawing, int raw_mode) { int i, offset; FT_BBox bbox; @@ -127,6 +127,13 @@ static void drawing_finish(ass_drawing_t *drawing) printf("contour %d\n", ol->contours[i]); #endif + ass_msg(drawing->library, MSGL_V, + "Parsed drawing with %d points and %d contours", ol->n_points, + ol->n_contours); + + if (raw_mode) + return; + FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox); drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin); @@ -138,10 +145,6 @@ static void drawing_finish(ass_drawing_t *drawing) drawing->scale_y); for (i = 0; i < ol->n_points; i++) ol->points[i].y += offset; - - ass_msg(drawing->library, MSGL_V, - "Parsed drawing with %d points and %d contours", ol->n_points, - ol->n_contours); } /* @@ -361,7 +364,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, ass_drawing_t* drawing; drawing = calloc(1, sizeof(*drawing)); - drawing->text = malloc(DRAWING_INITIAL_SIZE); + drawing->text = calloc(1, DRAWING_INITIAL_SIZE); drawing->size = DRAWING_INITIAL_SIZE; drawing->ftlibrary = lib; @@ -381,6 +384,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, */ void ass_drawing_free(ass_drawing_t* drawing) { + FT_Done_Glyph((FT_Glyph) drawing->glyph); free(drawing->text); free(drawing); } @@ -411,7 +415,7 @@ void ass_drawing_hash(ass_drawing_t* drawing) /* * \brief Convert token list to outline. Calls the line and curve evaluators. */ -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode) { int started = 0; ass_drawing_token_t *token; @@ -474,9 +478,7 @@ FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) } } - drawing_finish(drawing); + drawing_finish(drawing, raw_mode); drawing_free_tokens(drawing->tokens); return &drawing->glyph; } - - diff --git a/aegisub/libass/ass_drawing.h b/aegisub/libass/ass_drawing.h index dfd68f01b..25cc4a721 100644 --- a/aegisub/libass/ass_drawing.h +++ b/aegisub/libass/ass_drawing.h @@ -72,6 +72,6 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, void ass_drawing_free(ass_drawing_t* drawing); void ass_drawing_add_char(ass_drawing_t* drawing, char symbol); void ass_drawing_hash(ass_drawing_t* drawing); -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing); +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode); #endif /* LIBASS_DRAWING_H */ diff --git a/aegisub/libass/ass_font.c b/aegisub/libass/ass_font.c index 34ae292c4..35d34cf49 100644 --- a/aegisub/libass/ass_font.c +++ b/aegisub/libass/ass_font.c @@ -268,8 +268,9 @@ void ass_font_get_asc_desc(ass_font_t *font, uint32_t ch, int *asc, for (i = 0; i < font->n_faces; ++i) { FT_Face face = font->faces[i]; if (FT_Get_Char_Index(face, ch)) { - *asc = face->size->metrics.ascender; - *desc = -face->size->metrics.descender; + int y_scale = face->size->metrics.y_scale; + *asc = FT_MulFix(face->ascender, y_scale); + *desc = FT_MulFix(-face->descender, y_scale); return; } } diff --git a/aegisub/libass/ass_fontconfig.c b/aegisub/libass/ass_fontconfig.c index d8d64e229..4eee1975f 100644 --- a/aegisub/libass/ass_fontconfig.c +++ b/aegisub/libass/ass_fontconfig.c @@ -427,11 +427,15 @@ static void process_fontdata(fc_instance_t *priv, ass_library_t *library, * \param ftlibrary freetype library object * \param family default font family * \param path default font path + * \param fc whether fontconfig should be used + * \param config path to a fontconfig configuration file, or NULL + * \param update whether the fontconfig cache should be built/updated * \return pointer to fontconfig private data */ fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc, const char *config) + const char *path, int fc, const char *config, + int update) { int rc; fc_instance_t *priv = calloc(1, sizeof(fc_instance_t)); @@ -444,20 +448,18 @@ fc_instance_t *fontconfig_init(ass_library_t *library, goto exit; } - if (config) { - priv->config = FcConfigCreate(); - rc = FcConfigParseAndLoad(priv->config, (unsigned char *)config, - FcTrue); + if (!config) + config = (char *) FcConfigFilename(NULL); + + priv->config = FcConfigCreate(); + rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue); + if (rc && update) { FcConfigBuildFonts(priv->config); - FcConfigSetCurrent(priv->config); - } else { - rc = FcInit(); - assert(rc); - priv->config = FcConfigGetCurrent(); } if (!rc || !priv->config) { ass_msg(library, MSGL_FATAL, "%s failed", "FcInitLoadConfigAndFonts"); + FcConfigDestroy(priv->config); goto exit; } @@ -507,13 +509,18 @@ fc_instance_t *fontconfig_init(ass_library_t *library, } priv->family_default = family ? strdup(family) : NULL; - exit: +exit: priv->path_default = path ? strdup(path) : NULL; priv->index_default = 0; return priv; } +int fontconfig_update(fc_instance_t *priv) +{ + return FcConfigBuildFonts(priv->config); +} + #else /* CONFIG_FONTCONFIG */ char *fontconfig_select(fc_instance_t *priv, const char *family, @@ -526,7 +533,8 @@ char *fontconfig_select(fc_instance_t *priv, const char *family, fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc) + const char *path, int fc, const char *config, + int update) { fc_instance_t *priv; @@ -540,6 +548,11 @@ fc_instance_t *fontconfig_init(ass_library_t *library, return priv; } +int fontconfig_update(fc_instance_t *priv) +{ + // Do nothing +} + #endif void fontconfig_done(fc_instance_t *priv) diff --git a/aegisub/libass/ass_fontconfig.h b/aegisub/libass/ass_fontconfig.h index 53441cce6..24c164d9d 100644 --- a/aegisub/libass/ass_fontconfig.h +++ b/aegisub/libass/ass_fontconfig.h @@ -23,6 +23,7 @@ #include #include "ass_types.h" +#include "ass.h" #include #include FT_FREETYPE_H @@ -34,11 +35,13 @@ typedef struct fc_instance_s fc_instance_t; fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc, const char *config); + const char *path, int fc, const char *config, + int update); char *fontconfig_select(ass_library_t *library, fc_instance_t *priv, const char *family, int treat_family_as_pattern, unsigned bold, unsigned italic, int *index, uint32_t code); void fontconfig_done(fc_instance_t *priv); +int fontconfig_update(fc_instance_t *priv); #endif /* LIBASS_FONTCONFIG_H */ diff --git a/aegisub/libass/ass_render.c b/aegisub/libass/ass_render.c index ae65119af..f86c73e95 100644 --- a/aegisub/libass/ass_render.c +++ b/aegisub/libass/ass_render.c @@ -44,6 +44,8 @@ #define MAX_BE 127 #define SUBPIXEL_MASK 63 #define SUBPIXEL_ACCURACY 7 // d6 mask for subpixel accuracy adjustment +#define GLYPH_CACHE_MAX 1000 +#define BITMAP_CACHE_MAX_SIZE 50 * 1048576; static int last_render_id = 0; @@ -59,6 +61,11 @@ typedef struct double_vector_s { double y; } double_vector_t; +typedef struct free_list_s { + void *object; + struct free_list_s *next; +} free_list_t; + typedef struct ass_settings_s { int frame_width; int frame_height; @@ -174,6 +181,8 @@ typedef struct render_context_s { double shadow_y; int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags ass_drawing_t *drawing; // current drawing + ass_drawing_t *clip_drawing;// clip vector + int clip_drawing_mode; // 0 = regular clip, 1 = inverse clip effect_t effect_type; int effect_timing; @@ -199,6 +208,8 @@ typedef struct cache_store_s { hashmap_t *glyph_cache; hashmap_t *bitmap_cache; hashmap_t *composite_cache; + size_t glyph_max; + size_t bitmap_max_size; } cache_store_t; struct ass_renderer_s { @@ -230,6 +241,9 @@ struct ass_renderer_s { render_context_t state; text_info_t text_info; cache_store_t cache; + + free_list_t *free_head; + free_list_t *free_tail; }; struct render_priv_s { @@ -309,6 +323,8 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library) priv->cache.bitmap_cache = ass_bitmap_cache_init(library); priv->cache.composite_cache = ass_composite_cache_init(library); priv->cache.glyph_cache = ass_glyph_cache_init(library); + priv->cache.glyph_max = GLYPH_CACHE_MAX; + priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; priv->text_info.max_lines = MAX_LINES_INITIAL; @@ -325,6 +341,28 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library) return priv; } +void ass_set_cache_limits(ass_renderer_t *render_priv, int glyph_max, + int bitmap_max) +{ + render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX; + render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max : + BITMAP_CACHE_MAX_SIZE; +} + +static void free_list_clear(ass_renderer_t *render_priv) +{ + if (render_priv->free_head) { + free_list_t *item = render_priv->free_head; + while(item) { + free_list_t *oi = item; + free(item->object); + item = item->next; + free(oi); + } + render_priv->free_head = NULL; + } +} + void ass_renderer_done(ass_renderer_t *render_priv) { ass_font_cache_done(render_priv->cache.font_cache); @@ -349,6 +387,8 @@ void ass_renderer_done(ass_renderer_t *render_priv) free(render_priv->settings.default_font); free(render_priv->settings.default_family); + + free_list_clear(render_priv); } /** @@ -663,6 +703,125 @@ render_overlap(ass_renderer_t *render_priv, ass_image_t **last_tail, cache_add_composite(render_priv->cache.composite_cache, &hk, &chv); } +static void free_list_add(ass_renderer_t *render_priv, void *object) +{ + if (!render_priv->free_head) { + render_priv->free_head = calloc(1, sizeof(free_list_t)); + render_priv->free_head->object = object; + render_priv->free_tail = render_priv->free_head; + } else { + free_list_t *l = calloc(1, sizeof(free_list_t)); + l->object = object; + render_priv->free_tail->next = l; + render_priv->free_tail = render_priv->free_tail->next; + } +} + +/** + * Iterate through a list of bitmaps and blend with clip vector, if + * applicable. The blended bitmaps are added to a free list which is freed + * at the start of a new frame. + */ +static void blend_vector_clip(ass_renderer_t *render_priv, + ass_image_t *head) +{ + FT_Glyph glyph; + FT_BitmapGlyph clip_bm; + ass_image_t *cur; + ass_drawing_t *drawing = render_priv->state.clip_drawing; + + if (!drawing) + return; + + // Rasterize it + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph); + FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); + clip_bm = (FT_BitmapGlyph) glyph; + clip_bm->top = -clip_bm->top; + + assert(clip_bm->bitmap.pitch >= 0); + + // Iterate through bitmaps and blend/clip them + for (cur = head; cur; cur = cur->next) { + int left, top, right, bottom, apos, bpos, y, x, w, h; + int ax, ay, aw, ah, as; + int bx, by, bw, bh, bs; + int aleft, atop, bleft, btop; + unsigned char *abuffer, *bbuffer, *nbuffer; + + abuffer = cur->bitmap; + bbuffer = clip_bm->bitmap.buffer; + ax = cur->dst_x; + ay = cur->dst_y; + aw = cur->w; + ah = cur->h; + as = cur->stride; + bx = clip_bm->left; + by = clip_bm->top; + bw = clip_bm->bitmap.width; + bh = clip_bm->bitmap.rows; + bs = clip_bm->bitmap.pitch; + + // 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); + aleft = left - ax; + atop = top - ay; + w = right - left; + h = bottom - top; + bleft = left - bx; + btop = top - by; + + if (render_priv->state.clip_drawing_mode) { + // Inverse clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + continue; + } + + // Allocate new buffer and add to free list + nbuffer = malloc(as * ah); + free_list_add(render_priv, nbuffer); + + // Blend together + memcpy(nbuffer, abuffer, as * ah); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); + } + } else { + // Regular clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + cur->w = cur->h = 0; + continue; + } + + // Allocate new buffer and add to free list + nbuffer = calloc(as, ah); + free_list_add(render_priv, nbuffer); + + // Blend together + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; + } + } + cur->bitmap = nbuffer; + } + + // Free clip vector and its bitmap, we don't need it anymore + FT_Done_Glyph(glyph); + ass_drawing_free(render_priv->state.clip_drawing); + render_priv->state.clip_drawing = 0; +} + /** * \brief Convert text_info_t struct to ass_image_t list * Splits glyphs in halves when needed (for \kf karaoke). @@ -760,6 +919,8 @@ static ass_image_t *render_text(ass_renderer_t *render_priv, int dst_x, } *tail = 0; + blend_vector_clip(render_priv, head); + return head; } @@ -1018,6 +1179,53 @@ interpolate_alpha(long long now, return a; } +#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} +#define skip(x) if (*p == (x)) ++p; else { return p; } +#define skipopt(x) if (*p == (x)) { ++p; } + +/** + * Parse a vector clip into an outline, using the proper scaling + * parameters. Translate it to correct for screen borders, if needed. + */ +static char *parse_vector_clip(ass_renderer_t *render_priv, char *p) +{ + int scale = 1; + int res = 0; + ass_drawing_t *drawing; + render_priv->state.clip_drawing = ass_drawing_new( + render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + drawing = render_priv->state.clip_drawing; + skipopt('('); + res = mystrtoi(&p, &scale); + skipopt(',') + if (!res) + scale = 1; + drawing->scale = scale; + drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale; + drawing->scale_y = render_priv->font_scale; + while (*p != ')' && *p != '}' && p != 0) + ass_drawing_add_char(drawing, *p++); + skipopt(')'); + ass_drawing_parse(drawing, 1); + // We need to translate the clip according to screen borders + if (render_priv->settings.left_margin != 0 || + render_priv->settings.top_margin != 0) { + FT_Vector trans = { + .x = int_to_d6(render_priv->settings.left_margin), + .y = -int_to_d6(render_priv->settings.top_margin), + }; + FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y); + } + ass_msg(render_priv->library, MSGL_DBG2, + "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n", + scale, drawing->scale_x, drawing->scale_y, drawing->text); + + return p; +} + static void reset_render_context(ass_renderer_t *); /** @@ -1027,9 +1235,6 @@ static void reset_render_context(ass_renderer_t *); */ static char *parse_tag(ass_renderer_t *render_priv, 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)) @@ -1081,15 +1286,16 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) } else if (mystrcmp(&p, "iclip")) { int x0, y0, x1, y1; int res = 1; - skip('('); + char *start = p; + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1100,6 +1306,9 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; render_priv->state.clip_mode = 1; + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 1; } else render_priv->state.clip_mode = 0; } else if (mystrcmp(&p, "blur")) { @@ -1398,17 +1607,18 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) skip_to(')'); // in case there is some unknown tag or a comment skip(')'); } else if (mystrcmp(&p, "clip")) { + char *start = p; int x0, y0, x1, y1; int res = 1; - skip('('); + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1418,6 +1628,10 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr; render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; + // Might be a vector clip + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 0; } else { render_priv->state.clip_x0 = 0; render_priv->state.clip_y0 = 0; @@ -1556,6 +1770,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) return p; #undef skip +#undef skipopt #undef skip_to } @@ -1856,7 +2071,7 @@ static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, static void stroke_outline_glyph(ass_renderer_t *render_priv, FT_OutlineGlyph *glyph, int sx, int sy) { - if (sx <= 0 || sy <= 0) + if (sx <= 0 && sy <= 0) return; fix_freetype_stroker(*glyph, sx, sy); @@ -1948,7 +2163,7 @@ get_outline_glyph(ass_renderer_t *render_priv, int symbol, } else { glyph_hash_val_t v; if (drawing->hash) { - ass_drawing_parse(drawing); + ass_drawing_parse(drawing, 0); FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph); } else { info->glyph = @@ -2078,14 +2293,15 @@ static void measure_text(ass_renderer_t *render_priv) text_info_t *text_info = &render_priv->text_info; int cur_line = 0; double max_asc = 0., max_desc = 0.; + glyph_info_t *last = NULL; int i; int empty_line = 1; text_info->height = 0.; for (i = 0; i < text_info->length + 1; ++i) { if ((i == text_info->length) || text_info->glyphs[i].linebreak) { - if (empty_line && cur_line > 0) { - max_asc = text_info->lines[cur_line - 1].asc / 2.0; - max_desc = text_info->lines[cur_line - 1].desc / 2.0; + if (empty_line && cur_line > 0 && last && i < text_info->length) { + max_asc = d6_to_double(last->asc) / 2.0; + max_desc = d6_to_double(last->desc) / 2.0; } text_info->lines[cur_line].asc = max_asc; text_info->lines[cur_line].desc = max_desc; @@ -2101,6 +2317,8 @@ static void measure_text(ass_renderer_t *render_priv) max_asc = d6_to_double(cur->asc); if (d6_to_double(cur->desc) > max_desc) max_desc = d6_to_double(cur->desc); + if (cur->symbol != '\n' && cur->symbol != 0) + last = cur; } } text_info->height += @@ -2668,6 +2886,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, } } + if (text_info->length == 0) { // no valid symbols in the event; this can be smth like {comment} free_render_context(render_priv); @@ -2719,7 +2938,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, last_glyph--; width = d6_to_double( - last_glyph->pos.x + last_glyph->advance.x - + last_glyph->pos.x + last_glyph->advance.x - first_glyph->pos.x); if (halign == HALIGN_LEFT) { // left aligned, no action shift = 0; @@ -2769,7 +2988,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, } else if (valign == VALIGN_CENTER) { // midtitle double scr_y = y2scr(render_priv, render_priv->track->PlayResY / 2.0); - device_y = scr_y - (bbox.yMax - bbox.yMin) / 2.0; + device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0; } else { // subtitle double scr_y; if (valign != VALIGN_SUB) @@ -2877,7 +3096,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, g->hash_key.advance.x = double_to_d6(device_x - (int) device_x + d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; - g->hash_key.advance.y = + g->hash_key.advance.y = double_to_d6(device_y - (int) device_y + d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; get_bitmap_glyph(render_priv, text_info->glyphs + i); @@ -2984,8 +3203,9 @@ void ass_set_line_spacing(ass_renderer_t *priv, double line_spacing) priv->settings.line_spacing = line_spacing; } -int ass_set_fonts(ass_renderer_t *priv, const char *default_font, - const char *default_family, int fc, const char *config) +void ass_set_fonts(ass_renderer_t *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update) { if (priv->settings.default_font) free(priv->settings.default_font); @@ -3002,9 +3222,12 @@ int ass_set_fonts(ass_renderer_t *priv, const char *default_font, fontconfig_done(priv->fontconfig_priv); priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, - default_font, fc, config); + default_font, fc, config, update); +} - return !!priv->fontconfig_priv; +int ass_fonts_update(ass_renderer_t *render_priv) +{ + return fontconfig_update(render_priv->fontconfig_priv); } /** @@ -3014,15 +3237,18 @@ static int ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track, long long now) { - if (render_priv->library != track->library) - return 1; - ass_settings_t *settings_priv = &render_priv->settings; + cache_store_t *cache = &render_priv->cache; if (!render_priv->settings.frame_width && !render_priv->settings.frame_height) return 1; // library not initialized + if (render_priv->library != track->library) + return 1; + + free_list_clear(render_priv); + if (track->n_events == 0) return 1; // nothing to do @@ -3063,6 +3289,24 @@ ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track, render_priv->prev_images_root = render_priv->images_root; render_priv->images_root = 0; + if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard bitmap cache limit (was: %ld bytes), " + "resetting.", (long) cache->bitmap_cache->cache_size); + cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache); + cache->composite_cache = ass_composite_cache_reset( + cache->composite_cache); + ass_free_images(render_priv->prev_images_root); + render_priv->prev_images_root = 0; + } + + if (cache->glyph_cache->count > cache->glyph_max) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard glyph cache limit (was: %ld glyphs), resetting.", + (long) cache->glyph_cache->count); + cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache); + } + return 0; } diff --git a/aegisub/libass/ass_types.h b/aegisub/libass/ass_types.h index cdea14ef4..fb74872e3 100644 --- a/aegisub/libass/ass_types.h +++ b/aegisub/libass/ass_types.h @@ -30,7 +30,7 @@ #define HALIGN_CENTER 2 #define HALIGN_RIGHT 3 -/// ass Style: line +/* ASS Style: line */ typedef struct ass_style_s { char *Name; char *FontName; @@ -54,15 +54,16 @@ typedef struct ass_style_s { int MarginL; int MarginR; int MarginV; -// int AlphaLevel; int Encoding; int treat_fontname_as_pattern; } ass_style_t; typedef struct render_priv_s render_priv_t; -/// ass_event_t corresponds to a single Dialogue line -/// Text is stored as-is, style overrides will be parsed later +/* + * ass_event_t corresponds to a single Dialogue line; + * text is stored as-is, style overrides will be parsed later. + */ typedef struct ass_event_s { long long Start; // ms long long Duration; // ms @@ -81,26 +82,31 @@ typedef struct ass_event_s { } ass_event_t; typedef struct parser_priv_s parser_priv_t; - typedef struct ass_library_s ass_library_t; -/// ass track represent either an external script or a matroska subtitle stream (no real difference between them) -/// it can be used in rendering after the headers are parsed (i.e. events format line read) +/* + * ass track represent either an external script or a matroska subtitle stream + * (no real difference between them); it can be used in rendering after the + * headers are parsed (i.e. events format line read). + */ typedef struct ass_track_s { - int n_styles; // amount used - int max_styles; // amount allocated + int n_styles; // amount used + int max_styles; // amount allocated int n_events; int max_events; - ass_style_t *styles; // array of styles, max_styles length, n_styles used - ass_event_t *events; // the same as styles + ass_style_t *styles; // array of styles, max_styles length, n_styles used + ass_event_t *events; // the same as styles - char *style_format; // style format line (everything after "Format: ") - char *event_format; // event format line + char *style_format; // style format line (everything after "Format: ") + char *event_format; // event format line - enum { TRACK_TYPE_UNKNOWN = - 0, TRACK_TYPE_ASS, TRACK_TYPE_SSA } track_type; + enum { + TRACK_TYPE_UNKNOWN = 0, + TRACK_TYPE_ASS, + TRACK_TYPE_SSA + } track_type; - // script header fields + // Script header fields int PlayResX; int PlayResY; double Timer; @@ -108,11 +114,11 @@ typedef struct ass_track_s { char ScaledBorderAndShadow; - int default_style; // index of default style - char *name; // file name in case of external subs, 0 for streams + int default_style; // index of default style + char *name; // file name in case of external subs, 0 for streams ass_library_t *library; parser_priv_t *parser_priv; } ass_track_t; -#endif /* LIBASS_TYPES_H */ +#endif /* LIBASS_TYPES_H */ diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp index 1c1b53d87..966d3c00d 100644 --- a/aegisub/src/subtitles_provider_libass.cpp +++ b/aegisub/src/subtitles_provider_libass.cpp @@ -91,7 +91,7 @@ LibassSubtitlesProvider::LibassSubtitlesProvider() { const char *config_path = NULL; #endif - ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path); + ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path, 1); }