Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Creating a Custom Filter

This section shows how to create a Core Image filter that has an Objective-C portion and a kernel portion. By following the steps in this section, you’ll create a filter that is CPU executable. You can package this filter, along with other filters if you’d like, as an image unit by following the instructions in “Creating Custom Filters.” Or, you can simply use the filter from within your own application. See “Using Your Own Custom Filter” for details.

The filter in this section assumes that the region of interest (ROI) and the domain of definition coincide. If you want to write a filter for which this assumption isn’t true, make sure you also read “Supplying an ROI Function.” Before you create your own custom filter, make sure you understand Core Image coordinate spaces. See “Coordinate Spaces .”

To create a custom CPU executable filter, perform the following steps:

  1. “Write the Kernel Code”

  2. “Use Quartz Composer to Test the Kernel Routine”

  3. “Declare an Interface for the Filter”

  4. “Write an Init Method for the CIKernel Object”

  5. “Write a Custom Attributes Method”

  6. “Write an Output Image Method”

  7. “Register the Filter”

  8. “Write a Method to Create Instances of the Filter”

Each step is described in detail in the sections that follow using a haze removal filter as an example. The effect of the haze removal filter is to adjust the brightness and contrast of an image, and to apply sharpening to it. This filter is useful for correcting images taken through light fog or haze, which is typically the case when taking an image from an airplane. Figure 3-1 shows an image before and after processing with the haze removal filter. The application using the filter provides sliders that allow the user to adjust the input parameters to the filter.


Figure 3-1  An image before and after processing with the haze removal filter

An image before and after processing with the haze removal filter

Write the Kernel Code

The code that performs per-pixel processing resides in a file with the .cikernel extension. You can include more than one kernel routine in this file. You can also include other routines if you want to make your code modular. You specify a kernel using a subset of OpenGL Shading Language and the Core Image extensions to it. See Core Image Kernel Language Reference for information on allowable elements of the language.

A kernel routine signature must return a vector (vec4) that contains the result of mapping the source to the destination. Core Image invokes a kernel routine once for each pixel. Keep in mind that your code can’t accumulate knowledge from pixel to pixel. A good strategy when you write your code is to move as much invariant calculation as possible from the actual kernel and place it in the Objective-C portion of the filter.

Listing 3-1 shows the kernel routine for a haze removal filter. A detailed explanation for each numbered line of code follows the listing. (There are examples of other pixel-processing routines in “Kernel Routine Examples” and in Image Unit Tutorial.)

Listing 3-1  A kernel routine for the haze removal filter

kernel vec4 myHazeRemovalKernel(sampler src,// 1
                     __color color,
                    float distance,
                    float slope)
{
    vec4   t;
    float  d;
 
    d = destCoord().y * slope  +  distance; // 2
    t = unpremultiply(sample(src, samplerCoord(src))); // 3
    t = (t - d*color) / (1.0-d); // 4
 
    return premultiply(t); // 5
}

Here’s what the code does:

  1. Takes four input parameters and returns a vector. When you declare the interface for the filter, you must make sure to declare the same number of input parameters as you specify in the kernel. The kernel must return a vec4 data type.

  2. Calculates a value based on the y-value of the destination coordinate and the slope and distance input parameters. The destCoord routine (provided by Core Image) returns the position, in working space coordinates, of the pixel currently being computed.

  3. Gets the pixel value, in sampler space, of the sampler src that is associated with the current output pixel after any transformation matrix associated with the src is applied. Recall that Core Image uses color components with premultiplied alpha values. Before processing, you need to unpremultiply the color values you receive from the sampler.

  4. Calculates the output vector by applying the haze removal formula, which incorporates the slope and distance calculations and adjusts for color.

  5. Returns a vec4 vector, as required. The kernel performs a premultiplication operation before returning the result because Core Image uses color components with premultiplied alpha values.

A few words about samplers and sample coordinate space:  The samplers you set up to provide samples to kernels that you write can contain any values necessary for the filter calculation, not just color values. For example, a sampler can provide values from numerical tables, vector fields in which the x and y values are represented by the red and green components respectively, height fields, and so forth. This means that you can store any vector-value field with up to four components in a sampler. To avoid confusion on the part of the filter client, it’s best to provide documentation that states when a vector is not used for color. When you use a sampler that doesn’t provide color, you can bypass the color correction that Core Image usually performs by providing a nil colorspace.

Use Quartz Composer to Test the Kernel Routine

Quartz Composer is an easy-to-use development tool (provided starting in Mac OS X v10.4) that you can use to test kernel routines. The Quartz Composer application is located in this directory:

/Developer/Applications/Graphics Tools/

Quartz Composer User Guide describes the Quartz Composer user interface and provides details on how to create compositions. You’ll want to read that document before you use Quartz Composer to test your kernel routine.

