Reputation: 1708
I have an application that uses camera to take images. Application works but is very slow, taking ~10 seconds to process images.
By process i mean, rotating, creating thumbnail and saving them both to flash memory.
Application is based on Cordova, but i dont have a part in that. And this is running on older model, iPhone 3G, but trying this app on newer model doesn't yield much better results.
The main bottleneck is aforementioned CGContextDrawImage and UIImageJPEGRepresentation functions.
I cant separate that code into second thread because results must be visible as soon as possible.
I have searched for two days and found no applicable solutions.
Also i found out about apple's private API's but i can't use them.
Here is the code samples for time-critical code and if you have any advice on how to speed this up it would be great.
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
NSLog(@"TEST imagePickerController");
NSDate* dateFunctionStart = [NSDate date];
NSLog(@"respond...");
if ([picker respondsToSelector:@selector(presentingViewController)]) {
[[picker presentingViewController] dismissModalViewControllerAnimated:YES];
} else {
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}
NSLog(@"done");
// get the image
NSLog(@"get the image...");
UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSLog(@"done");
NSLog(@"correct image for orientation...");
image = [self imageCorrectedForCaptureOrientation:image]; // 3 sec
//image = [image rotate:[image imageOrientation]]; // 1 - 6 sec
NSLog(@"done");
NSLog(@"make thumbnail...");
CGSize thumbnailSize = CGSizeMake(200, 200);
UIImage *thumbnailImage = [self imageByScalingNotCroppingForSize:image toSize:thumbnailSize];
NSLog(@"done");
NSLog(@"jpeg representation 1...");
NSData* imageData = UIImageJPEGRepresentation(image, 1.0); // 4 sec
NSLog(@"done");
NSLog(@"jpeg representation 2...");
NSData* thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 1.0);
NSLog(@"done");
NSLog(@"save image and thumbnail...");
NSString* imageRet = [self saveData:imageData toFile:self.imageName];
NSString* thumbRet = [self saveData:thumbnailData toFile:self.thumbnailName];
NSLog(@"done");
NSLog(@"array two images...");
//NSString *jsResult = [NSString stringWithFormat:@"{%@, %@}", imageRet, thumbRet];
NSArray *jsResultA = [NSArray arrayWithObjects:imageRet, thumbRet, nil];
NSLog(@"done");
NSLog(@"plugin stuff...");
CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsArray:jsResultA];
NSString* jsString = [result toSuccessCallbackString:self.callBackID];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
NSLog(@"done");
timeFunc(dateFunctionStart, @"total time");
NSLog(@"TEST END imagePickerController");
}
Here is the imageCorrectedForCaptureOrientation
method:
- (UIImage*) imageCorrectedForCaptureOrientation:(UIImage*)anImage
{
NSLog(@"IMAGE CORRECTION IN");
NSDate* start = nil;
float rotation_radians = 0;
bool perpendicular = false;
switch ([anImage imageOrientation]) {
case UIImageOrientationUp:
rotation_radians = 0.0;
break;
case UIImageOrientationDown:
rotation_radians = M_PI;
break;
case UIImageOrientationRight:
rotation_radians = M_PI_2;
perpendicular = true;
break;
case UIImageOrientationLeft:
rotation_radians = -M_PI_2;
perpendicular = true;
break;
default:
break;
}
UIGraphicsBeginImageContext(CGSizeMake(anImage.size.width, anImage.size.height));
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate around the center point
CGContextTranslateCTM(context, anImage.size.width/2, anImage.size.height/2);
CGContextRotateCTM(context, rotation_radians);
CGContextScaleCTM(context, 1.0, -1.0);
float width = perpendicular ? anImage.size.height : anImage.size.width;
float height = perpendicular ? anImage.size.width : anImage.size.height;
CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [anImage CGImage]);
// Move the origin back since the rotation might've change it (if its 90 degrees)
if (perpendicular) {
CGContextTranslateCTM(context, -anImage.size.height/2, -anImage.size.width/2);
}
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(@"IMAGE CORRECTION OUT");
return newImage;
}
I didnt found any better way to save image other that
NSData* imageData = UIImageJPEGRepresentation(image, 1.0); // 4 sec
How does regular camera application work so fast? It takes image, rotates it and saves image to flash in a second. How to produce that effect?
Any help is appreciated.
EDIT: No solution. I will put some custom UIView to play some animation to notify user that something is happening. Since the image is large, 1.5 MB, these operations must last some time. Maybe there is solution to speed up this whole process of taking image, process it and then saving to disk but i do not know it.
I've decided for https://github.com/matej/MBProgressHUD as it seems to be complete solution. If it proves complicated as i am objective-c noob, i'll put UIProgressView or something.
Anyway, thanks to all for trying to help.
Upvotes: 0
Views: 979
Reputation: 14677
I suggest you use GCD and process the image in another thread.... Every call for UIImageJpegRepresentation does cost a lot, since it does decode the image to the device's memory...
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
NSLog(@"TEST imagePickerController");
NSDate* dateFunctionStart = [NSDate date];
NSLog(@"respond...");
if ([picker respondsToSelector:@selector(presentingViewController)]) {
[[picker presentingViewController] dismissModalViewControllerAnimated:YES];
} else {
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}
NSLog(@"done");
// get the image
NSLog(@"get the image...");
UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSLog(@"done");
NSLog(@"correct image for orientation...");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void) {
image = [self imageCorrectedForCaptureOrientation:image]; // 3 sec
//image = [image rotate:[image imageOrientation]]; // 1 - 6 sec
NSLog(@"done");
NSLog(@"make thumbnail...");
CGSize thumbnailSize = CGSizeMake(200, 200);
UIImage *thumbnailImage = [self imageByScalingNotCroppingForSize:image toSize:thumbnailSize];
NSLog(@"done");
NSLog(@"jpeg representation 1...");
NSData* imageData = UIImageJPEGRepresentation(image, 1.0); // 4 sec
NSLog(@"done");
NSLog(@"jpeg representation 2...");
NSData* thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 1.0);
NSLog(@"done");
NSLog(@"save image and thumbnail...");
NSString* imageRet = [self saveData:imageData toFile:self.imageName];
NSString* thumbRet = [self saveData:thumbnailData toFile:self.thumbnailName];
NSLog(@"done");
NSLog(@"array two images...");
//NSString *jsResult = [NSString stringWithFormat:@"{%@, %@}", imageRet, thumbRet];
NSArray *jsResultA = [NSArray arrayWithObjects:imageRet, thumbRet, nil];
NSLog(@"done");
dispatch_async(dispatch_get_main_queue(),^ {
NSLog(@"plugin stuff...");
CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsArray:jsResultA];
NSString* jsString = [result toSuccessCallbackString:self.callBackID];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
NSLog(@"done");
timeFunc(dateFunctionStart, @"total time");
NSLog(@"TEST END imagePickerController");
});
});
}
Another idea would be to look and work with GPUImage by Brad Larson, however remember that full image size operations will be costly no matter where, how and with way.
The best idea is to only prepare ASAP only what you will be use immediately in your app and run the other tasks in a background queue and finish them there...
Upvotes: 2
Reputation: 4452
I haven't used CoreGraphics much to know how to make it more efficient, but one thing for sure is that the iPhone 3G is not a good model to base performance on. Also, the size of the image greatly influences the time it takes to process.
One final thing, if you want, you can use a custom UIView subclass to do the drawing on. Taking this approach, you can change the subclass's CALayer to CATiledLayer, effectively drawing in the background.
Upvotes: 0