]> granicus.if.org Git - handbrake/commitdiff
libhb: Add LapSharp sharpening filter.
authorBradley Sepos <bradley@bradleysepos.com>
Sat, 27 May 2017 12:46:11 +0000 (08:46 -0400)
committerBradley Sepos <bradley@bradleysepos.com>
Tue, 30 May 2017 18:00:27 +0000 (14:00 -0400)
libhb/common.c
libhb/common.h
libhb/internal.h
libhb/lapsharp.c [new file with mode: 0644]
libhb/param.c
libhb/preset.c
test/test.c

index c8a2f22f770484c9427fc20ef474022fd1fb53da..71a8d0e266dfcd48d65fc76d15692505bc24a55a 100644 (file)
@@ -3959,6 +3959,10 @@ hb_filter_object_t * hb_filter_get( int filter_id )
             filter = &hb_filter_crop_scale;
             break;
 
+        case HB_FILTER_LAPSHARP:
+            filter = &hb_filter_lapsharp;
+            break;
+
         case HB_FILTER_UNSHARP:
             filter = &hb_filter_unsharp;
             break;
index 562bbc362d91e56a9518fb858476e7696871442f..a251365ac5740b38f4e49d823d221c1f8ab9d596 100644 (file)
@@ -1270,6 +1270,7 @@ enum
     HB_FILTER_NLMEANS,
     HB_FILTER_RENDER_SUB,
     HB_FILTER_CROP_SCALE,
+    HB_FILTER_LAPSHARP,
     HB_FILTER_UNSHARP,
     HB_FILTER_ROTATE,
     HB_FILTER_GRAYSCALE,
index 0cffbdff418d53aeb9d229ab0ce033ec57609311..4ffd6422a47afcdebde1544f1360078b416b750e 100644 (file)
@@ -465,6 +465,7 @@ extern hb_filter_object_t hb_filter_crop_scale;
 extern hb_filter_object_t hb_filter_rotate;
 extern hb_filter_object_t hb_filter_grayscale;
 extern hb_filter_object_t hb_filter_pad;
+extern hb_filter_object_t hb_filter_lapsharp;
 extern hb_filter_object_t hb_filter_unsharp;
 extern hb_filter_object_t hb_filter_avfilter;
 
