7

I want to preload images sot that when I add their UIImageView to my currentView, the PNG are already in memory, loaded, inflated etc.

As you can see from this time profiling, the images are loaded when they are displayed: enter image description here

In the same time, I'm already preloading these images before. Just when I load that view, I thoroughly create all images and image views for these animations using that code:

- (UIImageView*)createAnimationForReel:(NSString*)reel ofType:(NSString*)type usingFrames:(NSInteger)frames{
    UIImageView *imageView = [[UIImageView alloc] init];
    NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:frames];

    for (int i=0;i<frames;i++){
        NSString *image;
        if(i<10){
            image = [NSString stringWithFormat:@"%@_0%i", reel, i];
        } else {
            image = [NSString stringWithFormat:@"%@_%i", reel, i];
        }
        NSString *path = [[NSBundle mainBundle] pathForResource:image ofType:@"png" inDirectory:type];
        UIImage *anImage = [[UIImage alloc] initWithContentsOfFile:path];
        [imageArray addObject:anImage];
    }
    imageView.animationImages = [NSArray arrayWithArray:imageArray];
    imageView.animationDuration = 2.0;
    return imageView;
}

And when I read the documentation it says: "This method loads the image data into memory and marks it as purgeable. If the data is purged and needs to be reloaded, the image object loads that data again from the specified path." talking about initWithContentOfFile. So my image "should" be loaded.

But no.

Surely something is missing in my reflection. But what?

Cyril Godefroy
  • 1,400
  • 12
  • 15

3 Answers3

14

To force the full preloading of a UIImage, you'll need to actually draw it, or so it seems. For example using:

- (void)preload:(UIImage *)image
{
    CGImageRef ref = image.CGImage;
    size_t width = CGImageGetWidth(ref);
    size_t height = CGImageGetHeight(ref);
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, space, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(space);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), ref);
    CGContextRelease(context);
}

Not pretty, but it makes drawing the image about 4x faster. I tested this with an 1 MB 640 × 1136 PNG on an iPhone 4S:

  • Drawing it without preload takes about 80 ms.
  • Preloading the image header takes about 10 ms, but doesn't speedup the drawing afterwards.
  • Preloading the data though the data provider takes about 100 ms, but hardly speeds up the drawing afterwards.
  • Preloading by drawing, illustrated in the code above, takes 100 ms but the next draw only 20 ms.

Check https://gist.github.com/3923434 for the test code.

leo
  • 7,518
  • 1
  • 24
  • 25
  • Using this on the background thread improved the performance of my app significantly, since it would not block the main thread to load the data. Thanks :) – bendahmon Mar 11 '13 at 18:20
  • This is an awesome improvement to drawing images - thanks! I do get a warning about an implicit conversion though in regards to "kCGImageAlphaPremultipliedFirst" – Jay Versluis Nov 23 '13 at 14:11
  • Thanks Jay for pointing out the warning, it should be fixed now. – leo Nov 27 '13 at 21:00
  • should have been kCGBitmapAlphaInfoMask | kCGImageAlphaPremultipliedFirst. See http://ioscodesnippet.com/2011/10/02/force-decompressing-uiimage-in-background-to-achieve/ – Jeow Li Huan Nov 04 '14 at 11:04
0

Leo answer almost didn't give me any speed loading boost. Effect was noticeable only when i preload one image second time right after first. If you call preload by button click, for example, first preload give about 40ms boost if initial load is 200ms. It isn't much.

To solve my problem I just created hidden imageViews and added them to view hierarchy, where i needed a preload. Next load of those images took near ~10ms.

Anton Plebanovich
  • 1,296
  • 17
  • 17
0

The way would be to use ImageIO framework (iOS 4.0+) and do something like that :

NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:(id)kCGImageSourceShouldCache];

CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)dict);

UIImage* retImage = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CFRelease(source);
Nyx0uf
  • 4,609
  • 1
  • 25
  • 26
  • Looks terrific, and does indeed load the images, except they're not cached, and still reloaded whenever displayed. Such a shame! I'm starting to think that it has more to do with the way UIImageView works, not UIImage. – Cyril Godefroy Feb 10 '12 at 10:51