ass_render.c
来自「君正早期ucos系统(只有早期的才不没有打包成库),MPLAYER,文件系统,图」· C语言 代码 · 共 2,195 行 · 第 1/5 页
C
2,195 行
* This function goes through text_info and calculates text parameters. * The following text_info fields are filled: * height * lines[].height * lines[].asc * lines[].desc */static void measure_text(void){ int cur_line = 0, max_asc = 0, max_desc = 0; int i; text_info.height = 0; for (i = 0; i < text_info.length + 1; ++i) { if ((i == text_info.length) || text_info.glyphs[i].linebreak) { text_info.lines[cur_line].asc = max_asc; text_info.lines[cur_line].desc = max_desc; text_info.height += max_asc + max_desc; cur_line ++; max_asc = max_desc = 0; } if (i < text_info.length) { glyph_info_t* cur = text_info.glyphs + i; if (cur->asc > max_asc) max_asc = cur->asc; if (cur->desc > max_desc) max_desc = cur->desc; } } text_info.height += (text_info.n_lines - 1) * double_to_d6(global_settings->line_spacing);}/** * \brief rearrange text between lines * \param max_text_width maximal text line width in pixels * The algo is similar to the one in libvo/sub.c: * 1. Place text, wrapping it when current line is full * 2. Try moving words from the end of a line to the beginning of the next one while it reduces * the difference in lengths between this two lines. * The result may not be optimal, but usually is good enough. */static void wrap_lines_smart(int max_text_width){ int i, j; glyph_info_t *cur, *s1, *e1, *s2, *s3, *w; int last_space; int break_type; int exit; int pen_shift_x; int pen_shift_y; int cur_line; last_space = -1; text_info.n_lines = 1; break_type = 0; s1 = text_info.glyphs; // current line start for (i = 0; i < text_info.length; ++i) { int break_at, s_offset, len; cur = text_info.glyphs + i; break_at = -1; s_offset = s1->bbox.xMin + s1->pos.x; len = (cur->bbox.xMax + cur->pos.x) - s_offset; if (cur->symbol == '\n') { break_type = 2; break_at = i; mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at); } if (len >= max_text_width) { break_type = 1; break_at = last_space; if (break_at == -1) break_at = i - 1; if (break_at == -1) break_at = 0; mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i); mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at); } if (break_at != -1) { // need to use one more line // 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 ! // no more linebreaks for (j = lead; j < text_info.length; ++j) text_info.glyphs[j].linebreak = 0; break; } if (lead < text_info.length) text_info.glyphs[lead].linebreak = break_type; last_space = -1; s1 = text_info.glyphs + lead; s_offset = s1->bbox.xMin + s1->pos.x; text_info.n_lines ++; } if (cur->symbol == ' ') last_space = i; // make sure the hard linebreak is not forgotten when // there was a new soft linebreak just inserted if (cur->symbol == '\n' && break_type == 1) i--; }#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) exit = 0; while (!exit) { exit = 1; w = s3 = text_info.glyphs; s1 = s2 = 0; for (i = 0; i <= text_info.length; ++i) { cur = text_info.glyphs + i; if ((i == text_info.length) || cur->linebreak) { s1 = s2; s2 = s3; s3 = cur; if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' int l1, l2, l1_new, l2_new; w = s2; do { --w; } while ((w > s1) && (w->symbol == ' ')); while ((w > s1) && (w->symbol != ' ')) { --w; } e1 = w; while ((e1 > s1) && (e1->symbol == ' ')) { --e1; } if (w->symbol == ' ') ++w; l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x); l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x); l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x); l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x); if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { w->linebreak = 1; s2->linebreak = 0; exit = 0; } } } if (i == text_info.length) break; } } assert(text_info.n_lines >= 1);#undef DIFF measure_text(); pen_shift_x = 0; pen_shift_y = 0; cur_line = 1; for (i = 0; i < text_info.length; ++i) { cur = text_info.glyphs + i; if (cur->linebreak) { int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc; cur_line ++; pen_shift_x = - cur->pos.x; pen_shift_y += d6_to_int(height + double_to_d6(global_settings->line_spacing)); mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y); } cur->pos.x += pen_shift_x; cur->pos.y += pen_shift_y; }}/** * \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 * (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) * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts * (left part is filled with PrimaryColour, right one - with SecondaryColour). */static void process_karaoke_effects(void){ glyph_info_t *cur, *cur2; glyph_info_t *s1, *e1; // start and end of the current word glyph_info_t *s2; // start of the next word int i; int timing; // current timing int tm_start, tm_end; // timings at start and end of the current word int tm_current; double dt; int x; int x_start, x_end; tm_current = frame_context.time - render_context.event->Start; timing = 0; s1 = s2 = 0; for (i = 0; i <= text_info.length; ++i) { cur = text_info.glyphs + i; if ((i == text_info.length) || (cur->effect_type != EF_NONE)) { s1 = s2; s2 = cur; if (s1) { e1 = s2 - 1; tm_start = timing + s1->effect_skip_timing; tm_end = tm_start + s1->effect_timing; timing = tm_end; x_start = 1000000; x_end = -1000000; for (cur2 = s1; cur2 <= e1; ++cur2) { x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x); x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x); } dt = (tm_current - tm_start); if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) { if (dt > 0) x = x_end + 1; else x = x_start; } else if (s1->effect_type == EF_KARAOKE_KF) { dt /= (tm_end - tm_start); x = x_start + (x_end - x_start) * dt; } else { mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError); continue; } for (cur2 = s1; cur2 <= e1; ++cur2) { cur2->effect_type = s1->effect_type; cur2->effect_timing = x - cur2->pos.x; } } } }}/** * \brief Calculate base point for positioning and rotation * \param bbox text bbox * \param alignment alignment * \param bx, by out: base point coordinates */static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by){ const int halign = alignment & 3; const int valign = alignment & 12; if (bx) switch(halign) { case HALIGN_LEFT: *bx = bbox.xMin; break; case HALIGN_CENTER: *bx = (bbox.xMax + bbox.xMin) / 2; break; case HALIGN_RIGHT: *bx = bbox.xMax; break; } if (by) switch(valign) { case VALIGN_TOP: *by = bbox.yMin; break; case VALIGN_CENTER: *by = (bbox.yMax + bbox.yMin) / 2; break; case VALIGN_SUB: *by = bbox.yMax; break; }}/** * \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. */static inline void transform_vector_3d(FT_Vector* v, double *m) { const double camera = 2500 * frame_context.border_scale; // camera distance 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] < 0.001 && b[3] > -0.001) b[3] = b[3] < 0. ? -0.001 : 0.001; 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; FT_Vector* p = outline->points; 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; } //transform_vector_3d(&glyph->advance, m);}/** * \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(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 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); if (glyph2 && *glyph2) transform_glyph_3d(*glyph2, m, shift); }}/** * \brief Main ass rendering function, glues everything together * \param event event to render * 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 num_glyphs; FT_Vector pen; unsigned code; FT_BBox bbox; int i, j; FT_Vector shift; int MarginL, MarginR, MarginV; int last_break; int alignment, halign, valign; int device_x = 0, device_y = 0; if (event->Style >= frame_context.track->n_styles) { mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound); return 1; } if (!event->Text) { mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent); return 1; } init_render_context(event); text_info.length = 0; pen.x = 0; pen.y = 0; previous = 0; num_glyphs = 0; p = event->Text; // Event parsing. while (1) { // get next char, executing style override // this affects render_context code = get_next_char(&p); // face could have been changed in get_next_char if (!render_context.font) { free_render_context(); return 1; } if (code == 0) break; if (text_info.length >= MAX_GLYPHS) { mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text); break; } if ( previous && code ) { FT_Vector delta; delta = ass_font_get_kerning(render_context.font, previous, code); pen.x += delta.x * render_context.scale_x; pen.y += delta.y * render_context.scale_y; }
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?