Get size of image without loading in to memory
Asked Answered
L

9

8

I have several .png images (ETA: but the format could also be JPEG or something else) that I am going to display in UITableViewCells. Right now, in order to get the row heights, I load in the images, get their size properties, and use that to figure out how high to make the rows (calculating any necessary changes along the way, since most of the images get resized before being displayed). In order to speed things up and reduce memory usage, I'd like to be able to get size without loading the images. Is there a way to do this?

Note: I know that there are a number of shortcuts I could implement to eliminate this issue, but for several reasons I can't resize images in advance or collect the image sizes in advance, forcing me to get this info at run time.

Linlithgow answered 11/10, 2009 at 18:2 Comment(1)
CGImageSource is the perfect API for this, but annoyingly it's not available on the iPhone. You'll probably have to implement it yourself. That said, bear in mind that UIImage will purge its data if you use +imageWithContentsOfFile: which should remove your memory use concerns.Silvas
A
5

As of iOS SDK 4.0, this task can be accomplished with the ImageIO framework (CGImageSource...). I have answered a similar question here.

Annmarie answered 13/11, 2010 at 0:31 Comment(0)
D
6

It should be pretty simple. PNG spec has an explanation of a PNG datastream (which is effectively a file). IHDR section contains information about image dimensions.

So what you have to do is to read in PNG "magic value" and then read two four-byte integers, which will be width and height, respectively. You might also need to reorder bits in these values (not sure how are they stored), but once you figure that out, it will be very simple.

Disremember answered 11/10, 2009 at 18:7 Comment(1)
Good idea, except that I forgot to mention that it might not be a PNG, that's just what I happen to have right now (edited post to reflect this). I should support JPEG too, at least, although I suppose I could do it in a similar way to what you describe.Linlithgow
A
5

As of iOS SDK 4.0, this task can be accomplished with the ImageIO framework (CGImageSource...). I have answered a similar question here.

Annmarie answered 13/11, 2010 at 0:31 Comment(0)
M
4

imageUrl is an NSURL, also import ImageIO/ImageIO.h with <> around it.

CGImageSourceRef imageSourceRef = CGImageSourceCreateWithURL((CFURLRef)imageUrl, NULL);

if (!imageSourceRef)
    return;

CFDictionaryRef props = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);

NSDictionary *properties = (NSDictionary*)CFBridgingRelease(props);

if (!properties) {
    return;
}

NSNumber *height = [properties objectForKey:@"PixelHeight"];
NSNumber *width = [properties objectForKey:@"PixelWidth"];
int height = 0;
int width = 0;

if (height) {
    height = [height intValue];
}
if (width) {
    width = [width intValue];
}
Malleus answered 7/12, 2016 at 21:50 Comment(0)
A
1

This is nicely implemented in Perl's Image::Size module for about a dozen formats -- including PNG and JPEG. In order to re-implement it in Objective C just take the perl code and read it as pseudocode ;-)

For instance, pngsize() is defined as

# pngsize : gets the width & height (in pixels) of a png file
# cor this program is on the cutting edge of technology! (pity it's blunt!)
#
# Re-written and tested by [email protected]
sub pngsize
{
    my $stream = shift;

    my ($x, $y, $id) = (undef, undef, "could not determine PNG size");
    my ($offset, $length);

    # Offset to first Chunk Type code = 8-byte ident + 4-byte chunk length + 1
    $offset = 12; $length = 4;
    if (&$read_in($stream, $length, $offset) eq 'IHDR')
    {
        # IHDR = Image Header
        $length = 8;
        ($x, $y) = unpack("NN", &$read_in($stream, $length));
        $id = 'PNG';
    }

    ($x, $y, $id);
}

jpegsize is only a few lines longer.

Alight answered 12/10, 2009 at 4:12 Comment(0)
A
1

Note: This function doesn't work with iPhone compressed PNGs, this compression is automatically performed by XCode and change the image header, see more details here and how to disable this feature: http://discussions.apple.com/thread.jspa?threadID=1751896

Future versions of PSFramework will interpret this headers too, stay tuned.


