3

I have an app that uses UIImage objects. Up to this point, I've been using image objects initialized using something like this:

UIImage *image = [UIImage imageNamed:imageName];

using an image in my app bundle. I've been adding functionality to allow users to use imagery from the camera or their library using UIImagePickerController. These images, obviously, can't be in my app bundle, so I initialize the UIImage object a different way:

UIImage *image = [UIImage imageWithContentsOfFile:pathToFile];

This is done after first resizing the image to a size similar to the other files in my app bundle, in both pixel dimensions and total bytes, both using Jpeg format (interestingly, PNG was much slower, even for the same file size). In other words, the file pointed to by pathToFile is a file of similar size as an image in the bundle (pixel dimensions match, and compression was chosen so byte count was similar).

The app goes through a loop making small pieces from the original image, among other things that are not relevant to this post. My issue is that going through the loop using an image created the second way takes much longer than using an image created the first way.

I realize the first method caches the image, but I don't think that's relevant, unless I'm not understanding how the caching works. If it is the relevant factor, how can I add caching to the second method?

The relevant portion of code that is causing the bottleneck is this:

[image drawInRect:self.imageSquare];

Here, self is a subclass of UIImageView. Its property imageSquare is simply a CGRect defining what gets drawn. This portion is the same for both methods. So why is the second method so much slower with similar sized UIImage object?

Is there something I could be doing differently to optimize this process?

EDIT: I change access to the image in the bundle to imageWithContentsOfFile and the time to perform the loop changed from about 4 seconds to just over a minute. So it's looking like I need to find some way to do caching like imageNamed does, but with non-bundled files.

Victor Engel
  • 2,037
  • 2
  • 25
  • 46
  • 2
    It seems from this post: http://stackoverflow.com/questions/924740/dispelling-the-uiimage-imagenamed-fud that the issue I'm running into is that the `imageNamed` method caches the decompressed version of the image, whereas the other one has to decompress each time the image is accessed. Somehow I need an efficient way to access the same decompressed version of the image for the duration of the loop, at which point I can dispose it. – Victor Engel Sep 11 '13 at 22:44
  • I solved it with the help of that thread in the post I cited. I created a method `+(UIImage *)decompressedImage:(UIImage *)compressedImage;` which essentially uses the code to create a `UIImage` that is not compressed. I tried it out, and the result is an order of magnitude faster. It's useful enough that I've added the method to my utilities class. – Victor Engel Sep 11 '13 at 22:53

1 Answers1

1

UIImage imageNamed doesn't simply cache the image. It caches an uncompressed image. The extra time spent was not caused by reading from local storage to RAM but by decompressing the image.

The solution was to create a new uncompressed UIImage object and use it for the time sensitive portion of the code. The uncompressed object is discarded when that section of code is complete. For completeness, here is a copy of the class method to return an uncompressed UIImage object from a compressed one, thanks to another thread. Note that this assumes data is in CGImage. That is not always true for UIImage objects.

+(UIImage *)decompressedImage:(UIImage *)compressedImage
{
   CGImageRef originalImage = compressedImage.CGImage;
   CFDataRef imageData = CGDataProviderCopyData(
                         CGImageGetDataProvider(originalImage));
   CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
   CFRelease(imageData);
   CGImageRef image = CGImageCreate(
                                CGImageGetWidth(originalImage),
                                CGImageGetHeight(originalImage),
                                CGImageGetBitsPerComponent(originalImage),
                                CGImageGetBitsPerPixel(originalImage),
                                CGImageGetBytesPerRow(originalImage),
                                CGImageGetColorSpace(originalImage),
                                CGImageGetBitmapInfo(originalImage),
                                imageDataProvider,
                                CGImageGetDecode(originalImage),
                                CGImageGetShouldInterpolate(originalImage),
                                CGImageGetRenderingIntent(originalImage));
   CGDataProviderRelease(imageDataProvider);
   UIImage *decompressedImage = [UIImage imageWithCGImage:image];
   CGImageRelease(image);
   return decompressedImage;
}
Community
  • 1
  • 1
Victor Engel
  • 2,037
  • 2
  • 25
  • 46
  • where do you exactly use this method ? you said: "...and use it for the time sensitive portion of the code..", which portion you mean ? this method takes a UIImage as an input parameter, so you probably pass the UIImage from either imagedNamed or from imageWithContentsOfFile ... if so, then you didn't get much benefit out of this piece of code – JAHelia May 29 '14 at 13:07
  • Please look at the original question. I'm using it for a `drawInRect:` call. This call occurs in a loop. Were you accessing an image only once, this exercise would be pointless, but in my application, going through the loop multiple times causes a decompression of the image for each iteration of the loop. That is what was bogging things down. Getting an object containing a decompressed image solves the problem. Since it's decompressed already, no time is spent decompressing it within the loop. – Victor Engel May 30 '14 at 16:56
  • I got it .. the decompression code above is useless in my case, where I loop through 100 images getting each image with imageWithContentsOfFile so there is no good place in the code to use the decompression code – JAHelia Jun 02 '14 at 07:59