Question

I'm using Dropbox Core API and I'm getting stuck while I'm looking for a way to get images files dimensions. I retrieve thumbnails on the device, but I need to know the width and the height of images to process modifications on them.

And I definitely don't want to download the entire file on the phone to check its dimensions. Is there any tricks you think about to get them. The only thing I have in metadata is the file size, which is quite useless in my case.

Thanks a lot.

Was it helpful?

Solution

I figured out my answer. I use an UIImage Category than download part of the file through an URL. Once it get enough data to define the size it stops the download.

I did some tests and it downloads approximately 30 kB to get the picture's dimensions, whatever if the file 300 kB or 10 MB big, which is really fast.

It could be used for any image file, not only Dropbox API.

Here is the header of the Category :

#import <UIKit/UIKit.h>

typedef void (^UIImageSizeRequestCompleted) (NSURL* imgURL, CGSize size);

@interface UIImage (RemoteSize)

+ (void) requestSizeFor: (NSURL*) imgURL completion: (UIImageSizeRequestCompleted) completion;

@end

And here are the source file :

#import "UIImage+RemoteSize.h"
#import <objc/runtime.h>`

#import <objc/runtime.h>

static char *kSizeRequestDataKey = "NSURL.sizeRequestData";
static char *kSizeRequestTypeKey = "NSURL.sizeRequestType";
static char *kSizeRequestCompletionKey = "NSURL.sizeRequestCompletion";

typedef uint32_t dword;

@interface NSURL (RemoteSize)

@property (nonatomic, strong) NSMutableData* sizeRequestData;
@property (nonatomic, strong) NSString* sizeRequestType;
@property (nonatomic, copy) UIImageSizeRequestCompleted sizeRequestCompletion;

@end

@implementation NSURL (RemoteSize)

- (void) setSizeRequestCompletion: (UIImageSizeRequestCompleted) block {
objc_setAssociatedObject(self, &kSizeRequestCompletionKey, block, OBJC_ASSOCIATION_COPY);
}

- (UIImageSizeRequestCompleted) sizeRequestCompletion {
return objc_getAssociatedObject(self, &kSizeRequestCompletionKey);
}

- (void) setSizeRequestData:(NSMutableData *)sizeRequestData {
objc_setAssociatedObject(self, &kSizeRequestDataKey, sizeRequestData, OBJC_ASSOCIATION_RETAIN);
}

- (NSMutableData*) sizeRequestData {
return objc_getAssociatedObject(self, &kSizeRequestDataKey);
}

- (void) setSizeRequestType:(NSString *)sizeRequestType {
objc_setAssociatedObject(self, &kSizeRequestTypeKey, sizeRequestType, OBJC_ASSOCIATION_RETAIN);
}

- (NSString*) sizeRequestType {
return objc_getAssociatedObject(self, &kSizeRequestTypeKey);
}

#pragma mark - NSURLConnectionDelegate

- (void) connection: (NSURLConnection*) connection didReceiveResponse:(NSURLResponse *)response {
[self.sizeRequestData setLength: 0];    //Redirected => reset data
}

