如何调整UIImage的大小以减小上传图像的大小


76

我一直在搜索google,只遇到了降低高度/宽度或通过CoreImage如何编辑UIImage外观的库。但是我还没有看到或找到一个库,这篇文章解释了如何减小图像大小,因此在上载时,它不是完整的图像大小。

到目前为止,我有这个:

        if image != nil {
        //let data = NSData(data: UIImagePNGRepresentation(image))
        let data = UIImagePNGRepresentation(image)
        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"randomName\"\r\n")
        body.appendString("Content-Type: image/png\r\n\r\n")
        body.appendData(data)
        body.appendString("\r\n")
    }

它正在发送12MB的照片。如何将其减小到1mb?谢谢!

Answers:


188

Xcode 9•Swift 4或更高版本

编辑/更新:对于iOS10 +,我们可以使用UIGraphicsImageRenderer。对于较旧的Swift语法,请检查编辑历史记录。

extension UIImage {
    func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
        let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
        let format = imageRendererFormat
        format.opaque = isOpaque
        return UIGraphicsImageRenderer(size: canvas, format: format).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }
    func resized(toWidth width: CGFloat, isOpaque: Bool = true) -> UIImage? {
        let canvas = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
        let format = imageRendererFormat
        format.opaque = isOpaque
        return UIGraphicsImageRenderer(size: canvas, format: format).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }
}

用法:

let image = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!

let thumb1 = image.resized(withPercentage: 0.1)
let thumb2 = image.resized(toWidth: 72.0)

2
好东西,狮子座 谢谢。
MolonLabe

2
您应该在下面添加swift 2语法。有人复制/粘贴了您的代码并获得了9票赞成票。.如果您问我,这是不当的……
David Seek

6
LeoDabus,我认为这不是正确的答案,但对于其他问题来说却是非常好的解决方案。这里的问题是,如何不根据百分比或图像大小(宽度或高度)而是根据文件大小将12MB减小到1MB。由于减小图像大小(宽度和高度)不能保证文件大小的减小,@ EBDOKUM可能会产生该结果。仅尺寸会按比例缩小,但文件大小会根据所使用的图像而增大或减小。
nycdanie '16

2
@ChrisW。不谈论photoshop或Web引擎。随意发表您对显示如何在iOS(Swift)中压缩PNG的问题的答案。
Leo Dabus

3
您应该添加,guard size.width > width else { return self }以便较小的图像不会被调整为更大。
体积

70

这是我遵循的调整图像大小的方法。

 -(UIImage *)resizeImage:(UIImage *)image
{
   float actualHeight = image.size.height;
   float actualWidth = image.size.width;
   float maxHeight = 300.0;
   float maxWidth = 400.0;
   float imgRatio = actualWidth/actualHeight;
   float maxRatio = maxWidth/maxHeight;
   float compressionQuality = 0.5;//50 percent compression

   if (actualHeight > maxHeight || actualWidth > maxWidth)
   {
    if(imgRatio < maxRatio)
    {
        //adjust width according to maxHeight
        imgRatio = maxHeight / actualHeight;
        actualWidth = imgRatio * actualWidth;
        actualHeight = maxHeight;
    }
    else if(imgRatio > maxRatio)
    {
        //adjust height according to maxWidth
        imgRatio = maxWidth / actualWidth;
        actualHeight = imgRatio * actualHeight;
        actualWidth = maxWidth;
    }
    else
    {
        actualHeight = maxHeight;
        actualWidth = maxWidth;
    }
   }

   CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
   UIGraphicsBeginImageContext(rect.size);
   [image drawInRect:rect];
   UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
   NSData *imageData = UIImageJPEGRepresentation(img, compressionQuality);
   UIGraphicsEndImageContext();

   return [UIImage imageWithData:imageData];

}

使用这种方法,我的图像从6.5 MB减少到104 KB。

Swift 4代码:

func resize(_ image: UIImage) -> UIImage {
    var actualHeight = Float(image.size.height)
    var actualWidth = Float(image.size.width)
    let maxHeight: Float = 300.0
    let maxWidth: Float = 400.0
    var imgRatio: Float = actualWidth / actualHeight
    let maxRatio: Float = maxWidth / maxHeight
    let compressionQuality: Float = 0.5
    //50 percent compression
    if actualHeight > maxHeight || actualWidth > maxWidth {
        if imgRatio < maxRatio {
            //adjust width according to maxHeight
            imgRatio = maxHeight / actualHeight
            actualWidth = imgRatio * actualWidth
            actualHeight = maxHeight
        }
        else if imgRatio > maxRatio {
            //adjust height according to maxWidth
            imgRatio = maxWidth / actualWidth
            actualHeight = imgRatio * actualHeight
            actualWidth = maxWidth
        }
        else {
            actualHeight = maxHeight
            actualWidth = maxWidth
        }
    }
    let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
    UIGraphicsBeginImageContext(rect.size)
    image.draw(in: rect)
    let img = UIGraphicsGetImageFromCurrentImageContext()
    let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality)) 
    UIGraphicsEndImageContext()
    return UIImage(data: imageData!) ?? UIImage()
}

