From 36db47b89cd5f4edfb2b02780ff2ddd8022190db Mon Sep 17 00:00:00 2001 From: Oleg Oshmyan Date: Sat, 23 May 2015 00:06:40 +0300 Subject: Parse and animate all colors and alpha values like VSFilter * Allow exactly one of these prefixes in header values: 0x, 0X, &h, &H. Note that "0x0xFFFFFF" is a correct value, as the first 0x is consumed by the parser and the second by the string-to-number conversion following strtol semantics. * Allow arbitrary numbers of leading & and H (and not h) in any order in override tag values. * Reduce header values modulo 2**32 instead of saturating them to LLONG_MIN/MAX. * Saturate override tag values to INT32_MIN/MAX rather than to LLONG_MIN/MAX. * Don't fiddle with bytes in alpha override tag values. (They can be outside of the 0..255 range.) Also change the byte swapping code to be more sensible. Fixes #80. Fixes #145. Fixes #178. Also fixes our behavior in the case described in https://code.google.com/p/xy-vsfilter/issues/detail?id=80. --- libass/ass.c | 5 +-- libass/ass_parse.c | 50 +++++++++++----------- libass/ass_parse.h | 2 +- libass/ass_utils.c | 123 +++++++++++++++++++++++++++++++++++++++++------------ libass/ass_utils.h | 16 ++++++- 5 files changed, 135 insertions(+), 61 deletions(-) diff --git a/libass/ass.c b/libass/ass.c index 594cca3..c9855ea 100644 --- a/libass/ass.c +++ b/libass/ass.c @@ -242,10 +242,7 @@ static int numpad2align(int val) while (*token == '*') ++token; \ target->name = strdup(token); -#define COLORVAL(name) \ - } else if (strcasecmp(tname, #name) == 0) { \ - target->name = string2color(track->library, token, 0); - +#define COLORVAL(name) ANYVAL(name,parse_color_header) #define INTVAL(name) ANYVAL(name,atoi) #define FPVAL(name) ANYVAL(name,ass_atof) #define TIMEVAL(name) \ diff --git a/libass/ass_parse.c b/libass/ass_parse.c index a639dc5..d9749fd 100644 --- a/libass/ass_parse.c +++ b/libass/ass_parse.c @@ -184,11 +184,11 @@ static void change_color(uint32_t *var, uint32_t new, double pwr) } // like change_color, but for alpha component only -inline void change_alpha(uint32_t *var, uint32_t new, double pwr) +inline void change_alpha(uint32_t *var, int32_t new, double pwr) { *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + - (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr); + (uint8_t) (_a(*var) * (1 - pwr) + new * pwr); } /** @@ -545,23 +545,20 @@ char *parse_tag(ASS_Renderer *render_priv, char *p, char *end, double pwr) render_priv->state.family = family; update_font(render_priv); } else if (tag("alpha")) { - uint32_t val; int i; - int hex = render_priv->track->track_type == TRACK_TYPE_ASS; if (nargs) { - val = string2color(render_priv->library, args->start, hex); - unsigned char a = val >> 24; + int32_t a = parse_alpha_tag(args->start); for (i = 0; i < 4; ++i) change_alpha(&render_priv->state.c[i], a, pwr); } else { change_alpha(&render_priv->state.c[0], - render_priv->state.style->PrimaryColour, 1); + _a(render_priv->state.style->PrimaryColour), 1); change_alpha(&render_priv->state.c[1], - render_priv->state.style->SecondaryColour, 1); + _a(render_priv->state.style->SecondaryColour), 1); change_alpha(&render_priv->state.c[2], - render_priv->state.style->OutlineColour, 1); + _a(render_priv->state.style->OutlineColour), 1); change_alpha(&render_priv->state.c[3], - render_priv->state.style->BackColour, 1); + _a(render_priv->state.style->BackColour), 1); } // FIXME: simplify } else if (tag("an")) { @@ -715,10 +712,8 @@ char *parse_tag(ASS_Renderer *render_priv, char *p, char *end, double pwr) render_priv->state.clip_drawing_mode = 0; } } else if (tag("c")) { - uint32_t val; - int hex = render_priv->track->track_type == TRACK_TYPE_ASS; if (nargs) { - val = string2color(render_priv->library, args->start, hex); + uint32_t val = parse_color_tag(args->start); change_color(&render_priv->state.c[0], val, pwr); } else change_color(&render_priv->state.c[0], @@ -728,39 +723,42 @@ char *parse_tag(ASS_Renderer *render_priv, char *p, char *end, double pwr) char n = *(p - 2); int cidx = n - '1'; char cmd = *(p - 1); - uint32_t val; - int hex = render_priv->track->track_type == TRACK_TYPE_ASS; + int32_t alpha; + uint32_t color; assert((n >= '1') && (n <= '4')); - if (nargs) - val = string2color(render_priv->library, args->start, hex); - else { + if (nargs) { + if (cmd == 'a') + alpha = parse_alpha_tag(args->start); + else + color = parse_color_tag(args->start); + } else { switch (n) { case '1': - val = render_priv->state.style->PrimaryColour; + color = render_priv->state.style->PrimaryColour; break; case '2': - val = render_priv->state.style->SecondaryColour; + color = render_priv->state.style->SecondaryColour; break; case '3': - val = render_priv->state.style->OutlineColour; + color = render_priv->state.style->OutlineColour; break; case '4': - val = render_priv->state.style->BackColour; + color = render_priv->state.style->BackColour; break; default: - val = 0; + color = 0; break; // impossible due to assert; avoid compilation warning } if (cmd == 'a') - val <<= 24; + alpha = _a(color); pwr = 1; } switch (cmd) { case 'c': - change_color(render_priv->state.c + cidx, val, pwr); + change_color(render_priv->state.c + cidx, color, pwr); break; case 'a': - change_alpha(render_priv->state.c + cidx, val >> 24, pwr); + change_alpha(render_priv->state.c + cidx, alpha, pwr); break; default: ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c", diff --git a/libass/ass_parse.h b/libass/ass_parse.h index e8512b2..2b4cee0 100644 --- a/libass/ass_parse.h +++ b/libass/ass_parse.h @@ -36,7 +36,7 @@ void process_karaoke_effects(ASS_Renderer *render_priv); unsigned get_next_char(ASS_Renderer *render_priv, char **str); char *parse_tag(ASS_Renderer *render_priv, char *p, char *end, double pwr); int event_has_hard_overrides(char *str); -extern void change_alpha(uint32_t *var, uint32_t new, double pwr); +extern void change_alpha(uint32_t *var, int32_t new, double pwr); extern uint32_t mult_alpha(uint32_t a, uint32_t b); diff --git a/libass/ass_utils.c b/libass/ass_utils.c index b1c736c..a6a063b 100644 --- a/libass/ass_utils.c +++ b/libass/ass_utils.c @@ -165,50 +165,117 @@ int mystrtoll(char **p, long long *res) return *p != start; } -int mystrtou32(char **p, int base, uint32_t *res) +int mystrtod(char **p, double *res) { char *start = *p; - *res = strtoll(*p, p, base); + *res = ass_strtod(*p, p); return *p != start; } -int mystrtod(char **p, double *res) +int mystrtoi32(char **p, int base, int32_t *res) { char *start = *p; - *res = ass_strtod(*p, p); + long long temp_res = strtoll(*p, p, base); + *res = FFMINMAX(temp_res, INT32_MIN, INT32_MAX); return *p != start; } -uint32_t string2color(ASS_Library *library, char *p, int hex) +static int read_digits(char **str, int base, uint32_t *res) { - uint32_t color = 0; - int base = hex ? 16 : 10; + char *p = *str; + char *start = p; + uint32_t val = 0; + + while (1) { + int digit; + if (*p >= '0' && *p < base + '0') + digit = *p - '0'; + else if (*p >= 'a' && *p < base - 10 + 'a') + digit = *p - 'a' + 10; + else if (*p >= 'A' && *p < base - 10 + 'A') + digit = *p - 'A' + 10; + else + break; + val = val * base + digit; + ++p; + } + + *res = val; + *str = p; + return p != start; +} - if (*p == '&') - while (*p == '&') - ++p; - else - ass_msg(library, MSGL_DBG2, "suspicious color format: \"%s\"\n", p); +/** + * \brief Convert a string to an integer reduced modulo 2**32 + * Follows the rules for strtoul but reduces the number modulo 2**32 + * instead of saturating it to 2**32 - 1. + */ +static int mystrtou32_modulo(char **p, int base, uint32_t *res) +{ + // This emulates scanf with %d or %x format as it works on + // Windows, because that's what is used by VSFilter. In practice, + // scanf works the same way on other platforms too, but + // the standard leaves its behavior on overflow undefined. - if (*p == 'H' || *p == 'h') { - ++p; - mystrtou32(&p, 16, &color); - } else - mystrtou32(&p, base, &color); + // Unlike scanf and like strtoul, produce 0 for invalid inputs. - while (*p == '&' || *p == 'H') - ++p; + char *start = *p; + int sign = 1; + + skip_spaces(p); + + if (**p == '+') + ++*p; + else if (**p == '-') + sign = -1, ++*p; - unsigned char *tmp = (unsigned char *) (&color); - unsigned char b; - b = tmp[0]; - tmp[0] = tmp[3]; - tmp[3] = b; - b = tmp[1]; - tmp[1] = tmp[2]; - tmp[2] = b; + if (base == 16 && !strncasecmp(*p, "0x", 2)) + *p += 2; + + if (read_digits(p, base, res)) { + *res *= sign; + return 1; + } else { + *p = start; + return 0; + } +} + +int32_t parse_alpha_tag(char *str) +{ + int32_t alpha = 0; + + while (*str == '&' || *str == 'H') + ++str; + + mystrtoi32(&str, 16, &alpha); + return alpha; +} + +uint32_t parse_color_tag(char *str) +{ + int32_t color = 0; + + while (*str == '&' || *str == 'H') + ++str; + + mystrtoi32(&str, 16, &color); + return ass_bswap32((uint32_t) color); +} + +uint32_t parse_color_header(char *str) +{ + uint32_t color = 0; + int base; + + if (!strncasecmp(str, "&h", 2) || !strncasecmp(str, "0x", 2)) { + str += 2; + base = 16; + } else + base = 10; - return color; + mystrtou32_modulo(&str, base, &color); + return ass_bswap32(color); } // Return a boolean value for a string diff --git a/libass/ass_utils.h b/libass/ass_utils.h index 2ad8c5b..45ebbb6 100644 --- a/libass/ass_utils.h +++ b/libass/ass_utils.h @@ -85,9 +85,11 @@ void skip_spaces(char **str); void rskip_spaces(char **str, char *limit); int mystrtoi(char **p, int *res); int mystrtoll(char **p, long long *res); -int mystrtou32(char **p, int base, uint32_t *res); int mystrtod(char **p, double *res); -uint32_t string2color(ASS_Library *library, char *p, int hex); +int mystrtoi32(char **p, int base, int32_t *res); +int32_t parse_alpha_tag(char *str); +uint32_t parse_color_tag(char *str); +uint32_t parse_color_header(char *str); char parse_bool(char *str); int parse_ycbcr_matrix(char *str); unsigned ass_utf8_get_char(char **str); @@ -111,6 +113,16 @@ static inline size_t ass_align(size_t alignment, size_t s) return (s + (alignment - 1)) & ~(alignment - 1); } +static inline uint32_t ass_bswap32(uint32_t x) +{ +#ifdef _MSC_VER + return _byteswap_ulong(x); +#else + return (x & 0x000000FF) << 24 | (x & 0x0000FF00) << 8 | + (x & 0x00FF0000) >> 8 | (x & 0xFF000000) >> 24; +#endif +} + static inline int d6_to_int(int x) { return (x + 32) >> 6; -- cgit v1.2.3