Question Comment réduire un UIImage dans IOS par la taille des données


Je cherche à réduire un UIImage dans iOS.

J'ai vu d'autres questions ci-dessous et leur approche sur la réduction de la taille de l'image. Redimensionnement des images Objective-C
Comment redimensionner l'image par programmation dans objective-c dans iphone
Le moyen le plus simple de redimensionner un UIImage?

Ces questions sont toutes basées sur le redimensionnement de l'image à une taille spécifique. Dans mon cas, je cherche à redimensionner / réduire l'image en fonction d'une taille maximale.

A titre d'exemple, je voudrais fixer un maximum NSData taille à 500 Ko. Je sais que je peux obtenir la taille de l'image comme ceci: // Vérifiez la taille de l'image renvoyée

NSData *imageData = UIImageJPEGRepresentation(image, 0.5);

// Log out the image size
NSLog(@"%lu KB",(imageData.length/1024));

Ce que je voudrais faire, c'est une forme de boucle ici. Si la taille est supérieure à la taille maximale définie, j'aimerais réduire légèrement l'image, puis vérifier sa taille. Si la taille est encore trop réduite, vérifiez à nouveau jusqu'à ce qu'elle soit inférieure à la taille maximale définie.

Je ne suis pas sûr de la meilleure approche pour cela. Idéalement, je ne veux pas réduire l'image à une taille spécifique tout le temps, mais seulement réduire légèrement l'image à chaque fois. De cette façon, je peux avoir la plus grande taille (taille w / h) de l'image elle-même et à sa taille maximale (octets). Si je diminuais légèrement à la fois, quelle serait la meilleure façon d'y parvenir?

MODIFIER Pour confirmer, je cherche à redimensionner l'image réelle, mais redimensionne l'image de sorte qu'elle soit plus petite que le maximum NSData Longueur. Par exemple:
-Vérifier la NSData Longueur
-Si au dessus du maximum je veux passer l'UIImage dans une méthode
-Puis en boucle cette méthode redimensionnant légèrement la taille de l'image réelle à chaque fois
Jusqu'à ce qu'il soit sous le maximum NSData longueur, puis renvoyer l'image?


17
2017-12-05 15:22


origine


Réponses:


En ce moment, vous avez une routine qui dit:

// Check if the image size is too large
if ((imageData.length/1024) >= 1024) {

    while ((imageData.length/1024) >= 1024) {
        NSLog(@"While start - The imagedata size is currently: %f KB",roundf((imageData.length/1024)));

        // While the imageData is too large scale down the image

        // Get the current image size
        CGSize currentSize = CGSizeMake(image.size.width, image.size.height);

        // Resize the image
        image = [image resizedImage:CGSizeMake(roundf(((currentSize.width/100)*80)), roundf(((currentSize.height/100)*80))) interpolationQuality:kMESImageQuality];

        // Pass the NSData out again
        imageData = UIImageJPEGRepresentation(image, kMESImageQuality);

    }
}

Je ne conseillerais pas de redimensionner récursivement l'image. Chaque fois que vous redimensionnez, vous perdez de la qualité (se traduisant souvent par un "adoucissement" de l'image avec une perte de détails, avec des effets cumulatifs). Vous voulez toujours revenir à l’image d’origine et redimensionner les images de plus en plus petites. (Mineure à part, que if déclaration est également redondante.)

Je pourrais suggérer ce qui suit:

NSData  *imageData    = UIImageJPEGRepresentation(image, kMESImageQuality);
double   factor       = 1.0;
double   adjustment   = 1.0 / sqrt(2.0);  // or use 0.8 or whatever you want
CGSize   size         = image.size;
CGSize   currentSize  = size;
UIImage *currentImage = image;

while (imageData.length >= (1024 * 1024))
{
    factor      *= adjustment;
    currentSize  = CGSizeMake(roundf(size.width * factor), roundf(size.height * factor));
    currentImage = [image resizedImage:currentSize interpolationQuality:kMESImageQuality];
    imageData    = UIImageJPEGRepresentation(currentImage, kMESImageQuality);
}

Note, je ne touche pas image, l'image d'origine, mais plutôt l'assignation currentImage en effectuant un redimensionnement à partir de l'image d'origine à chaque fois, par une échelle décroissante à chaque fois.

BTW, si vous vous posez des questions sur mon cryptique 1.0 / sqrt(2.0), J'essayais de tracer un compromis entre votre facteur itératif de 80% et mon désir de privilégier le redimensionnement par une puissance de 2 où je peux (car une réduction conserve plus de netteté lorsqu'elle est obtenue par une puissance de 2). Mais utilisez n'importe quoi adjustment facteur que vous voulez.

