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

IosImageUikitCore GraphicsScaling

Ios Problem Overview


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?

Ios Solutions


Solution 1 - Ios

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 - Ios

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 3 - Ios

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 4 - Ios

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 - Ios

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
 }

Solution 6 - Ios

@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 ;
}

Solution 7 - Ios

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
    }
}

Solution 8 - Ios

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
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionProud MemberView Question on Stackoverflow
Solution 1 - IosDan RosenstarkView Answer on Stackoverflow
Solution 2 - IosbzmwView Answer on Stackoverflow
Solution 3 - IosSarazView Answer on Stackoverflow
Solution 4 - IosNSExplorerView Answer on Stackoverflow
Solution 5 - IosSazzad Hissain KhanView Answer on Stackoverflow
Solution 6 - IosAlexanderView Answer on Stackoverflow
Solution 7 - Iosgabriel_vincentView Answer on Stackoverflow
Solution 8 - IosJovan StankovicView Answer on Stackoverflow