Skip to content

Commit

Permalink
Reworked the box blur in the same fashion as the new Gaussian blur, s…
Browse files Browse the repository at this point in the history
…o it is now truly smooth across various radii.
  • Loading branch information
BradLarson committed Oct 19, 2013
1 parent 4fc4cfa commit 619ea47
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
CIContext *coreImageContext = [CIContext contextWithEAGLContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]];

// CIContext *coreImageContext = [CIContext contextWithOptions:nil];

// NSArray *cifilters = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
// for (NSString *ciFilterName in cifilters)
// {
// NSLog(@"%@", ciFilterName);
// }
CIImage *inputCIGaussianImage = [[CIImage alloc] initWithCGImage:gaussianBlurInput.CGImage];
CIFilter *gaussianBlurCIFilter = [CIFilter filterWithName:@"CIGaussianBlur"
keysAndValues: kCIInputImageKey, inputCIGaussianImage,
Expand All @@ -127,8 +133,27 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
CGImageRef resultRef = [coreImageContext createCGImage:coreImageResult fromRect:CGRectMake(0, 0, gaussianBlurInput.size.width, gaussianBlurInput.size.height)];
UIImage *coreImageResult2 = [UIImage imageWithCGImage:resultRef];
[self saveImage:coreImageResult2 fileName:@"Gaussian-CoreImage.png"];

CGImageRelease(resultRef);

GPUImageBoxBlurFilter *boxBlur = [[GPUImageBoxBlurFilter alloc] init];
boxBlur.blurRadiusInPixels = 3.0;
[gaussianImage removeAllTargets];
[gaussianImage addTarget:boxBlur];
[gaussianImage processImage];
UIImage *boxOutput = [boxBlur imageFromCurrentlyProcessedOutput];
[self saveImage:boxOutput fileName:@"BoxBlur-GPUImage.png"];

CIImage *inputCIBoxImage = [[CIImage alloc] initWithCGImage:gaussianBlurInput.CGImage];
CIFilter *boxBlurCIFilter = [CIFilter filterWithName:@"CIBoxBlur"
keysAndValues: kCIInputImageKey, inputCIBoxImage,
@"inputRadius", [NSNumber numberWithFloat:2.0], nil];

NSLog(@"Box blur: %@", boxBlurCIFilter);
CIImage *coreImageResult3 = [boxBlurCIFilter outputImage];
CGImageRef resultRef2 = [coreImageContext createCGImage:coreImageResult3 fromRect:CGRectMake(0, 0, gaussianBlurInput.size.width, gaussianBlurInput.size.height)];
UIImage *coreImageResult4 = [UIImage imageWithCGImage:resultRef2];
[self saveImage:coreImageResult4 fileName:@"BoxBlur-CoreImage.png"];
CGImageRelease(resultRef2);

[chairPicture removeAllTargets];
[chairPicture addTarget:averageColor];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1154,15 +1154,19 @@ - (void)setupFilter;
self.filterSettingsSlider.hidden = NO;

[self.filterSettingsSlider setMinimumValue:1.0];
[self.filterSettingsSlider setMaximumValue:14.0];
[self.filterSettingsSlider setMaximumValue:24.0];
[self.filterSettingsSlider setValue:2.0];

filter = [[GPUImageGaussianBlurFilter alloc] init];
}; break;
case GPUIMAGE_BOXBLUR:
{
self.title = @"Box Blur";
self.filterSettingsSlider.hidden = YES;
self.filterSettingsSlider.hidden = NO;

[self.filterSettingsSlider setMinimumValue:1.0];
[self.filterSettingsSlider setMaximumValue:24.0];
[self.filterSettingsSlider setValue:2.0];

filter = [[GPUImageBoxBlurFilter alloc] init];
}; break;
Expand Down Expand Up @@ -1589,6 +1593,7 @@ - (IBAction)updateFilterFromSlider:(id)sender;
case GPUIMAGE_PERLINNOISE: [(GPUImagePerlinNoiseFilter *)filter setScale:[(UISlider *)sender value]]; break;
case GPUIMAGE_MOSAIC: [(GPUImageMosaicFilter *)filter setDisplayTileSize:CGSizeMake([(UISlider *)sender value], [(UISlider *)sender value])]; break;
case GPUIMAGE_VIGNETTE: [(GPUImageVignetteFilter *)filter setVignetteEnd:[(UISlider *)sender value]]; break;
case GPUIMAGE_BOXBLUR: [(GPUImageBoxBlurFilter *)filter setBlurRadiusInPixels:[(UISlider*)sender value]]; break;
case GPUIMAGE_GAUSSIAN: [(GPUImageGaussianBlurFilter *)filter setBlurRadiusInPixels:[(UISlider*)sender value]]; break;
// case GPUIMAGE_GAUSSIAN: [(GPUImageGaussianBlurFilter *)filter setBlurPasses:round([(UISlider*)sender value])]; break;
// case GPUIMAGE_BILATERAL: [(GPUImageBilateralFilter *)filter setBlurSize:[(UISlider*)sender value]]; break;
Expand Down
37 changes: 35 additions & 2 deletions framework/Source/GPUImageBilateralFilter.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
#import "GPUImageBilateralFilter.h"

NSString *const kGPUImageBilateralBlurVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTextureCoordinate;

const int GAUSSIAN_SAMPLES = 9;

uniform float texelWidthOffset;
uniform float texelHeightOffset;

varying vec2 textureCoordinate;
varying vec2 blurCoordinates[GAUSSIAN_SAMPLES];

void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;

// Calculate the positions for the blur
int multiplier = 0;
vec2 blurStep;
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);

for (int i = 0; i < GAUSSIAN_SAMPLES; i++)
{
multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2));
// Blur in x (horizontal)
blurStep = float(multiplier) * singleStepOffset;
blurCoordinates[i] = inputTextureCoordinate.xy + blurStep;
}
}
);

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageBilateralFilterFragmentShaderString = SHADER_STRING
(
Expand Down Expand Up @@ -161,9 +194,9 @@ @implementation GPUImageBilateralFilter
- (id)init;
{

if (!(self = [super initWithFirstStageVertexShaderFromString:nil
if (!(self = [super initWithFirstStageVertexShaderFromString:kGPUImageBilateralBlurVertexShaderString
firstStageFragmentShaderFromString:kGPUImageBilateralFilterFragmentShaderString
secondStageVertexShaderFromString:nil
secondStageVertexShaderFromString:kGPUImageBilateralBlurVertexShaderString
secondStageFragmentShaderFromString:kGPUImageBilateralFilterFragmentShaderString])) {
return nil;
}
Expand Down
9 changes: 3 additions & 6 deletions framework/Source/GPUImageBoxBlurFilter.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#import "GPUImageTwoPassTextureSamplingFilter.h"
#import "GPUImageGaussianBlurFilter.h"

/** A hardware-accelerated 9-hit box blur of an image
/** A hardware-accelerated box blur of an image
*/
@interface GPUImageBoxBlurFilter : GPUImageTwoPassTextureSamplingFilter

/// A scaling for the size of the applied blur, default of 1.0
@property(readwrite, nonatomic) CGFloat blurSize;
@interface GPUImageBoxBlurFilter : GPUImageGaussianBlurFilter

@end
225 changes: 143 additions & 82 deletions framework/Source/GPUImageBoxBlurFilter.m
Original file line number Diff line number Diff line change
@@ -1,112 +1,173 @@
#import "GPUImageBoxBlurFilter.h"

NSString *const kGPUImageBoxBlurVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec2 inputTextureCoordinate;

uniform float texelWidthOffset;
uniform float texelHeightOffset;
@implementation GPUImageBoxBlurFilter

varying vec2 centerTextureCoordinate;
varying vec2 oneStepLeftTextureCoordinate;
varying vec2 twoStepsLeftTextureCoordinate;
varying vec2 oneStepRightTextureCoordinate;
varying vec2 twoStepsRightTextureCoordinate;
+ (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius == 0)
{
return nil;
}

void main()
{
gl_Position = position;

vec2 firstOffset = vec2(1.5 * texelWidthOffset, 1.5 * texelHeightOffset);
vec2 secondOffset = vec2(3.5 * texelWidthOffset, 3.5 * texelHeightOffset);

centerTextureCoordinate = inputTextureCoordinate;
oneStepLeftTextureCoordinate = inputTextureCoordinate - firstOffset;
twoStepsLeftTextureCoordinate = inputTextureCoordinate - secondOffset;
oneStepRightTextureCoordinate = inputTextureCoordinate + firstOffset;
twoStepsRightTextureCoordinate = inputTextureCoordinate + secondOffset;
}
);
// From these weights we calculate the offsets to read interpolated values from
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);

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[%d];\n\
\n\
void main()\n\
{\n\
gl_Position = position;\n\
\n\
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", 1 + (numberOfOptimizedOffsets * 2)];

// Inner offset loop
[shaderString appendString:@"blurCoordinates[0] = inputTextureCoordinate.xy;\n"];
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
{
GLfloat optimizedOffset = (GLfloat)(currentOptimizedOffset * 2) + 1.5;

[shaderString appendFormat:@"\
blurCoordinates[%d] = inputTextureCoordinate.xy + singleStepOffset * %f;\n\
blurCoordinates[%d] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (currentOptimizedOffset * 2) + 1, optimizedOffset, (currentOptimizedOffset * 2) + 2, optimizedOffset];
}

// Footer
[shaderString appendString:@"}\n"];

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageBoxBlurFragmentShaderString = SHADER_STRING
(
precision highp float;
return shaderString;
}

uniform sampler2D inputImageTexture;

varying vec2 centerTextureCoordinate;
varying vec2 oneStepLeftTextureCoordinate;
varying vec2 twoStepsLeftTextureCoordinate;
varying vec2 oneStepRightTextureCoordinate;
varying vec2 twoStepsRightTextureCoordinate;

void main()
{
lowp vec4 fragmentColor = texture2D(inputImageTexture, centerTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, oneStepLeftTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, oneStepRightTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, twoStepsLeftTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, twoStepsRightTextureCoordinate) * 0.2;

gl_FragColor = fragmentColor;
}
);
+ (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius == 0)
{
return nil;
}

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[%d];\n\
\n\
void main()\n\
{\n\
lowp vec4 sum = vec4(0.0);\n", 1 + (numberOfOptimizedOffsets * 2) ];
#else
NSString *const kGPUImageBoxBlurFragmentShaderString = SHADER_STRING
(
uniform sampler2D inputImageTexture;

varying vec2 centerTextureCoordinate;
varying vec2 oneStepLeftTextureCoordinate;
varying vec2 twoStepsLeftTextureCoordinate;
varying vec2 oneStepRightTextureCoordinate;
varying vec2 twoStepsRightTextureCoordinate;

void main()
{
vec4 fragmentColor = texture2D(inputImageTexture, centerTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, oneStepLeftTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, oneStepRightTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, twoStepsLeftTextureCoordinate) * 0.2;
fragmentColor += texture2D(inputImageTexture, twoStepsRightTextureCoordinate) * 0.2;

gl_FragColor = fragmentColor;
}
);
[shaderString appendFormat:@"\
uniform sampler2D inputImageTexture;\n\
uniform float texelWidthOffset;\n\
uniform float texelHeightOffset;\n\
\n\
varying vec2 blurCoordinates[%d];\n\
\n\
void main()\n\
{\n\
vec4 sum = vec4(0.0);\n", 1 + (numberOfOptimizedOffsets * 2) ];
#endif

GLfloat boxWeight = 1.0 / (GLfloat)((blurRadius * 2) + 1);

// Inner texture loop
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0]) * %f;\n", boxWeight];

for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
{
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%d]) * %f;\n", (currentBlurCoordinateIndex * 2) + 1, boxWeight * 2.0];
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%d]) * %f;\n", (currentBlurCoordinateIndex * 2) + 2, boxWeight * 2.0];
}

