Simplest way to handle graphics for iPhone Retina / Not Retina and iPad
Hello All,
I'm working on a universal app right now and I want to have the fewest assets in my bundle as possible. For a simplified example lets assume I have a single asset in my bundle that is 512x512. When the app is running on the iPad I want it displayed as 512x512 at normal resolution (iPad doesn't have retina yet), when the app is running on the iPhone 4 I want it displayed as 512x512 retina resolution (so equivalent to a 256x256 area on a non-retina iPhone), and when the app is running on earlier iPhones I want it displayed as 256x256 regular resolution.
Do I have to make duplicate versions of my asset involving some shenanigans with @2x, or can I simply have that one asset in my bundle and detect with code whether it is Ipad/iPhoneRetina/iPhoneNonRetina and just size it accordingly by setting appropriate bounds on the UIImageView or OpenGL drawing area and such?
The coordinate system is in points, not pixels. So keep that in mind. On devices with retina display, the scale is 2.0 so 1 point equals two pixels. For the sake of simplicity, lets say you want to show an image that takes the full screen, 320x480 points (status bar removed). For non retina devices you would include an image, call it Photo.png with the size of 320x480, and one called Photo@2x.png with the size of 640x960. Retina devices, with 2.0 scale will know to automatically load the file with @2x suffix. On all devices the UIImage will report the size as 320x480, because it's in points, but the one on retina device will have a scale of 2.0 and others 1.0.
If you don't include @2x images, iOS will load the regular images and scale them to fit, but it wont look as good.
Thanks for responding. I am aware of the method you suggest; this is exactly what I want to avoid having to do (because in actually I have many large assets and including both regular versions and @2x puts me over a file size threshold that I do not with to go over). Hence I am trying to find a a way to only include the larger versions of the files and have them shrunken down when on appropriate devices (thus instead of needing the Regular Size AND @2x assets in my bundle would only need the larger sized assets).
What I REALLY want to do is only incorporate the largest version of my assets in my bundle (so in the future that would be say 1024x1024 versions for iPad Retina (of the future) ). Then when the app is run for the very first time (can keep track of this with an NSDefault), run some code that goes through the images in the bundle and saves them down to the size appropriate for the device. So, for example my future universal app comes bundled with all my assets as 1024; but on first run it detects that the device is iPhone Retina so it goes through and resaves all the files as 512 (just on that first run) to be used by the app forever more (so the RAM usage is appropriate).
This would mean you only ever have to include one set of assets in your bundle (the largest). Of course it might take a few extra seconds when that app is first loaded to do the bulk resize, but I think its a streamlined approach compared to 3 (or in the future 4) differently sized copies of all your assets in your bundle.
I haven't converted my app to support Retina or iPad yet, but I am planning to soon, and have been thinking it over for a long time. I have many of the same concerns about it as you, and have thought of the following ideas to deal with it. I haven't tried them yet, though.
You said you wanted the following for your sizes:
iPad (non-Retina): 512 x 512
iPhone (Retina): 512 x 512
iPhone (non-Retina): 256 x 256
While you have 3 screen resolutions to accommodate in this scenario, you only have 2 desired image resolutions as far as what you have mentioned. Considering that, I do see 2 possibilities which might allow you to save space.
Possibility 1: Regardless of the device's screen resolution, your desired image resolution for the iPad and the iPhone Retina are both 512 x 512 as far as you have mentioned. That means you could potentially use the same image file for both the iPhone Retina and the iPad, saving space. I don't know if it will work, but you might try naming the file whatevernameyouwant@2x.png so that the Retina device will recognize it as the proper file to use and then simply use the @2x.png file over again for your iPad code rather than having a separate but identical file without @2x in the filename. You would still have to have a separate file for the 256 x 256 pixel image for the non-Retina iPhone / iPod though. So, in this scenario, you can have 2 files instead of 3, but cannot get it down to only 1 file for each image.
Possibility 2: Do exactly the same as above, but instead of having a separate 256 x 256 pixel file for the non-Retina iPhone, simply use the same 512 x 512 file with the @2x.png filename that you are already sharing between the Retina iPhone and the iPad. If you tell your code specifically to use that filename with the @2x.png code for a non-Retina device, it may allow you to, since you are specifically telling it to. This may allow you to have only 1 file, rather than 2 or 3. The downside of this method is that even if it does work, and I haven't tried it, so I can't guarantee you that it will, the non-Retina iPhone / iPod will automatically scale down the 512 x 512 image to fit within the point-based frame that you have allocated for it, and the scaling will almost certainly make the image grainy looking. If you want to use this method, but XCode turns out not to allow you to use the @2x.png filename on the iPad or the non-Retina iPhone, you could also try having only the 1 512 x 512 file and giving it a normal non-@2x.png filename, and it would still do the same thing anyway, because it is already the larger resolution no matter what you name it, so you would still only have the downside of it being scaled down on the non-Retina iPhone.
If neither of those ideas work for you, you might also consider using something like the GNU Image Manipulation Project (free) or Adobe Photoshop ($$$) to scale down the quality of your photos in .jpeg format, for example, to a quality that has a smaller file size but still a high level of quality when you look at it, and then convert it back to .png or whatever file format you prefer to use. I use the GNU Image Manipulation Project, and have had wonderful results with it. You can download it for free online for any major operating system. Some people prefer Photoshop though. It's all just a matter of preference and what you're used to.
I am not sure how any of these ideas would work with OpenGL, but maybe you can modify one of them to work with it.
Also I am using the images as OpenGL textures (so not using UIImage imageNamed which autograbs @2x images)
If you're using the images as OpenGL textures and don't ever load them into UIImageViews, just save the larger textures and scale them as needed for the target device.
I haven't used OpenGL on iOS, so I don't know if it's supported in OpenGL ES, but at least on the desktop, mip-mapping makes handling large textures gracefully quite easy. You can have OpenGL generate smaller versions of your textures for rendering at different sizes. It only takes a couple of calls to set it up, and uses less memory that you would think.
Check out this password generator app that shows various techniques including using a data container singleton object to share data between objects in your project.
If you're using the images as OpenGL textures and don't ever load them into UIImageViews, just save the larger textures and scale them as needed for the target device.
Hey Duncan. Actually I'm kind of using a combination: most of my assets are displayed via openGL, but some buttons and core graphics type things are overlayed at points. So i'm just looking for kind of a nice universal way to handle all my assets. To me the whole @2x thing seems unnecessary. So my gameplan is basically this so far:
1) only include the largest version of all assets needed in my bundle (no @2x copies or anything)
2) at application startup detect what type of device it is by using
code like:
Code:
CGSize PhysicalPixelSizeOfScreen(UIScreen *s) {
CGSize result = s.bounds.size;
if ([s respondsToSelector: @selector(scale)]) {
CGFloat scale = s.scale;
result = CGSizeMake(result.width * scale, result.height * scale);
}
return result;
}
and set a variable based on this.
3) based on above variable have branching cases throughout code to display things properly for each device
Does this sound like a proper approach? I have a follow up question, but first let me see if I'm on target with my thinking
I find that most of the UIKit classes that take images will automatically scale down an image to fit into a view or control. So I just put the higher resolution image in and forget about it. And CGContextDrawImage also seems to scale well if you need to draw an image yourself with Quartz.
Now, others have claimed their images don't scale well, so test it with a few of the images you are using before you get too far into it. You might find a few images are worth making @2x version of.
Yeah; I think I just need to play around some more. To that point; do you know if there is a way to set the simulator to an earlier generation iPhone (pre retina)? My only options seem to be iPad or iPhone 4; do I have to set my target back to an earlier setting or something (my app is new so it only has the newest options defaultly available); thanks.
I'm using the latest XCode4, and the iPhone simulator show iPhone and iPhone(Retina) as 2 choices under Hardware->Device.
Thanks; I see that I can change that in the Simulator and then just open the app; but is there away to get that right from xCode (so I can build it and see the debugger output for the different versions?). For instance my only build options are iPhone 4.3 and iPad 4.3 (so I can see debugger output). Now, once the simulator is opened I can change the device to to pre-retina iPhone and open the app; but no debugger output.
I also just realized that a UIView's frame is specified in points (not pixels); so for instance if my base asset was 512 x 512 and it was in a UIImage called displayImage:
It's annoying, you have to change to iPhone(Retina) first in the Simulator, then go back to XCode to debug again. I get debugger output either way as long as I pick the device in the simulator first.
It's annoying, you have to change to iPhone(Retina) first in the Simulator, then go back to XCode to debug again. I get debugger output either way as long as I pick the device in the simulator first.