如何使用 iOS 轻松调整/优化图像大小?

我的应用程序正在从网络下载一组图像文件,并将它们保存到本地 iPhone 磁盘。其中一些图像的尺寸相当大(例如,宽度大于500像素)。由于 iPhone 甚至没有足够大的显示屏来显示原始尺寸的图像,我打算将图像调整到更小的尺寸,以节省空间/性能。

此外,其中一些图像是 JPEG 格式的,它们并没有被保存为通常的60% 的质量设置。

如何使用 iPhoneSDK 调整图片大小,如何更改 JPEG 图像的质量设置?

179048 次浏览

作为对 这个问题的回答,提供了一些建议。我建议使用 这篇文章中描述的技术和相关代码:

+ (UIImage*)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


return newImage;
}

至于图像的存储,iPhone 使用的最快的图像格式是 PNG,因为它对该格式进行了优化。然而,如果你想以 JPEG 格式存储这些图像,你可以使用你的 UIImage 并执行以下操作:

NSData *dataForJPEGFile = UIImageJPEGRepresentation(theImage, 0.6);

这将创建一个 NSData 实例,其中包含质量设置为60% 的 JPEG 图像的原始字节。然后,可以将该 NSData 实例的内容写入磁盘或缓存在内存中。

调整图像大小最简单和最直接的方法是这样的

float actualHeight = image.size.height;
float actualWidth = image.size.width;
float imgRatio = actualWidth/actualHeight;
float maxRatio = 320.0/480.0;


if(imgRatio!=maxRatio){
if(imgRatio < maxRatio){
imgRatio = 480.0 / actualHeight;
actualWidth = imgRatio * actualWidth;
actualHeight = 480.0;
}
else{
imgRatio = 320.0 / actualWidth;
actualHeight = imgRatio * actualHeight;
actualWidth = 320.0;
}
}
CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

如果您可以控制服务器,我强烈建议使用 ImageMagik调整图像服务器端的大小。在手机上下载大图片并调整大小是对许多宝贵资源的浪费——带宽、电池和内存。这些在手机上都很少见。

缩放图像而不损失高宽比(即不拉伸图像)的最佳方法是使用以下方法:

//to scale images without changing aspect ratio
+ (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)newSize {


float width = newSize.width;
float height = newSize.height;


UIGraphicsBeginImageContext(newSize);
CGRect rect = CGRectMake(0, 0, width, height);


float widthRatio = image.size.width / width;
float heightRatio = image.size.height / height;
float divisor = widthRatio > heightRatio ? widthRatio : heightRatio;


width = image.size.width / divisor;
height = image.size.height / divisor;


rect.size.width  = width;
rect.size.height = height;


//indent in case of width or height difference
float offset = (width - height) / 2;
if (offset > 0) {
rect.origin.y = offset;
}
else {
rect.origin.x = -offset;
}


[image drawInRect: rect];


UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


return smallImage;


}

将此方法添加到您的实用程序类中,以便您可以在整个项目中使用它,并像下面这样访问它:

xyzImageView.image = [Utility scaleImage:yourUIImage toSize:xyzImageView.frame.size];

这个方法在保持纵横比的同时考虑缩放问题。 它还为图像添加缩进,以防缩小后的图像的宽度大于高度(反之亦然)。

为了调整图像的大小,我用这个函数代替 DrawInRect 得到了更好的(图形)效果:

- (UIImage*) reduceImageSize:(UIImage*) pImage newwidth:(float) pWidth
{
float lScale = pWidth / pImage.size.width;
CGImageRef cgImage = pImage.CGImage;
UIImage   *lResult = [UIImage imageWithCGImage:cgImage scale:lScale
orientation:UIImageOrientationRight];
return lResult;
}

长宽比是自动处理的

上面的方法对于小图片效果很好,但是当你试图调整一个非常大的图片的大小时,你将很快耗尽内存并使应用程序崩溃。一个更好的方法是使用 CGImageSourceCreateThumbnailAtIndex来调整图像的大小,而不必首先完全解码它。

如果您有要调整图像大小的路径,可以使用以下命令:

- (void)resizeImageAtPath:(NSString *)imagePath {
// Create the image source (from path)
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) [NSURL fileURLWithPath:imagePath], NULL);


// To create image source from UIImage, use this
// NSData* pngData =  UIImagePNGRepresentation(image);
// CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);


