4

I have two CIFilters, exposure and hue. I need to combine the filters over one UIImage. How should i go about this? Below is some code that i have so far...

CIFilter *hueFilter;
CIFilter *exposureFilter;
CIImage *adjustedImage;

hueFilter = [CIFilter filterWithName:@"CIHueAdjust"];
exposureFilter = [CIFilter filterWithName:@"CIExposureAdjust"];
[hueFilter setValue:[NSNumber numberWithFloat:5] forKey: @"inputAngle"];
[exposureFilter setValue:[NSNumber numberWithFloat:5] forKey: @"inputEV"];

adjustedImage = [CIImage imageWithCGImage:inputCGImage];
[hueFilter setValue:adjustedImage forKey:@"inputImage"];
[exposureFilter setValue:adjustedImage forKey:@"inputImage"];
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
MikeE
  • 41
  • 1
  • 5
  • 1
    See also this [similar question](https://stackoverflow.com/questions/55553869/on-ios-can-you-add-multiple-cifilters-to-a-spritekit-node) with Swift examples. – zekel Apr 09 '19 at 18:43

2 Answers2

7

Core Image filters can be chained together, one after the other. I find the code easier to read (and write) if it’s written with that idea.

CIFilter *hueFilter;
CIFilter *exposureFilter;
CIImage *inputImage; // assume this has already been created
CIImage *outputImage;

hueFilter = [CIFilter filterWithName:@"CIHueAdjust"];
[hueFilter setValue:inputImage forKey:kCIInputImageKey];
[hueFilter setValue:[NSNumber numberWithFloat:5] forKey:@"inputAngle"];
outputImage = [hueFilter valueForKey:kCIOutputImageKey];

exposureFilter = [CIFilter filterWithName:@"CIExposureAdjust"];
[exposureFilter setValue:outputImage forKey:kCIInputImageKey];
[exposureFilter setValue:[NSNumber numberWithFloat:5] forKey:@"inputEV"];
outputImage = [exposureFilter valueForKey:kCIOutputImageKey];

Above, the first filter is created. Note the use of the constants for the keys where available. At the end of the block the filter has been set for the image, but the calculations are not actually performed until the image is rendered. Any new filters applied will be combined for the most efficient operation.

The next block then applies the next filter, using the output of the first filter as the input for the second. This can be repeated as many times as needed. By writing the code as above, you can easily turn on/off filters as needed, or even reorder them if you have several.

Apple’s documentation is very good and has many examples: Core Image Programming Guide.

Demitri
  • 13,134
  • 4
  • 40
  • 41
  • This is not filter chaining; you're simply using as input to the second filter the output of the first. – James Bush Mar 14 '18 at 21:44
  • 1
    @james-bush That is literally Apple's definition of a filter chain, see for example [Chaining Filters for Complex Effects](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_tasks/ci_tasks.html#//apple_ref/doc/uid/TP30001185-CH3-DontLinkElementID_5) and [Create a CI Filter Chain](https://stackoverflow.com/questions/22183085/create-a-ci-filter-chain). – Demitri Feb 10 '19 at 10:56
2

Here's the easiest and most concise way to chain Core Image filters without recursion:

NSDictionary *filters = @{
                                  @"CIScreenBlendMode" : @{kCIInputImageKey : inputImage, kCIInputBackgroundImageKey : inputImage},
                                  @"CIOverlayBlendMode" : @{kCIInputImageKey : inputImage, kCIInputBackgroundImageKey : inputImage},
                                  @"CIMultiplyBlendMode" : @{kCIInputImageKey : inputImage, kCIInputBackgroundImageKey : inputImage}
                                  };
        [filters enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            inputImage = [inputImage imageByApplyingFilter:key withInputParameters:obj];
        }];

You supply the CIImage (inputImage in the sample code above), and then populate the NSDictionary with the name of the filter as the key, and another NSDictionary (which contains the input parameters for the filter) as the object.

The code above enumerates the key-object pairs in the NSDictionary; each pair is used to supply the parameters required by the CIImage imageByApplyingFilter:withInputParameters method.

James Bush
  • 1,485
  • 14
  • 19
  • 1
    Important note from the docs: This method, though convenient, is inefficient if used multiple times in succession. Achieve better performance by chaining filters without asking for the outputs of individual filters. – p10ben Nov 05 '20 at 18:06