Last active
February 27, 2021 21:12
-
-
Save xhruso00/a3f8a9c8ae7e33b8b23d to your computer and use it in GitHub Desktop.
Black & White monochrome 1bit threshold filter. Uses modern 10.15 approach with protocols and metal and half data type. To use simply call [CIFilter blackAndWhiteThresholdFilter]; (add -fcikernel to "Other metal compiler flags" and add -cikernel for User-defined MTTLINKER_FLAGS . Enable fast math in build settings section metal. Targeting earlie…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Cocoa/Cocoa.h> | |
#import <CoreImage/CoreImage.h> | |
@protocol BlackAndWhiteThreshold <CIFilter> | |
@property (nonatomic, retain) CIImage *inputImage; | |
@property (nonatomic) float threshold; | |
@end | |
@interface CIFilter(BlackAndWhiteThresholdFilter) | |
+ (CIFilter<BlackAndWhiteThreshold>*) blackAndWhiteThresholdFilter; | |
@property (nonatomic, retain) CIImage *inputImage; | |
@property (nonatomic) float threshold; | |
@end | |
/// 1-bit monochrome filter | |
@interface BlackAndWhiteThresholdFilter : CIFilter <CIFilter> | |
//WWDC2014 Session 514 CIFilter subclasses can use @property instead of ivars | |
@property (nonatomic, retain) NSNumber *inputThreshold; | |
@property (nonatomic, retain) CIImage *inputImage; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "CIFilter+BlackAndWhiteThresholdFilter.h" | |
#include <objc/runtime.h> | |
#import <CoreImage/CoreImageDefines.h> | |
#import "CIColorKernelMetal.h" | |
#import "CIColorKernelGLSL.h" | |
@class BlackAndWhiteThresholdFilter; | |
static NSString *const kCIInputThreshold = @"inputThreshold"; | |
NSString *const kBlackAndWhiteThresholdFilterName = @"BlackAndWhiteThreshold"; | |
static NSString *const kBlackAndWhiteThresholdFilterDisplayName = @"Black & White Threshold"; | |
@implementation CIFilter(BlackAndWhiteThresholdFilter) | |
@dynamic inputImage; | |
@dynamic threshold; | |
+ (CIFilter<BlackAndWhiteThreshold>*) blackAndWhiteThresholdFilter | |
{ | |
[BlackAndWhiteThresholdFilter class]; //kick off initialize to register filter | |
CIFilter<BlackAndWhiteThreshold>*filter = (CIFilter<BlackAndWhiteThreshold>*)[CIFilter filterWithName:kBlackAndWhiteThresholdFilterName]; | |
static dispatch_once_t oncePredicate; | |
dispatch_once(&oncePredicate, ^{ | |
/// convenience removing input keyword | |
class_addMethod([self class], @selector(threshold), (IMP)floatGetter, "f@:"); | |
class_addMethod([self class], @selector(setThreshold:), (IMP)floatSetter, "v@:f"); | |
}); | |
return filter; | |
} | |
static float floatGetter(id self, SEL _cmd) { | |
NSString *selector = NSStringFromSelector(_cmd); | |
///capitalize first letter | |
NSString *firstLetter = [[selector substringWithRange:NSMakeRange(0, 1)] uppercaseString]; | |
NSString *key = [@"input" stringByAppendingString:[selector stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLetter]]; | |
id value = [self valueForKey:key]; | |
float number = NAN; | |
if (value && [value isKindOfClass:[NSNumber class]]) { | |
number = [value floatValue]; | |
} | |
return number; | |
} | |
static void floatSetter(id self, SEL _cmd, float value) { | |
NSString *selector = NSStringFromSelector(_cmd); | |
NSString *aaa = [selector stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@"input"]; | |
[self setValue:@(value) forKey:[aaa substringWithRange:NSMakeRange(0, [aaa length] - 1)]]; | |
} | |
@end | |
@interface BlackAndWhiteThresholdFilter() | |
{ | |
CIColorKernel *_kernel; | |
} | |
@end | |
@implementation BlackAndWhiteThresholdFilter | |
//more https://developer.apple.com/library/ios/documentation/graphicsimaging/Conceptual/CoreImaging/ci_image_units/ci_image_units.html#//apple_ref/doc/uid/TP30001185-CH7-SW8 | |
+ (void)initialize | |
{ | |
//verify registration with [CIFilter filterNamesInCategories:@[kCICategoryVideo]] | |
//registering class responsible for CIFilter execution | |
[CIFilter registerFilterName:kBlackAndWhiteThresholdFilterName | |
constructor:(id <CIFilterConstructor>)self //self means class BlackAndWhiteThresholdFilter | |
classAttributes:@{ | |
kCIAttributeFilterCategories: @[ | |
kCICategoryVideo, | |
kCICategoryStillImage, | |
kCICategoryCompositeOperation, | |
kCICategoryInterlaced, | |
kCICategoryNonSquarePixels | |
], | |
kCIAttributeFilterDisplayName: kBlackAndWhiteThresholdFilterDisplayName, | |
}]; | |
} | |
+ (CIFilter *)filterWithName:(NSString *)aName | |
{ | |
CIColorKernel *kernel; | |
if (@available(macOS 10.13, *)) { | |
kernel = [CIColorKernelMetal blackAndWhiteThresholdKernel]; | |
} | |
if (kernel == nil) { | |
kernel = [CIColorKernelGLSL blackAndWhiteThresholdKernel]; | |
} | |
return [[self alloc] initWithKernel:kernel]; | |
} | |
- (instancetype) initWithKernel:(CIColorKernel *)kernel | |
{ | |
if (kernel == nil) return nil; | |
self = [super init]; | |
if (self) { | |
_kernel = kernel; | |
} | |
return self; | |
} | |
- (NSArray *)inputKeys { | |
return @[kCIInputImageKey, kCIInputThreshold]; | |
} | |
- (NSArray *)outputKeys { | |
return @[kCIOutputImageKey]; | |
} | |
// ------------ ------------ ------------ ------------ ------------ ------------ | |
#pragma mark - CIFilter Protocol | |
+ (NSDictionary *)customAttributes | |
{ | |
NSDictionary *inputThreshold = @{ | |
kCIAttributeType: kCIAttributeTypeScalar, | |
kCIAttributeMin: @0.0f, | |
kCIAttributeMax: @1.0f, | |
kCIAttributeIdentity : @0.00, | |
kCIAttributeDefault: @0.5f, | |
}; | |
return @{ | |
kCIInputThreshold : inputThreshold, | |
// This is needed because the filter is registered under a different name than the class. | |
kCIAttributeFilterName : kBlackAndWhiteThresholdFilterName | |
}; | |
} | |
- (CIImage *)outputImage { | |
CIImage *inputImage = [self inputImage]; | |
if ([self inputImage] == nil) { | |
return nil; | |
} | |
CIImage *outputImage; | |
outputImage = [_kernel applyWithExtent:[inputImage extent] | |
roiCallback:^CGRect(int index, CGRect destRect) { return destRect; } | |
arguments:@[inputImage, [self inputThreshold]]]; | |
return outputImage; | |
} | |
@end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <CoreImage/CoreImage.h> | |
@interface CIColorKernelGLSL : CIColorKernel | |
+ (CIColorKernel *)blackAndWhiteThresholdKernel; | |
- (instancetype)init NS_UNAVAILABLE; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "CIColorKernelGLSL.h" | |
@implementation CIColorKernelGLSL | |
+ (CIColorKernel *)blackAndWhiteThresholdKernel | |
{ | |
// WWDC 2017 510 - disadvanage is that this needs to be compiled on first run (performance penalty) | |
return [self kernelWithString:[self kernelText]]; | |
} | |
+ (NSString *)kernelText | |
{ | |
return | |
@"" | |
"float lumin601(vec3 p)" | |
"{" | |
" return dot(p, vec3(0.299 , 0.587 , 0.114));" | |
"}" | |
"" | |
"kernel vec4 thresholdFilter(__sample image, float inputThreshold)" | |
"{" | |
" vec4 src = unpremultiply( image) );" | |
" float luma = lumin601( src.rgb );" | |
" src.rgb = vec3( step( inputThreshold, luma));" | |
" return premultiply(src);" | |
"}"; | |
} | |
//kept for reference purpose | |
+ (NSString *)oldNonColorKernelText | |
{ | |
return | |
@"" | |
"float lumin601(vec3 p)" | |
"{" | |
" return dot(p, vec3(0.299 , 0.587 , 0.114));" | |
"}" | |
"" | |
"kernel vec4 thresholdFilter(sampler image, float inputThreshold)" | |
"{" | |
" vec4 src = unpremultiply( sample(image, samplerCoord(image)) );" | |
" float luma = lumin601( src.rgb );" | |
" src.rgb = vec3( step( inputThreshold, luma));" | |
" return premultiply(src);" | |
"}"; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <CoreImage/CoreImage.h> | |
FOUNDATION_EXPORT NSString *const kMetalLibraryOldTarget; | |
FOUNDATION_EXPORT NSString *const kMetalLibraryFastMathTarget; | |
NS_AVAILABLE(10_13, 11_0) | |
@interface CIColorKernelMetal : CIColorKernel | |
+ (CIColorKernel *)blackAndWhiteThresholdKernel; | |
- (instancetype)init NS_UNAVAILABLE; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "CIColorKernelMetal.h" | |
#import <Metal/Metal.h> | |
static NSString *const kMetallibExtension = @"metallib"; | |
NSString *const kMetalLibraryOldTarget = @"Metal_10_13"; | |
NSString *const kMetalLibraryFastMathTarget = @"Metal_10_14"; | |
@implementation CIColorKernelMetal | |
+ (CIColorKernel *)blackAndWhiteThresholdKernel | |
{ | |
BOOL supportsMetal; | |
#if TARGET_OS_IOS | |
supportsMetal = MTLCreateSystemDefaultDevice() != nil; //this forces GPU on macbook to switch immediatelly | |
#else | |
supportsMetal = [MTLCopyAllDevices() count] >= 1; | |
#endif | |
//10.13 fully supports metal with fast math, however there are hackintoshes etc... | |
if (supportsMetal == NO) return nil; | |
NSError *error; | |
CIColorKernel *kernel; | |
kernel = [self kernelWithFunctionName:@"thresholdFilter" fromMetalLibraryData:[self data] error:&error]; | |
if (error) { | |
NSLog(@"%@", error); | |
} | |
return kernel; | |
} | |
+ (NSData *)data | |
{ | |
NSURL *URL = [[NSBundle mainBundle] URLForResource:[self metalLibraryName] withExtension:kMetallibExtension]; | |
NSData *data = [NSData dataWithContentsOfURL:URL]; | |
NSError *error; | |
if (error) { | |
NSLog(@"%@", error); | |
} | |
return data; | |
} | |
+ (NSString *)metalLibraryName | |
{ | |
if (@available(macOS 10.14, *)) { | |
return kMetalLibraryFastMathTarget; | |
} else { | |
return kMetalLibraryOldTarget; | |
} | |
//use default | |
//return @"default"; | |
} | |
@end |
Updated with your suggestions and modernised. Everyone who stumbles here from google or stackoverflow can look at history
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To make it work in iOS, you should use this edit in the last method.
and rename
- (NSDictionary *)customAttributes
to
+ (NSDictionary *)customAttributes