GPUImageDivideBlendFilter in GPUImage, what's corresponding filter for CIImage?

534 views Asked by At

I have try this:

CIFilter *dodgeFilter = [CIFilter filterWithName:@"CIColorDodgeBlendMode"];

to replace:

GPUImageDivideBlendFilter *divideBlendFilter = [[GPUImageDivideBlendFilter alloc] init];

but the effects are not same.

3

There are 3 answers

0
Tomas Camin On

Builtin filter

Have you tried with CIDivideBlendMode?

CIImage *img1 = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"img1.jpg"]];
CIImage *img2 = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"img2.jpg"]];

CIFilter *filterBuiltin = [CIFilter filterWithName:@"CIDivideBlendMode"
                                     keysAndValues:@"inputImage", img1,
                                                   @"inputBackgroundImage", img2, nil];
CIImage *outputImageBuiltin = [filterBuiltin outputImage];
UIImage *filteredImageBuiltin = [self imageWithCIImage:outputImageBuiltin];

Custom filter

I thought it would be fun to try to create a custom CIFilter based on an existing GPUImageFilter now that iOS8 allows us to do so. This should allow to translate any GPUImageFilter to its CIFilter counterpart.

Before starting it's worth checking out What You Need to Know Before Writing a Custom Filter and Core Image Kernel Language Reference

We'll start by writing our custom kernel which will be very similar to the GPUImageDivideBlendFilter shader. The one exception is the control flow part that seems unsupported in the Core Image Kernel language which we'll workaround using the *_branch1 and *_branch2 multipliers.

Creating a CIFilter is simple:

  1. Create a new ImageDivideBlendFilter.cikernel (your custom filter kernel) file to your Xcode project:

    kernel vec4 GPUImageDivideBlendFilter(sampler image1, sampler image2)
    {
        float EPSILON = 1e-4;
        vec4 base = sample(image1, samplerCoord(image1));
        vec4 overlay = sample(image2, samplerCoord(image2));
    
        float ra1 = overlay.a * base.a + overlay.r * (1.0 - base.a) + base.r * (1.0 - overlay.a);
        float ra2 = (base.r * overlay.a * overlay.a) / overlay.r + overlay.r * (1.0 - base.a) + base.r * (1.0 - overlay.a);
    
        // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CIKernelLangRef/ci_gslang_ext.html#//apple_ref/doc/uid/TP40004397-CH206-TPXREF101
        // "Other flow control statements (if, for, while, do while) are supported only when the loop condition can be inferred at the time the code compiles"
        float ra_branch2 = step(EPSILON, overlay.a) * step(base.r / overlay.r, base.a / overlay.a);
        float ra_branch1 = step(ra_branch2, 0.5);
    
        float ra = ra1 * ra_branch1 + ra2 * ra_branch2;
    
    
        float ga1 = overlay.a * base.a + overlay.g * (1.0 - base.a) + base.g * (1.0 - overlay.a);
        float ga2 = (base.g * overlay.a * overlay.a) / overlay.g + overlay.g * (1.0 - base.a) + base.g * (1.0 - overlay.a);
    
        float ga_branch2 = step(EPSILON, overlay.a) * step(base.g / overlay.g, base.a / overlay.a);
        float ga_branch1 = step(ga_branch2, 0.5);
    
        float ga = ga1 * ga_branch1 + ga2 * ga_branch2;
    
    
        float ba1 = overlay.a * base.a + overlay.b * (1.0 - base.a) + base.b * (1.0 - overlay.a);
        float ba2 = (base.b * overlay.a * overlay.a) / overlay.b + overlay.b * (1.0 - base.a) + base.b * (1.0 - overlay.a);
    
        float ba_branch2 = step(EPSILON, overlay.a) * step(base.b / overlay.b, base.a / overlay.a);
        float ba_branch1 = step(ba_branch2, 0.5);
    
        float ba = ba1 * ba_branch1 + ba2 * ba_branch2;
    
        return vec4(ra, ga, ba, 1.0);
    }
    
  2. Add the interface and implementation for your filter

    // ImageDivideBlendFilter.h
    #import <CoreImage/CoreImage.h>
    
    @interface ImageDivideBlendFilter : CIFilter
    
    @end
    
    
    // ImageDivideBlendFilter.m
    #import "ImageDivideBlendFilter.h"
    
    @interface ImageDivideBlendFilter()
    {
        CIImage *_image1;
        CIImage *_image2;
    }
    
    @end
    
    @implementation ImageDivideBlendFilter
    
    static CIColorKernel *imageDivideBlendKernel = nil;
    
    + (void)initialize
    {
        // This will load the kernel code which will compiled at run time. We do this just once to optimize performances
        if (!imageDivideBlendKernel)
        {
            NSBundle *bundle = [NSBundle bundleForClass:[self class]];
            NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource: @"ImageDivideBlendFilter" ofType: @"cikernel"] encoding:NSUTF8StringEncoding error:nil];
            NSArray *kernels = [CIColorKernel kernelsWithString:code];
            imageDivideBlendKernel = [kernels firstObject];
        }
    }
    
    - (CIImage *)outputImage
    {
        return [imageDivideBlendKernel applyWithExtent:_image1.extent roiCallback:nil arguments:@[_image1, _image2]];
    }
    
    + (CIFilter *)filterWithName: (NSString *)name
    {
        CIFilter  *filter;
        filter = [[self alloc] init];
        return filter;
    }
    
    @end
    
  3. We're ready to use our newly created custom filter in our application

    - (void)filterDemo
    {
        CIImage *img1 = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"img1.jpg"]];
        CIImage *img2 = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"img2.jpg"]];
    
        [ImageDivideBlendFilter class]; // preload kernel, it speeds up loading the filter if used multiple times
    
        CIFilter *filterCustom = [CIFilter filterWithName:@"ImageDivideBlendFilter" keysAndValues:@"image1", img2, @"image2", img1, nil];
        CIImage *outputImageCustom = [filterCustom outputImage];
    
        UIImage *filteredImageCustom = [self imageWithCIImage:outputImageCustom];
    }
    
    - (UIImage *)imageWithCIImage:(CIImage *)ciimage
    {
        CIContext *context = [CIContext contextWithOptions:nil];
        CGImageRef cgimg = [context createCGImage:ciimage fromRect:[ciimage extent]];
        UIImage *newImg = [UIImage imageWithCGImage:cgimg];
    
        CGImageRelease(cgimg);
    
        return newImg;
    }
    

Builtin and custom filter produce the same result.

Edit: Swift version

I made a sample project available on Github https://github.com/tcamin/CustomCoreImageFilteringDemo that shows how to make CIFiltering in Swift.

0
James Bush On

The CIColorKernel code in the answer does not work; in fact, any attempt to pass more than one sampler object (image) to a kernel fails.

0
James Bush On

Also—and I know this doesn't relate to the question, but I feel like this should be pointed out—the kernel code has one thing backwards, specifically, the premultiply-related functions. Unpremultiply any sampler (or color) object when working with the alpha channel independently from the remaining three; recombine them with premultiply when you have the finished product. Do neither if you are not changing or mixing or otherwise using two sampler (or color) objects in your calculations.