I am working on a universal app with hundreds of background images. To save disk space and prevent further duplication and disk spamming I want to reuse the non-retina #1x iPad images as retina #2x iPhone images.
Example:
background125_iPad#2x.png
background125_iPad.png
iPhone 4 and 5 have a different aspect ratio so I will scale the 1024x768 images to fit.
But the problem is, if I use this on iPhone 5:
UIImage *img = [UIImage imageNamed:#"background125_iPad.png"];
then iOS will try to be smarter than me and pick the huge memory monster #"background125_iPad#2x.png" version.
Is there a way of saying: "iOS, look. I am smarter than you. I want that you load this file. And I really mean this file. THIS one. And treat it as if it was a #2x version with a scale factor of 2." such that it really loads the requested "background125_iPad.png" file, but then UIImageView acts as if it had 512 x 384 points (= 1024x768 px)?
I assume UIImage imageNamed is not the way to go then?
I don't think you can turn off that functionality.
But you can always do that:
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"background125_iPad" ofType:#"png"]];
UIImage *scaledImage = [UIImage imageWithCGImage:[img CGImage]
scale:[[UIScreen mainScreen] scale]
orientation:img.imageOrientation];
That will not add automatically device specific postfixes.
I would recommend encapsulating that into UIImage category for simpler usage :)
To load the image exactly as specified and get the scale factor right, this should work:
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"background125_iPad" ofType:#"png"]];
img = [UIImage imageWithCGImage:[tmpImage CGImage]
scale:[[UIScreen mainScreen] scale]
orientation:UIImageOrientationUp];
Thanks to Grzegorz for the -imageWithContentsOfFile: and [[UIScreen mainScreen] scale] hint.
Related
I have a UIImage that I'm loading into one of my app's views. It is a 10.7 MB image, but when it loads in the app, the app's resource usage suddenly jumps by 50 MB. Why does it do this? Shouldn't memory used increase by only about 10.7MB? I am certain that loading the image is what causes the jump in memory usage because I tried commenting these lines out and the memory usage went back to around 8 MB. Here's how I load the image:
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:self.backgroundImageView];
If there is no way to decrease the memory used by this image, is there a way to force it to deallocate when I want it to? I'm using ARC.
No, it should not be 10.7MB. The 10.7MB is the compressed size of the image.
The image loaded in to the UIImage object is a decoded image.
For each pixel in the image 4 bytes (R,G,B and Alpha) are used, therefore you can calculate the memory size, height x width x 4 = total bytes in memory.
So the moment you loaded the image into memory it will take up lots of memory, and since a UIImageView is used to present the image and as a subview the images is kept in memory.
You should try and change the size of the image to match the size of the iOS screen size.
As #rckoenes said
Don't show the images with high file size.
You need to resize the image before you display it.
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView =[self imageWithImage:display scaledToSize:CGSizeMake(20, 20)];//Give your CGSize of the UIImageView.
[self.view addSubview:self.backgroundImageView];
-(UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
//UIGraphicsBeginImageContext(newSize);
// In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
// Pass 1.0 to force exact pixel size.
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
You can do one thing. if you can afford 50 MB for this image. If this image with 10 mb size is that much critical to your application then. you can release it just after its use to keep memory usage in control.
As you are using ARC there is no option for release but you can do this
#autoreleasepool {
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:self.backgroundImageView];
}
using autoreleasepool it will be sure that after this autoreleasepool{} block memory for fat image will be deallocated. making your device RAM happy again.
Hope it helps !
I display a retina image (with #2x.png extension) using:
myImage = [UIImage imageNamed:#"iPhoneBackground#2x.jpg"];
UIGraphicsBeginImageContext(myImage.size);
[myImage drawAtPoint: CGPointZero];
myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imageView = [[UIImageView alloc] initWithImage:myImage];
NSLog(#"Dimension:%f x %f",myImage.size.width,myImage.size.height);
[self.view addSubview:imageView];
However the image is displayed twice its size on the retina simulation. Image and simulator both have 640 x 960 resolution, so I would expect the image filling the screen.
I know there are other ways than CGContext to display an image, but that's the way I would need to other purposes in my code.
Any idea why I have this definition issue ?
Don't use #2x suffix
From apple documentation:
The UIImage class handles all of the work needed to load
high-resolution images into your app. When creating new image objects,
you use the same name to request both the standard and the
high-resolution versions of your image. For example, if you have two
image files, named Button.png and Button#2x.png, you would use the
following code to request your button image:
UIImage *anImage = [UIImage imageNamed:#"Button"];
You do not need to explicitly load a retina image, #2x will be automatically appended to the image name if the device has a retina display.
Change your UIImage code to: myImage = [UIImage imageNamed:#"iPhoneBackground.jpg"];
I use bg.png for iPhone, bg#2x for both iPhone retina and iPad, bg#4x for iPad retina.
Here is the code i wrote: (in Helper.m)
+ (UIImage *) imageNamed:(NSString *)name
{
name = [name stringByReplacingOccurrencesOfString:#".png" withString:#""];
UIImage *image;
if (IS_IPAD) {
if (IS_RETINA) {
image = [UIImage imageNamed:[NSString stringWithFormat:#"%##4x.png", name]];
if (image) {
return image;
}
}
return [UIImage imageNamed:[NSString stringWithFormat:#"%##2x.png", name]];
}
else {
if (IS_RETINA) {
image = [UIImage imageNamed:[NSString stringWithFormat:#"%##2x.png", name]];
if (image) {
return image;
}
}
return [UIImage imageNamed:name];
}
}
The file is correct, but the size of image is wrong.
If the file is automatically picked by the system (use [UIImage imageNamed:#"bg.png"]), then on iPhone retina, the size is still 320x480 (1 point = 4 pixels).
but if i use [Helper imageNamed:#"bg.png"], the size is 640x960. (1 point = 1 pixel)
So anyway to correct the size?
On a retina device
[UIImage imageNamed:#"bg.png"]
searches for bg#2x.png first. If that image exists, it is loaded and the scale property
of the image is automatically set to 2.0. On the other hand,
[UIImage imageNamed:#"bg#2x.png"]
also loads that image, but uses the default scale = 1.0.
If you have to use a custom loading mechanism, you have to adjust the scale factor.
Since scale is a read-only property of UIImage it cannot be set directly. One method
I know of is
UIImage *tmpImage = [UIImage imageNamed:#"bg#2x.png"];
UIImage *properlyScaledImage = [UIImage imageWithCGImage:[tmpImage CGImage]
scale:2.0
orientation:UIImageOrientationUp];
Why reinvent what Apple already provides? The imageNamed: method already supports the ability to load iPhone or iPad specific images by using the ~iphone and ~ipad suffixes.
To get an iPad specific retina image you just name it bg#2x~ipad.png.
To get an iPad specific non-retina image you name it bg~ipad.png.
The problem you have with your code is due to the non-standard naming conventions, the image is loaded with the wrong scale.
Update: based on additional information in the comments, a better approach to solve this issue is to replace the use of the UIImage imageNamed: method with calls to the UIImage imageWithData:scale: method. This provides better memory management as well as the ability to specify the proper scale for the custom naming conventions and image sharing between iPhone retina and iPad non-retina.
I have a crash which only occurs on 4S (not on 3GS). I am doubting its because of #2x. Basically I get raw bytes of image and manipulate. Here's the question I have.
I load a image as mentioned in the sample code below. At the end, uiWidth should be 2000 and cgwidth should be 2000. Correct? (Would it still be true if image is loaded from camera rolls? Or its autoscaling and uiWidth will be 4000?)
//test.jpg is 2000x 1500 pixels.
NSString *fileName = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
int uiWidth = image.size.width;
CGImageRef cgimg = image.CGImage;
int cgWidth = CGImageGetWidth(cgimg);
Thank you for your help.
The size reported by UIImage is in points, not pixels. You need to take into account the scale property of UIImage.
In other words, if test.jpg is 1000x1000 then UIImage.size will report 1000x1000. If test#2x.png is 2000x2000 then UIImage.size will also report 1000x1000. But in the 2nd case, UIImage.scale will report 2.
CGImageGetWidth reports its width in pixels, not points.
I am going to update my app for retina display.For this purpose I have set image name with #2x suffix.But iPad 3 not load hd graphic. I also try with suffix #2x~ipad but no any luck.
How can I set retina graphic for IPad3?.
#2x~ipad postfix should do the trick, there might be something else wrong.
When you load the image, leave off the suffix. For example, do
UIImage *image = [UIImage imageNamed:#"myImage"];
and not
UIImage *image = [UIImage imageNamed:#"myImage.png"];