21

As you know the iphone guidelines discourage loading uiimages that are greater than 1024x1024.

The size of the images that i would have to load varies, and i would like to check the size of the image i am about to load; however using the .size property of uiimage requires the image to be laoded... which is exactly what i am trying to avoid.

Is there something wrong in my reasoning or is there a solution to that?

thank you all

koda
  • 741
  • 1
  • 7
  • 18
  • 1
    This is a good question. Android provides a means to do this, but I don't know of an iOS solution offhand. EDIT: This has been asked before. http://stackoverflow.com/questions/1551300/get-size-of-image-without-loading-in-to-memory – Justin Nov 12 '10 at 22:37
  • i searched before, but i couldn't find it! many thanks! – koda Nov 13 '10 at 02:05

4 Answers4

39

As of iOS 4.0, the iOS SDK includes the CGImageSource... functions (in the ImageIO framework). It's a very flexible API to query metadata without loading the image into memory. Getting the pixel dimensions of an image should work like this (make sure to include the ImageIO.framework in your target):

#import <ImageIO/ImageIO.h>

NSURL *imageFileURL = [NSURL fileURLWithPath:...];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL);
if (imageSource == NULL) {
    // Error loading image
    ...
    return;
}

CGFloat width = 0.0f, height = 0.0f;
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);

CFRelease(imageSource);

if (imageProperties != NULL) {

    CFNumberRef widthNum  = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
    if (widthNum != NULL) {
        CFNumberGetValue(widthNum, kCFNumberCGFloatType, &width);
    }

    CFNumberRef heightNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
    if (heightNum != NULL) {
        CFNumberGetValue(heightNum, kCFNumberCGFloatType, &height);
    }

    // Check orientation and flip size if required
    CFNumberRef orientationNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation);
    if (orientationNum != NULL) {
        int orientation;
        CFNumberGetValue(orientationNum, kCFNumberIntType, &orientation);
        if (orientation > 4) {
            CGFloat temp = width;
            width = height;
            height = temp;
        }
    }

    CFRelease(imageProperties);
}

NSLog(@"Image dimensions: %.0f x %.0f px", width, height);

(adapted from "Programming with Quartz" by Gelphman and Laden, listing 9.5, page 228)

Michael Waterfall
  • 20,497
  • 27
  • 111
  • 168
Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
  • 2
    Why use `CGImageSourceCopyPropertiesAtIndex` instead of just `CGImageSourceCopyProperties`? – jcm Feb 13 '12 at 04:28
  • 3
    It is very important to pass `kCFNumberCGFloatType` instead of `kCFNumberFloatType` when calling CFNumberGetValue() since the variables `width` and `height` are declared as `CGFloat`. The code above will work on 32-bit systems but the values will contain garbage on 64-bit systems. – fjoachim Dec 05 '13 at 08:30
  • 4
    @OleBegemann Should also take image orientation (`kCGImagePropertyOrientation`) into account - flip width and height for orientations 5 to 8. You can test with [these images](https://github.com/recurser/exif-orientation-examples). – Pang May 29 '14 at 03:19
  • does it support remote image or only support local image? – LiangWang Jun 22 '14 at 05:23
  • According to that book whose authors include frameworks authors, it should accept a remote URL but the odd of that going badly are huge. Better to download it first if the remote host cannot provide the info. – uchuugaka Feb 09 '15 at 00:29
  • @Pang I've added the orientation check, thanks for linking to those test images, very useful. This code now works correctly for them all. – Michael Waterfall Sep 11 '15 at 08:19
  • I have done a number of experiments and timings on macOS, iterating over 200k images on an external HD, and it seems that `CGImageSourceCreateWithURL()` does load the _whole_ image file. – Gab Jan 07 '23 at 17:00
7

Swift 3 version of the answer:

import Foundation
import ImageIO

func sizeForImage(at url: URL) -> CGSize? {

    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)
        , let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [AnyHashable: Any]
        , let pixelWidth = imageProperties[kCGImagePropertyPixelWidth as String]
        , let pixelHeight = imageProperties[kCGImagePropertyPixelHeight as String]
        , let orientationNumber = imageProperties[kCGImagePropertyOrientation as String]
        else {
            return nil
    }

    var width: CGFloat = 0, height: CGFloat = 0, orientation: Int = 0

    CFNumberGetValue(pixelWidth as! CFNumber, .cgFloatType, &width)
    CFNumberGetValue(pixelHeight as! CFNumber, .cgFloatType, &height)
    CFNumberGetValue(orientationNumber as! CFNumber, .intType, &orientation)

    // Check orientation and flip size if required
    if orientation > 4 { let temp = width; width = height; height = temp }

    return CGSize(width: width, height: height)
}
Aleks N.
  • 6,051
  • 3
  • 42
  • 42
  • Small detail: I am finding that imageProperties[kCGImagePropertyOrientation as String] may be nil for some images. – emp Sep 16 '18 at 04:50
