407 lines
18 KiB
C
407 lines
18 KiB
C
/* unsharp.c
|
|
|
|
Copyright (c) 2002 Rémi Guyomarch <rguyom at pobox.com>
|
|
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 UNSHARP_STRENGTH_LUMA_DEFAULT 0.25
|
|
#define UNSHARP_SIZE_LUMA_DEFAULT 7
|
|
#define UNSHARP_STRENGTH_CHROMA_DEFAULT 0.25
|
|
#define UNSHARP_SIZE_CHROMA_DEFAULT 7
|
|
#define UNSHARP_SIZE_MIN 3
|
|
#define UNSHARP_SIZE_MAX 15
|
|
|
|
typedef struct
|
|
{
|
|
int pix_fmt; // source pixel format
|
|
int bps;
|
|
int max_value;
|
|
int width; // source video width
|
|
double strength; // strength
|
|
int size; // pixel context region width (must be odd)
|
|
|
|
int steps;
|
|
int amount;
|
|
int scalebits;
|
|
int32_t halfscale;
|
|
} unsharp_plane_context_t;
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t * SC[UNSHARP_SIZE_MAX - 1];
|
|
} unsharp_thread_context_t;
|
|
|
|
typedef unsharp_thread_context_t unsharp_thread_context3_t[3];
|
|
|
|
struct hb_filter_private_s
|
|
{
|
|
int depth;
|
|
|
|
unsharp_plane_context_t plane_ctx[3];
|
|
unsharp_thread_context3_t * thread_ctx;
|
|
int threads;
|
|
|
|
hb_filter_init_t input;
|
|
hb_filter_init_t output;
|
|
};
|
|
|
|
static int unsharp_init(hb_filter_object_t *filter,
|
|
hb_filter_init_t *init);
|
|
|
|
static int unsharp_init_thread(hb_filter_object_t *filter, int threads);
|
|
|
|
static int unsharp_work(hb_filter_object_t *filter,
|
|
hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out);
|
|
static int unsharp_work_thread(hb_filter_object_t *filter,
|
|
hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out, int thread);
|
|
|
|
static void unsharp_close(hb_filter_object_t *filter);
|
|
|
|
static const char unsharp_template[] =
|
|
"y-strength=^"HB_FLOAT_REG"$:y-size=^"HB_INT_REG"$:"
|
|
"cb-strength=^"HB_FLOAT_REG"$:cb-size=^"HB_INT_REG"$:"
|
|
"cr-strength=^"HB_FLOAT_REG"$:cr-size=^"HB_INT_REG"$";
|
|
|
|
hb_filter_object_t hb_filter_unsharp =
|
|
{
|
|
.id = HB_FILTER_UNSHARP,
|
|
.enforce_order = 1,
|
|
.name = "Sharpen (unsharp)",
|
|
.settings = NULL,
|
|
.init = unsharp_init,
|
|
.init_thread = unsharp_init_thread,
|
|
.work = unsharp_work,
|
|
.work_thread = unsharp_work_thread,
|
|
.close = unsharp_close,
|
|
.settings_template = unsharp_template,
|
|
};
|
|
|
|
|
|
#define DEF_UNSHARP_FUNC(name, nbits) \
|
|
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, \
|
|
unsharp_plane_context_t *ctx, \
|
|
unsharp_thread_context_t *tctx) \
|
|
{ \
|
|
uint32_t **SC = tctx->SC; \
|
|
uint32_t SR[UNSHARP_SIZE_MAX - 1]; \
|
|
const uint##nbits##_t *src = (const uint##nbits##_t *)frame_src; \
|
|
uint##nbits##_t *dst = (uint##nbits##_t *)frame_dst; \
|
|
const uint##nbits##_t *src2 = (const uint##nbits##_t *)frame_src; \
|
|
const int amount = ctx->amount; \
|
|
const int steps = ctx->steps; \
|
|
const int scalebits = ctx->scalebits; \
|
|
const int32_t halfscale = ctx->halfscale; \
|
|
const int16_t max_value = ctx->max_value; \
|
|
\
|
|
int32_t res; \
|
|
int x, y, z; \
|
|
uint32_t Tmp1, Tmp2; \
|
|
\
|
|
if (!amount) \
|
|
{ \
|
|
if (src != dst) \
|
|
{ \
|
|
if (stride_src == stride_dst) \
|
|
{ \
|
|
memcpy(dst, src, stride_dst * height); \
|
|
} \
|
|
else \
|
|
{ \
|
|
const int size = stride_src < stride_dst ? ABS(stride_src) : stride_dst; \
|
|
for (int yy = 0; yy < height; yy++) \
|
|
{ \
|
|
memcpy(dst, src, size); \
|
|
dst += stride_dst; \
|
|
src += stride_src; \
|
|
} \
|
|
} \
|
|
} \
|
|
\
|
|
return; \
|
|
} \
|
|
\
|
|
for (y = 0; y < 2 * steps; y++) \
|
|
{ \
|
|
memset(SC[y], 0, sizeof(SC[y][0]) * (width + 2 * steps)); \
|
|
} \
|
|
\
|
|
stride_src /= ctx->bps; \
|
|
stride_dst /= ctx->bps; \
|
|
\
|
|
for (y = -steps; y < height + steps; y++) \
|
|
{ \
|
|
if (y < height) \
|
|
{ \
|
|
src2 = src; \
|
|
} \
|
|
\
|
|
memset(SR, 0, sizeof(SR[0]) * (2 * steps)); \
|
|
\
|
|
for (x = -steps; x < width + steps; x++) \
|
|
{ \
|
|
Tmp1 = x <= 0 ? src2[0] : x >= width ? src2[width - 1] : src2[x]; \
|
|
\
|
|
for (z = 0; z < steps * 2; z += 2) \
|
|
{ \
|
|
Tmp2 = SR[z + 0] + Tmp1; SR[z + 0] = Tmp1; \
|
|
Tmp1 = SR[z + 1] + Tmp2; SR[z + 1] = Tmp2; \
|
|
} \
|
|
\
|
|
for (z = 0; z < steps * 2; z += 2) \
|
|
{ \
|
|
Tmp2 = SC[z + 0][x + steps] + Tmp1; SC[z + 0][x + steps] = Tmp1; \
|
|
Tmp1 = SC[z + 1][x + steps] + Tmp2; SC[z + 1][x + steps] = Tmp2; \
|
|
} \
|
|
\
|
|
if (x >= steps && y >= steps) \
|
|
{ \
|
|
const uint##nbits##_t *srx = src - steps * stride_src + x - steps; \
|
|
uint##nbits##_t *dsx = dst - steps * stride_dst + x - steps; \
|
|
\
|
|
res = (int32_t)*srx + ((((int32_t)*srx - \
|
|
(int32_t)((Tmp1 + halfscale) >> scalebits)) * amount) >> 16); \
|
|
*dsx = res > max_value ? max_value : res < 0 ? 0 : (uint##nbits##_t)res; \
|
|
} \
|
|
} \
|
|
\
|
|
if (y >= 0) \
|
|
{ \
|
|
dst += stride_dst; \
|
|
src += stride_src; \
|
|
} \
|
|
} \
|
|
} \
|
|
|
|
DEF_UNSHARP_FUNC(unsharp, 16)
|
|
DEF_UNSHARP_FUNC(unsharp, 8)
|
|
|
|
#define unsharp(...) \
|
|
switch (pv->depth) \
|
|
{ \
|
|
case 8: unsharp_8(__VA_ARGS__); break; \
|
|
default: unsharp_16(__VA_ARGS__); break; \
|
|
} \
|
|
|
|
static int unsharp_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("Unsharp calloc failed");
|
|
return -1;
|
|
}
|
|
hb_filter_private_t * pv = filter->private_data;
|
|
|
|
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].size = -1;
|
|
}
|
|
|
|
// 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_int(&pv->plane_ctx[0].size, dict, "y-size");
|
|
|
|
hb_dict_extract_double(&pv->plane_ctx[1].strength, dict, "cb-strength");
|
|
hb_dict_extract_int(&pv->plane_ctx[1].size, dict, "cb-size");
|
|
|
|
hb_dict_extract_double(&pv->plane_ctx[2].strength, dict, "cr-strength");
|
|
hb_dict_extract_int(&pv->plane_ctx[2].size, dict, "cr-size");
|
|
}
|
|
|
|
// Cascade values
|
|
// Cr not set; inherit Cb. Cb not set; inherit Y. Y not set; defaults.
|
|
for (int c = 1; c < 3; c++)
|
|
{
|
|
unsharp_plane_context_t * prev_ctx = &pv->plane_ctx[c - 1];
|
|
unsharp_plane_context_t * ctx = &pv->plane_ctx[c];
|
|
|
|
if (ctx->strength == -1) ctx->strength = prev_ctx->strength;
|
|
if (ctx->size == -1) ctx->size = prev_ctx->size;
|
|
}
|
|
|
|
for (int c = 0; c < 3; c++)
|
|
{
|
|
unsharp_plane_context_t * ctx = &pv->plane_ctx[c];
|
|
|
|
ctx->width = init->geometry.width;
|
|
ctx->pix_fmt = init->pix_fmt;
|
|
ctx->bps = pv->depth > 8 ? 2 : 1;
|
|
ctx->max_value = (1 << pv->depth) - 1;
|
|
|
|
// Replace unset values with defaults
|
|
if (ctx->strength == -1)
|
|
{
|
|
ctx->strength = c ? UNSHARP_STRENGTH_CHROMA_DEFAULT :
|
|
UNSHARP_STRENGTH_LUMA_DEFAULT;
|
|
}
|
|
if (ctx->size == -1)
|
|
{
|
|
ctx->size = c ? UNSHARP_SIZE_CHROMA_DEFAULT :
|
|
UNSHARP_SIZE_LUMA_DEFAULT;
|
|
}
|
|
|
|
// Sanitize
|
|
if (ctx->strength < 0) ctx->strength = 0;
|
|
if (ctx->strength > 1.5) ctx->strength = 1.5;
|
|
if (ctx->size % 2 == 0) ctx->size--;
|
|
if (ctx->size < UNSHARP_SIZE_MIN) ctx->size = UNSHARP_SIZE_MIN;
|
|
if (ctx->size > UNSHARP_SIZE_MAX) ctx->size = UNSHARP_SIZE_MAX;
|
|
|
|
ctx->amount = ctx->strength * 65536.0;
|
|
ctx->steps = ctx->size / 2;
|
|
ctx->scalebits = ctx->steps * 4;
|
|
ctx->halfscale = 1 << (ctx->scalebits - 1);
|
|
}
|
|
|
|
if (unsharp_init_thread(filter, 1) < 0)
|
|
{
|
|
unsharp_close(filter);
|
|
return -1;
|
|
}
|
|
|
|
pv->output = *init;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void unsharp_thread_close(hb_filter_private_t *pv)
|
|
{
|
|
int c, z;
|
|
for (c = 0; c < 3; c++)
|
|
{
|
|
unsharp_plane_context_t * ctx = &pv->plane_ctx[c];
|
|
for (int t = 0; t < pv->threads; t++)
|
|
{
|
|
unsharp_thread_context_t * tctx = &pv->thread_ctx[t][c];
|
|
for (z = 0; z < 2 * ctx->steps; z++)
|
|
{
|
|
free(tctx->SC[z]);
|
|
tctx->SC[z] = NULL;
|
|
}
|
|
}
|
|
}
|
|
free(pv->thread_ctx);
|
|
}
|
|
|
|
static int unsharp_init_thread(hb_filter_object_t *filter, int threads)
|
|
{
|
|
hb_filter_private_t * pv = filter->private_data;
|
|
|
|
unsharp_thread_close(pv);
|
|
pv->thread_ctx = calloc(threads, sizeof(unsharp_thread_context3_t));
|
|
if (pv->thread_ctx == NULL)
|
|
{
|
|
hb_error("Unsharp calloc failed");
|
|
return -1;
|
|
}
|
|
pv->threads = threads;
|
|
for (int c = 0; c < 3; c++)
|
|
{
|
|
unsharp_plane_context_t * ctx = &pv->plane_ctx[c];
|
|
int w = hb_image_width(ctx->pix_fmt, ctx->width, c);
|
|
|
|
for (int t = 0; t < threads; t++)
|
|
{
|
|
unsharp_thread_context_t * tctx = &pv->thread_ctx[t][c];
|
|
int z;
|
|
for (z = 0; z < 2 * ctx->steps; z++)
|
|
{
|
|
tctx->SC[z] = malloc(sizeof(*(tctx->SC[z])) *
|
|
(w + 2 * ctx->steps));
|
|
if (tctx->SC[z] == NULL)
|
|
{
|
|
hb_error("Unsharp calloc failed");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void unsharp_close(hb_filter_object_t * filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsharp_thread_close(pv);
|
|
free(pv);
|
|
filter->private_data = NULL;
|
|
}
|
|
|
|
static int unsharp_work_thread(hb_filter_object_t *filter,
|
|
hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out, int thread)
|
|
{
|
|
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(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++)
|
|
{
|
|
unsharp_plane_context_t * ctx = &pv->plane_ctx[c];
|
|
unsharp_thread_context_t * tctx = &pv->thread_ctx[thread][c];
|
|
unsharp(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, tctx);
|
|
}
|
|
|
|
hb_buffer_copy_props(out, in);
|
|
*buf_out = out;
|
|
|
|
return HB_FILTER_OK;
|
|
}
|
|
|
|
static int unsharp_work(hb_filter_object_t *filter,
|
|
hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out)
|
|
{
|
|
return unsharp_work_thread(filter, buf_in, buf_out, 0);
|
|
}
|