Enfin, si vous faites cela sur des images énormes, vous pourriez penser à utiliser @autoreleasepool des blocs. Vous voulez profiler votre application dans Allocations in Instruments et voir où se situe votre high water, car en l'absence de pools d'autorelease, cela peut constituer une utilisation assez agressive de la mémoire.


10
2017-12-11 19:31



Outre la taille maximale, vous devez également choisir une taille minimale et décider des performances. Par exemple, vous pouvez vérifier la taille de UIImageJPEGRepresentation(image, 1.0). Si trop grand, est-ce que vous vérifiez ensuite à 0.95 ou 0.1?

Une approche possible consiste à obtenir la taille de UIImageJPEGRepresentation(image, 1.0) et voir par quel pourcentage il est trop gros. Par exemple, disons que c'est 600 Ko. Vous devriez alors calculer 500.0 / 600 qui est grosso modo 0.83. Alors fais UIImageJPEGRepresentation(image, 0.83). Cela ne donnera pas exactement 500 Ko mais cela pourrait être assez proche.

Une autre approche serait de commencer avec UIImageJPEGRepresentation(image, 1.0). C'est trop gros alors fais UIImageJPEGRepresentation(image, 0.5) Si trop grand alors allez avec 0.25 mais si trop petit aller avec 0.75. Continuez à diviser la différence jusqu'à ce que vous soyez dans une plage acceptable de la taille souhaitée.


7
2017-12-05 15:36



C'était mon approche:

// Check if the image size is too large
if ((imageData.length/1024) >= 1024) {

    while ((imageData.length/1024) >= 1024) {
        NSLog(@"While start - The imagedata size is currently: %f KB",roundf((imageData.length/1024)));

        // While the imageData is too large scale down the image

        // Get the current image size
        CGSize currentSize = CGSizeMake(image.size.width, image.size.height);

        // Resize the image
        image = [image resizedImage:CGSizeMake(roundf(((currentSize.width/100)*80)), roundf(((currentSize.height/100)*80))) interpolationQuality:kMESImageQuality];

        // Pass the NSData out again
        imageData = UIImageJPEGRepresentation(image, kMESImageQuality);

    }
}

La méthode de redimensionnement de l'image est la suivante:

// Returns a rescaled copy of the image, taking into account its orientation
// The image will be scaled disproportionately if necessary to fit the bounds specified by the parameter
- (UIImage *)resizedImage:(CGSize)newSize interpolationQuality:(CGInterpolationQuality)quality {
    BOOL drawTransposed;

    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            drawTransposed = YES;
            break;

        default:
            drawTransposed = NO;
    }

    return [self resizedImage:newSize
                    transform:[self transformForOrientation:newSize]
               drawTransposed:drawTransposed
         interpolationQuality:quality];
}

Suivi par:

// Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size
// The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation
// If the new size is not integral, it will be rounded up
- (UIImage *)resizedImage:(CGSize)newSize
                transform:(CGAffineTransform)transform
           drawTransposed:(BOOL)transpose
     interpolationQuality:(CGInterpolationQuality)quality {
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
    CGRect transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width);
    CGImageRef imageRef = self.CGImage;

    // Build a context that's the same dimensions as the new size
    CGContextRef bitmap = CGBitmapContextCreate(NULL,
                                                newRect.size.width,
                                                newRect.size.height,
                                                CGImageGetBitsPerComponent(imageRef),
                                                0,
                                                CGImageGetColorSpace(imageRef),
                                                CGImageGetBitmapInfo(imageRef));

    // Rotate and/or flip the image if required by its orientation
    CGContextConcatCTM(bitmap, transform);

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(bitmap, quality);

    // Draw into the context; this scales the image
    CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef);

    // Get the resized image from the context and a UIImage
    CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef];

    // Clean up
    CGContextRelease(bitmap);
    CGImageRelease(newImageRef);

    return newImage;
}

// Méthodes supplémentaires pour référence

