Skip to content

Commit

Permalink
Added a smoother toon filter.
Browse files Browse the repository at this point in the history
  • Loading branch information
BradLarson committed Apr 18, 2012
1 parent 5917d97 commit 48b2936
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 5 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ For example, an application that takes in live video from the camera, converts t
- *imageWidthFactor*:
- *imageHeightFactor*: These parameters affect the visibility of the detected edges

- **GPUImageCannyEdgeDetectionFilter**: This uses a Gaussian blur before applying a Sobel operator to highlight edges
- *imageWidthFactor*:
- *imageHeightFactor*: These parameters affect the visibility of the detected edges
- *blurSize*: A multiplier for the prepass blur size, ranging from 0.0 on up, with a default of 1.0
- *threshold*: Any edge above this threshold will be black, and anything below white. Ranges from 0.0 to 1.0, with 0.5 as the default

- **GPUImageSketchFilter**: Converts video to look like a sketch. This is just the Sobel edge detection filter with the colors inverted
- *intensity*: The degree to which the original image colors are replaced by the detected edges (0.0 - 1.0, with 1.0 as the default)
- *imageWidthFactor*:
Expand All @@ -177,6 +183,15 @@ For example, an application that takes in live video from the camera, converts t
- **GPUImageToonFilter**: This uses Sobel edge detection to place a black border around objects, and then it quantizes the colors present in the image to give a cartoon-like quality to the image.
- *imageWidthFactor*:
- *imageHeightFactor*: These parameters affect the visibility of the detected edges
- *threshold*: The sensitivity of the edge detection, with lower values being more sensitive. Ranges from 0.0 to 1.0, with 0.2 as the default
- *quantizationLevels*: The number of color levels to represent in the final image. Default is 10.0

- **GPUImageSmoothToonFilter**: This uses a similar process as the GPUImageToonFilter, only it precedes the toon effect with a Gaussian blur to smooth out noise.
- *imageWidthFactor*:
- *imageHeightFactor*: These parameters affect the visibility of the detected edges
- *blurSize*: A multiplier for the prepass blur size, ranging from 0.0 on up, with a default of 0.5
- *threshold*: The sensitivity of the edge detection, with lower values being more sensitive. Ranges from 0.0 to 1.0, with 0.2 as the default
- *quantizationLevels*: The number of color levels to represent in the final image. Default is 10.0

