Create new UIImage by adding shadow to existing UIImage

15,769

Solution 1

There are several problems with your code:

  • The target image is too small. Be sure there enough place to draw the shadow.
  • Consider using CGContextSetShadowWithColor to define both the shadow and its color.
  • Don't forget that coordinate system is flipped, so the origin is bottom-left, not top-left.

By fixing these issues, the shadow should be drawn.

- (UIImage*)imageWithShadow {
    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef shadowContext = CGBitmapContextCreate(NULL, self.size.width + 10, self.size.height + 10, CGImageGetBitsPerComponent(self.CGImage), 0, 
                                             colourSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colourSpace);

    CGContextSetShadowWithColor(shadowContext, CGSizeMake(5, -5), 5, [UIColor blackColor].CGColor);
    CGContextDrawImage(shadowContext, CGRectMake(0, 10, self.size.width, self.size.height), self.CGImage);

    CGImageRef shadowedCGImage = CGBitmapContextCreateImage(shadowContext);
    CGContextRelease(shadowContext);

    UIImage * shadowedImage = [UIImage imageWithCGImage:shadowedCGImage];
    CGImageRelease(shadowedCGImage);

    return shadowedImage;
}

Solution 2

Swift extension to UIImage:

extension UIImage {
    
    func addShadow(blurSize: CGFloat = 6.0) -> UIImage {
                    
        let shadowColor = UIColor(white:0.0, alpha:0.8).cgColor
        
        let context = CGContext(data: nil,
                                width: Int(self.size.width + blurSize),
                                height: Int(self.size.height + blurSize),
                                bitsPerComponent: self.cgImage!.bitsPerComponent,
                                bytesPerRow: 0,
                                space: CGColorSpaceCreateDeviceRGB(),
                                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
        
        context.setShadow(offset: CGSize(width: blurSize/2,height: -blurSize/2),
                          blur: blurSize,
                          color: shadowColor)
        context.draw(self.cgImage!,
                     in: CGRect(x: 0, y: blurSize, width: self.size.width, height: self.size.height),
                     byTiling:false)
        
        return UIImage(cgImage: context.makeImage()!)
    }
}

(converted from Laurent Etiemble's & capikaw answers)

Usage:

let sourceImage: UIImage(named: "some image")
let shadowImage = sourceImage.addShadow()

Solution 3

I needed a bigger shadow for my UIImages. Here is my variant of the answer that allows for a customized shadow size with no offset.

- (UIImage*)imageWithShadow:(UIImage*)originalImage BlurSize:(float)blurSize {     
    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef shadowContext = CGBitmapContextCreate(NULL, originalImage.size.width + (blurSize*2), originalImage.size.height + (blurSize*2), CGImageGetBitsPerComponent(originalImage.CGImage), 0, colourSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colourSpace);
    
    CGContextSetShadowWithColor(shadowContext, CGSizeMake(0, 0), blurSize, [UIColor blackColor].CGColor);
    CGContextDrawImage(shadowContext, CGRectMake(blurSize, blurSize, originalImage.size.width, originalImage.size.height), originalImage.CGImage);
    
    CGImageRef shadowedCGImage = CGBitmapContextCreateImage(shadowContext);
    CGContextRelease(shadowContext);
    
    UIImage * shadowedImage = [UIImage imageWithCGImage:shadowedCGImage];
    CGImageRelease(shadowedCGImage);
    
    return shadowedImage;
}

Use it like:

UIImage *shadowedImage = [self imageWithShadow:myImage BlurSize:30.0f];

Solution 4

I needed a method that would accept an UIImage as a parameter and return a slightly larger UIImage with shadows. The posts here have been very helpful to me so here's my method.

The returned image has a centered 5px shadow on each side.

+ (UIImage*)imageWithShadowForImage:(UIImage *)initialImage {

    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef shadowContext = CGBitmapContextCreate(NULL, initialImage.size.width + 10, initialImage.size.height + 10, CGImageGetBitsPerComponent(initialImage.CGImage), 0, colourSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colourSpace);
    
    CGContextSetShadowWithColor(shadowContext, CGSizeMake(0,0), 5, [UIColor blackColor].CGColor);
    CGContextDrawImage(shadowContext, CGRectMake(5, 5, initialImage.size.width, initialImage.size.height), initialImage.CGImage);
    
    CGImageRef shadowedCGImage = CGBitmapContextCreateImage(shadowContext);
    CGContextRelease(shadowContext);
    
    UIImage * shadowedImage = [UIImage imageWithCGImage:shadowedCGImage];
    CGImageRelease(shadowedCGImage);
    
    return shadowedImage;
}

Solution 5

Whilst trying to use some of the other examples here, I noticed that they seem to either hard-code the offset & blur, or do not correctly take them into account when sizing and positioning the output.

The following code solves these issues (it's based on the original work by Igor Kulagin):

extension UIImage {
    /// Returns a new image with the specified shadow properties.
    /// This will increase the size of the image to fit the shadow and the original image.
    func withShadow(blur: CGFloat = 6, offset: CGSize = .zero, color: UIColor = UIColor(white: 0, alpha: 0.8)) -> UIImage {

        let shadowRect = CGRect(
            x: offset.width - blur,
            y: offset.height - blur,
            width: size.width + blur * 2,
            height: size.height + blur * 2
        )
        
        UIGraphicsBeginImageContextWithOptions(
            CGSize(
                width: max(shadowRect.maxX, size.width) - min(shadowRect.minX, 0),
                height: max(shadowRect.maxY, size.height) - min(shadowRect.minY, 0)
            ),
            false, 0
        )
        
        let context = UIGraphicsGetCurrentContext()!

        context.setShadow(
            offset: offset,
            blur: blur,
            color: color.cgColor
        )
        
        draw(
            in: CGRect(
                x: max(0, -shadowRect.origin.x),
                y: max(0, -shadowRect.origin.y),
                width: size.width,
                height: size.height
            )
        )
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        
        UIGraphicsEndImageContext()
        return image
    }
}
Share:
15,769
Tom Irving
Author by

Tom Irving

Mac / iOS developer. I made an Xbox LIVE client, Friendz. You can find my open source stuff on GitHub I'm available for work :)

Updated on June 06, 2022

Comments

  • Tom Irving
    Tom Irving almost 2 years

    I've taken a look at this question: UIImage Shadow Trouble

    But the accepted answer didn't work for me.

    What I'm trying to do is take a UIImage and add a shadow to it, then return a whole new UIImage, shadow and all.

    This is what I'm trying:

    - (UIImage*)imageWithShadow {
    
        CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef shadowContext = CGBitmapContextCreate(NULL, self.size.width, self.size.height + 1, CGImageGetBitsPerComponent(self.CGImage), 0, 
                                                     colourSpace, kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(colourSpace);
    
        CGContextSetShadow(shadowContext, CGSizeMake(0, -1), 1);
        CGContextDrawImage(shadowContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
    
        CGImageRef shadowedCGImage = CGBitmapContextCreateImage(shadowContext);
        CGContextRelease(shadowContext);
    
        UIImage * shadowedImage = [UIImage imageWithCGImage:shadowedCGImage];
        CGImageRelease(shadowedCGImage);
    
        return shadowedImage;
    }
    

    The result is that I get exactly the same image as before I put it through this method.

    I am doing this the correct way, or is there something obvious I'm missing?

  • Tom Irving
    Tom Irving almost 14 years
    Perfect! I was actually adding 1 to the height to draw the shadow, but the other stuff (setting the shadow, flipped coordinate system) was spot on.
  • Christian Schlensker
    Christian Schlensker over 13 years
    Where would this code be implemented and how would you call it to add a shadow to an image?
  • Laurent Etiemble
    Laurent Etiemble over 13 years
    The method assumes that the class can provide a CGImage and its dimensions. You can alter the method to take parameters (CGImage and CGSize) instead.
  • user403015
    user403015 almost 13 years
    How to convert an UIImage to CGImage ?
  • Laurent Etiemble
    Laurent Etiemble almost 13 years
    @user403015: use the CGImage property of UIImage.