Speed up UIImagePickerController by using a low res image then later swapping in the high res image - iphone

There is a great wiki about image loading from the camera picker. Which made me aware of costs of taking an image at full resolution.
At the moment, when a photo is picked, I push a new view controller and display the image at full resolution. Pushing the view is a really slow and choppy experience (about 1 fps!) that I want to smooth out. Comparing to picking a photo on Instagram, I notice that they use a low resolution image and later swap in the full image. (I need the full res image because the user should be able to zoom and pan)
The idea I want is somthing like this:
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage* fullImage = [info objectForKey:UIImagePickerControllerOriginalImage];
// Push a view controller and give it the image.....
}
- (void) viewDidLoad {
CGSize smallerImageSize = _imageView.bounds;
UIImage* smallerImage = [MyHelper quickAndDirtyImageResize:_fullImage
toSize:smallerImageSize];
// Set the low res image for now... then later swap in the high res
_imageView.image = smallerImage;
// Swap in high res image async
// This is the part im unsure about... Im sure UIKit isn't thread-safe!
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, NULL), ^{
_imageView.image = _fullImage;
});
}
I think that UIImage isn't memory mapped in until it is used. Therefore it dons't slow things down until its given to the imageView. Is this correct?
I think image decoding is already done asynchronously by the system, however, it is sill slowing the phone down considerably while its loading.
Is there a way do perform some of the work required to display an image in a very low priority background queue?

You're trying to do things the most complicated way :)
Why not just prepare the small image before pushing the view controller and pass it to them? Look at this code:
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *fullImage = [info objectForKey:UIImagePickerControllerOriginalImage];
UIImage *smallImage = [fullImage imageScaledToSize:self.view.bounds];
// Push a view controller and give it BOTH images
}
// And in your pushed view controller
- (void)viewDidLoad
{
_imageView.image = self.smallImage;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_imageView.image = self.fullImage;
}
The main thing is that viewDidAppear: will be called right after the animation is done so you can switch images here without any worries.

In addition to Andrey's answer, instead of using imageScaledToSize, use CGImageSourceCreateThumbnailAtIndex. In fact, it's very possible (I am pretty sure it's the case) that any image used in the photo album already has a thumbnail. So instead of bothering with the image itself, grab the existing thumbnail and display it, then use Andrey's code to switch in the main image. This way you do as little work as possible during the animation period.
Calling CGImageSourceCreateThumbnailAtIndex will return the thumbnail image, whether it's already there or needs to be generated. So it'll be quite safe to use, and probably at least as fast as imageScaledToSize.
You can find complete code samples to use it in the Apple docs, no need to duplicate it here.

Have you tried using ALAssetsLibrary to load the thumbnail of that image instead of trying to load the image at full resolution? It's faster than resize it as well.

Related

App crashes after showing memory warning in ios 5.1.1 only while taking image from camera

Hi I have gone through many question on SO this too but its not helping me for ios 5.1.1.When i take image for first 2 times its working fine then n3rd time the app shows memory warning and gets crashed.Here is my code:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
UIImage *image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
if (image) {
if ([appdel.arrImageData count]==0) {
count=0;
}
count++;
[appdel.arrImageData addObject:[image copy]];
}}
Any help would be appreciated.
Each time you take a picture, you keep a copy of it in arrImageData and so filling up memory until iOS kills you app since you take too much memory. Re-think your design so you keep only one image in memory. If you need all the pictures for what ever reason, save it in the documents or cache directory and clean memory before taking another picture.
i Corrected your code check it it will help or not
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
if (image) {
if ([appdel.arrImageData count]==0) {
count=0;
}
count++;
[appdel.arrImageData addObject:[image copy]];
}
[picker dismissModalViewControllerAnimated:YES];
[picker release];
}
I do not see the memory management of your ImagePickerController. But I had a problem with deallocating my picker just after I called dismissModal...
Try to dismiss modal view and picker after you get the image.
UPDATE:
I agree with you. I had too many issues related to trying to make pickerController work for many iOS versions on several devices starting from iOS 3 to 5. As result I've skipped supporting of iOS 3 and started implementing own code to work with pictures and movies based on AV Foundation Programming Guide, AV Foundation Framework Reference.
An app quite often receives memory warning when it is using UIImagePickerController. What happens is when you take the image and take the image again and again your memory keeps on increasing every time (If you are not managing memory correctly.In my case it used to increase every 1.5MB). So it might work for the first,second or third time and receive memory warning the very next time or it could receive memory warning on the very first time if there are too many apps running in the background.
What is important is here how you to handle this memory warning. Once an app receives a memory warning the viewDidUnload of all the active view controllers is called where you should release all unwanted objects which can be created again. So your app might be crashing because you are doing something wrong there.. So in short we will need to see both your .h and .m files..
here you can get what you want.
in that code i have simply put autorelease pool to release memory.
i hope this may help you.