diff --git a/libhb/lapsharp.c b/libhb/lapsharp.c
new file mode 100644 (file)
index 0000000..2170247
--- /dev/null
@@ -0,0 +1,282 @@
+/* lapsharp.c
+
+   Copyright (c) 2003-2017 HandBrake Team
+   This file is part of the HandBrake source code
+   Homepage: <http://handbrake.fr/>.
+   It may be used under the terms of the GNU General Public License v2.
+   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+#include "hb.h"
+
+#define LAPSHARP_STRENGTH_LUMA_DEFAULT   0.2
+#define LAPSHARP_STRENGTH_CHROMA_DEFAULT 0.2
+
+#define LAPSHARP_KERNELS 3
+#define LAPSHARP_KERNEL_LUMA_DEFAULT   2
+#define LAPSHARP_KERNEL_CHROMA_DEFAULT 2
+
+typedef struct
+{
+    double strength;  // strength
+    int    kernel;    // which kernel to use; lapsharp_kernels[kernel]
+} lapsharp_plane_context_t;
+
+typedef struct {
+    const int   *mem;
+    const double coef;
+    const int    size;
+} lapsharp_kernel_t;
+
+// 4-neighbor laplacian kernel (lap)
+// Sharpens vertical and horizontal edges, less effective on diagonals
+static const int lapsharp_kernel_lap[] =
+{
+ 0, -1,  0,
+-1,  5, -1,
+ 0, -1,  0
+};
+
+// Isotropic laplacian kernel (isolap)
+// Minimial directionality, sharpens all edges similarly
+static const int lapsharp_kernel_isolap[] =
+{
+-1, -4, -1,
+-4, 25, -4,
+-1, -4, -1
+};
+
+// Laplacian of gaussian kernel (log)
+// Slightly better at noise rejection
+static const int lapsharp_kernel_log[] =
+{
+ 0,  0, -1,  0,  0,
+ 0, -1, -2, -1,  0,
+-1, -2, 21, -2, -1,
+ 0, -1, -2, -1,  0,
+ 0,  0, -1,  0,  0
+};
+
+static lapsharp_kernel_t lapsharp_kernels[] =
+{
+    { lapsharp_kernel_lap,    (1.0 / 1), 3 },
+    { lapsharp_kernel_isolap, (1.0 / 5), 3 },
+    { lapsharp_kernel_log,    (1.0 / 5), 5 }
+};
+
+struct hb_filter_private_s
+{
+    lapsharp_plane_context_t plane_ctx[3];
+};
+
+static int hb_lapsharp_init(hb_filter_object_t *filter,
+                            hb_filter_init_t   *init);
+
+static int hb_lapsharp_work(hb_filter_object_t *filter,
+                            hb_buffer_t ** buf_in,
+                            hb_buffer_t ** buf_out);
+
+static void hb_lapsharp_close(hb_filter_object_t *filter);
+
+static const char hb_lapsharp_template[] =
+    "y-strength=^"HB_FLOAT_REG"$:y-kernel=^"HB_ALL_REG"$:"
+    "cb-strength=^"HB_FLOAT_REG"$:cb-kernel=^"HB_ALL_REG"$:"
+    "cr-strength=^"HB_FLOAT_REG"$:cr-kernel=^"HB_ALL_REG"$";
+
+hb_filter_object_t hb_filter_lapsharp =
+{
+    .id                = HB_FILTER_LAPSHARP,
+    .enforce_order     = 1,
+    .name              = "Sharpen (lapsharp)",
+    .settings          = NULL,
+    .init              = hb_lapsharp_init,
+    .work              = hb_lapsharp_work,
+    .close             = hb_lapsharp_close,
+    .settings_template = hb_lapsharp_template,
+};
+
+static void hb_lapsharp(const uint8_t *src,
+                              uint8_t *dst,
+                        const int width,
+                        const int height,
+                        const int stride,
+                        lapsharp_plane_context_t * ctx)
+{
+    const lapsharp_kernel_t *kernel = &lapsharp_kernels[ctx->kernel];
+
+    // Sharpen using selected kernel
+    const int offset_min    = -((kernel->size - 1) / 2);
+    const int offset_max    =   (kernel->size + 1) / 2;
+    const int stride_border =   (stride - width) / 2;
+    int16_t   pixel;
+    for (int y = 0; y < height; y++)
+    {
+        for (int x = 0; x < width; x++)
+        {
+            if ((y < offset_max) ||
+                (y > height - offset_max) ||
+                (x < stride_border + offset_max) ||
+                (x > width + stride_border - offset_max))
+            {
+                *(dst + stride*y + x) = *(src + stride*y + x);
+                continue;
+            }
+            pixel = 0;
+            for (int k = offset_min; k < offset_max; k++)
+            {
+                for (int j = offset_min; j < offset_max; j++)
+                {
+                    pixel += kernel->mem[((j - offset_min) * kernel->size) + k - offset_min] * *(src + stride*(y + j) + (x + k));
+                }
+            }
+            pixel = (int16_t)(((pixel * kernel->coef) - *(src + stride*y + x)) * ctx->strength) + *(src + stride*y + x);
+            pixel = pixel < 0 ? 0 : pixel;
+            pixel = pixel > 255 ? 255 : pixel;
+            *(dst + stride*y + x) = (uint8_t)(pixel);
+        }
+    }
+}
+
+static int hb_lapsharp_init(hb_filter_object_t *filter,
+                            hb_filter_init_t   *init)
+{
+    filter->private_data = calloc(sizeof(struct hb_filter_private_s), 1);
+    hb_filter_private_t * pv = filter->private_data;
+
+    char *kernel_string[3];
+
+    // Mark parameters unset
+    for (int c = 0; c < 3; c++)
+    {
+        pv->plane_ctx[c].strength = -1;
+        pv->plane_ctx[c].kernel   = -1;
+        kernel_string[c]          = NULL;
+    }
+
+    // Read user parameters
+    if (filter->settings != NULL)
+    {
+        hb_dict_t * dict = filter->settings;
+        hb_dict_extract_double(&pv->plane_ctx[0].strength, dict, "y-strength");
+        hb_dict_extract_string(&kernel_string[0],          dict, "y-kernel");
+
+        hb_dict_extract_double(&pv->plane_ctx[1].strength, dict, "cb-strength");
+        hb_dict_extract_string(&kernel_string[1],          dict, "cb-kernel");
+
+        hb_dict_extract_double(&pv->plane_ctx[2].strength, dict, "cr-strength");
+        hb_dict_extract_string(&kernel_string[2],          dict, "cr-kernel");
+    }
+
+    // Convert kernel user string to internal id
+    for (int c = 0; c < 3; c++)
+    {
+        lapsharp_plane_context_t * ctx = &pv->plane_ctx[c];
+
+        ctx->kernel = -1;
+
+        if (kernel_string[c] == NULL)
+        {
+            continue;
+        }
+
+        if (!strcasecmp(kernel_string[c], "lap"))
+        {
+            ctx->kernel = 0;
+        }
+        else if (!strcasecmp(kernel_string[c], "isolap"))
+        {
+            ctx->kernel = 1;
+        }
+        else if (!strcasecmp(kernel_string[c], "log"))
+        {
+            ctx->kernel = 2;
+        }
+
+        free(kernel_string[c]);
+    }
+
+    // Cascade values
+    // Cr not set; inherit Cb. Cb not set; inherit Y. Y not set; defaults.
+    for (int c = 1; c < 3; c++)
+    {
+        lapsharp_plane_context_t * prev_ctx = &pv->plane_ctx[c - 1];
+        lapsharp_plane_context_t * ctx      = &pv->plane_ctx[c];
+
+        if (ctx->strength == -1) ctx->strength = prev_ctx->strength;
+        if (ctx->kernel   == -1) ctx->kernel   = prev_ctx->kernel;
+    }
+
+    for (int c = 0; c < 3; c++)
+    {
+        lapsharp_plane_context_t * ctx = &pv->plane_ctx[c];
+
+        // Replace unset values with defaults
+        if (ctx->strength == -1)
+        {
+            ctx->strength = c ? LAPSHARP_STRENGTH_CHROMA_DEFAULT :
+                                LAPSHARP_STRENGTH_LUMA_DEFAULT;
+        }
+        if (ctx->kernel   == -1)
+        {
+            ctx->kernel   = c ? LAPSHARP_KERNEL_CHROMA_DEFAULT :
+                                LAPSHARP_KERNEL_LUMA_DEFAULT;
+        }
+
+        // Sanitize
+        if (ctx->strength < 0)   ctx->strength = 0;
+        if (ctx->strength > 1.5) ctx->strength = 1.5;
+        if ((ctx->kernel < 0) || (ctx->kernel >= LAPSHARP_KERNELS))
+        {
+            ctx->kernel = c ? LAPSHARP_KERNEL_CHROMA_DEFAULT : LAPSHARP_KERNEL_LUMA_DEFAULT;
+        }
+    }
+
+    return 0;
+}
+
+static void hb_lapsharp_close(hb_filter_object_t * filter)
+{
+    hb_filter_private_t *pv = filter->private_data;
+
+    if (pv == NULL)
+    {
+        return;
+    }
+
+    free(pv);
+    filter->private_data = NULL;
+}
+
+static int hb_lapsharp_work(hb_filter_object_t *filter,
+                           hb_buffer_t ** buf_in,
+                           hb_buffer_t ** buf_out)
+{
+    hb_filter_private_t *pv = filter->private_data;
+    hb_buffer_t *in = *buf_in, *out;
+
+    if (in->s.flags & HB_BUF_FLAG_EOF)
+    {
+        *buf_out = in;
+        *buf_in = NULL;
+        return HB_FILTER_DONE;
+    }
+
+    out = hb_frame_buffer_init(in->f.fmt, in->f.width, in->f.height);
+
+    int c;
+    for (c = 0; c < 3; c++)
+    {
+        lapsharp_plane_context_t * ctx = &pv->plane_ctx[c];
+        hb_lapsharp(in->plane[c].data,
+                    out->plane[c].data,
+                    in->plane[c].width,
+                    in->plane[c].height,
+                    in->plane[c].stride,
+                    ctx);
+    }
+
+    out->s = in->s;
+    *buf_out = out;
+
+    return HB_FILTER_OK;
+}
index ae8d9961821d6c76ecfa7f34d005148b06808e47..92c972ed5c7e6579b59f39d854011a1fda8653fe 100644 (file)
@@ -90,6 +90,27 @@ static hb_filter_param_t unsharp_tunes[] =
     { 0, NULL,          NULL,         NULL              }
 };
 
+static hb_filter_param_t lapsharp_presets[] =
+{
+    { 1, "Custom",      "custom",     NULL              },
+    { 2, "Ultralight",  "ultralight", NULL              },
+    { 3, "Light",       "light",      NULL              },
+    { 4, "Medium",      "medium",     NULL              },
+    { 5, "Strong",      "strong",     NULL              },
+    { 6, "Stronger",    "stronger",   NULL              },
+    { 0, NULL,          NULL,         NULL              }
+};
+
+static hb_filter_param_t lapsharp_tunes[] =
+{
+    { 0, "None",        "none",       NULL              },
+    { 1, "Film",        "film",       NULL              },
+    { 2, "Grain",       "grain",      NULL              },
+    { 3, "Animation",   "animation",  NULL              },
+    { 4, "Sprite",      "sprite",     NULL              },
+    { 0, NULL,          NULL,         NULL              }
+};
+
 static hb_filter_param_t detelecine_presets[] =
 {
     { 0, "Off",         "off",        "disable=1"       },
@@ -164,6 +185,9 @@ static filter_param_map_t param_map[] =
     { HB_FILTER_UNSHARP,     unsharp_presets,     unsharp_tunes,
       sizeof(unsharp_presets) / sizeof(hb_filter_param_t)        },
 
+    { HB_FILTER_LAPSHARP,    lapsharp_presets,    lapsharp_tunes,
+      sizeof(lapsharp_presets) / sizeof(hb_filter_param_t)        },
+
     { HB_FILTER_DETELECINE,  detelecine_presets,  NULL,
       sizeof(detelecine_presets) / sizeof(hb_filter_param_t)     },
 
@@ -562,6 +586,158 @@ static hb_dict_t * generate_unsharp_settings(const char *preset,
     return settings;
 }
 
+static hb_dict_t * generate_lapsharp_settings(const char *preset,
+                                              const char *tune,
+                                              const char *custom)
+{
+    hb_dict_t * settings;
+
+    if (preset == NULL)
+        return NULL;
+
+    if (preset == NULL || !strcasecmp(preset, "custom"))
+    {
+        return hb_parse_filter_settings(custom);
+    }
+    if (!strcasecmp(preset, "ultralight") ||
+        !strcasecmp(preset, "light") ||
+        !strcasecmp(preset, "medium") ||
+        !strcasecmp(preset, "strong") ||
+        !strcasecmp(preset, "stronger"))
+    {
+        double strength[2];
+        const char *kernel_string[2];
+
+        if (tune == NULL || !strcasecmp(tune, "none"))
+        {
+            strength[0]      = strength[1]      = 0.2;
+            kernel_string[0] = kernel_string[1] = "isolap";
+            if (!strcasecmp(preset, "ultralight"))
+            {
+                strength[0]  = strength[1] = 0.05;
+            }
+            else if (!strcasecmp(preset, "light"))
+            {
+                strength[0]  = strength[1] = 0.1;
+            }
+            else if (!strcasecmp(preset, "strong"))
+            {
+                strength[0]  = strength[1] = 0.3;
+            }
+            else if (!strcasecmp(preset, "stronger"))
+            {
+                strength[0]  = strength[1] = 0.5;
+            }
+        }
+        else if (!strcasecmp(tune, "film"))
+        {
+            strength[0]      = 0.2;  strength[1] = 0.12;
+            kernel_string[0] = kernel_string[1]  = "isolap";
+            if (!strcasecmp(preset, "ultralight"))
+            {
+                strength[0]  = 0.05; strength[1] = 0.03;
+            }
+            else if (!strcasecmp(preset, "light"))
+            {
+                strength[0]  = 0.1;  strength[1] = 0.06;
+            }
+            else if (!strcasecmp(preset, "strong"))
+            {
+                strength[0]  = 0.3;  strength[1] = 0.2;
+            }
+            else if (!strcasecmp(preset, "stronger"))
+            {
+                strength[0]  = 0.5;  strength[1] = 0.3;
+            }
+        }
+        else if (!strcasecmp(tune, "grain"))
+        {
+            strength[0]      = 0.2; strength[1] = 0.1;
+            kernel_string[0] = kernel_string[1] = "log";
+            if (!strcasecmp(preset, "ultralight"))
+            {
+                strength[0] = 0.05; strength[1] = 0.025;
+            }
+            else if (!strcasecmp(preset, "light"))
+            {
+                strength[0] = 0.1;  strength[1] = 0.05;
+            }
+            else if (!strcasecmp(preset, "strong"))
+            {
+                strength[0] = 0.3;  strength[1] = 0.15;
+            }
+            else if (!strcasecmp(preset, "stronger"))
+            {
+                strength[0] = 0.5;  strength[1] = 0.25;
+            }
+        }
+        else if (!strcasecmp(tune, "animation"))
+        {
+            strength[0]      = 0.15; strength[1]   = 0.09;
+            kernel_string[0] = kernel_string[1]    = "isolap";
+            if (!strcasecmp(preset, "ultralight"))
+            {
+                strength[0]  = 0.0375; strength[1] = 0.0225;
+            }
+            else if (!strcasecmp(preset, "light"))
+            {
+                strength[0]  = 0.075;  strength[1] = 0.05625;
+            }
+            else if (!strcasecmp(preset, "strong"))
+            {
+                strength[0]  = 0.225;  strength[1] = 0.15;
+            }
+            else if (!strcasecmp(preset, "stronger"))
+            {
+                strength[0]  = 0.375;  strength[1] = 0.225;
+            }
+        }
+        else if (!strcasecmp(tune, "sprite"))
+        {
+            strength[0]      = strength[1]      = 0.15;
+            kernel_string[0] = kernel_string[1] = "lap";
+            if (!strcasecmp(preset, "ultralight"))
+            {
+                strength[0]  = strength[1]      = 0.0375;
+            }
+            else if (!strcasecmp(preset, "light"))
+            {
+                strength[0]  = strength[1]      = 0.075;
+            }
+            else if (!strcasecmp(preset, "strong"))
+            {
+                strength[0]  = strength[1]      = 0.225;
+            }
+            else if (!strcasecmp(preset, "stronger"))
+            {
+                strength[0]  = strength[1]      = 0.375;
+            }
+        }
+        else
+        {
+            fprintf(stderr, "Unrecognized lapsharp tune (%s).\n", tune);
+            return NULL;
+        }
+
+        settings = hb_dict_init();
+        hb_dict_set(settings, "y-strength", hb_value_double(strength[0]));
+        hb_dict_set(settings, "y-kernel",   hb_value_string(kernel_string[0]));
+
+        hb_dict_set(settings, "cb-strength", hb_value_double(strength[1]));
+        hb_dict_set(settings, "cb-kernel",   hb_value_string(kernel_string[1]));
+    }
+    else
+    {
+        settings = hb_parse_filter_settings(preset);
+        if (tune != NULL)
+        {
+            fprintf(stderr, "Custom lapsharp parameters specified; ignoring lapsharp tune (%s).\n", tune);
+        }
+    }
+
+    return settings;
+}
+
 int hb_validate_param_string(const char *regex_pattern, const char *param_string)
 {
     regex_t regex_temp;
@@ -822,6 +998,9 @@ hb_generate_filter_settings(int filter_id, const char *preset, const char *tune,
         case HB_FILTER_NLMEANS:
             settings = generate_nlmeans_settings(preset, tune, custom);
             break;
+        case HB_FILTER_LAPSHARP:
+            settings = generate_lapsharp_settings(preset, tune, custom);
+            break;
         case HB_FILTER_UNSHARP:
             settings = generate_unsharp_settings(preset, tune, custom);
             break;
index 08f7d186c00e93593658636e51f9eaf5f30aba40..6c32eb9f6569554323fd2c6643e7f6078ac9cba8 100644 (file)
@@ -1385,7 +1385,11 @@ int hb_preset_apply_filters(const hb_dict_t *preset, hb_dict_t *job_dict)
         strcasecmp(sharpen_filter, "off"))
     {
         int filter_id;
-        if (!strcasecmp(sharpen_filter, "unsharp"))
+        if (!strcasecmp(sharpen_filter, "lapsharp"))
+        {
+            filter_id = HB_FILTER_LAPSHARP;
+        }
+        else if (!strcasecmp(sharpen_filter, "unsharp"))
         {
             filter_id = HB_FILTER_UNSHARP;
         }
index cc84d3e7b4f0c974faf00dce36da2c35abd422e4..7cf7d4f47b27502816cf95d9eac0b3a3ad241918 100644 (file)
@@ -46,6 +46,7 @@
 #include <IOKit/storage/IODVDMedia.h>
 #endif
 
+#define LAPSHARP_DEFAULT_PRESET     "medium"
 #define UNSHARP_DEFAULT_PRESET      "medium"
 #define NLMEANS_DEFAULT_PRESET      "medium"
 #define DEINTERLACE_DEFAULT_PRESET  "default"
@@ -86,6 +87,10 @@ static int     unsharp_disable     = 0;
 static int     unsharp_custom      = 0;
 static char *  unsharp             = NULL;
 static char *  unsharp_tune        = NULL;
+static int     lapsharp_disable    = 0;
+static int     lapsharp_custom     = 0;
+static char *  lapsharp            = NULL;
+static char *  lapsharp_tune       = NULL;
 static int     detelecine_disable  = 0;
 static int     detelecine_custom   = 0;
 static char *  detelecine          = NULL;
@@ -578,6 +583,8 @@ cleanup:
     free(nlmeans_tune);
     free(unsharp);
     free(unsharp_tune);
+    free(lapsharp);
+    free(lapsharp_tune);
     free(preset_export_name);
     free(preset_export_desc);
     free(preset_export_file);
@@ -1095,6 +1102,9 @@ static void showFilterDefault(FILE* const out, int filter_id)
         case HB_FILTER_UNSHARP:
             preset = UNSHARP_DEFAULT_PRESET;
             break;
+        case HB_FILTER_LAPSHARP:
+            preset = LAPSHARP_DEFAULT_PRESET;
+            break;
         case HB_FILTER_NLMEANS:
             preset = NLMEANS_DEFAULT_PRESET;
             break;
@@ -1121,6 +1131,7 @@ static void showFilterDefault(FILE* const out, int filter_id)
         case HB_FILTER_DEINTERLACE:
         case HB_FILTER_NLMEANS:
         case HB_FILTER_UNSHARP:
+        case HB_FILTER_LAPSHARP:
         case HB_FILTER_DECOMB:
         case HB_FILTER_DETELECINE:
         case HB_FILTER_HQDN3D:
@@ -1624,6 +1635,19 @@ static void ShowHelp()
     fprintf( out,
 "                           Applies to unsharp presets only (does not affect\n"
 "                           custom settings)\n"
+"   --lapsharp[=string]     Sharpen video with lapsharp filter\n");
+    showFilterPresets(out, HB_FILTER_LAPSHARP);
+    showFilterKeys(out, HB_FILTER_LAPSHARP);
+    showFilterDefault(out, HB_FILTER_LAPSHARP);
+    fprintf( out,
+
+"   --no-lapsharp           Disable preset lapsharp filter\n"
+"   --lapsharp-tune <string>\n"
+"                           Tune lapsharp filter\n");
+    showFilterTunes(out, HB_FILTER_LAPSHARP);
+    fprintf( out,
+"                           Applies to lapsharp presets only (does not affect\n"
+"                           custom settings)\n"
 "   -7, --deblock[=string]  Deblock video with pp7 filter\n");
     showFilterKeys(out, HB_FILTER_DEBLOCK);
     showFilterDefault(out, HB_FILTER_DEBLOCK);
@@ -1981,6 +2005,8 @@ static int ParseOptions( int argc, char ** argv )
     #define QUEUE_IMPORT         311
     #define FILTER_UNSHARP       312
     #define FILTER_UNSHARP_TUNE  313
+    #define FILTER_LAPSHARP      314
+    #define FILTER_LAPSHARP_TUNE 315
 
     for( ;; )
     {
@@ -2059,6 +2085,9 @@ static int ParseOptions( int argc, char ** argv )
             { "unsharp",     optional_argument, NULL,    FILTER_UNSHARP },
             { "no-unsharp",  no_argument,       &unsharp_disable,     1 },
             { "unsharp-tune",required_argument, NULL,    FILTER_UNSHARP_TUNE },
+            { "lapsharp",    optional_argument, NULL,    FILTER_LAPSHARP },
+            { "no-lapsharp", no_argument,       &lapsharp_disable,     1 },
+            { "lapsharp-tune", required_argument, NULL,  FILTER_LAPSHARP_TUNE },
             { "detelecine",  optional_argument, NULL,    '9' },
             { "no-detelecine", no_argument,     &detelecine_disable,  1 },
             { "no-comb-detect", no_argument,    &comb_detect_disable, 1 },
@@ -2501,6 +2530,21 @@ static int ParseOptions( int argc, char ** argv )
                 free(unsharp_tune);
                 unsharp_tune = strdup(optarg);
                 break;
+            case FILTER_LAPSHARP:
+                free(lapsharp);
+                if (optarg != NULL)
+                {
+                    lapsharp = strdup(optarg);
+                }
+                else
+                {
+                    lapsharp = strdup(LAPSHARP_DEFAULT_PRESET);
+                }
+                break;
+            case FILTER_LAPSHARP_TUNE:
+                free(lapsharp_tune);
+                lapsharp_tune = strdup(optarg);
+                break;
             case '9':
                 free(detelecine);
                 if (optarg != NULL)
@@ -3018,6 +3062,32 @@ static int ParseOptions( int argc, char ** argv )
         }
     }
 
+    if (lapsharp != NULL)
+    {
+        if (lapsharp_disable)
+        {
+            fprintf(stderr,
+                    "Incompatible options --lapsharp and --no-lapsharp\n");
+            return -1;
+        }
+        if (!hb_validate_filter_preset(HB_FILTER_LAPSHARP, lapsharp,
+                                       lapsharp_tune, NULL))
+        {
+            // Nothing to do, but must validate preset before
+            // attempting to validate custom settings to prevent potential
+            // false positive
+        }
+        else if (!hb_validate_filter_string(HB_FILTER_LAPSHARP, lapsharp))
+        {
+            lapsharp_custom = 1;
+        }
+        else
+        {
+            fprintf(stderr, "Invalid lapsharp option %s\n", lapsharp);
+            return -1;
+        }
+    }
+
     return 0;
 }
 
@@ -3958,6 +4028,31 @@ static hb_dict_t * PreparePreset(const char *preset_name)
                         hb_value_string(unsharp));
         }
     }
+    if (lapsharp_disable && !strcasecmp(s, "lapsharp"))
+    {
+        hb_dict_set(preset, "PictureSharpenFilter", hb_value_string("off"));
+    }
+    if (lapsharp != NULL)
+    {
+        hb_dict_set(preset, "PictureSharpenFilter", hb_value_string("lapsharp"));
+        if (!lapsharp_custom)
+        {
+            hb_dict_set(preset, "PictureSharpenPreset",
+                        hb_value_string(lapsharp));
+            if (lapsharp_tune != NULL)
+            {
+                hb_dict_set(preset, "PictureSharpenTune",
+                            hb_value_string(lapsharp_tune));
+            }
+        }
+        else
+        {
+            hb_dict_set(preset, "PictureSharpenPreset",
+                        hb_value_string("custom"));
+            hb_dict_set(preset, "PictureSharpenCustom",
+                        hb_value_string(lapsharp));
+        }
+    }
     if (deblock_disable)
     {
         hb_dict_set(preset, "PictureDeblock", hb_value_string("0"));