76

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?

Proud Member
  • 40,078
  • 47
  • 146
  • 231
  • possible duplicate of [How to scale a UIImage with high quality.](http://stackoverflow.com/questions/6052188/how-to-scale-a-uiimage-with-high-quality) – DarkDust May 26 '11 at 15:59
  • Also see [Any code/library to scale down an UIImage?](http://stackoverflow.com/questions/1573758/any-code-library-to-scale-down-an-uiimage). – DarkDust May 26 '11 at 16:01
  • See this question. http://stackoverflow.com/questions/2658738/the-simplest-way-to-resize-an-uiimage The most voted answer is the simplest solution for this problem that I've found yet. – alper_k Jan 10 '13 at 15:52

9 Answers9

127

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;
}
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 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? – pixelfreak Oct 22 '11 at 22:22
  • @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 – Dan Rosenstark Oct 22 '11 at 23:40
  • 1
    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. – pixelfreak Oct 23 '11 at 00:44
  • 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 :) – Dan Rosenstark Oct 23 '11 at 01:26
  • 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. – Kyle May 21 '12 at 21:41
  • 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);` – Holtwick Jul 25 '12 at 11:43
  • @Holtwick but then the transparent pixels would lose transparency, right? – Dan Rosenstark Jul 25 '12 at 14:41
  • 1
    @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);` – Holtwick Jul 29 '12 at 11:56
  • 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 http://stackoverflow.com/questions/23316634/how-to-cache-image-and-scale-it-correctly – Lion789 Apr 27 '14 at 20:28
  • I can't get it to work correctly using the ContentMode either for AspectFit or AspectFill is giving me a stretched image – cdub Jul 16 '14 at 04:40
  • Why do you need to flip the image to resize it? – Crashalot Jan 26 '16 at 22:17
  • @Crashalot IDK but you could ask a question here on SO to find out. – Dan Rosenstark Jan 27 '16 at 02:53
  • @DanRosenstark Do I have to add these Code "CGImageRelease(imageRef);" because i think it is causing memory leak. – Mihir Gurjar May 07 '16 at 13:12
  • @DanRosenstark Sorry Sir I got my mistake no need to release. – Mihir Gurjar May 07 '16 at 13:25
  • unfortunately can only do +1 once :) – Cherpak Evgeny Jun 28 '16 at 12:19
21

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
}
bzmw
  • 5,897
  • 3
  • 22
  • 31
  • Cool! then you could do it as an extension, too. – Dan Rosenstark Jun 06 '15 at 21:26
  • 2
    Why do you need to flip the image to resize it (i.e., why do you need `CGContextConcatCTM(context, flipVertical)`? – Crashalot Jan 26 '16 at 22:18
  • 5
    kCGInterpolationHigh -> CGInterpolation.High in Swift 2 or you get a compilation error – Crashalot Jan 27 '16 at 01:41
  • 1
    @Crashalot use CGInterpolationQuality.High instead of kCGInterpolationHigh for swift 2.0 – Deepak Thakur Mar 16 '16 at 09:35
  • 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. – RowanPD Jun 13 '16 at 08:35
  • @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. – Dan Rosenstark Jun 28 '16 at 14:24
  • @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. – RowanPD Jun 30 '16 at 03:09
  • There is no GC, but you can specify that the autorelease pool drains in an inner loop. Glad you're up and running, however! – Dan Rosenstark Jul 05 '16 at 13:27
  • 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 – Lukas1 Dec 05 '16 at 13:18
  • 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 Jul 31 '20 at 08:46
  • 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 Jul 31 '20 at 08:53
20

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
}
LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
Saraz
  • 524
  • 7
  • 16
  • 1
    thanks for answer also we can use is as an extension public extension UIImage { func ... } – mert Jan 28 '16 at 11:32
  • Sister Ray's answer in following link works perfectly for me http://stackoverflow.com/questions/6052188/high-quality-scaling-of-uiimage – Deepak Thakur Mar 16 '16 at 09:50