Add multiple pictures to UIViewcontroller

I Want to add four different pictures to a ViewController. I am able to add one picture. And when i try to add another picture in a different Imageview, the same pictures appears on both the imageviews. Could anyone please give an idea of how to add unique pictures.
- (void)imagePickerControllerUIImagePickerController *)picker didFinishPickingMediaWithInfoNSDictionary *)info {
if(choosePhoto1){
NSLog(#"buttonclicked:button1");
theimageView1.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[picker dismissModalViewControllerAnimated:YES];
}
else if(choosePhoto2) {
NSLog(#"buttonclicked:button2");
theimageView2.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[picker dismissModalViewControllerAnimated:YES];
}
}
Thanks.
If you created the .xib in interface builder, be sure to go into it and ensure that you've wired up each imageview to the IBOutlets for their views and not the same one for all.
Add some code ... simply from your description nobody can help you.
The educated guess is that your not creating a new object each time you add or your passing the same reference several times
Form your code I would say your setting twice the same image:
in both cases you use #"UIImagePickerControllerOriginalImage"
so if info is the same object both times its normal your getting the same image

Add 2 images to separate UIImageView

I hava an app where i need to add 2 or more images. The images should be displayed in UIImageview. I use two separate buttons to add 2 images. i am able to add only one image. When i choose the second button, the image gets displayed on the first UIImageview. Here is the code where i do it.
- (void)imagePickerControllerUIImagePickerController *)picker didFinishPickingMediaWithInfoNSDictionary *)info {
if(choosePhoto1){
NSLog(#"buttonclicked:button1");
theimageView1.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[picker dismissModalViewControllerAnimated:YES];
}
else if(choosePhoto2) {
NSLog(#"buttonclicked:button2");
theimageView2.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[picker dismissModalViewControllerAnimated:YES];
}
}
I want to know if this is the right way to do it or is there any other method. I looked in to the forums but there is no method for adding 2 pictures.
Any help is appreciated.
Thanks
Viki
One UIImageView displays a single image. If you want to display two different images in two different places at the same time, then you need two UIImageView objects. If you do have two UIImageView objects, and only one is changing, perhaps in interface builder you set both outlets to one UIImageView?
If your Interface Builder connections are all correct, I'd suggest inspecting your values for choosePhoto1 and choosePhoto2. Are you sure these are set to the correct values when you click the buttons? (Maybe show that code too). I'd guess you are toggling the values of them like so:
- (IBAction)btnOneClicked:(id)sender {
self.choosePhoto1 = YES;
self.choosePhoto2 = NO;
}

AVCaptureVideoPreviewLayer: taking a snapshot

I'm trying to emulate the animation seen in the default camera app, where a snapshot of the cameras viewfinder is animated into the corner of the apps display.
The AVCaptureVideoPreviewLayer object that holds the key to solving this problem isn't very open to these requirements: trying to create a copy of it in a new layer with ..
- (id)initWithLayer:(id)layer
.. returns an empty layer, without the image snapshot, so clearly there is some deeper magic going on here.
Your clues/boos are most welcome.
M.
facing the same woes, from a slightly different angle.
Here are possible solutions, that none are too great IMO:
You can add to an AVCaptureSession both an AVCaptureStillImageOutput and an AVCaptureVideoDataOutput. When you set the sessionPreset to AVCaptureSessionPresetHigh you'll start getting frames by the API, and when you switch to AVCaptureSessionPresetPhoto you can take real images. So right before taking the picture, you can switch to video, get a frame, and then return to camera. Major caveat is that it takes a "long" time (couple of seconds) for the camera to switch between the video camera and picture camera.
Another option would be to use only the camera output (AVCaptureStillImageOutput), and use UIGetScreenImage to get a screen capture of the phone. You could then crop out the controls and leave only the image. This gets complicated if you're showing UI controls over the image. Also, according to this post, Apple started rejecting apps that use this function (it was always iffy).
Aside from these I also tried playing with AVCaptureVideoPreviewLayer. There's this post to save a UIView or CALayer to a UIImage. But it all produces clear or white images. I tried accessing the layer, the view's layer, the superlayer, the presentationLayer, the modelLayer, but to no avail. I guess the data in AVCaptureVideoPreviewLayer is very internal, and not really part of the regular layer infrastructure.
Hope this helps,
Oded.
I think you should add an AVCaptureVideoDataOutput to the current session with:
AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
videoOutput.videoSettings = #{ (NSString *)kCVPixelBufferPixelFormatTypeKey : #(kCVPixelFormatType_32BGRA) };
[session addOutput:videoOutput];
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[videoOutput setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
Then, implement the delegate method below to get your image snapshot:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
// Add your code here that uses the image.
dispatch_async(dispatch_get_main_queue(), ^{
_imageView.image = image;
});
}
This will consume memory and reduce the performance of the app. To improve, you can also optimize your AVCaptureVideoDataOutput with:
videoOutput.minFrameDuration = CMTimeMake(1, 15);
You can also use alwaysDiscardsLateVideoFrames.
there are 2 ways to grab frames of the preview.. AVCaptureVideoDataOutput & AVCaptureStillImageOutput :)
is your capture session is setup to grab video frames, then make your layer with the cgimage from a chosen frame. if it's setup for stills, wait until getting your still image and make your layer from a scaled down version of that cgimage. if you don't have an output on your session yet, you'll have to add one i think.
Starting in iOS 7, you can use UIView::snapshotViewAfterScreenUpdates to snapshot the UIView wrapping your AVCaptureVideoPreviewLayer. This is not the same as UIGetScreenImage, which will get your app rejected.
UIView *snapshot = [self.containerView snapshotViewAfterScreenUpdates:YES];
Recall the old-school way of turning a view into an image. For some reason it worked on everything except for camera previews:
UIGraphicsBeginImageContextWithOptions(self.containerView.bounds.size, NO, [UIScreen mainScreen].scale);
[self.containerView drawViewHierarchyInRect:self.containerView.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

AssetsLibrary and ImageView -setImage Slowness

So this one is pretty odd ad I'm not sure if the trouble is with the AssetsLibrary API, but I can't figure out what else might be happening.
I am loading an array with ALAssets using the -enumerateAssetsUsingBlock method on ALAssetsGroup. When it completes, I am loading a custom image scroller. As the scroller finishes scrolling, I use NSInvocationOperations to load the images for the currently visible views (pages) from the photo library on disk. Once the image is loaded and is cached, it notifies the delegate which then grabs the image from the cache and displays it in an image view in the scroller.
Everything works fine, but the time it takes from when -setImage: actually gets called to the time it actually shows up visibly on the screen is unbearable--sometimes 10 seconds or more to actually show up.
I have tried it both with and without image resizing which adds almost nothing to the processing time when I do the resizing. As I said, the slowdown is somewhere after I call -setImage on the image view. Is anyone aware of some sort of aspect of the AssetLibrary API that might cause this?
Here's some relevant code:
- (void)setImagesForVisiblePages;
{
for (MomentImageView *page in visiblePages)
{
int index = [page index];
ALAsset *asset = [photos objectAtIndex:index];
UIImage *image = [assetImagesDictionary objectForKey:[self idForAsset:asset]];
// If the image has already been cached, load it into the
// image view. Otherwise, request the image be loaded from disk.
if (image)
{
[[page imageView] setImage:image];
}
else {
[self requestLoadImageForAsset:asset];
[[page imageView] setImage:nil];
}
}
}
This will probably mess up any web searches looking to solve problems with the AssetsLibrary, so for that I apologize. It turns out that the problem wasn't the AssetsLibrary at all, but rather my use of multi-threading. Once the image finished loading, I was posting a notification using the default NSNotificationCenter. It was posting it on the background thread which was then updating (or trying to update, at least) the UIImageView with -setImage. Once I changed it to use -performSelectorOnMainThread and had that selector set the image instead, all was well.
Seems no matter how familiar I get with multi-threading, I still forget the little gotchas from time to time.