Quartz Composer provides a patch into which you can place your kernel routine. (In Mac OS X v10.4 this patch is named Core Image Kernel patch; it’s called Core Image Filter patch in Mac OS X v10.5 and later.) You simply open the Inspector for the Core Image patch, and either paste or type your code into the text field, as shown in Figure 3-2.


Figure 3-2  The haze removal kernel routine pasted into the Settings pane

The haze removal kernel routine pasted into the Settings pane

After you enter the code, the patch inputs ports are automatically created according to the prototype of the kernel function, as you can see in Figure 3-3. The patch always has a single output port, which represents the resulting image produced by the kernel.

The simple composition shown in the figure imports an image file using the Image Importer patch, processes it through the kernel, then renders the result on screen using the Billboard patch. (See Quartz Composer User Guide for information on the Image Importer and Billboard patches). Your kernel can use more than one image or, if it generates output, it might not require any input images.

The composition you build to test your kernel can be more complex than that shown in Figure 3-3. For example, you might want to chain your kernel routine with other built-in Core Image filters or with other kernel routines. Quartz Composer provides many, many other patches that you can use in the course of testing your kernel routine.


Figure 3-3  A Quartz Composer composition that tests a kernel routine

A Quartz Composer composition that tests a kernel routine

Declare an Interface for the Filter

The .h file for the filter contains the interface that specifies the filter inputs, as shown in Listing 3-2. The haze removal kernel has four input parameters: a source, color, distance, and slope. The interface for the filter must also contain these input parameters. The input parameters must be in the same order as specified for the filter, and the data types must be compatible between the two.

Listing 3-2  Code that declares the interface for a haze removal filter

@interface MyHazeFilter: CIFilter
{
    CIImage   *inputImage;
    CIColor   *inputColor;
    NSNumber  *inputDistance;
    NSNumber  *inputSlope;
}
 
@end

Write an Init Method for the CIKernel Object

The implementation file for the filter contains a method that initializes a Core Image kernel object (CIKernel) with the kernel routine specified in the .cikernel file. A .cikernel file can contain more than one kernel routine. A detailed explanation for each numbered line of code appears following the listing.

Listing 3-3  An init method that initializes the kernel

static CIKernel *hazeRemovalKernel = nil;
 
- (id)init
{
    if(hazeRemovalKernel == nil)// 1
    {
        NSBundle    *bundle = [NSBundle bundleForClass: [self class]];// 2
        NSString    *code = [NSString stringWithContentsOfFile: [bundle// 3
                                pathForResource: @"MyHazeRemoval"
                                ofType: @"cikernel"]];
        NSArray     *kernels = [CIKernel kernelsWithString: code];// 4
 
        hazeRemovalKernel = [[kernels objectAtIndex:0] retain];// 5
    }
 
    return [super init];
}

Here’s what the code does:

  1. Checks whether the CIKernel object is already initialized.

  2. Returns the bundle that dynamically loads the CIFilter class.

  3. Returns a string created from the file name at the specified path, which in this case is the MyHazeRemoval.cikernel file.

  4. Creates a CIKernel object from the string specified by the code argument. Each routine in the .cikernel file that is marked as a kernel is returned in the kernels array. This example has only one kernel in the .cikernel file, so the array contains only one item.

  5. Sets hazeRemovalKernel to the first kernel in the kernels array. If the .cikernel file contains more than one kernel, you would also initialize those kernels in this routine.

Write a Custom Attributes Method

A customAttributes method allows clients of the filter to obtain the filter attributes such as the input parameters, default values, and minimum and maximum values. (See CIFilter Class Reference for a complete list of attributes.) A filter is not required to provide any information about an attribute other than its class, but a filter must behave in a reasonable manner if attributes are not present.

Typically, these are the attributes that your customAttributes method would return:

Listing 3-4 shows the customAttributes method for the Haze filter. The input parameters inputDistance and inputSlope each have minimum, maximum, slider minimum, slider maximum, default and identity values set. The slider minimum and maximum values are used to set up the sliders shown in Figure 3-1. The inputColor parameter has a default value set.

Listing 3-4  The customAttributes method for the Haze filter

