web-dev-qa-db-fra.com

Comment redimensionner/optimiser facilement une taille d'image avec iOS?

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?

108
jpm

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.

240
Brad Larson

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();
64
lostInTransit

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 CGImageSourceCreateThumbnailAtIndexpour 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 .

24
Pulkit Goyal

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).

18
codeburn

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.

7
Rog

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:

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

Un autre exemple de aspect fit:

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

Aspect fit avec alignement supérieur:

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

Remplissage d'aspect:

 Aspect fill (before)  Aspect fill (after)

Remplir:

 Fill (before)  Fill (after)

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.

5
mixel

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)
3
Alexandre Lara

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;
}
2
Amit Shelgaonkar

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

1
Harry Bloom

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.

0
Magoo

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.

0
Zaid Pathan
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
}
0
Softlabsindia

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

}
0
Hope

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;
}
0
Vincent