- **GPUImagePosterizeFilter**: This reduces the color dynamic range into the number of steps specified, leading to a cartoon-like simple shading of the image.
- *colorLevels*: The number of color levels to reduce the image space to. This ranges from 1 to 256, with a default of 10.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
case GPUIMAGE_CANNYEDGEDETECTION: cell.textLabel.text = @"Canny edge detection"; break;
case GPUIMAGE_SKETCH: cell.textLabel.text = @"Sketch"; break;
case GPUIMAGE_TOON: cell.textLabel.text = @"Toon"; break;
case GPUIMAGE_SMOOTHTOON: cell.textLabel.text = @"Smooth toon"; break;
case GPUIMAGE_TILTSHIFT: cell.textLabel.text = @"Tilt shift"; break;
case GPUIMAGE_CGA: cell.textLabel.text = @"CGA colorspace"; break;
case GPUIMAGE_CONVOLUTION: cell.textLabel.text = @"3x3 convolution"; break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ typedef enum {
GPUIMAGE_CANNYEDGEDETECTION,
GPUIMAGE_SKETCH,
GPUIMAGE_TOON,
GPUIMAGE_SMOOTHTOON,
GPUIMAGE_TILTSHIFT,
GPUIMAGE_CGA,
GPUIMAGE_POSTERIZE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,17 @@ - (void)setupFilter;

filter = [[GPUImageToonFilter alloc] init];
}; break;
case GPUIMAGE_SMOOTHTOON:
{
self.title = @"Smooth Toon";
self.filterSettingsSlider.hidden = NO;

[self.filterSettingsSlider setMinimumValue:0.0];
[self.filterSettingsSlider setMaximumValue:1.0];
[self.filterSettingsSlider setValue:0.5];

filter = [[GPUImageSmoothToonFilter alloc] init];
}; break;
case GPUIMAGE_TILTSHIFT:
{
self.title = @"Tilt Shift";
Expand Down Expand Up @@ -647,6 +658,7 @@ - (IBAction)updateFilterFromSlider:(id)sender;
case GPUIMAGE_SWIRL: [(GPUImageSwirlFilter *)filter setAngle:[(UISlider *)sender value]]; break;
case GPUIMAGE_EMBOSS: [(GPUImageEmbossFilter *)filter setIntensity:[(UISlider *)sender value]]; break;
case GPUIMAGE_CANNYEDGEDETECTION: [(GPUImageCannyEdgeDetectionFilter *)filter setBlurSize:[(UISlider*)sender value]]; break;
case GPUIMAGE_SMOOTHTOON: [(GPUImageSmoothToonFilter *)filter setBlurSize:[(UISlider*)sender value]]; break;
// case GPUIMAGE_BULGE: [(GPUImageBulgeDistortionFilter *)filter setRadius:[(UISlider *)sender value]]; break;
case GPUIMAGE_BULGE: [(GPUImageBulgeDistortionFilter *)filter setScale:[(UISlider *)sender value]]; break;
case GPUIMAGE_PINCH: [(GPUImagePinchDistortionFilter *)filter setScale:[(UISlider *)sender value]]; break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ - (void)viewDidLoad
// [(GPUImageTiltShiftFilter *)filter setFocusFallOffRate:0.2];

// filter = [[GPUImageSketchFilter alloc] init];
// filter = [[GPUImageUnsharpMaskFilter alloc] init];
// filter = [[GPUImageSmoothToonFilter alloc] init];
GPUImageRotationFilter *rotationFilter = [[GPUImageRotationFilter alloc] initWithRotation:kGPUImageRotateRight];

[videoCamera addTarget:rotationFilter];
Expand Down
8 changes: 8 additions & 0 deletions framework/GPUImage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@
BCF3D71E153E06C3009A1FE5 /* GPUImageCannyEdgeDetectionFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF3D71C153E06C3009A1FE5 /* GPUImageCannyEdgeDetectionFilter.m */; };
BCF3D722153E0E0C009A1FE5 /* GPUImageThresholdEdgeDetection.h in Headers */ = {isa = PBXBuildFile; fileRef = BCF3D720153E0E0B009A1FE5 /* GPUImageThresholdEdgeDetection.h */; };
BCF3D723153E0E0C009A1FE5 /* GPUImageThresholdEdgeDetection.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF3D721153E0E0B009A1FE5 /* GPUImageThresholdEdgeDetection.m */; };
BCF3D730153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = BCF3D72E153F0D6E009A1FE5 /* GPUImageSmoothToonFilter.h */; };
BCF3D731153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF3D72F153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -325,6 +327,8 @@
BCF3D71C153E06C3009A1FE5 /* GPUImageCannyEdgeDetectionFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageCannyEdgeDetectionFilter.m; path = Source/GPUImageCannyEdgeDetectionFilter.m; sourceTree = SOURCE_ROOT; };
BCF3D720153E0E0B009A1FE5 /* GPUImageThresholdEdgeDetection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageThresholdEdgeDetection.h; path = Source/GPUImageThresholdEdgeDetection.h; sourceTree = SOURCE_ROOT; };
BCF3D721153E0E0B009A1FE5 /* GPUImageThresholdEdgeDetection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageThresholdEdgeDetection.m; path = Source/GPUImageThresholdEdgeDetection.m; sourceTree = SOURCE_ROOT; };
BCF3D72E153F0D6E009A1FE5 /* GPUImageSmoothToonFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageSmoothToonFilter.h; path = Source/GPUImageSmoothToonFilter.h; sourceTree = SOURCE_ROOT; };
BCF3D72F153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageSmoothToonFilter.m; path = Source/GPUImageSmoothToonFilter.m; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -447,6 +451,8 @@
BCF3D721153E0E0B009A1FE5 /* GPUImageThresholdEdgeDetection.m */,
BC982C9D14F35C2D0001FF6F /* GPUImageToonFilter.h */,
BC982C9E14F35C2D0001FF6F /* GPUImageToonFilter.m */,
BCF3D72E153F0D6E009A1FE5 /* GPUImageSmoothToonFilter.h */,
BCF3D72F153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.m */,
BCC1E667152368130006EFA5 /* GPUImageCGAColorspaceFilter.h */,
BCC1E67B152368840006EFA5 /* GPUImageCGAColorspaceFilter.m */,
BCC1E5A6151E74B20006EFA5 /* GPUImagePosterizeFilter.h */,
Expand Down Expand Up @@ -704,6 +710,7 @@
BCF3D70F153DF9E7009A1FE5 /* GPUImageEmbossFilter.h in Headers */,
BCF3D71D153E06C3009A1FE5 /* GPUImageCannyEdgeDetectionFilter.h in Headers */,
BCF3D722153E0E0C009A1FE5 /* GPUImageThresholdEdgeDetection.h in Headers */,
BCF3D730153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -874,6 +881,7 @@
BCF3D710153DF9E7009A1FE5 /* GPUImageEmbossFilter.m in Sources */,
BCF3D71E153E06C3009A1FE5 /* GPUImageCannyEdgeDetectionFilter.m in Sources */,
BCF3D723153E0E0C009A1FE5 /* GPUImageThresholdEdgeDetection.m in Sources */,
BCF3D731153F0D6F009A1FE5 /* GPUImageSmoothToonFilter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
1 change: 1 addition & 0 deletions framework/Source/GPUImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#import "GPUImageSobelEdgeDetectionFilter.h"
#import "GPUImageSketchFilter.h"
#import "GPUImageToonFilter.h"
#import "GPUImageSmoothToonFilter.h"
#import "GPUImageMultiplyBlendFilter.h"
#import "GPUImageDissolveBlendFilter.h"
#import "GPUImageKuwaharaFilter.h"
Expand Down
25 changes: 25 additions & 0 deletions framework/Source/GPUImageSmoothToonFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#import "GPUImageFilterGroup.h"

@class GPUImageGaussianBlurFilter;
@class GPUImageToonFilter;

@interface GPUImageSmoothToonFilter : GPUImageFilterGroup
{
GPUImageGaussianBlurFilter *blurFilter;
GPUImageToonFilter *toonFilter;
}

// The image width and height factors tweak the appearance of the edges. By default, they match the filter size in pixels
@property(readwrite, nonatomic) CGFloat imageWidthFactor;
@property(readwrite, nonatomic) CGFloat imageHeightFactor;

// A multiplier for the blur size, ranging from 0.0 on up, with a default of 0.5
@property (readwrite, nonatomic) CGFloat blurSize;

// The threshold at which to apply the edges, default of 0.2
@property(readwrite, nonatomic) CGFloat threshold;

// The levels of quantization for the posterization of colors within the scene, with a default of 10.0
@property(readwrite, nonatomic) CGFloat quantizationLevels;

@end
94 changes: 94 additions & 0 deletions framework/Source/GPUImageSmoothToonFilter.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#import "GPUImageSmoothToonFilter.h"
#import "GPUImageGaussianBlurFilter.h"
#import "GPUImageToonFilter.h"

@implementation GPUImageSmoothToonFilter

@synthesize threshold;
@synthesize blurSize;
@synthesize quantizationLevels;
@synthesize imageWidthFactor;
@synthesize imageHeightFactor;

- (id)init;
{
if (!(self = [super init]))
{
return nil;
}

// First pass: apply a variable Gaussian blur
blurFilter = [[GPUImageGaussianBlurFilter alloc] init];
[self addFilter:blurFilter];

// Second pass: run the Sobel edge detection on this blurred image, along with a posterization effect
toonFilter = [[GPUImageToonFilter alloc] init];
[self addFilter:toonFilter];

// Texture location 0 needs to be the sharp image for both the blur and the second stage processing
[blurFilter addTarget:toonFilter];

self.initialFilters = [NSArray arrayWithObject:blurFilter];
self.terminalFilter = toonFilter;

self.blurSize = 0.5;
self.threshold = 0.2;
self.quantizationLevels = 10.0;

return self;
}

#pragma mark -
#pragma mark Accessors

- (void)setBlurSize:(CGFloat)newValue;
{
blurFilter.blurSize = newValue;
}

- (CGFloat)blurSize;
{
return blurFilter.blurSize;
}

- (void)setImageWidthFactor:(CGFloat)newValue;
{
toonFilter.imageWidthFactor = newValue;
}

- (CGFloat)imageWidthFactor;
{
return toonFilter.imageWidthFactor;
}

- (void)setImageHeightFactor:(CGFloat)newValue;
{
toonFilter.imageHeightFactor = newValue;
}

- (CGFloat)imageHeightFactor;
{
return toonFilter.imageHeightFactor;
}

- (void)setThreshold:(CGFloat)newValue;
{
toonFilter.threshold = newValue;
}

- (CGFloat)threshold;
{
return toonFilter.threshold;
}

- (void)setQuantizationLevels:(CGFloat)newValue;
{
toonFilter.quantizationLevels = newValue;
}

- (CGFloat)quantizationLevels;
{
return toonFilter.quantizationLevels;
}

@end
7 changes: 7 additions & 0 deletions framework/Source/GPUImageToonFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
@interface GPUImageToonFilter : GPUImageFilter
{
GLint imageWidthFactorUniform, imageHeightFactorUniform;
GLint thresholdUniform, quantizationLevelsUniform;
BOOL hasOverriddenImageSizeFactor;
}

// The image width and height factors tweak the appearance of the edges. By default, they match the filter size in pixels
@property(readwrite, nonatomic) CGFloat imageWidthFactor;
@property(readwrite, nonatomic) CGFloat imageHeightFactor;

// The threshold at which to apply the edges, default of 0.2
@property(readwrite, nonatomic) CGFloat threshold;

// The levels of quantization for the posterization of colors within the scene, with a default of 10.0
@property(readwrite, nonatomic) CGFloat quantizationLevels;

@end
32 changes: 28 additions & 4 deletions framework/Source/GPUImageToonFilter.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@
uniform sampler2D inputImageTexture;

uniform highp float intensity;
uniform highp float threshold;
uniform highp float quantizationLevels;

const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

const highp float threshold = 0.2;
const highp float quantize = 10.0;

void main()
{
vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
Expand All @@ -46,7 +45,7 @@ void main()

float mag = length(vec2(h, v));

vec3 posterizedImageColor = floor((textureColor.rgb * quantize) + 0.5) / quantize;
vec3 posterizedImageColor = floor((textureColor.rgb * quantizationLevels) + 0.5) / quantizationLevels;

float thresholdTest = 1.0 - step(threshold, mag);

Expand All @@ -72,6 +71,8 @@ @implementation GPUImageToonFilter

@synthesize imageWidthFactor = _imageWidthFactor;
@synthesize imageHeightFactor = _imageHeightFactor;
@synthesize threshold = _threshold;
@synthesize quantizationLevels = _quantizationLevels;

#pragma mark -
#pragma mark Initialization and teardown
Expand All @@ -87,7 +88,12 @@ - (id)init;

imageWidthFactorUniform = [filterProgram uniformIndex:@"imageWidthFactor"];
imageHeightFactorUniform = [filterProgram uniformIndex:@"imageHeightFactor"];
thresholdUniform = [filterProgram uniformIndex:@"threshold"];
quantizationLevelsUniform = [filterProgram uniformIndex:@"quantizationLevels"];

self.threshold = 0.2;
self.quantizationLevels = 10.0;

return self;
}

Expand Down Expand Up @@ -128,6 +134,24 @@ - (void)setImageHeightFactor:(CGFloat)newValue;
glUniform1f(imageHeightFactorUniform, 1.0 / _imageHeightFactor);
}

- (void)setThreshold:(CGFloat)newValue;
{
_threshold = newValue;

[GPUImageOpenGLESContext useImageProcessingContext];
[filterProgram use];
glUniform1f(thresholdUniform, _threshold);
}

- (void)setQuantizationLevels:(CGFloat)newValue;
{
_quantizationLevels = newValue;

[GPUImageOpenGLESContext useImageProcessingContext];
[filterProgram use];
glUniform1f(quantizationLevelsUniform, _quantizationLevels);
}


@end

0 comments on commit 48b2936

Please sign in to comment.