如何缩小一个 UIImage,并使它清晰/锐利的同时,而不是模糊?

我需要缩小图像的比例,但要用锐利的方式。例如,在 Photoshop 中有图像缩小选项“ BicubicSmoother”(模糊)和“ BicubicSharper”。

这个图像缩放算法是开源的还是有文档记录的,或者 SDK 提供了这样做的方法?

66759 次浏览

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

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

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

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
}

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
}

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
}

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

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