video: Add sigmoidal upscaling to avoid ringing artifacts

This avoids issues when upscaling directly in linear light, and is the
recommended way to upscale images according to imagemagick.

The default slope of 6.5 offers a reasonable compromise between
ringing artifacts eliminated and ringing artifacts introduced by
sigmoid-upscaling. Same goes for the default center of 0.75.
This commit is contained in:
Niklas Haas 2015-01-06 10:47:26 +01:00 committed by wm4
parent 33dd9147ae
commit 286340d7d0
4 changed files with 69 additions and 2 deletions

View File

@ -457,6 +457,18 @@ Available video output drivers are:
This is automatically disabled for anamorphic video, because this
feature doesn't work correctly with this.
``sigmoid-upscaling``
When upscaling in linear light, use a sigmoidal color transform
to avoid emphasizing ringing artifacts.
``sigmoid-center``
The center of the sigmoid curve used for ``sigmoid-upscaling``, must
be a float between 0.0 and 1.0. Defaults to 0.75 if not specified.
``sigmoid-slope``
The slope of the sigmoid curve used for ``sigmoid-upscaling``, must
be a float between 1.0 and 20.0. Defaults to 6.5 if not specified.
``no-npot``
Force use of power-of-2 texture sizes. For debugging only.
Borders will be distorted due to filtering.
@ -598,7 +610,7 @@ Available video output drivers are:
This is equivalent to::
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgba16:fancy-downscaling
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgba16:fancy-downscaling:sigmoid-upscaling
Note that some cheaper LCDs do dithering that gravely interferes with
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.

View File

