summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOneric <oneric@oneric.stub>2022-09-05 00:46:04 +0200
committerOneric <oneric@oneric.stub>2022-09-15 18:35:43 +0200
commit1db406a0fea1fee5b3e1a5a5544d7e968a3e0591 (patch)
tree505639da14ed438a57709053dca269c7fc6153ad
parentd8f056158abe9d671c53f430ecd21022cc983d47 (diff)
downloadlibass-1db406a0fea1fee5b3e1a5a5544d7e968a3e0591.tar.bz2
libass-1db406a0fea1fee5b3e1a5a5544d7e968a3e0591.tar.xz
Fix legacy effect's delay scaling and precision
Usually the delay parameter of legacy effects is scaled to be relative to the PlayRes canvas. This happens explicitly in VSFilter and automatically in libass. However, this scaling in VSFilter happens _before_ applying max(delay, 1) which means, if e.g. delay=0 it ends up as 1 ms per _storage pixel_. To get the same effect in libass we must explicitly "unscale" the fallback for small or negative delays. VSFilter also casts the scaled delay value to int afterwards, which can lead to noticeable differences if the scaled value isn't an integer. To emulate this in libass we do not want delay to be an int (which would also ruin the unscaling for delay=0), but we need to convert our already PlayRes-relative value to a storage-relative one, then cast to int and finally convert back to a PlayRes-relative value. This rounding error can already be observed after just one second for PlayResX=StorageX/8 and delay=25.
-rw-r--r--libass/ass_parse.c26
1 files changed, 20 insertions, 6 deletions
diff --git a/libass/ass_parse.c b/libass/ass_parse.c
index 8a0fc57..cc1213e 100644
--- a/libass/ass_parse.c
+++ b/libass/ass_parse.c
@@ -885,8 +885,9 @@ void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
v[cnt++] = atoi(++p);
}
+ ASS_Vector layout_res = ass_layout_res(render_priv);
if (strncmp(event->Effect, "Banner;", 7) == 0) {
- int delay;
+ double delay;
if (cnt < 1) {
ass_msg(render_priv->library, MSGL_V,
"Error parsing effect: '%s'", event->Effect);
@@ -898,8 +899,20 @@ void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
render_priv->state.scroll_direction = SCROLL_RL;
delay = v[0];
- if (delay == 0)
- delay = 1; // ?
+ // VSF works in storage coordinates, but scales delay to PlayRes canvas
+ // before applying max(scaled_ delay, 1). This means, if scaled_delay < 1
+ // (esp. delay=0) we end up with 1 ms per _storage pixel_ without any
+ // PlayRes scaling.
+ // The way libass deals with delay, it is automatically relative to the
+ // PlayRes canvas, so we only want to "unscale" the small delay values.
+ //
+ // VSF also casts the scaled delay to int, which if not emulated leads to
+ // easily noticeable deviations from VSFilter as the effect goes on.
+ // To achieve both we need to keep our Playres-relative delay with high precision,
+ // but must temporarily convert to storage-relative and truncate and take the
+ // maxuimum there, before converting back.
+ double scale_x = ((double) layout_res.x) / render_priv->track->PlayResX;
+ delay = ((int) FFMAX(delay / scale_x, 1)) * scale_x;
render_priv->state.scroll_shift =
(render_priv->time - render_priv->state.event->Start) / delay;
render_priv->state.evt_type |= EVENT_HSCROLL;
@@ -919,7 +932,7 @@ void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
}
// parse scroll up/down parameters
{
- int delay;
+ double delay;
int y0, y1;
if (cnt < 3) {
ass_msg(render_priv->library, MSGL_V,
@@ -927,8 +940,9 @@ void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
return;
}
delay = v[2];
- if (delay == 0)
- delay = 1; // ?
+ // See explanation for Banner
+ double scale_y = ((double) layout_res.y) / render_priv->track->PlayResY;
+ delay = ((int) FFMAX(delay / scale_y, 1)) * scale_y;
render_priv->state.scroll_shift =
(render_priv->time - render_priv->state.event->Start) / delay;
if (v[0] < v[1]) {