HandBrake/libhb/lapsharp.c
2025-01-22 09:11:40 +01:00

358 lines
13 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* lapsharp.c
Copyright (c) 2003-2025 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 "handbrake/handbrake.h"
#define LAPSHARP_STRENGTH_LUMA_DEFAULT 0.2
#define LAPSHARP_STRENGTH_CHROMA_DEFAULT 0.2
#define LAPSHARP_KERNELS 4
#define LAPSHARP_KERNEL_LUMA_DEFAULT 2
#define LAPSHARP_KERNEL_CHROMA_DEFAULT 2
typedef struct
{
int bps;
int max_value;
double strength; // strength
int kernel; // which kernel to use; kernels[kernel]
} lapsharp_plane_context_t;
typedef struct {
const int *mem;
const int size;
const double coef;
} kernel_t;
// 4-neighbor Laplacian kernel (lap)
// Sharpens vertical and horizontal edges, less effective on diagonals
// size = 3, coef = 1.0
static const int kernel_lap[] =
{
0, -1, 0,
-1, 5, -1,
0, -1, 0
};
// Isotropic Laplacian kernel (isolap)
// Minimal directionality, sharpens all edges similarly
// size = 3, coef = 1.0 / 5
static const int kernel_isolap[] =
{
-1, -4, -1,
-4, 25, -4,
-1, -4, -1
};
// Laplacian of Gaussian kernel (log)
// Slight noise and grain rejection
// σ ~= 1
// size = 5, coef = 1.0 / 5
static const int 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
};
// Isotropic Laplacian of Gaussian kernel (isolog)
// Minimal directionality, plus noise and grain rejection
// σ ~= 1.2
// size = 5, coef = 1.0 / 15
static const int kernel_isolog[] =
{
0, -1, -1, -1, 0,
-1, -3, -4, -3, -1,
-1, -4, 55, -4, -1,
-1, -3, -4, -3, -1,
0, -1, -1, -1, 0
};
static kernel_t kernels[] =
{
{ kernel_lap, 3, 1.0 },
{ kernel_isolap, 3, 1.0 / 5 },
{ kernel_log, 5, 1.0 / 5 },
{ kernel_isolog, 5, 1.0 / 15 }
};
struct hb_filter_private_s
{
int depth;
lapsharp_plane_context_t plane_ctx[3];
hb_filter_init_t input;
hb_filter_init_t output;
};
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,
};
#define DEF_LAPSHARP_FUNC(name, nbits, pixelbits) \
static void name##_##nbits(const uint8_t *frame_src, \
uint8_t *frame_dst, \
const int width, \
const int height, \
int stride_src, \
int stride_dst, \
lapsharp_plane_context_t *ctx) \
{ \
const kernel_t *kernel = &kernels[ctx->kernel]; \
\
const uint##nbits##_t *src = (const uint##nbits##_t *)frame_src; \
uint##nbits##_t *dst = (uint##nbits##_t *)frame_dst; \
\
stride_src /= ctx->bps; \
stride_dst /= ctx->bps; \
\
/* 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_src - width) / 2; \
const int max_value = ctx->max_value; \
\
int##pixelbits##_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_dst*y + x) = *(src + stride_src*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_src*(y + j) + (x + k)); \
} \
} \
pixel = (int##pixelbits##_t)(((pixel * kernel->coef) - *(src + stride_src*y + x)) * \
ctx->strength) + *(src + stride_src*y + x); \
pixel = pixel < 0 ? 0 : pixel; \
pixel = pixel > max_value ? max_value : pixel; \
*(dst + stride_dst*y + x) = (uint##nbits##_t)(pixel); \
} \
} \
} \
DEF_LAPSHARP_FUNC(lapsharp, 16, 32)
DEF_LAPSHARP_FUNC(lapsharp, 8, 16)
#define hb_lapsharp(...) \
switch (pv->depth) \
{ \
case 8: lapsharp_8(__VA_ARGS__); break; \
default: lapsharp_16(__VA_ARGS__); break; \
} \
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);
if (filter->private_data == NULL)
{
hb_error("lapsharp: calloc failed");
return -1;
}
hb_filter_private_t * pv = filter->private_data;
char *kernel_string[3];
pv->input = *init;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(init->pix_fmt);
pv->depth = desc->comp[0].depth;
// 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->bps = pv->depth > 8 ? 2 : 1;
ctx->max_value = (1 << pv->depth) - 1;
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;
}
else if (!strcasecmp(kernel_string[c], "isolog"))
{
ctx->kernel = 3;
}
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;
}
}
pv->output = *init;
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;
}
hb_frame_buffer_mirror_stride(in);
out = hb_frame_buffer_init(pv->output.pix_fmt, in->f.width, in->f.height);
out->f.color_prim = pv->output.color_prim;
out->f.color_transfer = pv->output.color_transfer;
out->f.color_matrix = pv->output.color_matrix;
out->f.color_range = pv->output.color_range;
out->f.chroma_location = pv->output.chroma_location;
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,
out->plane[c].stride,
ctx);
}
hb_buffer_copy_props(out, in);
*buf_out = out;
return HB_FILTER_OK;
}