parent
2edf81cdb0
commit
ca93a5191f
|
@ -99,6 +99,107 @@ float4 SharpBilinearSample(float3 uvw, float gamma)
|
|||
return BilinearSample(uvw, gamma);
|
||||
}
|
||||
|
||||
// By Sam Belliveau. Public Domain license.
|
||||
// Effectively a more accurate sharp bilinear filter when upscaling,
|
||||
// that also works as a mathematically perfect downscale filter.
|
||||
// https://entropymine.com/imageworsener/pixelmixing/
|
||||
// https://github.com/obsproject/obs-studio/pull/1715
|
||||
// https://legacy.imagemagick.org/Usage/filter/
|
||||
float4 BoxResample(float3 uvw, float gamma)
|
||||
{
|
||||
// Determine the sizes of the source and target images.
|
||||
float2 source_size = GetResolution();
|
||||
float2 inv_source_size = GetInvResolution();
|
||||
float2 inv_target_size = GetInvWindowResolution();
|
||||
|
||||
// Determine the range of the source image that the target pixel will cover.
|
||||
// We shift by one output pixel because that's a prerequisite of the algorithm.
|
||||
float2 range = source_size * inv_target_size;
|
||||
float2 beg = (uvw.xy - inv_target_size) * source_size;
|
||||
float2 end = beg + range;
|
||||
|
||||
// Compute the top-left and bottom-right corners of the pixel box.
|
||||
float2 f_beg = floor(beg);
|
||||
float2 f_end = floor(end);
|
||||
|
||||
// Compute how much of the start and end pixels are covered horizontally & vertically.
|
||||
float area_w = 1.0 - fract(beg.x);
|
||||
float area_n = 1.0 - fract(beg.y);
|
||||
float area_e = fract(end.x);
|
||||
float area_s = fract(end.y);
|
||||
|
||||
// Compute the areas of the corner pixels in the pixel box.
|
||||
float area_nw = area_n * area_w;
|
||||
float area_ne = area_n * area_e;
|
||||
float area_sw = area_s * area_w;
|
||||
float area_se = area_s * area_e;
|
||||
|
||||
// Initialize the color accumulator.
|
||||
float4 avg_color = float4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
// Presents rounding errors
|
||||
const float offset = 0.5;
|
||||
|
||||
// Accumulate corner pixels.
|
||||
avg_color += area_nw * QuickSample(float2(f_beg.x + offset, f_beg.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
avg_color += area_ne * QuickSample(float2(f_end.x + offset, f_beg.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
avg_color += area_sw * QuickSample(float2(f_beg.x + offset, f_end.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
avg_color += area_se * QuickSample(float2(f_end.x + offset, f_end.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
|
||||
// Determine the size of the pixel box.
|
||||
int x_range = int(f_end.x - f_beg.x + 0.5);
|
||||
int y_range = int(f_end.y - f_beg.y + 0.5);
|
||||
|
||||
// Workaround to compile the shader with DX11/12.
|
||||
// If this isn't done, it will complain that the loop could have too many iterations.
|
||||
// This number should be enough to guarantee downscaling from very high to very small resolutions.
|
||||
const int max_iterations = 16;
|
||||
|
||||
// Fix up the average calculations in case we reached the upper limit
|
||||
x_range = min(x_range, max_iterations);
|
||||
y_range = min(y_range, max_iterations);
|
||||
|
||||
// Accumulate top and bottom edge pixels.
|
||||
for (int ix = 0; ix < max_iterations; ++ix)
|
||||
{
|
||||
if (ix < x_range)
|
||||
{
|
||||
float x = f_beg.x + 1.0 + float(ix);
|
||||
avg_color += area_n * QuickSample(float2(x + offset, f_beg.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
avg_color += area_s * QuickSample(float2(x + offset, f_end.y + offset) * inv_source_size, uvw.z, gamma);
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate left and right edge pixels and all the pixels in between.
|
||||
for (int iy = 0; iy < max_iterations; ++iy)
|
||||
{
|
||||
if (iy < y_range)
|
||||
{
|
||||
float y = f_beg.y + 1.0 + float(iy);
|
||||
|
||||
avg_color += area_w * QuickSample(float2(f_beg.x + offset, y + offset) * inv_source_size, uvw.z, gamma);
|
||||
avg_color += area_e * QuickSample(float2(f_end.x + offset, y + offset) * inv_source_size, uvw.z, gamma);
|
||||
|
||||
for (int ix = 0; ix < max_iterations; ++ix)
|
||||
{
|
||||
if (ix < x_range)
|
||||
{
|
||||
float x = f_beg.x + 1.0 + float(ix);
|
||||
avg_color += QuickSample(float2(x + offset, y + offset) * inv_source_size, uvw.z, gamma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the area of the pixel box that was sampled.
|
||||
float area_corners = area_nw + area_ne + area_sw + area_se;
|
||||
float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
|
||||
float area_center = float(x_range) * float(y_range);
|
||||
|
||||
// Return the normalized average color.
|
||||
return avg_color / (area_corners + area_edges + area_center);
|
||||
}
|
||||
|
||||
float4 Cubic(float v)
|
||||
{
|
||||
float4 n = float4(1.0, 2.0, 3.0, 4.0) - v;
|
||||
|
@ -279,6 +380,10 @@ float4 LinearGammaCorrectedSample(float gamma)
|
|||
{
|
||||
color = SharpBilinearSample(uvw, gamma);
|
||||
}
|
||||
else if (resampling_method == 7) // BoxSampling
|
||||
{
|
||||
color = BoxResample(uvw, gamma);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
|
|
@ -120,6 +120,8 @@ void EnhancementsWidget::CreateWidgets()
|
|||
static_cast<int>(OutputResamplingMode::NearestNeighbor));
|
||||
m_output_resampling_combo->addItem(tr("Sharp Bilinear"),
|
||||
static_cast<int>(OutputResamplingMode::SharpBilinear));
|
||||
m_output_resampling_combo->addItem(tr("Box Resampling"),
|
||||
static_cast<int>(OutputResamplingMode::BoxResampling));
|
||||
|
||||
m_configure_color_correction = new ToolTipPushButton(tr("Configure"));
|
||||
|
||||
|
@ -497,8 +499,10 @@ void EnhancementsWidget::AddDescriptions()
|
|||
"<br>\"Hermite\" might offer the best quality when upscaling,"
|
||||
" at a slightly bigger perform cost.<br>\"Catmull-Rom\" is best for downscaling."
|
||||
"<br>\"Nearest Neighbor\" doesn't do any resampling, select if you like a pixelated look."
|
||||
"<br>\"Sharp Bilinear\" works best with 2D games at low resolutions, use if you like a"
|
||||
" sharp look.<br><br><dolphin_emphasis>If unsure, select 'Default'.</dolphin_emphasis>");
|
||||
"<br>\"Sharp Bilinear\" works best with 2D games at low resolutions, use if you like a sharp"
|
||||
" look."
|
||||
"<br>\"Box Resampling\" is most expensive but also most accurate downscaling method."
|
||||
"<br><dolphin_emphasis>If unsure, select 'Default'.</dolphin_emphasis>");
|
||||
static const char TR_COLOR_CORRECTION_DESCRIPTION[] =
|
||||
QT_TR_NOOP("A group of features to make the colors more accurate, matching the color space "
|
||||
"Wii and GC games were meant for.");
|
||||
|
|
|
@ -61,6 +61,7 @@ enum class OutputResamplingMode : int
|
|||
CatmullRom,
|
||||
NearestNeighbor,
|
||||
SharpBilinear,
|
||||
BoxResampling,
|
||||
};
|
||||
|
||||
enum class ColorCorrectionRegion : int
|
||||
|
|
Loading…
Reference in New Issue