Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a blurred shape?

How do I draw a blurred shape in Cocoa? Think of a shadow with a blurRadius accompanying a filled path, but without sharp-edged foreground path shape.

What I tried is using a filled path with a shadow, and setting the fill color to transparent (alpha 0.0). But that makes the shadow invisible as well, as it is apparently taking the shadow casting "object's" alpha into account.

like image 620
febeling Avatar asked Jun 03 '11 20:06

febeling


People also ask

How do you blur a shape in sketch?

To add a blur to your layer, click the Blur title in the Inspector and then choose a blur type from the drop-down menu: Gaussian Blur applies a blur in all directions. You can set the blur amount using its slider. Motion Blur blurs in one direction to imply motion.


2 Answers

This is actually reasonably tricky. I struggled with this for a while until I came up with this category on NSShadow:

@implementation NSShadow (Extras)

//draw a shadow using a bezier path but do not draw the bezier path
- (void)drawUsingBezierPath:(NSBezierPath*) path alpha:(CGFloat) alpha
{
    [NSGraphicsContext saveGraphicsState];
    //get the bounds of the path
    NSRect bounds = [path bounds];

    //create a rectangle that outsets the size of the path bounds by the blur radius amount
    CGFloat blurRadius = [self shadowBlurRadius];
    NSRect shadowBounds = NSInsetRect(bounds, -blurRadius, -blurRadius);

    //create an image to hold the shadow
    NSImage* shadowImage = [[NSImage alloc] initWithSize:shadowBounds.size];

    //make a copy of the shadow and set its offset so that when the path is drawn, the shadow is drawn in the middle of the image
    NSShadow* aShadow = [self copy];
    [aShadow setShadowOffset:NSMakeSize(0, -NSHeight(shadowBounds))];

    //lock focus on the image
    [shadowImage lockFocus];

    //we want to draw the path directly above the shadow image and offset the shadow so it is drawn in the image rect
    //to do this we must translate the drawing into the correct location
    NSAffineTransform* transform=[NSAffineTransform transform];
    //first get it to the zero point
    [transform translateXBy:-shadowBounds.origin.x yBy:-shadowBounds.origin.y];

    //now translate it by the height of the image so that it draws outside the image bounds
    [transform translateXBy:0.0 yBy:NSHeight(shadowBounds)];
    NSBezierPath* translatedPath = [transform transformBezierPath:path];

    //apply the shadow
    [aShadow set];

    //fill the path with an arbitrary black color
    [[NSColor blackColor] set];
    [translatedPath fill];
    [aShadow release];
    [shadowImage unlockFocus];

    //draw the image at the correct location relative to the original path
    NSPoint imageOrigin = bounds.origin;
    imageOrigin.x = (imageOrigin.x - blurRadius) + [self shadowOffset].width;
    imageOrigin.y = (imageOrigin.y - blurRadius) - [self shadowOffset].height;

    [shadowImage drawAtPoint:imageOrigin fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:alpha];
    [shadowImage release];
    [NSGraphicsContext restoreGraphicsState];
}

@end
like image 184
Rob Keniger Avatar answered Sep 21 '22 09:09

Rob Keniger


I found one implementation which does exactly what I meant online. Written by Sean Patrick O'Brien. It is a category on NSBezierPath, like so:

@interface NSBezierPath (MCAdditions)

- (void)drawBlurWithColor:(NSColor *)color radius:(CGFloat)radius;

@end

@implementation NSBezierPath (MCAdditions)

- (void)drawBlurWithColor:(NSColor *)color radius:(CGFloat)radius
{
    NSRect bounds = NSInsetRect(self.bounds, -radius, -radius);
    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowOffset = NSMakeSize(0, bounds.size.height);
    shadow.shadowBlurRadius = radius;
    shadow.shadowColor = color;
    NSBezierPath *path = [self copy];
    NSAffineTransform *transform = [NSAffineTransform transform];
    if ([[NSGraphicsContext currentContext] isFlipped])
        [transform translateXBy:0 yBy:bounds.size.height];
    else
        [transform translateXBy:0 yBy:-bounds.size.height];
    [path transformUsingAffineTransform:transform];

    [NSGraphicsContext saveGraphicsState];

    [shadow set];
    [[NSColor blackColor] set];
    NSRectClip(bounds);
    [path fill];

    [NSGraphicsContext restoreGraphicsState];

    [path release];
    [shadow release];
}

@end

This code is downloadable from this blog post. I didn't find it separately as code anywhere online.

like image 43
febeling Avatar answered Sep 19 '22 09:09

febeling