// 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)
{
[shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];

for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
{
GLfloat optimizedOffset = (GLfloat)(currentOverlowTextureRead * 2) + 1.5;

[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] + singleStepOffset * %f) * %f;\n", optimizedOffset, boxWeight * 2.0];
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] - singleStepOffset * %f) * %f;\n", optimizedOffset, boxWeight * 2.0];
}
}

// Footer
[shaderString appendString:@"\
gl_FragColor = sum;\n\
}\n"];

return shaderString;
}

@implementation GPUImageBoxBlurFilter

@synthesize blurSize = _blurSize;
- (void)setupFilterForSize:(CGSize)filterFrameSize;
{
[super setupFilterForSize:filterFrameSize];

if (shouldResizeBlurRadiusWithImageSize == YES)
{

}
}

#pragma mark -
#pragma mark Initialization and teardown

- (id)init;
{
if (!(self = [super initWithFirstStageVertexShaderFromString:kGPUImageBoxBlurVertexShaderString firstStageFragmentShaderFromString:kGPUImageBoxBlurFragmentShaderString secondStageVertexShaderFromString:kGPUImageBoxBlurVertexShaderString secondStageFragmentShaderFromString:kGPUImageBoxBlurFragmentShaderString]))
// NSString *currentGaussianBlurVertexShader = [GPUImageGaussianBlurFilter vertexShaderForStandardGaussianOfRadius:4 sigma:2.0];
// NSString *currentGaussianBlurFragmentShader = [GPUImageGaussianBlurFilter fragmentShaderForStandardGaussianOfRadius:4 sigma:2.0];

NSString *currentBoxBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:4 sigma:0.0];
NSString *currentBoxBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:4 sigma:0.0];

if (!(self = [super initWithFirstStageVertexShaderFromString:currentBoxBlurVertexShader firstStageFragmentShaderFromString:currentBoxBlurFragmentShader secondStageVertexShaderFromString:currentBoxBlurVertexShader secondStageFragmentShaderFromString:currentBoxBlurFragmentShader]))
{
return nil;
return nil;
}

self.blurSize = 1.0;
_blurRadiusInPixels = 4.0;

return self;
}

#pragma mark -
#pragma mark Accessors

- (void)setBlurSize:(CGFloat)newValue;
- (void)setBlurRadiusInPixels:(CGFloat)newValue;
{
_blurSize = newValue;

_verticalTexelSpacing = _blurSize;
_horizontalTexelSpacing = _blurSize;
CGFloat newBlurRadius = round(round(newValue / 2.0) * 2.0); // For now, only do even radii

[self setupFilterForSize:[self sizeOfFBO]];
if (newBlurRadius != _blurRadiusInPixels)
{
_blurRadiusInPixels = newBlurRadius;

NSString *newGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:_blurRadiusInPixels sigma:0.0];
NSString *newGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:_blurRadiusInPixels sigma:0.0];

// NSLog(@"Optimized vertex shader: \n%@", newGaussianBlurVertexShader);
// NSLog(@"Optimized fragment shader: \n%@", newGaussianBlurFragmentShader);
//
[self switchToVertexShader:newGaussianBlurVertexShader fragmentShader:newGaussianBlurFragmentShader];
}
shouldResizeBlurRadiusWithImageSize = NO;
}

@end
Expand Down

0 comments on commit 619ea47

Please sign in to comment.