// Create thumbnail options
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(640)
};
// Generate the thumbnail
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
// Write the thumbnail at path
CGImageWriteToFile(thumbnail, imagePath);
}

更多细节 给你

视网膜显示器上可能出现的一个问题是,图像的比例是由 ImageCapture 左右设置的。上面的调整大小函数不会改变这一点。在这些情况下,调整大小将不能正常工作。

在下面的代码中,比例被设置为1(未缩放) ,并且返回的图像具有预期的大小。这在 UIGraphicsBeginImageContextWithOptions调用中完成。

-(UIImage *)resizeImage :(UIImage *)theImage :(CGSize)theNewSize {
UIGraphicsBeginImageContextWithOptions(theNewSize, NO, 1.0);
[theImage drawInRect:CGRectMake(0, 0, theNewSize.width, theNewSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

如果有人还在寻找更好的选择

-(UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)targetSize {




UIImage *sourceImage = image;
UIImage *newImage = nil;


CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;


CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;


CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;


CGPoint thumbnailPoint = CGPointMake(0.0,0.0);


if (CGSizeEqualToSize(imageSize, targetSize) == NO) {


CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;


if (widthFactor < heightFactor)
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;


scaledWidth  = width * scaleFactor;
scaledHeight = height * scaleFactor;


// center the image




if (widthFactor < heightFactor) {
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
} else if (widthFactor > heightFactor) {
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}




// this is actually the interesting part:


UIGraphicsBeginImageContext(targetSize);


CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width  = scaledWidth;
thumbnailRect.size.height = scaledHeight;


[sourceImage drawInRect:thumbnailRect];


newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


if(newImage == nil) NSLog(@"could not scale image");




return newImage ;


}

您可以使用此代码缩放所需大小的图像。

+ (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)newSize
{
CGSize actSize = image.size;
float scale = actSize.width/actSize.height;


if (scale < 1) {
newSize.height = newSize.width/scale;
}
else {
newSize.width = newSize.height*scale;
}


UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


return newImage;
}

我最终使用布拉德技术在 UIImage+Extensions中创建了一个 scaleToFitWidth方法,如果这对任何人都有用的话..。

-(UIImage *)scaleToFitWidth:(CGFloat)width
{
CGFloat ratio = width / self.size.width;
CGFloat height = self.size.height * ratio;


NSLog(@"W:%f H:%f",width,height);


UIGraphicsBeginImageContext(CGSizeMake(width, height));
[self drawInRect:CGRectMake(0.0f,0.0f,width,height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


return newImage;
}

然后去你想去的任何地方

#import "UIImage+Extensions.h"

UIImage *newImage = [image scaleToFitWidth:100.0f];

同样值得注意的是,如果您想从 UIView 中呈现图像,您可以将其进一步下移到 UIView+Extensions类中

我在 Swift 中开发了一个图像缩放的终极解决方案。

您可以使用它来调整图像的大小,以填充,方面填充或方面适合指定的大小。

您可以将图像对齐到中心或四个边和四个角中的任何一个。

如果原始图像和目标大小的纵横比不相等,你也可以修剪额外的空间。

enum UIImageAlignment {
case Center, Left, Top, Right, Bottom, TopLeft, BottomRight, BottomLeft, TopRight
}


enum UIImageScaleMode {
case Fill,
AspectFill,
AspectFit(UIImageAlignment)
}


extension UIImage {
func scaleImage(width width: CGFloat? = nil, height: CGFloat? = nil, scaleMode: UIImageScaleMode = .AspectFit(.Center), trim: Bool = false) -> UIImage {
let preWidthScale = width.map { $0 / size.width }
let preHeightScale = height.map { $0 / size.height }
var widthScale = preWidthScale ?? preHeightScale ?? 1
var heightScale = preHeightScale ?? widthScale
switch scaleMode {
case .AspectFit(_):
let scale = min(widthScale, heightScale)
widthScale = scale
heightScale = scale
case .AspectFill:
let scale = max(widthScale, heightScale)
widthScale = scale
heightScale = scale
default:
break
}
let newWidth = size.width * widthScale
let newHeight = size.height * heightScale
let canvasWidth = trim ? newWidth : (width ?? newWidth)
let canvasHeight = trim ? newHeight : (height ?? newHeight)
UIGraphicsBeginImageContextWithOptions(CGSizeMake(canvasWidth, canvasHeight), false, 0)


var originX: CGFloat = 0
var originY: CGFloat = 0
switch scaleMode {
case .AspectFit(let alignment):
switch alignment {
case .Center:
originX = (canvasWidth - newWidth) / 2
originY = (canvasHeight - newHeight) / 2
case .Top:
originX = (canvasWidth - newWidth) / 2
case .Left:
originY = (canvasHeight - newHeight) / 2
case .Bottom:
originX = (canvasWidth - newWidth) / 2
originY = canvasHeight - newHeight
case .Right:
originX = canvasWidth - newWidth
originY = (canvasHeight - newHeight) / 2
case .TopLeft:
break
case .TopRight:
originX = canvasWidth - newWidth
case .BottomLeft:
originY = canvasHeight - newHeight
case .BottomRight:
originX = canvasWidth - newWidth
originY = canvasHeight - newHeight
}
default:
break
}
self.drawInRect(CGRectMake(originX, originY, newWidth, newHeight))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

下面有应用此解决方案的示例。

灰色矩形是将目标站点图像调整到的大小。 浅蓝色矩形中的蓝色圆圈就是图像(我使用圆圈是因为它在不保留方面的情况下缩放时很容易看到)。 浅橙色标记区域,将被修剪,如果你通过 trim: true

缩放前后的方面适合度 :

Aspect fit 1 (before) Aspect fit 1 (after)

外形尺寸合适的另一个例子:

Aspect fit 2 (before) Aspect fit 2 (after)

外形适合 顶部对齐:

Aspect fit 3 (before) Aspect fit 3 (after)

方面填充 :

Aspect fill (before) Aspect fill (after)

填充 :

Fill (before) Fill (after)

我在示例中使用了向上扩展,因为它更容易演示,但是解决方案也适用于所讨论的向下扩展。

对于 JPEG 压缩,您应该使用:

let compressionQuality: CGFloat = 0.75 // adjust to change JPEG quality
if let data = UIImageJPEGRepresentation(image, compressionQuality) {
// ...
}

You can check out my 大意 with Xcode playground.

我只是想为 Cocoa Swift 程序员回答这个问题。此函数返回具有新大小的 NSImage。你可以像这样使用这个函数。

        let sizeChangedImage = changeImageSize(image, ratio: 2)












// changes image size


func changeImageSize (image: NSImage, ratio: CGFloat) -> NSImage   {


// getting the current image size
let w = image.size.width
let h = image.size.height


// calculating new size
let w_new = w / ratio
let h_new = h / ratio


// creating size constant
let newSize = CGSizeMake(w_new ,h_new)


//creating rect
let rect  = NSMakeRect(0, 0, w_new, h_new)


// creating a image context with new size
let newImage = NSImage.init(size:newSize)






newImage.lockFocus()


// drawing image with new size in context
image.drawInRect(rect)


newImage.unlockFocus()




return newImage


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


CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);


CGContextConcatCTM(context, flipVertical);
CGContextDrawImage(context, newRect, imageRef);


CGImageRef newImageRef = CGBitmapContextCreateImage(context);
UIImage *newImage = [UIImage imageWithCGImage:newImageRef];


CGImageRelease(newImageRef);
UIGraphicsEndImageContext();


return newImage;
}

对于 Swift 3,下面的代码可以缩放保持高宽比的图像。你可以在 Apple's documentation中阅读更多关于 ImageContext 的内容:

extension UIImage {
class func resizeImage(image: UIImage, newHeight: CGFloat) -> UIImage {
let scale = newHeight / image.size.height
let newWidth = image.size.width * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}

要使用它,请调用 resizeImage()方法:

UIImage.resizeImage(image: yourImageName, newHeight: yourImageNewHeight)

这里添加了大量的答案,但是我已经找到了一个根据文件大小而不是尺寸调整大小的解决方案。

这将降低图像的尺寸和质量,直到它达到给定的尺寸。

func compressTo(toSizeInMB size: Double) -> UIImage? {
let bytes = size * 1024 * 1024
let sizeInBytes = Int(bytes)
var needCompress:Bool = true
var imgData:Data?
var compressingValue:CGFloat = 1.0


while (needCompress) {


if let resizedImage = scaleImage(byMultiplicationFactorOf: compressingValue), let data: Data = UIImageJPEGRepresentation(resizedImage, compressingValue) {


if data.count < sizeInBytes || compressingValue < 0.1 {
needCompress = false
imgData = data
} else {
compressingValue -= 0.1
}
}
}


if let data = imgData {
print("Finished with compression value of: \(compressingValue)")
return UIImage(data: data)
}
return nil
}


private func scaleImage(byMultiplicationFactorOf factor: CGFloat) -> UIImage? {
let size = CGSize(width: self.size.width*factor, height: self.size.height*factor)
UIGraphicsBeginImageContext(size)
draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
if let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
return newImage;
}
return nil
}

Credit for 按尺寸分级的答案

快速版

func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage? {


let scale = newWidth / image.size.width
let newHeight = CGFloat(200.0)
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()


return newImage
}

如果图像位于文档目录中,请添加此 网址扩展名:

extension URL {
func compressedImageURL(quality: CGFloat = 0.3) throws -> URL? {
let imageData = try Data(contentsOf: self)
debugPrint("Image file size before compression: \(imageData.count) bytes")


let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".jpg")


guard let actualImage = UIImage(data: imageData) else { return nil }
guard let compressedImageData = UIImageJPEGRepresentation(actualImage, quality) else {
return nil
}
debugPrint("Image file size after compression: \(compressedImageData.count) bytes")


do {
try compressedImageData.write(to: compressedURL)
return compressedURL
} catch {
return nil
}
}
}

用法:

guard let localImageURL = URL(string: "< LocalImagePath.jpg >") else {
return
}


//Here you will get URL of compressed image
guard let compressedImageURL = try localImageURL.compressedImageURL() else {
return
}


debugPrint("compressedImageURL: \(compressedImageURL.absoluteString)")

注意:-用您的本地 jpg 图像路径更改 < LocalImagePath.jpg > 。

根据这个课程,iOS Memory Deep Dive,我们最好使用 ImageIO来缩小图像。

使用 UIImage低成本图像的坏处。

  • Will decompress original image into memory
  • 内部坐标空间转换是昂贵的

使用 ImageIO

  • ImageIO 可以在不损耗内存的情况下读取图像大小和元数据信息。

  • ImageIO 只能以调整图像大小的代价来调整图像大小。

关于记忆中的形象

  • 内存使用与图像的尺寸有关,而与文件大小无关。
  • UIGraphicsBeginImageContextWithOptions始终使用 SRGB渲染格式,每像素使用4字节。
  • 图像有 load -> decode -> render3相。
  • UIImage对于调整大小和大小是昂贵的

对于下面的图像,如果使用 UIGraphicsBeginImageContextWithOptions 我们只需要590KB 加载一个图像,而我们需要 解码时 2048 pixels x 1536 pixels x 4 bytes per pixel = 10MB enter image description here

而在 iOS10中引入的 UIGraphicsImageRenderer将自动选择 iOS12中最佳的图形格式。这意味着,如果不需要 SRGB,用 UIGraphicsImageRenderer替换 UIGraphicsBeginImageContextWithOptions可以节省75% 的内存。

这是我关于 内存中的 iOS 图像的文章

func resize(url: NSURL?, maxPixelSize: Int) -> CGImage? {
guard let url = url else {
return nil;
}
  

let imgSource = CGImageSourceCreateWithURL(url, nil)
guard let imageSource = imgSource else {
return nil
}


var scaledImage: CGImage?
let options: [NSString: Any] = [
// The maximum width and height in pixels of a thumbnail.
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
kCGImageSourceCreateThumbnailFromImageAlways: true,
// Should include kCGImageSourceCreateThumbnailWithTransform: true in the options dictionary. Otherwise, the image result will appear rotated when an image is taken from camera in the portrait orientation.
kCGImageSourceCreateThumbnailWithTransform: true
]
scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)


return scaledImage
}






DispatchQueue.global().async {
let image: CGImage? = resize(url: NSURL.init(string: "https://i.stack.imgur.com/rPcHQ.jpg"), maxPixelSize: 600)
DispatchQueue.main.async {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0 ,width: 30, height: 30))
    

if let cgimage = image {
imageView.image = UIImage(cgImage: cgimage);
      

}
}
}


or

// Downsampling large images for display at smaller size
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions =
[kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
// Should include kCGImageSourceCreateThumbnailWithTransform: true in the options dictionary. Otherwise, the image result will appear rotated when an image is taken from camera in the portrait orientation.
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
let downsampledImage =
CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}