- (NSDictionary *)customAttributes
{
    return [NSDictionary dictionaryWithObjectsAndKeys:
 
        [NSDictionary dictionaryWithObjectsAndKeys:
            [NSNumber numberWithDouble:  0.0], kCIAttributeMin,
            [NSNumber numberWithDouble:  1.0], kCIAttributeMax,
            [NSNumber numberWithDouble:  0.0], kCIAttributeSliderMin,
            [NSNumber numberWithDouble:  0.7], kCIAttributeSliderMax,
            [NSNumber numberWithDouble:  0.2], kCIAttributeDefault,
            [NSNumber numberWithDouble:  0.0], kCIAttributeIdentity,
            kCIAttributeTypeScalar,            kCIAttributeType,
            nil],                              @"inputDistance",
 
        [NSDictionary dictionaryWithObjectsAndKeys:
            [NSNumber numberWithDouble: -0.01], kCIAttributeSliderMin,
            [NSNumber numberWithDouble:  0.01], kCIAttributeSliderMax,
            [NSNumber numberWithDouble:  0.00], kCIAttributeDefault,
            [NSNumber numberWithDouble:  0.00], kCIAttributeIdentity,
            kCIAttributeTypeScalar,             kCIAttributeType,
            nil],                               @"inputSlope",
 
        [NSDictionary dictionaryWithObjectsAndKeys:
            [CIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
                                 kCIAttributeDefault,
            nil],                               @"inputColor",
 
        nil];
}

Write an Output Image Method

An outputImage method creates a CISampler object for each input image (or image mask), creates a CIFilterShape object (if appropriate), and applies the kernel method. Listing 3-5 shows an outputImage method for the haze removal filter. The first thing the codes does is to set up a sampler to fetch pixels from the input image. Because this filter uses only one input image, the code sets up only one sampler.

The code calls the apply:arguments:options: method of CIFilter to produce a CIImage object. The first parameter to the apply method is the CIKernel object that contains the haze removal kernel function. (See “Write the Kernel Code.”) Recall that the haze removal kernel function takes four arguments: a sampler, a color, a distance, and the slope. These arguments are passed as the next four parameters to the apply:arguments:options: method in the listing. The remaining arguments to the apply method specify options (key-value pairs) that control how Core Image should evaluate the function. You can pass one of three keys: kCIApplyOptionExtent, kCIApplyOptionDefinition, or kCIApplyOptionUserInfo. This example uses the kCIApplyOptionDefinition key to specify the domain of definition (DOD) of the output image. See CIFilter Class Reference for a description of these keys and for more information on using the apply:arguments:options: method.

The final argument nil, specifies the end of the options list.

Listing 3-5  A method that returns the image output from a haze removal filter

- (CIImage *)outputImage
{
    CISampler *src = [CISampler samplerWithImage: inputImage];
 
    return [self apply: hazeRemovalKernel, src, inputColor, inputDistance,
        inputSlope, kCIApplyOptionDefinition, [src definition], nil];
}

Listing 3-5 is a simple example. The implementation for your outputImage method needs to be tailored to your filter. If your filter requires loop-invariant calculations, you would include them in the outputImage method rather than in the kernel.

Register the Filter

Ideally, you’ll package the filter as an image unit, regardless of whether you plan to distribute the filter to others or use it only in your own application. If you plan to package this filter as an image unit, you’ll register your filter using the CIPlugInRegistration protocol described in “Packaging Filters as Image Units.” You can skip the rest of this section.

Note: Packaging your custom filter as an image unit promotes modular programming and code maintainability.

If for some reason you do not want to package the filter as an image unit (which is not recommended), you’ll need to register your filter using the registration method of the CIFilter class described shown in Listing 3-6. The initialize method calls registerFilterName:constructor:classAttributes:. You should register only the display name (kCIAttributeFilterDisplayName) and the filter categories (kCIAttributeFilterCategories). All other filters attributes should be specified in the customAttributes method. (See “Write a Custom Attributes Method”).

The filter name is the string for creating the haze removal filter when you want to use it. The constructor object specified implements the filterWithName: method (see “Write a Method to Create Instances of the Filter”). The filter class attributes are specified as an NSDictionary object. The display name—what you’d show in the user interface—for this filter is Haze Remover.

Listing 3-6  Registering a filter that is not part of an image unit

+ (void)initialize
{
    [CIFilter registerFilterName: @"MyHazeRemover"
        constructor: self
        classAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
             @"Haze Remover", kCIAttributeFilterDisplayName,
             [NSArray arrayWithObjects:
                kCICategoryColorAdjustment, kCICategoryVideo,
                kCICategoryStillImage,kCICategoryInterlaced,
                kCICategoryNonSquarePixels,nil], kCIAttributeFilterCategories,
            nil]
            ];
}

Write a Method to Create Instances of the Filter

If you plan to use this filter only in your own application, then you’ll need to implement a filterWithName: method as described in this section. If you plan to package this filter as an image unit for use by third-party developers, then you can skip this section because your packaged filters can use the filterWithName: method provided by the CIFilter class.

The filterWithName: method shown in Listing 3-7 creates instances of the filter when they are requested.

Listing 3-7  A method that creates instance of a filter

+ (CIFilter *)filterWithName: (NSString *)name
{
    CIFilter  *filter;
 
    filter = [[self alloc] init];
    return [filter autorelease];
}

After you follow these steps to create a filter, you can use the filter in your own application. See “Using Your Own Custom Filter” for details. If you want to make a filter or set of filters available as a plug-in for other applications, see “Creating Custom Filters.”



< Previous PageNext Page > Hide TOC


Last updated: 2008-06-09




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice