如何缩小UIImage并使其保持清晰而不模糊?

77

我需要以锐利的方式缩小一张图片。例如在Photoshop中,有图像大小缩减选项"Bicubic Smoother"(模糊)和"Bicubic Sharper"(更加清晰)。

这个图像缩放算法是否有开源或文档记录?SDK是否提供实现此功能的方法?


还可以参考有没有缩小UIImage的代码/库? - DarkDust
请查看这个问题。https://dev59.com/63E85IYBdhLWcg3wr1ke 最受欢迎的答案是我找到的最简单的解决方案。 - alper_k
9个回答

127

仅仅使用 imageWithCGImage 是不够的。它会进行缩放,但无论是缩小还是放大,结果都会模糊且不够优化。

如果您想要获得正确的反锯齿效果并消除“锯齿”,您需要像这个链接中的代码一样做:http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/

我的测试代码与Trevor的解决方案类似,只需对其进行微小的调整即可用于我的透明PNG文件:

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

1
在Trevor的代码中,他正在检查imageOrientation(我猜是EXIF数据),并根据其值进行额外的转换。不确定是否有必要。我会试着调整一下。 - pixelfreak
1
@Yar 没错。如果您已经知道将在表格单元中使用哪种背景,则可以先绘制背景:[[UIColor whiteColor] set]; UIRectFill(newRect); - Holtwick
我无法正确使用ContentMode使其工作,无论是AspectFit还是AspectFill都会给我一个拉伸的图像。 - cdub
@Crashalot 我不知道,但你可以在 Stack Overflow 上提问来找到答案。 - Dan Rosenstark
@DanRosenstark 对不起,我犯了错误,不需要发布。 - Mihir Gurjar
显示剩余10条评论

21

对于使用Swift的用户,以下是接受的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
}

酷!那你也可以将其作为扩展来完成。 - Dan Rosenstark
2
为什么需要翻转图像来调整大小(即为什么需要CGContextConcatCTM(context, flipVertical))? - Crashalot
5
kCGInterpolationHigh 在 Swift 2 中应该写作 CGInterpolation.High,否则会导致编译错误。 - Crashalot
1
@Crashalot 在 Swift 2.0 中使用 CGInterpolationQuality.High 替代 kCGInterpolationHigh。 - Deepak Thakur
每次调用CGContextDrawImage时,我会失去40MB。有人遇到过这样的问题吗?对我来说这是无法使用的,因为我正在尝试缩小大量图像,并立即耗尽内存。 - RowanPD
显示剩余6条评论

21

如果有人在寻找Swift版本,这是@Dan Rosenstark的被采纳答案的Swift版本:

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
}

1
感谢您的回答,我们也可以将其用作扩展。public extension UIImage {func ...} - mert
Sister Ray在以下链接中的回答对我非常有效 https://dev59.com/gFfUa4cB1Zd3GeqPHFcS - Deepak Thakur

13

如果在缩放图像时保留原始纵横比,不论你缩小多少都会得到清晰的图像。

您可以使用以下方法进行缩放:

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

似乎对视图应用了转换,但没有调整底层数据的大小。 - Cbas

12

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

这是将图像视为纵横比填充,有没有办法使其适应纵横比?谢谢。 - Coder221
1
这个不起作用。当我打印newRect.size时,它给出了正确的缩小尺寸。然而,如果我打印newImage.size,我会看到image的原始尺寸。有什么想法吗?弄清楚了。UIGraphicsBeginImageContextWithOptions(newSize, false, 0)防止任何缩放。比例参数是0。如果你使用UIGraphicsBeginImageContext(newRect.size),一切都好。 - Schnodderbalken

2

@YAR 您的解决方案正常工作。

只有一件事不符合我的要求:整个图像都被调整大小。我编写了一个方法,它像 iPhone 上的 照片应用程序 一样执行此操作。这将计算“较长的一侧”并剪切“覆盖”,从而获得更好的图像质量。

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

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

@ShahbazSaleem,您能澄清一下质量问题是什么吗?我看到您以相同的“原因”对Jovan Stankovic的回答进行了投票。 - gabriel_vincent

0

iOS 15中的新功能,现已内置:

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

或者你可以使用同步版本prepareThumbnail(of:)

-1

这个扩展应该在保持原始宽高比的同时缩放图像。其余部分将被裁剪。(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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接