web-dev-qa-db-ja.com

IOSのUIImageをデータサイズでダウンスケールする方法

UIImageiOSをダウンスケールしようとしています。

以下で他の質問と、画像をサイズごとに縮小する方法に関する彼らのアプローチを見てきました。 画像のサイズ変更Objective-C
iphoneのobjective-cでプログラム的に画像のサイズを変更する方法
IImageのサイズを変更する最も簡単な方法は?

これらの質問はすべて、画像を特定のサイズにサイズ変更することに基づいています。私の場合、最大サイズに基づいて画像のサイズを変更/縮小しようとしています。

例として、NSDataの最大サイズを500 KBに設定したいとします。私はこのような画像のサイズを取得できることを知っています://返された画像のサイズを確認してください

NSData *imageData = UIImageJPEGRepresentation(image, 0.5);

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

私がやりたいのは、ここで何らかの形のループです。サイズが設定した最大サイズよりも大きい場合は、画像を少し縮小してからサイズを確認したいと思います。サイズがまだ大きすぎる場合は、もう一度少し縮小してから、最大設定サイズを下回るまでもう一度確認します。

これに対する最善のアプローチが何かはわかりません。理想的には、画像を常に特定のサイズに縮小するのではなく、毎回わずかに画像を縮小するだけです。そうすれば、画像自体の最大サイズ(サイズw/h)と最大サイズ(バイト)を使用できます。一度に少しだけ縮小する場合、これを達成するための最良の方法は何ですか?

[〜#〜] edit [〜#〜]確認のために、実際の画像のサイズを変更しようとしていますが、画像のサイズを変更して、最大NSData長さよりも小さいです。例えば:
-NSDataの長さを確認してください
-UIImageをメソッドに渡したい最大値を超える場合
-次に、このメソッドをループして、毎回実際の画像サイズのサイズを少し変更します
-NSDataの最大長を下回るまで、画像を返しますか?

18
StuartM

今、あなたは言うルーチンを持っています:

_// 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);

    }
}
_

画像のサイズを再帰的に変更することはお勧めしません。サイズを変更するたびに、ある程度の品質が失われます(多くの場合、画像の「柔らかさ」として現れ、細部が失われ、累積的な効果があります)。あなたはいつも元の画像に戻って、そのサイズをどんどん小さくしたいと思っています。 (マイナーなことはさておき、そのifステートメントも冗長です。)

私は次のことを提案するかもしれません:

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

元の画像であるimageに触れているのではなく、毎回元の画像からサイズを変更し、スケールを小さくしてcurrentImageを割り当てていることに注意してください。

ところで、私の不可解な1.0 / sqrt(2.0)について疑問に思っているのなら、私はあなたの反復的な80%の係数と、可能な限り2の累乗でサイズ変更を優先したいという私の願望との間で妥協点を見つけようとしていました(削減が維持されるため) 2)の累乗で行うとよりシャープになります。ただし、adjustment係数は何でも使用できます。

最後に、巨大な画像でこれを行う場合は、_@autoreleasepool_ブロックの使用を検討するかもしれません。自動解放プールがない場合のように、InstrumentsのAllocationsでアプリのプロファイルを作成し、最高水準点がどこにあるかを確認することをお勧めします。これは、かなり積極的なメモリの使用を構成する可能性があります。

10
Rob

最大サイズに加えて、最小サイズを選択し、パフォーマンスを決定する必要もあります。たとえば、UIImageJPEGRepresentation(image, 1.0)のサイズを確認できます。大きすぎる場合は、_0.95_または_0.1_で確認しますか?

考えられるアプローチの1つは、UIImageJPEGRepresentation(image, 1.0)のサイズを取得し、それが大きすぎるパーセントを確認することです。たとえば、600kBだとします。次に、おおよそ_500.0 / 600_である_0.83_を計算する必要があります。したがって、UIImageJPEGRepresentation(image, 0.83)を実行します。それは正確に500kBを与えることはありませんが、それは十分に近いかもしれません。

別のアプローチは、UIImageJPEGRepresentation(image, 1.0)から始めることです。大きすぎる場合はUIImageJPEGRepresentation(image, 0.5)を実行します。大きすぎる場合は_0.25_を使用し、小さすぎる場合は_0.75_を使用します。希望のサイズの許容範囲内に収まるまで、差を分割し続けます。

7
rmaddy

これが私のアプローチでした:

// 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);

    }
}

画像のサイズ変更方法は次のとおりです。

// 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];
}

に続く:

// 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;
}

//参照用の追加メソッド

// 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;
}
3
StuartM
-(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;
}

1MB以下の画質を維持します。

と呼んで、

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

改訂された質問では、画像をアップロードする際にファイルサイズの制限内に収めることが目標であることが明確にされました。その場合、rmaddyが提案しているように、JPEG圧縮オプションをいじってみるのは問題ありません。

興味深い質問は、JPEG圧縮と画像サイズの2つの変数があることです(他にもありますが、単純にしておきます)。どのようにして優先順位を付けたいですか?たとえば、フル解像度の不条理に圧縮された画像(たとえば、0.1の品質係数)を維持することは意味がないと思います。また、小さな解像度の非圧縮画像を保持することも意味がありません。個人的には、rmaddyが提案するように品質を繰り返し調整しますが、ある程度の妥当な下限を設定します(たとえば、JPEG品質は0.70以上)。その時点で、画像のサイズを変更し(ファイルのサイズも非常にすばやく変更します)、結果のNSDataが適切なサイズになるまでサイズを変更することを検討するかもしれません。

とにかく、私の元の答えでは、(ファイルサイズではなく)アプリ内のメモリ消費に焦点を当てました。後世のために、以下の回答を参照してください。


UIImageオブジェクトで使用するUIKitオブジェクトに画像をロードするときに使用されるメモリの量を制御しようとしている場合は、JPEG圧縮を試してみてもあまり役に立ちません。画像をUIKitオブジェクトにロードすると、画像の内部表現は圧縮されません。したがって、そのシナリオでは、JPEG圧縮オプションは(画質を犠牲にする以外は)あまり効果がありません。

アイデアを説明するために、私は1920 x 1080の画像を持っています。PNG形式(ファイルは629kb)、圧縮JPEG形式(217kb)、最小圧縮JPEG形式(1.1mb)です。しかし、これら3つの異なる画像をUIImageViewオブジェクトにロードすると(非常に小さいframeがある場合でも)、Instrumentの「割り当て」ツールはそれぞれが7.91MBを使用していることを示しています。

allocations

これは、画像を画像ビューにロードすると、これら3つの画像の内部の非圧縮表現がピクセルあたり4バイト(赤のバイト、緑のバイト、青のバイト、アルファのバイト)になるためです。したがって、1920 x 1080の画像は1920 x 1080 x 4 = 8,249,400 = 7.91 MBになります。

したがって、イメージビューオブジェクトにロードするときに500kbを超えるメモリを必要としない場合は、幅と高さの積が128,000以下になるようにサイズを変更する必要があります(つまり、正方形の場合、358 x 358ピクセル未満)。

ただし、画像をアップロードするときのネットワーク帯域幅や永続的なストレージ容量の1つが懸念事項である場合は、rmaddyの優れた回答が示すように、JPEG圧縮値を試してみてください。ただし、画像がUIKitオブジェクトに読み込まれている間にメモリ消費の問題に対処しようとしている場合は、圧縮に重点を置くのではなく、画像のサイズ変更に重点を置きます。

1
Rob