How to scale down a UIImage and make it crispy / sharp at the same time instead of blurry?
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
}
Related videos on Youtube
Comments
-
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?
-
DarkDust almost 13 yearspossible duplicate of How to scale a UIImage with high quality.
-
DarkDust almost 13 years
-
alper_k over 11 yearsSee this question. stackoverflow.com/questions/2658738/… The most voted answer is the simplest solution for this problem that I've found yet.
-
-
Deamon over 12 yearsThanks! 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 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 over 12 yearsin 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 over 12 yearsThanks @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 almost 12 yearsThe 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 almost 12 yearsI 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 almost 12 years@Holtwick but then the transparent pixels would lose transparency, right?
-
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 about 10 yearsI 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 almost 10 yearsI can't get it to work correctly using the ContentMode either for AspectFit or AspectFill is giving me a stretched image
-
Dan Rosenstark almost 9 yearsCool! then you could do it as an extension, too.
-
Crashalot over 8 yearsWhy do you need to flip the image to resize it?
-
Crashalot over 8 yearsWhy do you need to flip the image to resize it (i.e., why do you need
CGContextConcatCTM(context, flipVertical)
? -
Crashalot over 8 yearskCGInterpolationHigh -> CGInterpolation.High in Swift 2 or you get a compilation error
-
Dan Rosenstark over 8 years@Crashalot IDK but you could ask a question here on SO to find out.
-
mert over 8 yearsthanks for answer also we can use is as an extension public extension UIImage { func ... }
-
Deepak Thakur about 8 years@Crashalot use CGInterpolationQuality.High instead of kCGInterpolationHigh for swift 2.0
-
Deepak Thakur about 8 yearsSister Ray's answer in following link works perfectly for me stackoverflow.com/questions/6052188/…
-
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 about 8 years@DanRosenstark Sorry Sir I got my mistake no need to release.
-
RowanPD almost 8 yearsWhen 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 almost 8 yearsunfortunately can only do +1 once :)
-
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 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 almost 8 yearsThere is no GC, but you can specify that the autorelease pool drains in an inner loop. Glad you're up and running, however!
-
Cbas almost 8 yearsseems to apply a transformation on the view but not resize the underlying data
-
Lukas1 over 7 yearshow 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 over 6 yearsThis is treating image as aspect fill, is there a way to make it aspect fit?. Thanks
-
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 almost 4 yearsThis does not work. When I print
newRect.size
, it gives me the correct, downsized dimensions. However, if I printnewImage.size
, there I see the original dimensions ofimage
. I can't believe none of the upvoters or commenters has even checked if the algorithm does what it's supposed to do. -
Schnodderbalken almost 4 yearsThis does not work. When I print
newRect.size
, it gives me the correct, downsized dimensions. However, if I printnewImage.size
, there I see the original dimensions ofimage
. Any ideas why that is? Figured it out.UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
prevents any scaling. The scale parameter is0
. If you useUIGraphicsBeginImageContext(newRect.size)
instead, everything's fine. -
Schnodderbalken almost 4 yearsFigured it out.
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
prevents any scaling. The scale parameter is0
. If you useUIGraphicsBeginImageContext(newRect.size)
instead, everything's fine.