- (void) connection: (NSURLConnection*) connection didReceiveData:(NSData *)data {
NSMutableData* receivedData = self.sizeRequestData;

if( !receivedData ) {
    receivedData = [NSMutableData data];
    self.sizeRequestData = receivedData;
}

[receivedData appendData: data];

//Parse metadata
const unsigned char* cString = [receivedData bytes];
const NSInteger length = [receivedData length];
const char pngSignature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
const char bmpSignature[2] = {66, 77};
const char gifSignature[2] = {71, 73};
const char jpgSignature[2] = {255, 216};

if(!self.sizeRequestType ) {
    if( memcmp(pngSignature, cString, 8) == 0 ) {
        self.sizeRequestType = @"PNG";
    }
    else if( memcmp(bmpSignature, cString, 2) == 0 ) {
        self.sizeRequestType = @"BMP";
    }
    else if( memcmp(jpgSignature, cString, 2) == 0 ) {
        self.sizeRequestType = @"JPG";
    }
    else if( memcmp(gifSignature, cString, 2) == 0 ) {
        self.sizeRequestType = @"GIF";
    }
}

if( [self.sizeRequestType isEqualToString: @"PNG"] ) {
    char type[5];
    int offset = 8;

    dword chunkSize = 0;
    int chunkSizeSize = sizeof(chunkSize);

    if( offset+chunkSizeSize > length )
        return;

    memcpy(&chunkSize, cString+offset, chunkSizeSize);
    chunkSize = OSSwapInt32(chunkSize);
    offset += chunkSizeSize;

    if( offset + chunkSize > length )
        return;

    memcpy(&type, cString+offset, 4); type[4]='\0';
    offset += 4;

    if( strcmp(type, "IHDR") == 0 ) {   //Should always be first
        dword width = 0, height = 0;
        memcpy(&width, cString+offset, 4);
        offset += 4;
        width = OSSwapInt32(width);

        memcpy(&height, cString+offset, 4);
        offset += 4;
        height = OSSwapInt32(height);

        if( self.sizeRequestCompletion ) {
            self.sizeRequestCompletion(self, CGSizeMake(width, height));
        }

        self.sizeRequestCompletion = nil;

        [connection cancel];
    }
}
else if( [self.sizeRequestType isEqualToString: @"BMP"] ) {
    int offset = 18;
    dword width = 0, height = 0;
    memcpy(&width, cString+offset, 4);
    offset += 4;

    memcpy(&height, cString+offset, 4);
    offset += 4;

    if( self.sizeRequestCompletion ) {
        self.sizeRequestCompletion(self, CGSizeMake(width, height));
    }

    self.sizeRequestCompletion = nil;

    [connection cancel];
}
else if( [self.sizeRequestType isEqualToString: @"JPG"] ) {
    int offset = 4;
    dword block_length = cString[offset]*256 + cString[offset+1];

    while (offset<length) {
        offset += block_length;

        if( offset >= length )
            break;
        if( cString[offset] != 0xFF )
            break;
        if( cString[offset+1] == 0xC0 ||
           cString[offset+1] == 0xC1 ||
           cString[offset+1] == 0xC2 ||
           cString[offset+1] == 0xC3 ||
           cString[offset+1] == 0xC5 ||
           cString[offset+1] == 0xC6 ||
           cString[offset+1] == 0xC7 ||
           cString[offset+1] == 0xC9 ||
           cString[offset+1] == 0xCA ||
           cString[offset+1] == 0xCB ||
           cString[offset+1] == 0xCD ||
           cString[offset+1] == 0xCE ||
           cString[offset+1] == 0xCF ) {

            dword width = 0, height = 0;

            height = cString[offset+5]*256 + cString[offset+6];
            width = cString[offset+7]*256 + cString[offset+8];

            if( self.sizeRequestCompletion ) {
                self.sizeRequestCompletion(self, CGSizeMake(width, height));
            }

            self.sizeRequestCompletion = nil;

            [connection cancel];

        }
        else {
            offset += 2;
            block_length = cString[offset]*256 + cString[offset+1];
        }

    }
}
else if( [self.sizeRequestType isEqualToString: @"GIF"] ) {
    int offset = 6;
    dword width = 0, height = 0;
    memcpy(&width, cString+offset, 2);
    offset += 2;

    memcpy(&height, cString+offset, 2);
    offset += 2;

    if( self.sizeRequestCompletion ) {
        self.sizeRequestCompletion(self, CGSizeMake(width, height));
    }

    self.sizeRequestCompletion = nil;

    [connection cancel];
}
}

- (void) connection: (NSURLConnection*) connection didFailWithError:(NSError *)error {
if( self.sizeRequestCompletion )
    self.sizeRequestCompletion(self, CGSizeZero);
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return cachedResponse;
}

- (void) connectionDidFinishLoading: (NSURLConnection *)connection {
// Basically, we failed to obtain the image size using metadata and the
// entire image was downloaded...

if(!self.sizeRequestData.length) {
    self.sizeRequestData = nil;
}
else {
    //Try parse to UIImage
    UIImage* image = [UIImage imageWithData: self.sizeRequestData];

    if( self.sizeRequestCompletion && image) {
        self.sizeRequestCompletion(self, [image size]);
        return;
    }
}

self.sizeRequestCompletion(self, CGSizeZero);
}

@end

@implementation UIImage (RemoteSize)

+ (void) requestSizeFor: (NSURL*) imgURL completion: (UIImageSizeRequestCompleted) completion {

if( [imgURL isFileURL] ) {
    //Load from file stream
}
else {
    imgURL.sizeRequestCompletion = completion;

    NSURLRequest* request = [NSURLRequest requestWithURL: imgURL];
    NSURLConnection* conn = [NSURLConnection connectionWithRequest: request delegate: imgURL];
    [conn scheduleInRunLoop: [NSRunLoop mainRunLoop] forMode: NSDefaultRunLoopMode];
    [conn start];
}
}

@end

Thanks a lot to this post which help me a lot : Remote image size without downloading

I hope it will help you too.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top