What's the fastest way of getting a picture into a SQLite data store for compression, so I can return control to the user?

  • I'm using UIImagePickerController to take pictures in my application. The problem is using the picture is quite slow, because of the speed of UIImageJPEGRepresentation.
  • I want to push JPEG compression into a background thread, but before trying this I need to satisfy myself that I can persist the picture in a way that will survive across runs. That means either a blob in SQLite or a file. Which as far as I can tell, takes me right back to doing slow picture encoding right away.

What I want to achieve is speed fast enough that it feels instant to the user.

How should I be handling this? Is there anything else I should know?

+1  A: 

You should not save a picture in the database, unless it's very small in size. The threshold which determines if the picture is small enough is, of course, highly subjective. In my humble opinion (and experience on the iPhone), it should not exceed one megabyte. Therefore, you should only save in the database small sized images, such as icons, thumbnails etc. For images beyond one megabytes you should simply store them as files in the filesystem, and put the filename (the image path) in the database. By the way, storing the image on the filesystem and its pathname in the database is extremely fast.

About compression: you can certainly compress the image using another thread, but consider whether or not it's really worth doing this. You can use a thread to save the image to a file, save the pathname in the database and return immediately the control to your user. You have (usually) plenty of space, but a very small computational power, even on the latest iPhone 3GS. Also, you should verify (I really do not know this) whether or not loading a compressed image through UIImageView requires more time w.r.t. a non compressed one such as a PNG. If your application will incur an additional overhead when loading a compressed image, then it may definitely not worth compressing your images. It's basically a tradeoff between space and speed. Hope this helps to decide.

Since the picture is going to be uploaded, it becomes a tradeoff between space, speed and bandwidth... plus if I don't compress the photo, I'll need the server guy to be able to interpret the image on the server.We've done tests on the speed of the SQLite blob in the past, so I'm not too worried about the file vs. blob choice. Though I'll probably run them again.Thanks for the helpful response. From it, I gather I should be looking at pulling uncompressed data from UIImagePicker?
Steven Fisher
You are going to have to use either PNG or JPG representation methods in order to get NSData you can write to a file anyway. You could test to see if PNG is faster since the compression is less, and there's a chance that the internal representation is PNG anyway...
Kendall Helmstetter Gelner
As pointed out by Kendall, I think PNG will be faster. Give it a try.
I just ran a few tests. Intuition (including mine!) is wrong here: UIImagePNGRepresentation(image) takes 7 seconds, UIImageJPEGRepresentation(image, 0.5) takes only 2.5.
Steven Fisher
This is interesting. Thank you for sharing the result of your test.
+1  A: 

Based on comments and tests, here's what I'm currently doing:

When I get the image from the UIImageController, I retain it in a class ivar and dismiss the image picker. I show a view that blocks my main view and schedule a NSTimer event to do the compression in a second, then return to the caller.

This lets the animation run that dismisses the image controller. My blocker view is revealed under it.

(The blocker view fills the entire content area of the navigation controller, and is solid black with a UIActivityIndicatorView.)

- (void)imagePickerController: (UIImagePickerController *)picker
        didFinishPickingImage: (UIImage *)selectedImage
                  editingInfo: (NSDictionary *)editingInfo;
    busyView.userInteractionEnabled = YES;
    busyView.alpha = 0.7f;
    mainView.userInteractionEnabled = NO;
    [self dismissModalViewControllerAnimated: YES];
    [NSTimer scheduledTimerWithTimeInterval: 1.0f
                                     target: self
                                   selector: @selector(compress:)
                                   userInfo: selectedImage
                                    repeats: NO];

When the timer fires, I compress the image using JPEG (because it's faster than PNG, despite intuition) and fade away the blocker view.

- (void)compress: (NSTimer *)inTimer;
    [self gotJPEG: UIImageJPEGRepresentation( inTimer.userInfo, 0.5f )];
    [UIView beginAnimations: @"PostCompressFade" context: nil];
    [UIView setAnimationDuration: 0.5];
    busyView.userInteractionEnabled = NO;
    busyView.alpha = 0.0f;
    [UIView commitAnimations];
    mainView.userInteractionEnabled = YES;

Although this adds a second do the processing, it gets the image picker out of the way faster so it no longer feels like my application has frozen. The animation from the UIActivityIndicatorView does run while UIImageJPEGRepresentation is working.

A better answer than using the NSTimer with 1 second delay would be to get an event when the animation from dismissModalViewControllerAnimated: finishes, but I'm not sure how to do this.

(I don't consider this solved yet.)

Steven Fisher
Instead of using the timer, do it in the viewDidAppear method of the view that is shown after the picker is dismissed.