私のアプリケーションは、ネットワークから一連の画像ファイルをダウンロードし、ローカルのiPhoneディスクに保存しています。これらの画像の一部は、サイズがかなり大きい(たとえば、幅が500ピクセルより大きい)。 iPhoneには元のサイズで画像を表示するのに十分な大きさのディスプレイさえないので、スペース/パフォーマンスを節約するために画像を少し小さくすることを計画しています。
また、これらの画像の一部はJPEGであり、通常の60%品質設定として保存されません。
IPhone SDKを使用して画像のサイズを変更するにはどうすればよいですか?また、JPEG画像の品質設定を変更するにはどうすればよいですか?
この質問 に対する回答として、いくつかの提案が提供されています。 この投稿 で説明されている手法と関連するコードを提案しました。
+ (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);
これにより、JPEGイメージの生のバイトを含むNSDataインスタンスが60%の品質設定で作成されます。 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();
上記の方法は小さな画像には適していますが、非常に大きな画像のサイズを変更しようとすると、すぐにメモリ不足になり、アプリがクラッシュします。より良い方法は、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);
}
詳細 こちら 。
アスペクト比を失うことなく(つまり、画像を引き伸ばさずに)画像を拡大縮小する最良の方法は、次の方法を使用することです:
//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;
}
このメソッドをUtilityクラスに追加して、プロジェクト全体で使用できるようにし、次のようにアクセスします。
xyzImageView.image = [Utility scaleImage:yourUIImage toSize:xyzImageView.frame.size];
このメソッドは、アスペクト比を維持しながらスケーリングを処理します。また、縮小された画像の幅が高さよりも大きい(またはその逆)場合に、画像にインデントを追加します。
サーバーを制御できる場合は、 ImageMagik を使用して画像サーバー側のサイズを変更することを強くお勧めします。大きな画像をダウンロードして電話でサイズ変更することは、帯域幅、バッテリー、メモリなど多くの貴重なリソースの無駄です。これらはすべて携帯電話では不足しています。
Swiftでの画像スケーリングのための究極のソリューションを開発しました。
これを使用して、指定したサイズに合わせて画像のサイズを変更したり、縦横塗りや縦横比を調整したりできます。
画像を中央または4つのエッジと4つの角のいずれかに揃えることができます。
また、元の画像とターゲットサイズのアスペクト比が等しくない場合に追加される余分なスペースをトリミングすることもできます。
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
を渡すとトリミングされる領域を示します。
スケーリングの前後のアスペクトフィット:
アスペクトフィットの別の例:
上揃えでアスペクトフィット:
アスペクトフィル:
Fill:
例ではアップスケーリングを使用しましたが、これはデモンストレーションが簡単であるためですが、ソリューションは問題のようにダウンスケーリングにも機能します。
JPEG圧縮の場合、これを使用する必要があります。
let compressionQuality: CGFloat = 0.75 // adjust to change JPEG quality
if let data = UIImageJPEGRepresentation(image, compressionQuality) {
// ...
}
Xcodeプレイグラウンドで Gist を確認できます。
Swift 3の場合、以下のコードはアスペクト比を維持しながら画像をスケーリングします。 ImageContextの詳細については、 Appleのドキュメント をご覧ください。
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)
このコードを使用して、必要なサイズに画像を拡大縮小できます。
+ (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;
}
ここで多数の回答を追加しますが、サイズではなくファイルサイズでサイズを変更するソリューションを探しました。
これにより、指定したサイズに達するまで画像の寸法と品質が低下します。
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
}
サイズ回答によるスケーリング のクレジット
最終的にはBradsテクニックを使用して、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
クラスにさらに下げることができます。
画像がドキュメントディレクトリにある場合は、このURL拡張子を追加します。
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)")
注:-<LocalImagePath.jpg>をローカルのjpgイメージパスに変更します。
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
}
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
}
Retinaディスプレイで発生する可能性のある問題は、画像のスケールが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;
}