See this function, she does just that. Reads only 30 bytes of the PNG file and returns the size (CGSize). This function is part of a framework for processing images called PSFramework (http://sourceforge.net/projects/photoshopframew/). Not yet implemented for other image formats, developers are welcome. The project is Open Source under the GNU License.

CGSize PSPNGSizeFromMetaData( NSString* anFileName ) {

    // File Name from Bundle Path.
    NSString *fullFileName = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], anFileName ];

    // File Name to C String.
    const char* fileName = [fullFileName UTF8String];

    /* source file */ 
    FILE * infile;    

    // Check if can open the file.
    if ((infile = fopen(fileName, "rb")) == NULL) 
    {
        NSLog(@"PSFramework Warning >> (PSPNGSizeFromMetaData) can't open the file: %@", anFileName );
        return CGSizeZero;

    }

    //////  //////  //////  //////  //////  //////  //////  //////  //////  //////  ////// 

    // Lenght of Buffer.
    #define bytesLenght 30

    // Bytes Buffer.
    unsigned char buffer[bytesLenght];

    // Grab Only First Bytes.
    fread(buffer, 1, bytesLenght, infile);

    // Close File.
    fclose(infile);

    //////  //////  //////  //////  ////// 

    // PNG Signature.
    unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};

    // Compare File signature.
    if ((int)(memcmp(&buffer[0], &png_signature[0], 8))) {

        NSLog(@"PSFramework Warning >> (PSPNGSizeFromMetaData) : The file (%@) don't is one PNG file.", anFileName);    
        return CGSizeZero;

    }

    //////  //////  //////  //////  ////// //////   //////  //////  //////  ////// 

    // Calc Sizes. Isolate only four bytes of each size (width, height).
    int width[4];
    int height[4];
    for ( int d = 16; d < ( 16 + 4 ); d++ ) {
        width[ d-16] = buffer[ d ];
        height[d-16] = buffer[ d + 4];
    }

    // Convert bytes to Long (Integer)
    long resultWidth = (width[0] << (int)24) | (width[1] << (int)16) | (width[2] << (int)8) | width[3]; 
    long resultHeight = (height[0] << (int)24) | (height[1] << (int)16) | (height[2] << (int)8) | height[3]; 

    // Return Size.
    return CGSizeMake( resultWidth, resultHeight );

}

Armourer answered 5/1, 2010 at 13:29 Comment(0)
M
1

//Here's a quick & dirty port to C#


public static Size PNGSize(string fileName)
{
    // PNG Signature. 
    byte[] png_signature = {137, 80, 78, 71, 13, 10, 26, 10}; 

try
{
    using (FileStream stream = File.OpenRead(fileName))
    {
        byte[] buf = new byte[30];
        if (stream.Read(buf, 0, 30) == 30)
        {
            int i = 0;
            int imax = png_signature.Length;

            for (i = 0; i < imax; i++)
            {
                if (buf[i] != png_signature[i])
                    break;
            }

            // passes sig test
            if (i == imax)
            {
                    // Calc Sizes. Isolate only four bytes of each size (width, height). 
                    // Convert bytes to integer
                    int resultWidth = buf[16] << 24 | buf[17] << 16 | buf[18] << 8 | buf[19];
                    int resultHeight = buf[20] << 24 | buf[21] << 16 | buf[22] << 8 | buf[23];  

                    // Return Size. 
                    return new Size( resultWidth, resultHeight ); 
            }
        }
    }
}
catch
{

}

return new Size(0, 0);

}

Morgun answered 26/6, 2010 at 1:49 Comment(0)
L
0

Try using the CGImageCreateWithPNGDataProvider and CGImageCreateWithJPEGDataProvider functions. I don't know whether they're lazy enough or not, or whether that's even possible for JPEG, but it's worth trying.

Lottie answered 12/10, 2009 at 3:42 Comment(0)
P
0

low tech solutions:

if you know what the images are beforehand, store the image sizes along with their filenames in an XML file or plist (or whichever way you prefer) and just read those properties in.

if you don't know what the images are (i.e. they're going to be defined at runtime), then you must've had the images loaded at one time or another. the first time you do have them loaded, save their height and width in a file so you can access it later.

Pointless answered 12/10, 2009 at 14:59 Comment(0)
A
0

Adding Swift equiv of @The Jeff's solution for anyone else passing through:

    func getImageSize(url: URL) -> CGSize? {
        guard let src = CGImageSourceCreateWithURL(url as CFURL, nil) else {
            return nil
        }
        
        guard 
            let props = CGImageSourceCopyPropertiesAtIndex(src, 0, nil),
                let properties = props as? [String: AnyObject]  else {
            return nil
        }
        

        guard let w = properties["PixelWidth"], let h = properties["PixelHeight"] else {
            return nil
        }
        
        let size = CGSize(
            width: w.intValue as Int,
            height: h.intValue as Int)

        print("image src -- width: \(size.width), height: \(size.height)")
        
        return size
    }
Arlenearles answered 11/11, 2023 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.