@ -181,6 +181,9 @@ struct gl_video {
// state for luma (0) and chroma (1) scalers
struct scaler scalers[2];
// true if scaler is currently upscaling
bool upscaling;
struct mp_csp_equalizer video_eq;
struct mp_image_params image_params;
@ -319,6 +322,8 @@ const struct gl_video_opts gl_video_opts_def = {
.dither_size = 6,
.fbo_format = GL_RGBA,
.scale_sep = 1,
.sigmoid_center = 0.75,
.sigmoid_slope = 6.5,
.scalers = { "bilinear", "bilinear" },
.scaler_params = {{NAN, NAN}, {NAN, NAN}},
.scaler_radius = {NAN, NAN},
@ -333,6 +338,9 @@ const struct gl_video_opts gl_video_opts_hq_def = {
.fbo_format = GL_RGBA16,
.scale_sep = 1,
.fancy_downscaling = 1,
.sigmoid_center = 0.75,
.sigmoid_slope = 6.5,
.sigmoid_upscaling = 1,
.scalers = { "spline36", "bilinear" },
.scaler_params = {{NAN, NAN}, {NAN, NAN}},
.scaler_radius = {NAN, NAN},
@ -364,6 +372,9 @@ const struct m_sub_options gl_video_conf = {
OPT_FLOATRANGE("cradius", scaler_radius[1], 0, 1.0, 32.0),
OPT_FLAG("scaler-resizes-only", scaler_resizes_only, 0),
OPT_FLAG("fancy-downscaling", fancy_downscaling, 0),
OPT_FLAG("sigmoid-upscaling", sigmoid_upscaling, 0),
OPT_FLOATRANGE("sigmoid-center", sigmoid_center, 0, 0.0, 1.0),
OPT_FLOATRANGE("sigmoid-slope", sigmoid_slope, 0, 1.0, 20.0),
OPT_FLAG("indirect", indirect, 0),
OPT_FLAG("scale-sep", scale_sep, 0),
OPT_CHOICE("fbo-format", fbo_format, 0,
@ -405,6 +416,7 @@ static void uninit_rendering(struct gl_video *p);
static void delete_shaders(struct gl_video *p);
static void check_gl_features(struct gl_video *p);
static bool init_format(int fmt, struct gl_video *init);
static double get_scale_factor(struct gl_video *p);
static const struct fmt_entry *find_tex_format(GL *gl, int bytes_per_comp,
int n_channels)
@ -683,6 +695,21 @@ static void update_uniforms(struct gl_video *p, GLuint program)
gl->Uniform1f(gl->GetUniformLocation(program, "conv_gamma"),
p->conv_gamma);
// Coefficients for the sigmoidal transform are taken from the
// formula here: http://www.imagemagick.org/Usage/color_mods/#sigmoidal
float sig_center = p->opts.sigmoid_center;
float sig_slope = p->opts.sigmoid_slope;
// This function needs to go through (0,0) and (1,1) so we compute the
// values at 1 and 0, and then scale/shift them, respectively.
float sig_offset = 1.0/(1+expf(sig_slope * sig_center));
float sig_scale = 1.0/(1+expf(sig_slope * (sig_center-1))) - sig_offset;
gl->Uniform1f(gl->GetUniformLocation(program, "sig_center"), sig_center);
gl->Uniform1f(gl->GetUniformLocation(program, "sig_slope"), sig_slope);
gl->Uniform1f(gl->GetUniformLocation(program, "sig_scale"), sig_scale);
gl->Uniform1f(gl->GetUniformLocation(program, "sig_offset"), sig_offset);
float gamma = p->opts.gamma ? p->opts.gamma : 1.0;
gl->Uniform3f(gl->GetUniformLocation(program, "inv_gamma"),
1.0 / (cparams.rgamma * gamma),
@ -1034,6 +1061,12 @@ static void compile_shaders(struct gl_video *p)
gamma_fun = MP_CSP_TRC_BT_2020_EXACT;
}
bool use_linear_light = gamma_fun != MP_CSP_TRC_NONE || p->is_linear_rgb;
// Optionally transform to sigmoidal color space if requested, but only
// when upscaling in linear light
bool use_sigmoid = p->opts.sigmoid_upscaling && use_linear_light && p->upscaling;
// Figure out the right color spaces we need to convert, if any
enum mp_csp_prim prim_src = p->image_params.primaries, prim_dest;
if (use_cms) {
@ -1114,11 +1147,13 @@ static void compile_shaders(struct gl_video *p)
gamma_fun == MP_CSP_TRC_BT_2020_EXACT);
shader_def_opt(&header_conv, "USE_LINEAR_LIGHT_SRGB",
gamma_fun == MP_CSP_TRC_SRGB);
shader_def_opt(&header_conv, "USE_SIGMOID", use_sigmoid);
if (p->opts.alpha_mode > 0 && p->has_alpha && p->plane_count > 3)
shader_def(&header_conv, "USE_ALPHA_PLANE", "3");
if (p->opts.alpha_mode == 2 && p->has_alpha)
shader_def(&header_conv, "USE_ALPHA_BLEND", "1");
shader_def_opt(&header_final, "USE_SIGMOID_INV", use_sigmoid);
shader_def_opt(&header_final, "USE_GAMMA_POW", p->opts.gamma > 0);
shader_def_opt(&header_final, "USE_CMS_MATRIX", use_cms_matrix);
shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d);
@ -1146,7 +1181,7 @@ static void compile_shaders(struct gl_video *p)
// Don't sample from input video textures before converting the input to
// linear light.
if (use_input_gamma || use_conv_gamma || gamma_fun != MP_CSP_TRC_NONE)
if (use_input_gamma || use_conv_gamma || use_linear_light)
use_indirect = true;
// It doesn't make sense to scale the chroma with cscale in the 1. scale
@ -1849,6 +1884,12 @@ static void check_resize(struct gl_video *p)
if (strcmp(p->scalers[n].name, expected_scaler(p, n)) != 0)
need_scaler_reinit = true;
}
if (p->upscaling != (get_scale_factor(p) > 1.0)) {
p->upscaling = !p->upscaling;
// Switching between upscaling and downscaling also requires sigmoid
// to be toggled
need_scaler_reinit |= p->opts.sigmoid_upscaling;
}
if (need_scaler_reinit) {
reinit_rendering(p);
} else if (need_scaler_update) {

View File

@ -39,6 +39,9 @@ struct gl_video_opts {
int approx_gamma;
int scale_sep;
int fancy_downscaling;
int sigmoid_upscaling;
float sigmoid_center;
float sigmoid_slope;
int scaler_resizes_only;
int npot;
int pbo;

View File

@ -179,6 +179,10 @@ uniform mat2 dither_trafo;
uniform vec3 inv_gamma;
uniform float input_gamma;
uniform float conv_gamma;
uniform float sig_center;
uniform float sig_slope;
uniform float sig_scale;
uniform float sig_offset;
uniform float dither_quantization;
uniform float dither_center;
uniform float filter_param1_l;
@ -424,8 +428,15 @@ void main() {
// Calculate the green channel from the expanded RYcB
// The BT.2020 specification says Yc = 0.2627*R + 0.6780*G + 0.0593*B
color.g = (color.g - 0.2627*color.r - 0.0593*color.b)/0.6780;
#endif
#ifdef USE_SIGMOID
color = sig_center - log(1.0/(color * sig_scale + sig_offset) - 1.0)/sig_slope;
#endif
// Image upscaling happens roughly here
#ifdef USE_SIGMOID_INV
// Inverse of USE_SIGMOID
color = (1.0/(1.0 + exp(sig_slope * (sig_center - color))) - sig_offset) / sig_scale;
#endif
#ifdef USE_GAMMA_POW
// User-defined gamma correction factor (via the gamma sub-option)
color = pow(color, inv_gamma);