分辨率呢?我想使宽度最大为1024,但是高度将取决于图像,因此我可以保持宽高比。
杰伊

在这里,我也在压缩图像。如果要使用相同的分辨率,请在此行注释NSData * imageData = UIImageJPEGRepresentation(img,compressionQuality); 并且我们必须选择高宽比应该相同的方式来选择maxHeight和maxWidth。
user4261201 2015年

1
如何指定最小宽度和高度。这样该图像不会被压缩到特定大小以下
user2185354 '18 -10-29

SWIFT 4.2更改此行:let imageData = UIImageJPEGRepresentation(img !, CGFloat(compressionQuality))为此:let imageData = img?.jpegData(compressionQuality:CGFloat(compressionQuality))
ttorbik

30

如果有人希望使用Swift 3和Swift 4将图像调整小于1MB

只需复制并粘贴此扩展名:

extension UIImage {

func resized(withPercentage percentage: CGFloat) -> UIImage? {
    let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
    UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
    defer { UIGraphicsEndImageContext() }
    draw(in: CGRect(origin: .zero, size: canvasSize))
    return UIGraphicsGetImageFromCurrentImageContext()
}

func resizedTo1MB() -> UIImage? {
    guard let imageData = UIImagePNGRepresentation(self) else { return nil }

    var resizingImage = self
    var imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB

    while imageSizeKB > 1000 { // ! Or use 1024 if you need KB but not kB
        guard let resizedImage = resizingImage.resized(withPercentage: 0.9),
            let imageData = UIImagePNGRepresentation(resizedImage)
            else { return nil }

        resizingImage = resizedImage
        imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB
    }

    return resizingImage
}
}

使用:

let resizedImage = originalImage.resizedTo1MB()

编辑: 请注意,它阻止了UI,因此如果您认为这是适合您情况的正确方法,请转到后台线程


while循环中guardUIImagePNGRepresentation调用应使用resizedImage而不是resizingImage。导致它产生额外的循环...
Mike M

12

与Leo Answers相同,但对SWIFT 2.0的编辑很少

 extension UIImage {
    func resizeWithPercentage(percentage: CGFloat) -> UIImage? {
        let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: size.width * percentage, height: size.height * percentage)))
        imageView.contentMode = .ScaleAspectFit
        imageView.image = self
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        imageView.layer.renderInContext(context)
        guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        UIGraphicsEndImageContext()
        return result
    }

    func resizeWithWidth(width: CGFloat) -> UIImage? {
        let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))))
        imageView.contentMode = .ScaleAspectFit
        imageView.image = self
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        imageView.layer.renderInContext(context)
        guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        UIGraphicsEndImageContext()
        return result
    }
}

2
评论有什么区别会很好
David Seek'Nov

逻辑是相同的,但leo的答案是swift 3.0,而我所做的只是在swift 2.0中编写。
Jagdeep

5

这是user4261201的答案,但很快,我正在使用:

func compressImage (_ image: UIImage) -> UIImage {

    let actualHeight:CGFloat = image.size.height
    let actualWidth:CGFloat = image.size.width
    let imgRatio:CGFloat = actualWidth/actualHeight
    let maxWidth:CGFloat = 1024.0
    let resizedHeight:CGFloat = maxWidth/imgRatio
    let compressionQuality:CGFloat = 0.5

    let rect:CGRect = CGRect(x: 0, y: 0, width: maxWidth, height: resizedHeight)
    UIGraphicsBeginImageContext(rect.size)
    image.draw(in: rect)
    let img: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    let imageData:Data = UIImageJPEGRepresentation(img, compressionQuality)!
    UIGraphicsEndImageContext()

    return UIImage(data: imageData)!

}

1
将UIImage转换为数据然后再转换回UIImage是没有意义的。顺便说一句,您正在压缩图像质量。
Leo Dabus '17

1
@LeoDabus感谢您的见解,我将选择您的答案作为正确的答案!
杰伊


2

如果您要上传NSData格式的图片,请使用以下方法:

NSData *imageData = UIImageJPEGRepresentation(yourImage, floatValue);

yourImage是你的UIImagefloatvalue是压缩值(0.0到1.0)

上面是将图像转换为JPEG

对于PNG使用:UIImagePNGRepresentation

注意:上面的代码在Objective-C中。请检查如何在Swift中定义NSData。


