CIFilter guassianBlur and boxBlur are shrinking the image - how to avoid the resizing?
Asked Answered
C

3

9

I am taking a snapshot of the contents of an NSView, applying a CIFilter, and placing the result back into the view. If the CIFilter is a form of blur, such as CIBoxBlur or CIGuassianBlur, the filtered result is slightly smaller than the original. As I am doing this iteratively the result becomes increasingly small, which I want to avoid.

The issue is alluded to here albeit in a slightly different context (Quartz Composer). Apple FunHouse demo app applies a Guassian blur without the image shrinking, but I haven't yet worked out how this app does it (it seems to be using OpenGL which I am not familiar with).

Here is the relevant part of the code (inside an NSView subclass)

NSImage* background = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:[self bounds]]];

CIContext* context = [[NSGraphicsContext currentContext] CIContext];
CIImage* ciImage = [background ciImage];

 CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"
 keysAndValues: kCIInputImageKey, ciImage,
 @"inputRadius", [NSNumber numberWithFloat:10.0], nil];

CIImage *result = [filter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result
                                   fromRect:[result extent]];

NSImage* newBackground = [[NSImage alloc] initWithCGImage:cgImage size:background.size];

If I try a color-changing filter such as CISepiaTone, which is not shifting pixels around, the shrinking does not occur.

I am wondering if there is a quick fix that doesn't involve diving into openGL?

Corroboration answered 19/11, 2012 at 3:57 Comment(0)
M
18

They're actually not shrinking the image, they're expanding it (I think by 7 pixels around all edges) and the default UIView 'scale To View' makes it looks like it's been shrunk.

Crop your CIImage with:

CIImage *cropped=[output imageByCroppingToRect:CGRectMake(0, 0, view.bounds.size.width*scale, view.bounds.size.height*scale)];

where view is the original bounds of your NSView that you drew into and 'scale' is your [UIScreen mainScreen] scale].

Mcconnell answered 16/12, 2012 at 4:11 Comment(4)
Thanks, I will have a go at implementing this. But are you getting confused between OSX and iOS? This is an OSX app, so strictly NSViews not UIViews and no [UIScreen mainScreen] scale. As I understand it you are saying that I should take the filtered CIImage - which will be larger than the input image, and crop it back to the size of the input image. But presumably I would want to center the crop (i.e. crop the same from each edge) so the crop origin wouldn't be {0,0}? More like {(output.width - view.bounds.size.width)/2, (output.height - view.bounds.size.height)/2}?Corroboration
I've implemented this now and it works great - thanks! You are correct, origin is {0,0} to keep the result in the right position. This is what I used (omits scale): CIImage *cropped=[output imageByCroppingToRect:CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height)]Corroboration
It seems to me that the easier way to do this is just to do: CGImageRef cgImage = [context createCGImage:result fromRect:[**ciImage** extent]]; So the extent comes from the original image not the output of the filter.Eudemonia
@Eudemonia Your suggestion helped! Thanks!Ingrain
J
16

You probably want to clamp your image before using the blur:

- (CIImage*)imageByClampingToExtent {
    CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
    [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
    [clamp setValue:self forKey:@"inputImage"];
    return [clamp valueForKey:@"outputImage"];
}

Then blur, and then crop to the original extent. You'll get non-transparent edges this way.

Jakejakes answered 29/3, 2014 at 19:56 Comment(2)
solves problem with semi-transparent edges! BTW there is a standard function - (CIImage *)imageByClampingToExtent NS_AVAILABLE(10_10, 8_0);Synthiasyntonic
This was the only thing that worked for me. The image now has pixellation and keeps it's correct size and visual position.Mord
F
5

@BBC_Z's solution is correct.

Although I find it more elegant to crop not according to the view, but to the image.
And you can cut away the useless blurred edges:

// Crop transparent edges from blur
resultImage = [resultImage imageByCroppingToRect:(CGRect){
    .origin.x = blurRadius,
    .origin.y = blurRadius,
    .size.width = originalCIImage.extent.size.width - blurRadius*2,
    .size.height = originalCIImage.extent.size.height - blurRadius*2
}];
Fanelli answered 29/3, 2014 at 9:39 Comment(1)
Somebody should make a custom CIFilter that blurs an input image while taking care of all this stuff for convenience. I think I will...Urinary

© 2022 - 2024 — McMap. All rights reserved.