// Returns an affine transform that takes into account the image orientation when drawing a scaled image
- (CGAffineTransform)transformForOrientation:(CGSize)newSize {
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (self.imageOrientation) {
        case UIImageOrientationDown:           // EXIF = 3
        case UIImageOrientationDownMirrored:   // EXIF = 4
            transform = CGAffineTransformTranslate(transform, newSize.width, newSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:           // EXIF = 6
        case UIImageOrientationLeftMirrored:   // EXIF = 5
            transform = CGAffineTransformTranslate(transform, newSize.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:          // EXIF = 8
        case UIImageOrientationRightMirrored:  // EXIF = 7
            transform = CGAffineTransformTranslate(transform, 0, newSize.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        default:
            break;
    }

    switch (self.imageOrientation) {
        case UIImageOrientationUpMirrored:     // EXIF = 2
        case UIImageOrientationDownMirrored:   // EXIF = 4
            transform = CGAffineTransformTranslate(transform, newSize.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:   // EXIF = 5
        case UIImageOrientationRightMirrored:  // EXIF = 7
            transform = CGAffineTransformTranslate(transform, newSize.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        default:
            break;
    }

    return transform;
}

2
2017-12-09 23:38



-(NSData*)testData
{
    UIImage *imageToUpload=[UIImage imageNamed:@"images/lifestyle2.jpg"];
    NSData *imgData=UIImageJPEGRepresentation(imageToUpload,1.0);
    float compressionRate=10;
    while (imgData.length>1024)
    {
        if (compressionRate>0.5)
        {
            compressionRate=compressionRate-0.5;
            imgData=UIImageJPEGRepresentation(imageToUpload,compressionRate/10);
        }
        else
        {
            return imgData;
        }
    }
    return imgData;
}

Il maintient une qualité d'image inférieure à 1 Mo.

Appelez le avec

NSData *compressedImageData=[self testData];
NSLog(@"%lu KB",compressedImageData.length);

2
2017-09-23 06:48



Dans votre question révisée, vous avez précisé que votre objectif était de préciser la taille des fichiers lors du téléchargement des images. Dans ce cas, jouer avec les options de compression JPEG est parfait, comme le suggère rmaddy.

La question intéressante est que vous avez deux variables à jouer, la compression JPEG et les dimensions de l’image (il y en a d’autres également, mais je vais rester simple). Comment voulez-vous prioriser les uns sur les autres? Par exemple, je ne pense pas qu'il soit judicieux de conserver une image compressée absurdement en pleine résolution (par exemple, facteur de qualité 0,1). Cela n'a pas non plus de sens de garder une résolution infime, une image non compressée. Personnellement, j'ajusterais itérativement la qualité comme suggéré par rmaddy, mais fixez un plancher raisonnable (par exemple, une qualité JPEG au moins égale à 0,70, par exemple). À ce stade, je pourrais envisager de modifier les dimensions de l’image (et cela modifie assez rapidement la taille du fichier) et de modifier les dimensions NSData était une taille appropriée.

Quoi qu'il en soit, dans ma réponse initiale, je me suis concentré sur la consommation de mémoire dans l'application (par opposition à la taille du fichier). Pour la postérité, voyez la réponse ci-dessous:


Si vous essayez de contrôler la quantité de mémoire utilisée lorsque vous chargez les images dans UIImage objets à utiliser dans UIKit objets, puis jouer avec la compression JPEG ne vous aidera pas beaucoup, car la représentation interne des images une fois que vous les chargez dans UIKit les objets sont décompressés. Ainsi, dans ce scénario, les options de compression JPEG n'accomplissent pas grand chose (hormis le fait de sacrifier la qualité de l'image).

Pour illustrer cette idée, j'ai une image 1920 x 1080. Je l'ai au format PNG (le fichier est 629kb), un format JPEG compressé (217kb) et un format JPEG compressé au minimum (1.1mb). Mais lorsque je charge ces trois images différentes dans UIImageView objets (même s'ils ont un très petit frame), L’outil "Allocations" d’Instrument me montre qu’ils prennent chacun 7.91mb:

allocations

En effet, lorsque vous chargez l'image dans une vue d'image, la représentation interne non compressée de ces trois images est de quatre octets par pixel (un octet pour le rouge, un pour le vert, un pour l'alpha). Ainsi, mes images 1920 x 1080 prennent 1920 x 1080 x 4 = 8 249 400 = 7,91mb.

Donc, si vous ne voulez pas qu'ils prennent plus de 500 Ko de mémoire lors de leur chargement dans des objets d'affichage d'images, cela signifie que vous souhaitez les redimensionner de telle sorte que le produit de la largeur multipliée par la hauteur soit 128 000 ou moins si carré, moins de 358 x 358 pixels).

Toutefois, si votre préoccupation concerne la bande passante du réseau lorsque vous téléchargez des images ou une capacité de stockage persistante, continuez à jouer avec les valeurs de compression JPEG suggérées par l'excellente réponse de rmaddy. Mais si vous essayez de résoudre les problèmes de consommation de mémoire lorsque les images sont chargées dans des objets UIKit, ne vous concentrez pas sur la compression, mais concentrez-vous sur le redimensionnement de l'image.


1
2017-12-05 16:45