Mon application télécharge un ensemble de fichiers image du réseau et les enregistre sur le disque local de l'iPhone. Certaines de ces images sont assez grandes (largeurs supérieures à 500 pixels, par exemple). Étant donné que l'iPhone n'a même pas un écran suffisamment grand pour afficher l'image dans sa taille d'origine, je prévois de la redimensionner à une taille un peu plus petite pour gagner de l'espace/performances.
De plus, certaines de ces images sont au format JPEG et ne sont pas enregistrées avec le réglage de qualité habituel à 60%.
Comment redimensionner une image avec le SDK de l'iPhone et comment modifier le réglage de qualité d'une image JPEG?
Quelques suggestions sont proposées pour répondre à cette question . J'avais suggéré la technique décrite dans ce post , avec le code correspondant:
+ (UIImage*)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
En ce qui concerne le stockage de l'image, le format d'image le plus rapide à utiliser avec l'iPhone est le PNG, car il contient des optimisations pour ce format. Toutefois, si vous souhaitez stocker ces images au format JPEG, vous pouvez utiliser votre UIImage et procéder comme suit:
NSData *dataForJPEGFile = UIImageJPEGRepresentation(theImage, 0.6);
Cela crée une instance NSData contenant les octets bruts d'une image JPEG avec un paramètre de qualité de 60%. Le contenu de cette instance NSData peut ensuite être écrit sur le disque ou mis en cache dans la mémoire.
Le moyen le plus simple et le plus simple de redimensionner vos images serait le suivant.
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();
Les méthodes ci-dessus fonctionnent bien pour les petites images, mais lorsque vous essayez de redimensionner une très grande image, vous allez rapidement manquer de mémoire et bloquer l'application. Un meilleur moyen consiste à utiliser CGImageSourceCreateThumbnailAtIndex
pour redimensionner l'image sans la décoder complètement.
Si vous avez le chemin d'accès à l'image que vous souhaitez redimensionner, vous pouvez utiliser ceci:
- (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);
}
Plus de détails ici .
Le meilleur moyen de redimensionner les images sans perdre le rapport de format (c'est-à-dire sans allonger l'image) consiste à utiliser cette méthode:
//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;
}
Ajoutez cette méthode à votre classe d’utilitaire pour pouvoir l’utiliser tout au long de votre projet et accédez-y de la manière suivante:
xyzImageView.image = [Utility scaleImage:yourUIImage toSize:xyzImageView.frame.size];
Cette méthode prend en charge la mise à l'échelle tout en conservant les proportions . Elle ajoute également des retraits à l'image au cas où l'image réduite aurait plus de largeur que de hauteur (ou inversement).
Si vous avez le contrôle sur le serveur, je vous recommande fortement de redimensionner le côté serveur d'images avec ImageMagik . Télécharger de grandes images et les redimensionner au téléphone gaspille de nombreuses ressources précieuses - bande passante, batterie et mémoire. Qui sont rares sur les téléphones.
J'ai développé une solution ultime pour la mise à l'échelle des images dans Swift.
Vous pouvez l'utiliser pour redimensionner une image afin de remplir, d'aplatir ou d'ajuster la taille spécifiée.
Vous pouvez aligner l’image sur le centre ou l’un des quatre bords et des quatre coins.
Vous pouvez également réduire l'espace supplémentaire ajouté si les formats d'image de l'image d'origine et la taille cible ne sont pas égaux.
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
}
}
Il existe des exemples d'application de cette solution ci-dessous.
Le rectangle gris est l’image du site cible qui sera redimensionnée en . Les cercles bleus en rectangle bleu clair sont les images (j’ai utilisé des cercles car il est facile de voir quand il est mis à l’échelle sans préserver l’aspect) . La couleur orange pâle marque les zones être coupé si vous passez trim: true
.
Aspect fit avant et après la mise à l'échelle:
Un autre exemple de aspect fit:
Aspect fit avec alignement supérieur:
Remplissage d'aspect:
Remplir:
J'ai utilisé la conversion ascendante dans mes exemples car il est plus simple à démontrer, mais la solution fonctionne également pour la réduction d'échelle en question.
Pour la compression JPEG, vous devriez utiliser ceci:
let compressionQuality: CGFloat = 0.75 // adjust to change JPEG quality
if let data = UIImageJPEGRepresentation(image, compressionQuality) {
// ...
}
Vous pouvez consulter mon Gist avec Xcode playground.
Pour Swift 3, le code ci-dessous met à l'échelle l'image en conservant le rapport de format. Vous pouvez en savoir plus sur ImageContext dans Documentation 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!
}
}
Pour l'utiliser, appelez la méthode resizeImage()
:
UIImage.resizeImage(image: yourImageName, newHeight: yourImageNewHeight)
vous pouvez utiliser ce code pour redimensionner l'image à la taille requise.
+ (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;
}
Ajoutant au tas de réponses ici, mais j’ai opté pour une solution qui redimensionne par la taille du fichier plutôt que par les dimensions.
Cela réduira à la fois les dimensions et la qualité de l'image jusqu'à ce qu'elle atteigne votre taille donnée.
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
}
Crédit pour réponse en fonction de la taille
J'ai fini par utiliser la technique de Brad pour créer une méthode scaleToFitWidth
dans UIImage+Extensions
si cela peut être utile à quiconque ...
-(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;
}
alors où tu veux
#import "UIImage+Extensions.h"
UIImage *newImage = [image scaleToFitWidth:100.0f];
A noter également que vous pouvez déplacer ceci plus bas dans une classe UIView+Extensions
si vous voulez rendre des images à partir d’un UIView.
Si votre image est dans le répertoire du document, ajoutez cetteURLextension:
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
}
}
}
Usage:
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)")
Remarque: - Modifiez <LocalImagePath.jpg> avec votre chemin d’image jpg local.
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
}
Je voulais juste répondre à cette question pour les programmeurs de Cocoa Swift. Cette fonction renvoie NSImage avec une nouvelle taille. Vous pouvez utiliser cette fonction comme ceci.
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
}
Un problème qui peut survenir sur les écrans de rétine est que l’échelle de l’image est définie par ImageCapture ou autre. Les fonctions de redimensionnement ci-dessus ne changeront pas cela. Dans ces cas, le redimensionnement ne fonctionnera pas correctement.
Dans le code ci-dessous, l'échelle est définie sur 1 (non redimensionnée) et l'image renvoyée a la taille souhaitée. Ceci est fait dans l'appel 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;
}