CI Filter to create Black & White image?

前端 未结 3 2107
独厮守ぢ
独厮守ぢ 2020-12-09 06:03

I have a CIImage I need to convert from color to Black and White within my Cocoa / objective C program. Peter H. previously pointed me to this link http://www.codingadventu

3条回答
  •  执笔经年
    2020-12-09 06:54

    Many solutions on the internet create grayscale values. There is no need for filters. If you need such than use code such as

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGImageRef greyImage = CGImageCreateCopyWithColorSpace(backingImage, colorSpace);
    CGColorSpaceRelease(colorSpace);
    

    Proper threshold filter follows: inputThreshold can be float value from 0.0f to 0.5f

    https://gist.github.com/xhruso00/a3f8a9c8ae7e33b8b23d

    Detailed recipe can be found at https://developer.apple.com/library/ios/documentation/graphicsimaging/Conceptual/CoreImaging/ci_custom_filters/ci_custom_filters.html#//apple_ref/doc/uid/TP30001185-CH6-CJBEDHHH

    Multiple metallibs depending on target Compile each .metal file into a separate .metallib

    Metal file (fast math iOS12+)

    #include 
    using namespace metal;
    
    //https://en.wikipedia.org/wiki/List_of_monochrome_and_RGB_palettes
    
    //https://en.wikipedia.org/wiki/Relative_luminance
    //https://en.wikipedia.org/wiki/Grayscale
    constant half3 kRec709Luma  = half3(0.2126, 0.7152, 0.0722);
    constant half3 kRec601Luma  = half3(0.299 , 0.587 , 0.114);
    //constant float3 kRec2100Luma = float3(0.2627, 0.6780, 0.0593);
    
    #include 
    
    extern "C" { namespace coreimage {
    
        half lumin601(half3 p)
        {
            return dot(p.rgb, kRec601Luma);
        }
    
        half lumin709(half3 p)
        {
            return dot(p.rgb, kRec709Luma);
        }
    
        half4 thresholdFilter(sample_h image, half threshold)
        {
            half4 pix = unpremultiply(image);
            half luma = lumin601(pix.rgb);
            pix.rgb = half3(step(threshold, luma));
            return premultiply(pix);
        }
    }}
    

    Objective-C file

    #import "CIFilter+BlackAndWhiteThresholdFilter.h"
    #include 
    #import 
    #import 
    
    @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*) blackAndWhiteThresholdFilter
    {
        [BlackAndWhiteThresholdFilter class]; //kick off initialize to register filter
        CIFilter*filter = (CIFilter*)[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 )self //self means class BlackAndWhiteThresholdFilter
                     classAttributes:@{
                         kCIAttributeFilterCategories: @[
                                 kCICategoryVideo,
                                 kCICategoryStillImage,
                                 kCICategoryCompositeOperation,
                                 kCICategoryInterlaced,
                                 kCICategoryNonSquarePixels
                         ],
                         kCIAttributeFilterDisplayName: kBlackAndWhiteThresholdFilterDisplayName,
    
                     }];
    }
    
    
    + (CIFilter *)filterWithName:(NSString *)aName
    {
        return [[self alloc] init];
    }
    
    - (instancetype)init {
        self = [super init];
        if (self) {
    
            BOOL supportsMetal;
    
    #if TARGET_OS_IOS
            supportsMetal = MTLCreateSystemDefaultDevice() != nil; //this forces GPU on macbook to switch immediatelly
    #else
            supportsMetal = [MTLCopyAllDevices() count] >= 1;
    #endif
    
            if (@available(macOS 10.13, *)) {
    
                //10.13 there are macbooks without metal + other forced installations
                if (supportsMetal) {
                    _kernel = [self metalKernel];
                } else {
                    _kernel = [self GLSLKernel];
                }
    
            } else {
                _kernel = [self GLSLKernel];
            }
    
            if (_kernel == nil) return nil;
        }
    
        return self;
    }
    
    - (CIColorKernel *)metalKernel
    {
    
        NSURL *URL = [[NSBundle mainBundle] URLForResource:@"default" withExtension:@"metallib"]; //default is
        NSData *data = [NSData dataWithContentsOfURL:URL];
        NSError *error;
        if (error) {
            NSLog(@"%@", error);
        }
        return [CIColorKernel kernelWithFunctionName:@"thresholdFilter" fromMetalLibraryData:data error:&error];
    }
    
    - (CIColorKernel *)GLSLKernel //OpenGL Shading Language
    {
        // WWDC 2017 510 - disadvanage is that this needs to be compiled on first run (performance penalty)
        NSString *kernelString = [self colorKernelText];
        return [CIColorKernel kernelWithString:kernelString]; // to suppress warning define CI_SILENCE_GL_DEPRECATION in pre-processor macros
    }
    
    - (NSString *)colorKernelText
    {
        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);"
        "}";
    }
    
    - (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
    

    Updated with visual difference between black&white(left) and grayscale(right)

提交回复
热议问题