13

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
NSExplorer
  • 11,849
  • 12
  • 49
  • 62
12

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
 }
Sazzad Hissain Khan
  • 37,929
  • 33
  • 189
  • 256
  • This is treating image as aspect fill, is there a way to make it aspect fit?. Thanks – Coder221 Oct 10 '17 at 22:51
  • 1
    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 Jul 31 '20 at 08:49
2

@YAR your solution is working properly.

There is only one thing which does not fit my requirements: The whole image is resized. I wrote a Method which did it like the photos app on iphone. This calculates the "longer side" and cuts off the "overlay" resulting in getting much better results concerning the quality of the image.

- (UIImage *)resizeImageProportionallyIntoNewSize:(CGSize)newSize;
{
    CGFloat scaleWidth = 1.0f;
    CGFloat scaleHeight = 1.0f;

    if (CGSizeEqualToSize(self.size, newSize) == NO) {

        //calculate "the longer side"
        if(self.size.width > self.size.height) {
            scaleWidth = self.size.width / self.size.height;
        } else {
            scaleHeight = self.size.height / self.size.width;
        }
    }    

    //prepare source and target image
    UIImage *sourceImage = self;
    UIImage *newImage = nil;

    // Now we create a context in newSize and draw the image out of the bounds of the context to get
    // A proportionally scaled image by cutting of the image overlay
    UIGraphicsBeginImageContext(newSize);

    //Center image point so that on each egde is a little cutoff
    CGRect thumbnailRect = CGRectZero;
    thumbnailRect.size.width  = newSize.width * scaleWidth;
    thumbnailRect.size.height = newSize.height * scaleHeight;
    thumbnailRect.origin.x = (int) (newSize.width - thumbnailRect.size.width) * 0.5;
    thumbnailRect.origin.y = (int) (newSize.height - thumbnailRect.size.height) * 0.5;

    [sourceImage drawInRect:thumbnailRect];

    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    if(newImage == nil) NSLog(@"could not scale image");

    return newImage ;
}
sanna
  • 1,165
  • 1
  • 16
  • 26
Alexander
  • 7,178
  • 8
  • 45
  • 75
2

For swift 4.2:

extension UIImage {

    func resized(By coefficient:CGFloat) -> UIImage? {

        guard coefficient >= 0 && coefficient <= 1 else {

            print("The coefficient must be a floating point number between 0 and 1")
            return nil
        }

        let newWidth = size.width * coefficient
        let newHeight = size.height * coefficient

        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))

        draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))

        let newImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return newImage
    }
}
gabriel_vincent
  • 1,230
  • 3
  • 16
  • 35
  • @ShahbazSaleem Can you clarify what the quality issues are? I see you downvoted an answer by Jovan Stankovic with the same "reason". – gabriel_vincent May 16 '20 at 12:32
0

New in iOS15, this is now built in:

// let image = (some UIImage fetching code)
await image.byPreparingThumbnail(ofSize: size)

or you can use the sync version prepareThumbnail(of:).

iSpain17
  • 2,502
  • 3
  • 17
  • 26
-1

This extension should scale the image while keeping original aspect ratio. The rest of the image is cropped. (Swift 3)

extension UIImage {    
    func thumbnail(ofSize proposedSize: CGSize) -> UIImage? {

        let scale = min(size.width/proposedSize.width, size.height/proposedSize.height)

        let newSize = CGSize(width: size.width/scale, height: size.height/scale)
        let newOrigin = CGPoint(x: (proposedSize.width - newSize.width)/2, y: (proposedSize.height - newSize.height)/2)

        let thumbRect = CGRect(origin: newOrigin, size: newSize).integral

        UIGraphicsBeginImageContextWithOptions(proposedSize, false, 0)

        draw(in: thumbRect)

        let result = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return result
    }
}
andreamazz
  • 4,256
  • 1
  • 20
  • 36
Jovan Stankovic
  • 4,661
  • 4
  • 27
  • 16