accessing UIImage properties without loading the image in memory
Asked Answered
C

4

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 loaded... which is exactly what I am trying to avoid.

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

Conard answered 12/11, 2010 at 22:33 Comment(3)
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. #1551800Gemmagemmate
i searched before, but i couldn't find it! many thanks!Conard
See also: Accessing Image Properties Without Loading the Image Into MemorySuspensoid
P
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)

Primaveria answered 13/11, 2010 at 0:6 Comment(7)
Why use CGImageSourceCopyPropertiesAtIndex instead of just CGImageSourceCopyProperties?Conciliar
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.Gosse
@OleBegemann Should also take image orientation (kCGImagePropertyOrientation) into account - flip width and height for orientations 5 to 8. You can test with these images.Crowl
does it support remote image or only support local image?Strap
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.Noleta
@Crowl I've added the orientation check, thanks for linking to those test images, very useful. This code now works correctly for them all.Kettering
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.Daub
E
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)
}
Enceladus answered 15/3, 2017 at 9:29 Comment(1)
Small detail: I am finding that imageProperties[kCGImagePropertyOrientation as String] may be nil for some images.Circumferential
C
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.

Cordiacordial answered 6/11, 2019 at 11:43 Comment(0)
D
1

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.

Daub answered 27/4, 2023 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.