How to scale down a UIImage and make it crispy / sharp at the same time instead of blurry?

66,079

Solution 1

Merely using imageWithCGImage is not sufficient. It will scale, but the result will be blurry and suboptimal whether scaling up or down.

If you want to get the aliasing right and get rid of the "jaggies" you need something like this: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/.

My working test code looks something like this, which is Trevor's solution with one small adjustment to work with my transparent PNGs:

- (UIImage *)resizeImage:(UIImage*)image newSize:(CGSize)newSize {
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
    CGImageRef imageRef = image.CGImage;

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);

    CGContextConcatCTM(context, flipVertical);  
    // Draw into the context; this scales the image
    CGContextDrawImage(context, newRect, imageRef);

    // Get the resized image from the context and a UIImage
    CGImageRef newImageRef = CGBitmapContextCreateImage(context);
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef];

    CGImageRelease(newImageRef);
    UIGraphicsEndImageContext();    

    return newImage;
}

Solution 2

If someone is looking for Swift version, here is the Swift version of @Dan Rosenstark's accepted answer:

func resizeImage(image: UIImage, newHeight: CGFloat) -> UIImage {
    let scale = newHeight / image.size.height
    let newWidth = image.size.width * scale
    UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
    image.drawInRect(CGRectMake(0, 0, newWidth, newHeight))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage
}

Solution 3

For those using Swift here is the accepted answer in Swift:

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {
    let newRect = CGRectIntegral(CGRectMake(0,0, newSize.width, newSize.height))
    let imageRef = image.CGImage

    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh)
    let flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height)

    CGContextConcatCTM(context, flipVertical)
    // Draw into the context; this scales the image
    CGContextDrawImage(context, newRect, imageRef)

    let newImageRef = CGBitmapContextCreateImage(context) as CGImage
    let newImage = UIImage(CGImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
}

Solution 4

If you retain the original aspect ratio of the image while scaling, you'll always end up with a sharp image no matter how much you scale down.

You can use the following method for scaling:

+ (UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation

Solution 5

For Swift 3

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {

    let newRect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height).integral
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    context!.interpolationQuality = CGInterpolationQuality.default
    let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: newSize.height)

    context!.concatenate(flipVertical)
    // Draw into the context; this scales the image
    context?.draw(image.cgImage!, in: CGRect(x: 0.0,y: 0.0, width: newRect.width, height: newRect.height))

    let newImageRef = context!.makeImage()! as CGImage
    let newImage = UIImage(cgImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
 }
Share:
66,079

Related videos on Youtube

Proud Member
Author by

Proud Member

Eu gosto de queijo, pizza, e programação.

Updated on March 25, 2020