如果我担心分辨率,我将如何设置最大宽度,但是让高度由图像本身确定?
2015年

2
UIImagePNGRepresentation没有压缩选项。
亚历克。

1

使用此控件,您可以控制所需的大小:

func jpegImage(image: UIImage, maxSize: Int, minSize: Int, times: Int) -> Data? {
    var maxQuality: CGFloat = 1.0
    var minQuality: CGFloat = 0.0
    var bestData: Data?
    for _ in 1...times {
        let thisQuality = (maxQuality + minQuality) / 2
        guard let data = image.jpegData(compressionQuality: thisQuality) else { return nil }
        let thisSize = data.count
        if thisSize > maxSize {
            maxQuality = thisQuality
        } else {
            minQuality = thisQuality
            bestData = data
            if thisSize > minSize {
                return bestData
            }
        }
    }
    return bestData
}

方法调用示例:

jpegImage(image: image, maxSize: 500000, minSize: 400000, times: 10)  

它将尝试获取最大大小和最小大小之间的文件,但仅尝试次数。如果在这段时间内失败,它将返回nil。


1

我认为swift本身提供的最简单方法就是将图像压缩成压缩数据,下面是swift 4.2中的代码

let imageData = yourImageTobeCompressed.jpegData(compressionQuality: 0.5)

您可以发送此imageData上载到服务器。


1

我认为这里的问题的核心是如何在将UIImage的数据可靠地缩小到一定大小后再上传到服务器,而不只是缩小UIImage本身。

func jpegData(compressionQuality: CGFloat) -> Data?如果您不需要压缩到特定大小,使用效果很好。但是,在某些情况下,我发现能够压缩到特定的指定文件大小以下很有用。在这种情况下,它jpegData是不可靠的,并且以这种方式迭代压缩图像会导致文件大小达到稳定水平(并且可能非常昂贵)。相反,我更喜欢像Leo的回答中那样减小UIImage本身的大小,然后转换为jpegData,并反复检查缩小后的大小是否低于我选择的值(在我设置的边距内)。我根据当前文件大小与所需文件大小的比率来调整压缩步长系数,以加快最昂贵的第一次迭代(因为此时文件大小最大)。

迅捷5

extension UIImage {
    func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
        let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
        let format = imageRendererFormat
        format.opaque = isOpaque
        return UIGraphicsImageRenderer(size: canvas, format: format).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }
    
    func compress(to kb: Int, allowedMargin: CGFloat = 0.2) -> Data {
        guard kb > 10 else { return Data() } // Prevents user from compressing below a limit (10kb in this case).
        let bytes = kb * 1024
        var compression: CGFloat = 1.0
        let step: CGFloat = 0.05
        var holderImage = self
        var complete = false
        while(!complete) {
            guard let data = holderImage.jpegData(compressionQuality: 1.0) else { break }
            let ratio = data.count / bytes
            if data.count < Int(CGFloat(bytes) * (1 + allowedMargin)) {
                complete = true
                return data
            } else {
                let multiplier:CGFloat = CGFloat((ratio / 5) + 1)
                compression -= (step * multiplier)
            }
            guard let newImage = holderImage.resized(withPercentage: compression) else { break }
            holderImage = newImage
        }
        
        return Data()
    }
}
 

和用法:

let data = image.compress(to: 1000)

不过,您返回的是空数据。我想你的意思是return data
Bawenang Rukmoko Pardian Putra

@BawenangRukmokoPardianPutra在此函数中未调用,仅满足该函数的返回类型。数据在while循环的if语句中返回。我可能应该重构答案以抛出或使用可选选项,但是为了简化使用。我还应该添加一个保护声明,以不允许压缩到不切实际的较小值。
Iron John Bonney

添加了保护语句,以防止出现compress to 0大小写并将压缩限制为合理的值(在此示例中为10kb)。
铁约翰·邦尼

是的,我不好。我现在看到了。
Bawenang Rukmoko Pardian Putra

0

这是我在swift 3中为调整UIImage大小而做的。它将图像大小减小到小于100kb。它按比例工作!

extension UIImage {
    class func scaleImageWithDivisor(img: UIImage, divisor: CGFloat) -> UIImage {
        let size = CGSize(width: img.size.width/divisor, height: img.size.height/divisor)
        UIGraphicsBeginImageContext(size)
        img.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage!
    }
}

用法:

let scaledImage = UIImage.scaleImageWithDivisor(img: capturedImage!, divisor: 3)

0

根据同福的答案。调整为特定的文件大小。像0.7 MB一样,您可以使用此代码。

