Extending Custom Filters - emcconville/cif GitHub Wiki

The cif utility can load & run custom Core Image Filters. Cif will search the following directories for bundle packages.

  • Current user directory $HOME/Library/Application Support/cif/Filters/
  • System directory /Library/Application Support/cif/Filters/
  • Network Mount directory /Network/Library/Application Support/cif/Filters/

To create a bundle, you would need to use Xcode to generate a new "Bundle" project, provide the basic Kernel script, Obj-C endpoints, and some very light meta info.

Creating a Kernel

// chromaKeyKernel.cikernel

// Classic Chroma-Key algorithm defined as...
// "alpha = K0 * blue - K1 * green + K2"
kernel vec4 chromaKey(sampler image, vec3 k)
{
    __color pixel = sample(image, samplerCoord(image));
    pixel.a = k.x * pixel.b - k.y * pixel.g + k.z;
    return pixel;
}

Creating a CIFilter

// MyChromaKeyFilter.h
@import QuartzCore;
@interface MyChromeKeyFilter : CIFilter {
    CIImage * inputImage;
    CIVector * inputConstants;
}
@end

// MyChromaKeyFilter.m
#import "MyChromaKeyFilter.h"

static CIKernel * chromaKeyKernel = nil;

@implementation MyChromeKeyFilter

/**
 ~~ OPTIONAL ~~
 Initialize class method communicates to CIF what this filter does.
 The meta-information here populates the help/list display text.
 */
+(void) initialize
{
  NSDictionary *attributes = @{
    // Display name (should be the same as CIFilter class)
    kCIAttributeFilterDisplayName: @"MyChromeKeyFilter",
    kCIAttributeFilterName : @"MyChromeKeyFilter",
    // Description
    kCIAttributeDescription : @"Classic Chroma key algo"
  };
  // Register filter to populate CIF run-time
  [CIFilter registerFilterName:@"MyChromeKeyFilter"
                   constructor:(id<CIFilterConstructor>)self
               classAttributes:attributes];
}

/**
  ~~ REQUIRED ~~
  Read the kernel script into memory before working with
  class instance. (Better ways exists)
 */
-(id)init
{
  if (chromaKeyKernel == nil) {
    // Load kernel code
    NSBundle * bundle = [NSBundle bundleForClass:[self class]];
    NSString * path = [bundle pathForResource:@"chromaKeyKernel"
                                       ofType:@"cikernel"];
    NSString * code = [NSString stringWithContentsOfFile:path
                                                encoding:NSUTF8StringEncoding
                                                   error:nil];
    NSArray * kernels = [CIKernel kernelsWithString:code];
    chromaKeyKernel = kernels[0];
  }
  return [super init];
}

/**
 ~~ REQUIRED ~~
 This method communicates what the behavior of the user input values should be.
 Ensure all `kCIAttributeClass' values are standard
  - CIColor
  - CIImage
  - CINumber
  - CIVector
  - NSData
  - NSString
 */
- (NSDictionary *)customAttributes
{
    return @{
      kCIAttributeFilterName : @"MyChromeKeyFilter",
        kCIInputImageKey : @{
          kCIAttributeDescription : @"The image to apply chroma-key transparency"},
        @"inputConstants" : @{
          kCIAttributeDescription : @"Three K constants",
          kCIAttributeDefault : [CIVector vectorWithX:1.0 Y:1.0 Z:1.0],
          kCIAttributeClass : @"CIVector"}};
}

/**
  ~~ REQUIRED ~~
  Collect/verify arguments, and execute kernel script.
 */
- (CIImage *)outputImage
{
    CGRect extent = [inputImage extent];
    CISampler * src = [CISampler samplerWithImage: inputImage];
    CIImage * result = [self apply:chromaKeyKernel,
                                   src,
                                   inputConstants,
                                   nil];
    return [result imageByCroppingToRect:extent];
}
@end

Loading the CIFilter into the Bundle

If you only have a single filter, add the class name to the Bundle info as "Principal class". Cif assumes that each bundle's principal class is able to register any CIFilter. If your bundle has more than one filter, than create a simple generic "main" class to register other CIFilters.

// MyMain.m
#import "TomFilter.h"
#import "DickFilter.h"
#import "HarryFilter.h"
@implementation MyMain
+(void) initialize
{
  [TomFilter class];
  [DickFilter class];
  [HarryFilter class];
}
@end

Build the bundle, and copy it to cif/Filters directory (listed above.) Run the following commands to test

cif list

Your filter should be present in the list of available filters.

cif list MyChromeKeyFilter

Expect to see the descriptive information given by MyChromeKeyFilter::initialize & MyChromeKeyFilter::customAttributes methods.

And finally, run your custom kernel

cif MyChromeKeyFilter -inputImage greenscreen.jpg -inputConstants 0.9,1.23,0.89 transparent.png