Skip to content

Instantly share code, notes, and snippets.

@vgoltv
Last active July 7, 2024 09:04
Show Gist options
  • Save vgoltv/6d1f612573b48a74a434395f67af3ebf to your computer and use it in GitHub Desktop.
Save vgoltv/6d1f612573b48a74a434395f67af3ebf to your computer and use it in GitHub Desktop.
CoreImage filter "Sobel Sketch", kernel ported from GPUImageSketchFilter. Objective-C
//
// FWKSketchFilter.h
// LineEngraver
//
// Created by Viktor Goltvyanytsya on 8/4/16.
// http://www.fwkit.com
//
// Ported from https://github.com/BradLarson/GPUImage/blob/master/framework/Source/GPUImageSketchFilter.m
#import <CoreImage/CoreImage.h>
@interface FWKSketchFilter : CIFilter
@property (nonatomic, strong) CIImage *inputImage;
@end
//
// FWKSketchFilter.m
// LineEngraver
//
// Created by Viktor Goltvyanytsya on 8/4/16.
// http://www.fwkit.com
//
#import "FWKSketchFilter.h"
#define SKETCH_FILTER_STRENGTH @"strength"
#define SKETCH_FILTER_USE_MONOCHROME @"useMonochromeFilter"
@interface FWKSketchFilter()
{
CIFilter *_monochromeFilter;
}
@property (copy, nonatomic) NSNumber *strength;
@property (copy, nonatomic) NSNumber *useMonochromeFilter;
@end
@implementation FWKSketchFilter
@synthesize strength;
@synthesize useMonochromeFilter;
static CIKernel *filterKernel = nil;
+ (void)load
{
}
+ (void)initialize
{
[CIFilter registerFilterName: NSStringFromClass([self class])
constructor: (id<CIFilterConstructor>)self
classAttributes: [[self class] customAttributes]];
}
+ (CIFilter *)filterWithName:(NSString *)name
{
return [[self alloc] init];
}
- (void)dealloc
{
_monochromeFilter = nil;
}
- (id)init
{
if(!filterKernel)
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"Sketch"
ofType:@"cikernel"];
NSString *kernelStr = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:nil];
static dispatch_once_t once;
dispatch_once(&once, ^{
filterKernel = [CIKernel kernelWithString:kernelStr];
});
}
self = [super init];
if (self)
{
[self setDefaults];
_monochromeFilter = [CIFilter filterWithName:@"CIPhotoEffectMono"];
}
return self;
}
+ (NSDictionary *)customAttributes
{
return @{
kCIAttributeFilterDisplayName : @"Sobel Sketch",
kCIAttributeFilterCategories : @[kCICategoryGradient, kCICategoryVideo, kCICategoryInterlaced, kCICategoryStillImage],
SKETCH_FILTER_STRENGTH :
@{
kCIAttributeSliderMin : @0.0,
kCIAttributeSliderMax : @2.0,
kCIAttributeDefault : @1.0,
kCIAttributeType : kCIAttributeTypeScalar
},
SKETCH_FILTER_USE_MONOCHROME :
@{
kCIAttributeDefault : @YES,
kCIAttributeType : kCIAttributeTypeBoolean
}
};
}
- (void)setDefaults
{
NSDictionary *dict = [[self class] customAttributes];
NSDictionary *dictVal = [dict valueForKey:SKETCH_FILTER_STRENGTH];
self.strength = [dictVal valueForKey:kCIAttributeDefault];
dictVal = [dict valueForKey:SKETCH_FILTER_USE_MONOCHROME];
self.useMonochromeFilter = [dictVal valueForKey:kCIAttributeDefault];
}
- (CIImage *)outputImage
{
if( !_inputImage )
{
return nil;
}
CIImage *src = self.inputImage;
if( self.useMonochromeFilter.boolValue )
{
@autoreleasepool
{
[_monochromeFilter setDefaults];
[_monochromeFilter setValue:src forKey:kCIInputImageKey];
src = _monochromeFilter.outputImage;
}
}
else
{
src = self.inputImage;
}
@autoreleasepool
{
CGRect dod = self.inputImage.extent;
src = [filterKernel applyWithExtent:dod roiCallback:^CGRect(int index, CGRect destRect) {
return destRect;
} arguments:@[src, self.strength]];
}
return src;
}
- (id)copyWithZone:(NSZone *)zone
{
FWKSketchFilter *result = [super copyWithZone: zone];
result.useMonochromeFilter = self.useMonochromeFilter;
result.strength = self.strength;
return result;
}
@end
kernel vec4 sketch(sampler image, float strength)
{
vec2 d = destCoord();
vec2 bottomLeftTextureCoordinate = samplerTransform(image, d + vec2(-1.0, -1.0));
vec2 topRightTextureCoordinate = samplerTransform(image, d + vec2(1.0, 1.0));
vec2 topLeftTextureCoordinate = samplerTransform(image, d + vec2(-1.0, 1.0));
vec2 bottomRightTextureCoordinate = samplerTransform(image, d + vec2(1.0, -1.0));
vec2 leftTextureCoordinate = samplerTransform(image, d + vec2(-1.0, 0.0));
vec2 rightTextureCoordinate = samplerTransform(image, d + vec2(1.0, 0.0));
vec2 bottomTextureCoordinate = samplerTransform(image, d + vec2(0.0, -1.0));
vec2 topTextureCoordinate = samplerTransform(image, d + vec2(0.0, 1.0));
float bottomLeftIntensity = sample(image, bottomLeftTextureCoordinate).r;
float topRightIntensity = sample(image, topRightTextureCoordinate).r;
float topLeftIntensity = sample(image, topLeftTextureCoordinate).r;
float bottomRightIntensity = sample(image, bottomRightTextureCoordinate).r;
float leftIntensity = sample(image, leftTextureCoordinate).r;
float rightIntensity = sample(image, rightTextureCoordinate).r;
float bottomIntensity = sample(image, bottomTextureCoordinate).r;
float topIntensity = sample(image, topTextureCoordinate).r;
float h = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity;
float v = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity;
float mag = 1.0 - (length(vec2(h, v))*strength);
return vec4(vec3(mag), 1.0);
}
@vgoltv
Copy link
Author

vgoltv commented Aug 4, 2016

CIFilter *sobelFilter = [CIFilter filterWithName:@"FWKSketchFilter"];
[sobelFilter setDefaults];
[sobelFilter setValue:ciResult forKey:kCIInputImageKey];
ciResult = [sobelFilter valueForKey:kCIOutputImageKey];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment