26

I would like to compress images (camera/photo library) and then send it to the server. I know I can compress by height and width, but I would like to compress the images by size to a fixed size (200 KB) only and keep the original height and width. The scale factor in JPEGRepresentation does not represent the size and only compression quality. How can I achieve this (compress to a fixed size) without using any third party library? Thanks for any help.

DonDyck
  • 1,451
  • 5
  • 20
  • 35
  • There is no API for it in the SDK. As you found out, you can either resize by resolution or by quality. We had the same problem and eventually we decided to resize to the screen display and compress by 85% which reduces the file-size dramatically but yet provides a very good image quality. – Gilad Novik Feb 29 '12 at 22:20

7 Answers7

63

Heres some example code that will attempt to compress an image for you so that it doesn't exceed either a max compression or maximum file size

CGFloat compression = 0.9f;
CGFloat maxCompression = 0.1f;
int maxFileSize = 250*1024;

NSData *imageData = UIImageJPEGRepresentation(yourImage, compression);

while ([imageData length] > maxFileSize && compression > maxCompression)
{
    compression -= 0.1;
    imageData = UIImageJPEGRepresentation(yourImage, compression);
}
kgutteridge
  • 8,727
  • 1
  • 18
  • 23
  • 2
    The above written code works great:). But I want my image to compress upto 20 kb, I tried to play with your code. But only able to get the size around 200 kb, can you guide me a way how can I achieve 20 kb. – Deepak Khiwani Aug 12 '13 at 10:17
  • @DeepK What above solution does is compresses the original Image. The max compression might not get you the expecting solution. So create the image from the compressed data and then again compress the image until you get what you want. just an Idea. – parveen Feb 17 '15 at 14:01
  • @Codetrix Thank you for suggesting the solution. – Deepak Khiwani Feb 18 '15 at 04:34
  • @kgutteidge is this code working by converting in to swift....i got an error on while condition...thats why i wanted to ask you – Bhavin Bhadani May 16 '15 at 10:37
3

One way to do it, is to re-compress the file in a loop, until you find the desired size. You could first find height and width, and guess the compression factor (larger image more compression) then after you compress it, check the size, and split the difference again.

I know this is not super efficient, but I do not believe there is a single call to achieve a image of a specific size.

nycynik
  • 7,371
  • 8
  • 62
  • 87
1

Here, JPEGRepresentation is quite memory consuming and if we use in Loop so it is extremely high memory consuming. So use below code & ImageSize won't be more then 200KB.

UIImage* newImage = [self captureView:yourUIView];


- (UIImage*)captureView:(UIView *)view {  
CGRect rect = view.bounds;
UIGraphicsBeginImageContext(rect.size);  
CGContextRef context = UIGraphicsGetCurrentContext();  

[view.layer renderInContext:context];  
UIImage* img = [UIImage alloc]init];
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();  
NSLog(@"img=%@",img);
return img;
}
krunal
  • 452
  • 3
  • 11
1

I took the answer of @kgutteridge and made a similar solution for Swift 3.0 using recursive:

extension UIImage {
    static func compress(image: UIImage, maxFileSize: Int, compression: CGFloat = 1.0, maxCompression: CGFloat = 0.4) -> Data? {

        if let data = UIImageJPEGRepresentation(image, compression) {

            let bcf = ByteCountFormatter()
            bcf.allowedUnits = [.useMB] // optional: restricts the units to MB only
            bcf.countStyle = .file
            let string = bcf.string(fromByteCount: Int64(data.count))
            print("Data size is: \(string)")

            if data.count > (maxFileSize * 1024 * 1024) && (compression > maxCompression) {
                let newCompression = compression - 0.1
                let compressedData = self.compress(image: image, maxFileSize: maxFileSize, compression: newCompression, maxCompression: maxCompression)
                return compressedData
            }

            return data
        }

        return nil
    }
}
Mindhavok
  • 457
  • 4
  • 19
1

Swift 4:

extension UIImage {
    func compressTo(bytes: Int) -> UIImage {
        var compression: CGFloat = 0.9
        let maxCompression: CGFloat = 0.1
        let maxSize: Int = bytes * 1024

        var imageData = jpegData(compressionQuality: compression)!
        while imageData.count > maxSize && compression > maxCompression {
            compression -= 0.1
            imageData = jpegData(compressionQuality: compression)!
        }

        return UIImage(data: imageData)!
    }
}
Winter
  • 1,004
  • 8
  • 13
0

After doing a few tests, I was able to find A relationship between image size and compression value. Since this relationship is linear for all values where the compression is less than 1, I created an algorithm to try to always compress images to a certain value.

//Use 0.99 because at 1, the relationship between the compression and the file size is not linear
NSData *image = UIImageJPEGRepresentation(currentImage, 0.99);
float maxFileSize = MAX_IMAGE_SIZE * 1024;

//If the image is bigger than the max file size, try to bring it down to the max file size
if ([image length] > maxFileSize) {
    image = UIImageJPEGRepresentation(currentImage, maxFileSize/[image length]);
}
-1
- (UIImage *)resizeImageToSize:(CGSize)targetSize
{
    UIImage *sourceImage = captureImage;
    UIImage *newImage = nil;

    CGSize imageSize = sourceImage.size;
    CGFloat width = imageSize.width;
    CGFloat height = imageSize.height;

    CGFloat targetWidth = targetSize.width;
    CGFloat targetHeight = targetSize.height;

    CGFloat scaleFactor = 0.0;
    CGFloat scaledWidth = targetWidth;
    CGFloat scaledHeight = targetHeight;

    CGPoint thumbnailPoint = CGPointMake(0.0,0.0);

    if (CGSizeEqualToSize(imageSize, targetSize) == NO) {

        CGFloat widthFactor = targetWidth / width;
        CGFloat heightFactor = targetHeight / height;

        if (widthFactor < heightFactor)
            scaleFactor = widthFactor;
        else
            scaleFactor = heightFactor;

        scaledWidth  = width * scaleFactor;
        scaledHeight = height * scaleFactor;

        // make image center aligned
        if (widthFactor < heightFactor)
        {
            thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
        }
        else if (widthFactor > heightFactor)
        {
            thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
        }
    }

    UIGraphicsBeginImageContext(targetSize);
    CGRect thumbnailRect = CGRectZero;
    thumbnailRect.origin = thumbnailPoint;
    thumbnailRect.size.width  = scaledWidth;
    thumbnailRect.size.height = scaledHeight;

    [sourceImage drawInRect:thumbnailRect];
    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    if(newImage == nil)
        NSLog(@"could not scale image");

    return newImage ;
}
Maple
  • 741
  • 13
  • 28
Superdev
  • 562
  • 7
  • 13