4

In Swift 5, with ImageIO,

extension URL{
    var sizeOfImage: CGSize?{
        guard let imageSource = CGImageSourceCreateWithURL(self as CFURL, nil)
            , let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [AnyHashable: Any]
            , let pixelWidth = imageProperties[kCGImagePropertyPixelWidth as String] as! CFNumber?
            , let pixelHeight = imageProperties[kCGImagePropertyPixelHeight as String] as! CFNumber?
            else {
                return nil
        }
        var width: CGFloat = 0, height: CGFloat = 0
        CFNumberGetValue(pixelWidth, .cgFloatType, &width)
        CFNumberGetValue(pixelHeight, .cgFloatType, &height)
       }
       return CGSize(width: width, height: height)
    }
}

imageProperties[kCGImagePropertyOrientation as String] may be nil.

It is nil, I tested with png image file

as Apple says

kCGImagePropertyOrientation

The numeric value for this key encodes the intended display orientation for the image according to the TIFF and Exif specifications.

Community
  • 1
  • 1
dengST30
  • 3,643
  • 24
  • 25
0

Just for the record ...

I have tried this:

for ( NSString* filename in imagefiles ) 
{
    NSURL * imgurl = [NSURL fileURLWithPath: filename isDirectory: NO];
    CGImageSourceRef sourceref = CGImageSourceCreateWithURL( (__bridge CFURLRef) imgurl, NULL );
}

This takes 1 minute for around 300k images stored on my internal SSD. That would be OK.

However! .. if performed on a folder stored on an external hard disk, I get the following timings:

     - 20 min for 150k images (45 GB) 
     - 12 min for 150k images (45 GB), second time
     - 150 sec for 25k images (18 GB)
     - 170 sec for 25k images (18 GB), with the lines below (*)
     - 80 sec for 22k (3 GB) images
     - 80 sec for 22k (3 GB) images, with the lines below (*)

All experiments were done on different folders on the same hard disk, WD MyPassport Ultra, 1 TB, USB-A connector to Macbook Air M2. Timings with the same number of files/GB were the same folders, resp.
(*): these were timings where I added the following lines to the loop:

   CFDictionaryRef fileProps = CGImageSourceCopyPropertiesAtIndex( image, 0, NULL );
   bool success = CFDictionaryGetValueIfPresent( fileProps, kCGImagePropertyExifDictionary, (const void **) & exif_dict );
   CFDictionaryGetValueIfPresent( exif_dict, kCGImagePropertyExifDateTimeDigitized, (const void **) & dateref );
   iso_date = [isoDateFormatter_ dateFromString: (__bridge NSString * _Nonnull)(dateref) ];
   [datesAndTimes_ addObject: iso_date ];

(Plus some error checking, which I omit here.)

First of all, we can see that the vast majority of time is spent on CGImageSourceCreateWithURL(). Second, there seem to be some caching effects, although I have a hard time understanding that, but that is not the point. Third, the durations are not linear; I guess it might have something to do with the sizes of the files, too, but again, didn't investigate further.

So, it looks to me like CGImageSourceCreateWithURL() really loads the complete image file.

I don't see why Ole Bergmann can claim his approach does not load the whole image.

Gab
  • 306
  • 3
  • 7