Comments

  • Proud Member
    Proud Member about 4 years

    I need to scale down an image, but in a sharp way. In Photoshop for example there are the image size reduction options "Bicubic Smoother" (blurry) and "Bicubic Sharper".

    Is this image downscaling algorithm open sourced or documented somewhere or does the SDK offer methods to do this?

  • Deamon
    Deamon over 12 years
    Thanks! Was looking for this. This works for PNG, but bypass Trevor's transformForOrientation. Is that function really necessary? Is it only for JPG with orientation metadata?
  • Dan Rosenstark
    Dan Rosenstark over 12 years
    @pixelfreak are you saying the code I've presented is missing the piece necessary to work with jpegs that have their orientation set in their metadata? Feel free to edit my code if it doesn't complicate it too much. I just ripped out what I didn't need
  • Deamon
    Deamon over 12 years
    in Trevor's code, he's checking imageOrientation (EXIF data I assume?) and doing extra transformation depending on its value. Not sure if it's necessary. I'll play around with it.
  • Dan Rosenstark
    Dan Rosenstark over 12 years
    Thanks @pixelfreak, that would be great. As you can see, I was just trying to get the code to be simple for one use case. Then I do a UIImageView that caches different scaled versions (in a static array) to mitigate the slowness of this resize routine, and interrupts the setFrame method. I'm not sure that's of general interest :)
  • Kyle
    Kyle almost 12 years
    The image I get is rotated 90 degrees. I've tried doing rotations on the new image, however all I get is a blank white image.
  • Holtwick
    Holtwick almost 12 years
    I suggest to set opacity, because if you then use those scaled down images as thumbs they will result in a more responsive UI. here is the little change: UIGraphicsBeginImageContextWithOptions(newSize, YES, 0);
  • Dan Rosenstark
    Dan Rosenstark almost 12 years
    @Holtwick but then the transparent pixels would lose transparency, right?
  • Holtwick
    Holtwick almost 12 years
    @Yar That's right. If you already know which background you'll have e.g. in your table cell then you may draw the background first: [[UIColor whiteColor] set]; UIRectFill(newRect);
  • Lion789
    Lion789 about 10 years
    I am trying this but it is still blurry do I need to edit the code in it or just passing in the size and image should be all I need to do... this is my code stackoverflow.com/questions/23316634/…
  • cdub
    cdub almost 10 years
    I can't get it to work correctly using the ContentMode either for AspectFit or AspectFill is giving me a stretched image
  • Dan Rosenstark
    Dan Rosenstark almost 9 years
    Cool! then you could do it as an extension, too.
  • Crashalot
    Crashalot over 8 years
    Why do you need to flip the image to resize it?
  • Crashalot
    Crashalot over 8 years
    Why do you need to flip the image to resize it (i.e., why do you need CGContextConcatCTM(context, flipVertical)?
  • Crashalot
    Crashalot over 8 years
    kCGInterpolationHigh -> CGInterpolation.High in Swift 2 or you get a compilation error
  • Dan Rosenstark
    Dan Rosenstark over 8 years
    @Crashalot IDK but you could ask a question here on SO to find out.
  • mert
    mert over 8 years
    thanks for answer also we can use is as an extension public extension UIImage { func ... }
  • Deepak Thakur
    Deepak Thakur about 8 years
    @Crashalot use CGInterpolationQuality.High instead of kCGInterpolationHigh for swift 2.0
  • Deepak Thakur
    Deepak Thakur about 8 years
    Sister Ray's answer in following link works perfectly for me stackoverflow.com/questions/6052188/…
  • Mihir Gurjar
    Mihir Gurjar about 8 years
    @DanRosenstark Do I have to add these Code "CGImageRelease(imageRef);" because i think it is causing memory leak.
  • Mihir Gurjar
    Mihir Gurjar about 8 years
    @DanRosenstark Sorry Sir I got my mistake no need to release.
  • RowanPD
    RowanPD almost 8 years
    When I use this I lose 40MB every time CGContextDrawImage is called. Has anyone had an issue like this? This is unusable for me because I'm trying to downscale lots of images and run out of memory straight away.
  • Cherpak Evgeny
    Cherpak Evgeny almost 8 years
    unfortunately can only do +1 once :)
  • Dan Rosenstark
    Dan Rosenstark almost 8 years
    @RowanPD if you were to make a test project and post it on, say, github, then we could verify your finding. That would be cool and help you decide if the leak is here or elsewhere.
  • RowanPD
    RowanPD almost 8 years
    @DanRosenstark Thanks for the offer. I did find my issue. It wasn't a memory leak as such but more like the garbage collector couldn't keep up with the number of images I was processing all at once. I used a lazy loader to give it the time. However, in the end I discovered that the Photo Library class could scale down my images for me, didn't chew up the memory each time and it did it a lot faster.
  • Dan Rosenstark
    Dan Rosenstark almost 8 years
    There is no GC, but you can specify that the autorelease pool drains in an inner loop. Glad you're up and running, however!
  • Cbas
    Cbas almost 8 years
    seems to apply a transformation on the view but not resize the underlying data
  • Lukas1
    Lukas1 over 7 years
    how does this code even compile? Some of the variables created here are optional, yet they're later expected to be unwrapped in later calls. The code does not even compile
  • Coder221
    Coder221 over 6 years
    This is treating image as aspect fill, is there a way to make it aspect fit?. Thanks
  • gabriel_vincent
    gabriel_vincent about 4 years
    @ShahbazSaleem Can you clarify what the quality issues are? I see you downvoted an answer by Jovan Stankovic with the same "reason".
  • Schnodderbalken
    Schnodderbalken almost 4 years
    This does not work. When I print newRect.size, it gives me the correct, downsized dimensions. However, if I print newImage.size, there I see the original dimensions of image. I can't believe none of the upvoters or commenters has even checked if the algorithm does what it's supposed to do.
  • Schnodderbalken
    Schnodderbalken almost 4 years
    This does not work. When I print newRect.size, it gives me the correct, downsized dimensions. However, if I print newImage.size, there I see the original dimensions of image. Any ideas why that is? Figured it out. UIGraphicsBeginImageContextWithOptions(newSize, false, 0) prevents any scaling. The scale parameter is 0. If you use UIGraphicsBeginImageContext(newRect.size) instead, everything's fine.
  • Schnodderbalken
    Schnodderbalken almost 4 years
    Figured it out. UIGraphicsBeginImageContextWithOptions(newSize, false, 0) prevents any scaling. The scale parameter is 0. If you use UIGraphicsBeginImageContext(newRect.size) instead, everything's fine.