]> granicus.if.org Git - libass/commitdiff
renderer: quantize blur radius and shadow offset
authorDr.Smile <vabnick@gmail.com>
Sun, 19 May 2019 22:07:36 +0000 (01:07 +0300)
committerDr.Smile <vabnick@gmail.com>
Sun, 19 May 2019 22:07:36 +0000 (01:07 +0300)
libass/ass_bitmap.c
libass/ass_bitmap.h
libass/ass_cache_template.h
libass/ass_render.c

index e43f727375f06df57342624d2a91fa69b801c384..8fd842a5f9951554002e1fde5fb0a3d241b96a61 100644 (file)
 
 
 void ass_synth_blur(const BitmapEngine *engine, Bitmap *bm,
-                    int be, double blur_radius)
+                    int be, double blur_r2)
 {
     if (!bm->buffer)
         return;
 
     // Apply gaussian blur
-    double r2 = blur_radius * blur_radius / log(256);
-    if (r2 > 0.001)
-        ass_gaussian_blur(engine, bm, r2);
+    if (blur_r2 > 0.001)
+        ass_gaussian_blur(engine, bm, blur_r2);
 
     if (!be)
         return;
index 4c2162996571493e5fef624edb135d7f7556ec09..783dd6d026f83f092be363effff978a843045f95 100644 (file)
@@ -105,7 +105,7 @@ bool outline_to_bitmap(ASS_Renderer *render_priv, Bitmap *bm,
                        ASS_Outline *outline1, ASS_Outline *outline2);
 
 void ass_synth_blur(const BitmapEngine *engine, Bitmap *bm,
-                    int be, double blur_radius);
+                    int be, double blur_r2);
 
 int be_padding(int be);
 void be_blur_pre(uint8_t *buf, intptr_t w,
index 4bc6a90d26929cc53b34c19417d1b56c22fb2e6a..c0bcfac1a1c63507a56a8eb78325312ec0001dd7 100644 (file)
@@ -97,7 +97,7 @@ END(BorderHashKey)
 START(filter, filter_desc)
     GENERIC(int, flags)
     GENERIC(int, be)
-    GENERIC(double, blur)
+    GENERIC(int, blur)
     VECTOR(shadow)
 END(FilterDesc)
 
index 8338ef311d179ae251ea529993f322bf59c13ff3..1fb7f2978ea051e38f091c584e310dd7fb8e7249 100644 (file)
@@ -39,6 +39,7 @@
 #define POSITION_PRECISION 8.0   // rough estimate of transform error in 1/64 pixel units
 #define MAX_PERSP_SCALE 16.0
 #define SUBPIXEL_ORDER 3  // ~ log2(64 / POSITION_PRECISION)
+#define BLUR_PRECISION (1.0 / 256)  // blur error as fraction of full input range
 
 
 ASS_Renderer *ass_renderer_init(ASS_Library *library)
@@ -2165,6 +2166,54 @@ static void calculate_rotation_params(ASS_Renderer *render_priv, ASS_DRect *bbox
 }
 
 
+static int quantize_blur(double radius, int32_t *shadow_mask)
+{
+    // Gaussian filter kernel (1D):
+    // G(x, r2) = exp(-x^2 / (2 * r2)) / sqrt(2 * pi * r2),
+    // position unit is 1/64th of pixel, r = 64 * radius, r2 = r^2.
+
+    // Difference between kernels with different but near r2:
+    // G(x, r2 + dr2) - G(x, r2) ~= dr2 * G(x, r2) * (x^2 - r2) / (2 * r2^2).
+    // Maximal possible error relative to full pixel value is half of
+    // integral (from -inf to +inf) of absolute value of that difference.
+    // E_max ~= dr2 / 2 * integral(G(x, r2) * |x^2 - r2| / (2 * r2^2), x)
+    //  = dr2 / (4 * r2) * integral(G(y, 1) * |y^2 - 1|, y)
+    //  = dr2 / (4 * r2) * 4 / sqrt(2 * pi * e)
+    //  ~ dr2 / (4 * r2) ~= dr / (2 * r).
+    // E_max ~ BLUR_PRECISION / 2 as we have 2 dimensions.
+
+    // To get discretized blur radius solve the following
+    // differential equation (n--quantization index):
+    // dr(n) / dn = BLUR_PRECISION * r + POSITION_PRECISION, r(0) = 0,
+    // r(n) = (exp(BLUR_PRECISION * n) - 1) * POSITION_PRECISION / BLUR_PRECISION,
+    // n = log(1 + r * BLUR_PRECISION / POSITION_PRECISION) / BLUR_PRECISION.
+
+    // To get shadow offset quantization estimate difference of
+    // G(x + dx, r2) - G(x, r2) ~= dx * G(x, r2) * (-x / r2).
+    // E_max ~= dx / 2 * integral(G(x, r2) * |x| / r2, x)
+    //  = dx / sqrt(2 * pi * r2) ~ dx / (2 * r).
+    // 2^ord ~ dx ~ BLUR_PRECISION * r + POSITION_PRECISION.
+
+    const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION;
+    radius *= scale;
+
+    int ord;
+    // ord = floor(log2(BLUR_PRECISION * r + POSITION_PRECISION))
+    //     = floor(log2(64 * radius * BLUR_PRECISION + POSITION_PRECISION))
+    //     = floor(log2((radius * scale + 1) * POSITION_PRECISION)),
+    // floor(log2(x)) = frexp(x) - 1 = frexp(x / 2).
+    frexp((1 + radius) * (POSITION_PRECISION / 2), &ord);
+    *shadow_mask = ((uint32_t) 1 << ord) - 1;
+    return lrint(log1p(radius) / BLUR_PRECISION);
+}
+
+static double restore_blur(int qblur)
+{
+    const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION;
+    double sigma = expm1(BLUR_PRECISION * qblur) / scale;
+    return sigma * sigma;
+}
+
 // Convert glyphs to bitmaps, combine them, apply blur, generate shadows.
 static void render_and_combine_glyphs(ASS_Renderer *render_priv,
                                       double device_x, double device_y)
@@ -2223,14 +2272,20 @@ static void render_and_combine_glyphs(ASS_Renderer *render_priv,
                 current_info->effect_timing = info->effect_timing;
                 current_info->first_pos_x = info->bbox.x_max >> 6;
 
-                current_info->filter.flags = flags;
-                current_info->filter.be = info->be;
-                current_info->filter.blur = 2 * info->blur * render_priv->blur_scale;
+                FilterDesc *filter = &current_info->filter;
+                filter->flags = flags;
+                filter->be = info->be;
+
+                int32_t shadow_mask;
+                double blur_scale = render_priv->blur_scale * (2 / sqrt(log(256)));
+                filter->blur = quantize_blur(info->blur * blur_scale, &shadow_mask);
                 if (flags & FILTER_NONZERO_SHADOW) {
-                    current_info->filter.shadow.x = double_to_d6(info->shadow_x * render_priv->border_scale);
-                    current_info->filter.shadow.y = double_to_d6(info->shadow_y * render_priv->border_scale);
+                    int32_t x = double_to_d6(info->shadow_x * render_priv->border_scale);
+                    int32_t y = double_to_d6(info->shadow_y * render_priv->border_scale);
+                    filter->shadow.x = (x + (shadow_mask >> 1)) & ~shadow_mask;
+                    filter->shadow.y = (y + (shadow_mask >> 1)) & ~shadow_mask;
                 } else
-                    current_info->filter.shadow.x = current_info->filter.shadow.y = 0;
+                    filter->shadow.x = filter->shadow.y = 0;
 
                 current_info->x = current_info->y = INT_MAX;
                 current_info->bm = current_info->bm_o = current_info->bm_s = NULL;
@@ -2392,10 +2447,11 @@ size_t ass_composite_construct(void *key, void *value, void *priv)
     }
 
     int flags = k->filter.flags;
+    double r2 = restore_blur(k->filter.blur);
     bool no_blur = (flags & ~FILTER_NONZERO_SHADOW) == FILTER_NONZERO_BORDER;
     if (!no_blur)
-        ass_synth_blur(render_priv->engine, &v->bm, k->filter.be, k->filter.blur);
-    ass_synth_blur(render_priv->engine, &v->bm_o, k->filter.be, k->filter.blur);
+        ass_synth_blur(render_priv->engine, &v->bm, k->filter.be, r2);
+    ass_synth_blur(render_priv->engine, &v->bm_o, k->filter.be, r2);
 
     if (flags & FILTER_NONZERO_SHADOW) {
         if (flags & FILTER_NONZERO_BORDER)