How to properly crop and resize for maximum crispness in retina screens. It wasn’t so obvious…
Since October 2010 I became an Xcode developer, starting to build apps for all the 3 Apple available display sizes as of today: iPad, iPhone “old” and iPhone “new” (retina). The funny thing is, I had 0 experience at all in the mobile development world.
Ok, once you cope with the nice dpi boost in retina screens (as an user), you need also to take care of things you never thought you had to deal with as a coder.
One of these (I thought) obvious things was creating images and contents for the typical iOS on-screen widgets (buttons, labels, etc…). Some may say, “you don’t need to, Cocoa is pretty enough”. Well, for an uncomplicated application, it might be the case, but if your workteam has a GOOD designer, soon you start to spend some time not only coding, but also using your favorite cropping/editing/trimming/resizing program (typically paint.net, gimp, photoshop, etc…) to port his nice and professionally designed interface elements.
When I started trimming images from the shiny designs my coworker created, I somewhat put myself onto “autopilot mode”. That was: You opened the retina master image file (with the full UI on screen), cropped the screen area you wanted, saved it as a @2x version filename, resized the same image file to exactly 50%, then saved it into another file (without the @2x in the filename), and then you took care (just typing it down onto a notebook) of the image dimensions you just created, for a latter usage.
This process shouldn’t be tricky except for quite a common case: Retina buttons you crop from the big UI image, sometimes have odd resolution (here “odd” meaning “not a multiple of 2″).
Imagine you crop a button (in a coarse way), then you refine the crop using some “trim transparent pixels from around the button” tool to just fit the image size to pixels with content (we don’t want to store silly transparent areas surrounding your 24/32 bit png files). Now, look at the new trimmed image size, and type down the resolution you’ve got now (for this article, we’ll suppose our @2x image file measures 157 x 97 pixels).
Then, we might save it to our disk with this filename “email@example.com”. Ok, done.
Now, we need to create the “non-retina” version, for older iPhones, which obviously can only show half the resolution on the same screen area… But wait a moment… The original image resolution wasn’t a multiple of 2, so what does “half” mean in this case? Well, it means “bad news”.
In our case we have to decide which will be our “half resolution”. If the original image was 157 x 97… Half resolution would be 78.5 x 48.5 pixels, but as everyone knows, screen resolutions “don’t have half pixels”, so we have to make an intelligent guess: Do we choose (78 x 48) or (79 x 49)?
Fast answer: Both will give us headaches (in terms of retina display), and I’ll show you why just below.
Anyway, let’s choose the first one and let’s downsize the original retina image (157 x 97) onto a 78 x 48 pixel version. We’ll call it “some_button.png”.
In terms of image quality, when downsizing, we NEVER have to worry (no matter the resolution), because we’re losing resolution no matter what we do. IT’S NOT OUR PROBLEM (today). We should anyway use interpolation, and then our half sized image will be recreated from the bigger resolution one, yielding more than satisfactory results. We would have a problem if we were doing it the other way back: upsizing from low-resolution to high-resolution… But that’s not the case.
OK, right now, we have the 2 necessary versions: “firstname.lastname@example.org” (157 x 97) and “some_button.png” (78 x 48). Now we should import BOTH files inside XCode. Then, when we launch Interface Builder, our new image button appears as a the “named resource” “some_button.png”, ready to be used.
One of the nice things about this process, is that even we imported both PNG files into XCode, we only have to use the “non-retina” filename inside our source files. Why? Because the one dealing with the “which version of the file should I choose” question will be iOS runtime, once our code gets executed on the physical device (iPhone, iPad, iPhone-retina). And even more: all this stuff gets transparently done without our intervention.
In more detail, if iOS detects it’s been run inside a retina iPhone, it automagically chooses to show the @2x version of our image, even in our code we just wrote something like: load my image named “some_button.png” (without the @2x) part.
Ok, once we’ve roughly described how iOS deals with retina images, our problems might start now:
iOS runtime image manager is able to pick high resolution images for being used on the render process, BUT iOS screen resolution, at least “logically” is not 640 x 940 (the new dpi boosted one), but 320 x 480, the “crappy” and old iPhone3 one… That is, in Interface Builder we DESIGN our UI with non-retina resolution, expecting that in runtime, iOS-retina will deal with the pixel doubling process.
And what does it have to do with our image files? Well, here comes the funny part.
I said we were forced to choose among 2 “wrong” resolutions. In our first example, I said: “lets choose the 78 x 48 version”.
If we do that, iOS will build a 78 x 48 container on screen, which will be filled with the 78 x 48 version of our “some_button.png” file. So far so good: our low resolution button gets 100% perfectly shown on screen, no distortion, no nothing. Just crisp pixels (reduced, but crisp).
But if we run the very same code on a retina display, the logical container size will be 78 x 48 (as I said, that is iPhone 3 size), BUT this container will be doubled before rendering anything into it. So our logical 78 x 48 container becomes a physical 156 x 96 container, once it appears onto the retina screen. And now, that’s the point where our @2x file (157 x 97) gets rendered onto the actual screen pixels, inside this 156 x 96 container. And as you might think, yes, this creates UGLY resampling of our image, even the rendering process is only shrinking our image file by just a mere pixel.
One might say: “really? is it SO ugly to the eye?” Without any doubt I say, YES! and I invite you to do the test.
(TO DO: I’ll create a demo webpage to show all this in the near future).
So, first option, downsizing to half size -0.5 pixels was a bad choice. Let’s see what happens if we take the +0.5 pixels version for our “non-retina” file size: 79 x 49 (of course, keeping the retina file size 157 x 97, as it was/is the original resolution our designer created for our button).
If we do ALL the process again, we arrive to the very same conclusion, that is:
-Our non-retina container will now be 79 x 49 pixels.
-Our physical retina container on screen will measure 158 x 98 (doubling the size of the logical container, 79 x 49), and again our big file in disk is still 157 x 97, and then when iOS retina places the image into the container, it will suffer a resample of 1 pixel bigger in both directions: Result? Visual distortion again, and our beautiful and crisp button becomes absurdly blurry.
So, is there a solution for all this? Yes it is (probably more than one), but the one I like most is thinking a bit BEFORE SAVING our first @2x retina image file:
-Instead of just blindly saving it, we should check its canvas size, and we will INCREASE ALL DIMENSIONS (width and/or height) SHOWING odd numbers. In our example, our 157 x 97 file will be upgraded onto a 158 x 98 file, JUST ADDING a row and a column of transparent pixels (on the right and bottom sides of it for example).
Will it visually interfere? Not really, (assuming we’re using PNG-32/24 files), because transparent pixels, as its name say, are transparent.
OK, the second half of my “workaround”, just after having saved the @2x version of our retina button, is halving our new image dimensions to achieve half the resolution with no decimal numbers. In our case we get a 79 x 49 small file. One might say: “hey, that’s the same than the second choice of the wrong examples you showed us”. Well, “yes”, resolution is the same, but built up from a different (1 pixel bigger) master file. If you compare carefully, even resolution is the same, contents won’t.
Are we done yet? Absolutely.
We only need to update our Interface Builder UIButton/UIImageView dimensions container, setting it to 79 x 49.
That’s it? Yes it is. Because whenever iOS retina will render this button on our live application, it will create a (79 x 49) x 2 container size, that is 158 x 98, and now our carefully created @2x image file will EXACTLY match this resolution, rendering a sharp and crisp 1:1 image file with the contents of the @2x some_button.png file we carefully created.
I hope my explanation is kind of understandable, I’m open to corrections and suggestions, so feel free to ask.
I just wanted to write all this personal experience, hoping people will be able to create proper image buttons from day 1, and not from day 100, as it happened to me :)
NOTE: This method I described, is 100% valid also if you want to create “universal mobile web pages”. That means, “with the same .html file you create both normal and high resolution webpages at once”. Safari under iPhone retina will treat ALL measurements as “low resolution”, except for image files. Feed your webpage with “high resolution” images only, and you’re done (your low resolution webpage version will resample bigger resolution images on runtime).
BTW, I’ve made some preliminary tests on my own, and even Android high resolution display devices treat document dimensions as “low-res”, even image files can be “high-res”, so you kill many birds with just one stone.