I've been having a problem to show large images on a scrollview, the images are 2,4 - 4,7 MB. It runs fine on the 3GS and on the simulator. But whenever I try to run on a 3G or iPod Touch 2G, it crashes.
I searched across the web and found the "imageNamed is evil" article. Ok I changed all my image calls to imageWithContentsOfFile: but it still crashes, the only different that I see is that now images are deallocated after I leave the view just fine. But the memory usage is still very high.
Here is a screenshot of Instruments.
First peak is a video I show at startup, then the tableview shows a lot of images, until then no problems.
When I enter a 1,1mb - 2576 x 1000 picture
When I enter a 4,8mb - 7822 x 1000 picture
By the way the app was tested on iOS 4 and 3.1.2
Do you have any tips? Because this problem is driving me nuts.
Since now thanks a lot!
I recently ran into the same problem while testing an app on my 3G. I ended up scaling down any images larger than a maximum number of pixels (I found that 2 million pixels seemed to work reliably on my 3G, but hotpaw2's answer seems to suggest that 1 million pixels may be a safer bet).
UIImage *image = // ...;
if (image.size.width * image.size.height > MAX_PIXELS) {
// calculate the scaling factor that will reduce the image size to MAX_PIXELS
float actualHeight = image.size.height;
float actualWidth = image.size.width;
float scale = sqrt(image.size.width * image.size.height / MAX_PIXELS);
// resize the image
CGRect rect = CGRectMake(0.0, 0.0, floorf(actualWidth / scale), floorf(actualHeight / scale));
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage *imageToDraw = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// imageToDraw is a scaled version of image that preserves the aspect ratio
}
Apple also provides an example of developing a photo gallery app that uses CATiledLayer to tile very large images. Their example uses images that have been sliced into tiles of the appropriate sizes in advance. It is possible to slice the images into tiles on the fly in your iOS app, but doing so is quite slow on the device. Check out session 104 of this year's WWDC for the PhotoScroller example.
The GPUs in the iPhone 3G and iPod Touch 1G/2G only have room for 1k by 1k textures, which seems to also be used for 2D image rendering. Anything larger needs to be tiled to fit the hardware graphics renderer.
The 3GS and newer have a different GPU which can support larger textures, and thus images.
I ended up using UIWebView, I had to use smaller (in MB, not dimensions) images but it's working on, the only problem Is that I can't zoom... Using the scalePageToFit I can zoom but the image fits horizontally not vertically, that sux.
I will try to keep this issue updated.
If someone has this problem I would advice to use webview or an adapter version of three20 image viewer that handles memory better.
Related
I'm currently trying to load a map image which is a large(16mb) .jpg file inside a scrollView so you can zoom in and out.
When I launch the app inside the simulator everything runs fine and smooth. However once I run it on my test device (iPod 4.1, iOS 6.0) the app shows the launch image and then it crashes with no error messages at all.
This is how the code currently looks like.
myImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map.jpg"]];
myScrollView=[[UIScrollView alloc]initWithFrame:CGRectMake(15,120,290,365)];
myScrollView.showsVerticalScrollIndicator=YES;
myScrollView.scrollEnabled=YES;
myScrollView.maximumZoomScale = 10.0;
myScrollView.minimumZoomScale = [myScrollView frame].size.width / myImage.frame.size.width;
myScrollView.clipsToBounds = YES;
myScrollView.bounces = NO;
myScrollView.showsHorizontalScrollIndicator = NO;
myScrollView.showsVerticalScrollIndicator = NO;
[self.view addSubview:whiteFrame];
[self.view addSubview:myScrollView];
myScrollView.contentSize = CGSizeMake(myImage.frame.size.width,myImage.frame.size.height);
myScrollView.delegate = self;
myScrollView.zoomScale = [myScrollView frame].size.width / myImage.frame.size.width;
[myScrollView addSubview:myImage];
Any help is greatly appreciated.
EDIT:
I found this in the docs
If you have a very large image, or are loading image data over the web, you may want to create an incremental image source so that you can draw the image data as you accumulate it. You need to perform the following tasks to load an image incrementally from a CFData object:
Create the CFData object for accumulating the image data.
Create an incremental image source by calling the function CGImageSourceCreateIncremental.
Add image data to the CFData object.
Call the function CGImageSourceUpdateData, passing the CFData object and a Boolean value (bool data type) that specifies whether the data parameter contains the entire image, or just partial image data. In any case, the data parameter must contain all the image file data accumulated up to that point.
If you have accumulated enough image data, create an image by calling CGImageSourceCreateImageAtIndex, draw the partial image, and then release it.
Check to see if you have all the data for an image by calling the function CGImageSourceGetStatusAtIndex. If the image is complete, this function returns kCGImageStatusComplete. If the image is not complete, repeat steps 3 and 4 until it is.
Release the incremental image source.
Does anyone know about a sample code for that ?
A 16mb jpg is going to take a pretty decent chunk of RAM when uncompressed. Your app is probably crashing because it is using too much memory. You can check this by reproducing the crash and checking the device's console log in the Xcode organizer.
You will need to either:
Reduce the dimensions of the image as much as your design will permit to greatly reduce memory usage. For example, there's no reason to have a 16mb jpg if it will never be shown at full-resolution.
Chop the image up into tiles and only load the tiles currently displayed on the screen and the surrounding areas. Then load additional tiles as the user scrolls around. This is how maps apps are able to display extremely large images without running out of RAM.
Remember to also test your app on the supported device with the lowest amount of RAM. These devices will probably kill your app sooner than the newest devices.
At 16 MB, the image is too big for your iPod to handle. The app is crashing because it's loading the image directly, and asking for too much memory and the system has had to kill it.
You should create a smaller version of your image, in terms of both canvas size and image quality, for your app.
You could also incrementally load the image instead.
When you're testing on the simulator, it has full access to the gobs of memory on your computer. Way more than the 256MB available to your iPod 4 (there isn't such thing as an iPod 4.1).
Have a look at CATiledLayer
https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CATiledLayer_class/Introduction/Introduction.html
There is also an example Apple project using it
https://developer.apple.com/library/ios/samplecode/PhotoScroller/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010080
Good luck
I hope you are not loading this image on app launch. Application launch should be as light as possible. If launch time is more than 20 secs then iOS kills the app on its own. On simulator resources are much bigger and you may not see the similar behavior as that with device. You may also want to use the lazy loading technique to load lighter image first and then load heavy image only when needed while zooming, panning etc.
Having said that, one imortant aspect of [UIImage imageNamed] is that it caches all images loaded in that way and they never get unloaded, even if you dealloc the UIImage that was created! This is good in some circumstances (e.g. smallish images in UITableView cells), but bad in others (e.g. large images).
The solution is to use [UIImage imageWithData] which does not do this caching and which unloads the data when the UIImage is dealloc'd.
Another solution to this problem consists of either loading scaled version of the image (*1) or you have option to display it in full resolution with help of CATiledLayer(*2).
[UIImage imageWithCGImage:scale:orientation:];
CATiledLayer example
I'm working on a comic viewer app that downloads the latest content from a server. It downloads a single file regardless of the screen scale. What I'd like to do is make this file work correctly on both screens.
What's the procedure for this and how should I size the photos to fit? The trouble I'm having is that the graphics are retina screen size, but the iPhone doesn't interpret them as such. That means they're displayed twice as large as they should be.
CGImageRef cgImage = [myImage cgImage];
UIImage *retinaImage = [UIImage imageWithCGImage:cgImage scale:2.0 orientation:UIImageOrientationUp];
You could also change your image's dpi value in an image editor to make UIImage recognize the scale automatically.
Generally though, you should use lower-resolution images on devices that don't have a retina display because otherwise you're wasting precious memory.
For normal screen, you can resize them programmatically, by adding a scale category to the UIImage class by example, there are many code samples on stackoverflow like :
UIImage resize (Scale proportion)
For retina, you need to set the scale of the UIImage to let the ios know for what screen size it is used (you can set this with the initWithCGImage:scale:orientation: method of UIImage).
I have a 5MP image coming from the back camera. I want to downsize it to put it into an ImageView without having to wait too long (it take a logn time to the UIImageView to display a 5 MP picture). So I tried many methods to resize / resample the image to make it fit a just-screen resolution (retina one). But it take around 1 sec to downsize the image.
Would you know any optimised way to be able to resize this 5MP image to the retina 960 x 640 resolution as fast as possible, and at least at less that 0.7 sec on an iPhone 4 ?
This is my favorite blog post and code to resize images. I haven't benchmarked it but I know of no faster way.
http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
You're displaying an image in an UIImageView. I understand you want a version that is equal to the size of the screen. The way I would do it: display the original image in the UIImageView directly, this shouldn't take any noticeable amount of time.
Then in a background thread resize the same image down to the size you want and update the UIImageView as soon as that is done.
This gives you something on screen immediately while you generate an optimized version at the same time.
I don't know if you are using the original image for something else, but if you really only want the screen sized image it might be an idea to use an AVCaptureSession with a preset of AVCaptureSessionPresetHigh (see AVCaptureSession sessionPreset). This setting results in a more reasonably sized image, which might better suit your needs.
I'm trying to set the size of a UIImageView programmatically using:
[thisImageView setFrame:CGRectMake(x, y, image.size.width, image.size.height)];
I tried using x = 0, y = 0, but the UIImageView disappears.
More info on why I'm trying to do this. I'm trying to display a high-res image on an iPhone 4 without using the iOS 4 SDK (only using 3.2 SDK API calls). I have calls to see if I'm on an iPhone 4, so I'm trying to double the size of my UIImageView so that when I put the high-res UIImage in, it won't be grainy.
Not sure what there is so special about iOS. Just use the same method you use with 3.2: imageNamed: ... it will take care of everything as long as your images are named properly (yourimagename.png and yourimagename#2x.png). Nothing fancy in the iOS 4 SDK here, just give the size you use in points as before.
If this is not suitable in your case, can you explain why?
I'm trying to display a high-res image on an iPhone 4 without using the iOS 4 SDK (only using 3.2 SDK API calls)
Ouch. Don't do that.
Compile against 4.0, set the deployment target to 3.whatever.
When creating the image, check [UIImage respondsToSelector:#selector(imageWithCGImage:scale:orientation:)]. If it does, then you can use +[UIImage imageWithCGImage:scale:orientation:] to create a "scale 2" image, which will display correctly in a UIImageView on iPhone 4 (which has a "scale 2" screen).
If you really can't use the OS 4 SDK (you don't seem to state a reason why), then make the UIImageView smaller using the appropriate content mode (AspectFit/AspectFill/ScaleToFill), and set its frame to half the size in pixels (e.g. if the image is 100x100 px, set it to (CGSize){50,50}). This shrinks the image to half-size, but the double-res screen means you see it pixel-for-pixel.
I think your approach is wrong. iPhone 4 works with points instead of pixels, which makes doubling the size troublesome. 1 point on old devices equals 4 pixels on iPhone 4, but is still used as 1 point.
When you create the frame you're not actually giving the API the pixel dimensions, but the point dimensions.
I've got some image generating code that uses UIGraphicsBeginImageContext(), UIGraphicsGetImageFromCurrentImageContext() and UIImagePNGRepresentation() to do some drawing, then save it to disk as a PNG for later use.
Does UIImagePNGRepresentation() take into account scale? As in, if I have an image that is 20 points wide, will the resultant PNG be 20 pixels or 40 pixels?
Also, when I display these images, I use [UIImage imageWithContentsOfFile:] and [image drawInRect:]. Is there a way to hint to those methods to use higher resolution drawing?
Per the iPhone Application Programming Guide you should use UIGraphicsBeginImageContextWithOptions with a scale of 2.0 to create a properly scaled context for the iPhone 4.
As for the second part of your question, I believe you should save the resulting png with the #2x suffix on the base name (e.g., myImage#2x.png). Then when you load it back in using UIImage, its size and scale will be correctly set. Otherwise your image will be loaded at scale 1.0 and be twice as large (in points) as you expect it to be. This section of the same document goes into a bit of detail regarding high-resolution images for devices with high-resolution displays.