extension UIImage {

func resize(withPercentage percentage: CGFloat) -> UIImage? {
    var newRect = CGRect(origin: .zero, size: CGSize(width: size.width*percentage, height: size.height*percentage))
    UIGraphicsBeginImageContextWithOptions(newRect.size, true, 1)
    self.draw(in: newRect)
    defer {UIGraphicsEndImageContext()}
    return UIGraphicsGetImageFromCurrentImageContext()
}

func resizeTo(MB: Double) -> UIImage? {
    guard let fileSize = self.pngData()?.count else {return nil}
    let fileSizeInMB = CGFloat(fileSize)/(1024.0*1024.0)//form bytes to MB
    let percentage = 1/fileSizeInMB
    return resize(withPercentage: percentage)
}
}

0

与Objective-C相同:

界面:

@interface UIImage (Resize)

- (UIImage *)resizedWithPercentage:(CGFloat)percentage;
- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality;

@end

实施:

#import "UIImage+Resize.h"

@implementation UIImage (Resize)

- (UIImage *)resizedWithPercentage:(CGFloat)percentage {
    CGSize canvasSize = CGSizeMake(self.size.width * percentage, self.size.height * percentage);
    UIGraphicsBeginImageContextWithOptions(canvasSize, false, self.scale);
    [self drawInRect:CGRectMake(0, 0, canvasSize.width, canvasSize.height)];
    UIImage *sizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return sizedImage;
}

- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality {
    NSData *imageData = isPng ? UIImagePNGRepresentation(self) : UIImageJPEGRepresentation(self, compressionQuality);
    if (imageData && [imageData length] > 0) {
        UIImage *resizingImage = self;
        double imageSizeKB = [imageData length] / weight;

        while (imageSizeKB > weight) {
            UIImage *resizedImage = [resizingImage resizedWithPercentage:0.9];
            imageData = isPng ? UIImagePNGRepresentation(resizedImage) : UIImageJPEGRepresentation(resizedImage, compressionQuality);
            resizingImage = resizedImage;
            imageSizeKB = (double)(imageData.length / weight);
        }

        return resizingImage;
    }
    return nil;
}

用法:

#import "UIImage+Resize.h"

UIImage *resizedImage = [self.picture resizeTo:2048 isPng:NO jpegCompressionQuality:1.0];

0

当我尝试使用可接受的答案来调整要在我的项目中使用的图像的大小时,它会变得非常像素化和模糊。我最终得到了这段代码来调整图像的大小,而没有增加像素或模糊:

func scale(withPercentage percentage: CGFloat)-> UIImage? {
        let cgSize = CGSize(width: size.width * percentage, height: size.height * percentage)

        let hasAlpha = true
        let scale: CGFloat = 0.0 // Use scale factor of main screen

        UIGraphicsBeginImageContextWithOptions(cgSize, !hasAlpha, scale)
        self.draw(in: CGRect(origin: CGPoint.zero, size: cgSize))

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        return scaledImage
    }

0
extension UIImage {
func resized(toValue value: CGFloat) -> UIImage {
    if size.width > size.height {
        return self.resize(toWidth: value)!
    } else {
        return self.resize(toHeight: value)!
    }
}

0

我对这里的解决方案不满意,因为大多数解决方案都基于KB大小来生成图像.jpegData(compressionQuality: x)。此方法不适用于大图像,因为即使将压缩质量设置为0.0,大图像也将保持较大,例如,新iPhone的肖像模式产生的10 MB压缩属性设置为0.0时仍将大于1 MB。

因此,我使用了@Leo Dabus的答案并将其重写为Helper Struct,该结构可在背景que中转换图像:

import UIKit

struct ImageResizer {
    static func resize(image: UIImage, maxByte: Int, completion: @escaping (UIImage?) -> ()) {
        DispatchQueue.global(qos: .userInitiated).async {
            guard let currentImageSize = image.jpegData(compressionQuality: 1.0)?.count else { return completion(nil) }
            
            var imageSize = currentImageSize
            var percentage: CGFloat = 1.0
            var generatedImage: UIImage? = image
            let percantageDecrease: CGFloat = imageSize < 10000000 ? 0.1 : 0.3
            
            while imageSize > maxByte && percentage > 0.01 {
                let canvas = CGSize(width: image.size.width * percentage,
                                    height: image.size.height * percentage)
                let format = image.imageRendererFormat
                format.opaque = true
                generatedImage = UIGraphicsImageRenderer(size: canvas, format: format).image {
                    _ in image.draw(in: CGRect(origin: .zero, size: canvas))
                }
                guard let generatedImageSize = generatedImage?.jpegData(compressionQuality: 1.0)?.count else { return completion(nil) }
                imageSize = generatedImageSize
                percentage -= percantageDecrease
            }
            
            completion(generatedImage)
        }
    }
}

用法:

        ImageResizer.resize(image: image, maxByte: 800000) { img in
            guard let resizedImage = img else { return }
            // Use resizedImage
        }
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.