#import "GPUImageSingleComponentGaussianBlurFilter.h"
|
|
@implementation GPUImageSingleComponentGaussianBlurFilter
|
|
+ (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
|
{
|
if (blurRadius < 1)
|
{
|
return kGPUImageVertexShaderString;
|
}
|
|
// First, generate the normal Gaussian weights for a given sigma
|
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
|
GLfloat sumOfWeights = 0.0;
|
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
|
{
|
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
|
|
if (currentGaussianWeightIndex == 0)
|
{
|
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
|
}
|
else
|
{
|
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
|
}
|
}
|
|
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
|
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
|
{
|
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
|
}
|
|
// From these weights we calculate the offsets to read interpolated values from
|
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
|
GLfloat *optimizedGaussianOffsets = calloc(numberOfOptimizedOffsets, sizeof(GLfloat));
|
|
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
|
{
|
GLfloat firstWeight = standardGaussianWeights[currentOptimizedOffset*2 + 1];
|
GLfloat secondWeight = standardGaussianWeights[currentOptimizedOffset*2 + 2];
|
|
GLfloat optimizedWeight = firstWeight + secondWeight;
|
|
optimizedGaussianOffsets[currentOptimizedOffset] = (firstWeight * (currentOptimizedOffset*2 + 1) + secondWeight * (currentOptimizedOffset*2 + 2)) / optimizedWeight;
|
}
|
|
NSMutableString *shaderString = [[NSMutableString alloc] init];
|
// Header
|
[shaderString appendFormat:@"\
|
attribute vec4 position;\n\
|
attribute vec4 inputTextureCoordinate;\n\
|
\n\
|
uniform float texelWidthOffset;\n\
|
uniform float texelHeightOffset;\n\
|
\n\
|
varying vec2 blurCoordinates[%lu];\n\
|
\n\
|
void main()\n\
|
{\n\
|
gl_Position = position;\n\
|
\n\
|
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2))];
|
|
// Inner offset loop
|
[shaderString appendString:@"blurCoordinates[0] = inputTextureCoordinate.xy;\n"];
|
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
|
{
|
[shaderString appendFormat:@"\
|
blurCoordinates[%lu] = inputTextureCoordinate.xy + singleStepOffset * %f;\n\
|
blurCoordinates[%lu] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (unsigned long)((currentOptimizedOffset * 2) + 1), optimizedGaussianOffsets[currentOptimizedOffset], (unsigned long)((currentOptimizedOffset * 2) + 2), optimizedGaussianOffsets[currentOptimizedOffset]];
|
}
|
|
// Footer
|
[shaderString appendString:@"}\n"];
|
|
free(optimizedGaussianOffsets);
|
free(standardGaussianWeights);
|
return shaderString;
|
}
|
|
+ (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
|
{
|
if (blurRadius < 1)
|
{
|
return kGPUImagePassthroughFragmentShaderString;
|
}
|
|
// First, generate the normal Gaussian weights for a given sigma
|
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
|
GLfloat sumOfWeights = 0.0;
|
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
|
{
|
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
|
|
if (currentGaussianWeightIndex == 0)
|
{
|
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
|
}
|
else
|
{
|
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
|
}
|
}
|
|
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
|
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
|
{
|
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
|
}
|
|
// From these weights we calculate the offsets to read interpolated values from
|
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
|
NSUInteger trueNumberOfOptimizedOffsets = blurRadius / 2 + (blurRadius % 2);
|
|
NSMutableString *shaderString = [[NSMutableString alloc] init];
|
|
// Header
|
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
[shaderString appendFormat:@"\
|
uniform sampler2D inputImageTexture;\n\
|
uniform highp float texelWidthOffset;\n\
|
uniform highp float texelHeightOffset;\n\
|
\n\
|
varying highp vec2 blurCoordinates[%lu];\n\
|
\n\
|
void main()\n\
|
{\n\
|
lowp float sum = 0.0;\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2)) ];
|
#else
|
[shaderString appendFormat:@"\
|
uniform sampler2D inputImageTexture;\n\
|
uniform float texelWidthOffset;\n\
|
uniform float texelHeightOffset;\n\
|
\n\
|
varying vec2 blurCoordinates[%lu];\n\
|
\n\
|
void main()\n\
|
{\n\
|
float sum = 0.0;\n", 1 + (numberOfOptimizedOffsets * 2) ];
|
#endif
|
|
// Inner texture loop
|
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0]).r * %f;\n", standardGaussianWeights[0]];
|
|
for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
|
{
|
GLfloat firstWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 1];
|
GLfloat secondWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 2];
|
GLfloat optimizedWeight = firstWeight + secondWeight;
|
|
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]).r * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 1), optimizedWeight];
|
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]).r * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 2), optimizedWeight];
|
}
|
|
// If the number of required samples exceeds the amount we can pass in via varyings, we have to do dependent texture reads in the fragment shader
|
if (trueNumberOfOptimizedOffsets > numberOfOptimizedOffsets)
|
{
|
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
[shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
|
#else
|
[shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
|
#endif
|
|
for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
|
{
|
GLfloat firstWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 1];
|
GLfloat secondWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 2];
|
|
GLfloat optimizedWeight = firstWeight + secondWeight;
|
GLfloat optimizedOffset = (firstWeight * (currentOverlowTextureRead * 2 + 1) + secondWeight * (currentOverlowTextureRead * 2 + 2)) / optimizedWeight;
|
|
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] + singleStepOffset * %f).r * %f;\n", optimizedOffset, optimizedWeight];
|
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] - singleStepOffset * %f).r * %f;\n", optimizedOffset, optimizedWeight];
|
}
|
}
|
|
// Footer
|
[shaderString appendString:@"\
|
gl_FragColor = vec4(sum, sum, sum, 1.0);\n\
|
}\n"];
|
|
free(standardGaussianWeights);
|
return shaderString;
|
}
|
|
|
@end
|