Retina Image Replacement for the New iPad

Now that the New iPad is out, and there have been various clues that perhaps future Apple screens on other devices will have a similar resolution, handling high resolution images has become an important issue for many designers.

Below I'm going to outline four different ways to replace your images. I'm using PHP and jQuery for the example code as this is a good baseline for most developers. The logic of each is the key part, so porting ideas to other languages shouldn't pose much of a problem.

Whats the aim?

There are two types of image - ones included via an img element, and CSS background images. For ones using img, you need to specify the width and height, then provide an image that has double the size for each dimension. For CSS background images you need to use the CSS3 property background-size to scale the image correctly. This can be done simply using media queries - something like:

#image { background: url(image.png); } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2) { #image { background: url(image@2x.png); background-size: 50%; } }

In the way that Apple are doing this, I'm assuming that any image you want to be high res has a copy with @2x added before the file extension. To see what I mean, check out the images from the Apple site: Normal and Hi Resolution.

Technique 1 - everyone gets hi res!

OK, so this isn't the best way, but it's very easy. Simply replace:

<img src="image.jpg" alt="" width="200" height="100" />

with

<img src="image@2x.jpg" alt="" width="200" height="100" />

Simple

Easy control over what is hi res and what isn't.

Very wasteful for non retina devices

Page load time will be slow

Technique 2 - check the pixel ratio

Rather nicely, there is a property exposed by the browser called devicePixelRatio. This tells us what's going on. On retina devices, it's got the value of 2.

This means that we can just insert our images like normal, like this:

<img src="image.jpg" alt="" width="200" height="100" />

Then we can use jQuery like this:

// Set pixelRatio to 1 if the browser doesn't offer it up. var pixelRatio = !!window.devicePixelRatio ? window.devicePixelRatio : 1; // Rather than waiting for document ready, where the images // have already loaded, we'll jump in as soon as possible. $(window).on("load", function() { if (pixelRatio > 1) { $('img').each(function() { // Very naive replacement that assumes no dots in file names. $(this).attr('src', $(this).attr('src').replace(".","@2x.")); }); } });

If desired, you could add a class onto the images you want to be replaced, and only replace those by modifiying the selector.

Pretty simple

Only works on devices that need it

User gets the low res version quickly, then the high res later

Images load twice

In its current form will replace images for iPhone 4/4S and iPod Touch, which may not really be required.

Technique 3 - check the pixel ratio, then check if the files exist

Apple make use of a head request for every image before they try to replace it - this seems rather wasteful though. A better way might be to write a PHP script that returns a list of all the @2x images, then only replaces them if they are in the list.

Write some php like this,

<? function is_in_string($haystack, $needle) { if (strpos($haystack, $needle) !== false) { return 1; } else { return 0; } } function get_dir_contents($web_directory) { $directory = $_SERVER['DOCUMENT_ROOT'].$web_directory; if(file_exists($directory)) { $myDirectory = opendir($directory); while($entryName = readdir($myDirectory)) { if (is_in_string($entryName, "@2x")){ $dirArray[] = $entryName; } } return $dirArray; } } header('Content-type: application/json'); echo json_encode(get_dir_contents("/my/image/directory")); ?>

Then just check if the new image source is in that json before replacing. That script will return something like:

["image1@2x.jpg","image2@2x.png","image3@2x.jpeg"]

So, if we update our JS to be this:

// Set pixelRatio to 1 if the browser doesn't offer it up. var pixelRatio = !!window.devicePixelRatio ? window.devicePixelRatio : 1; $(window).on("load", function() { if (pixelRatio > 1) { $.getJSON('/that/php_file.php', function(data) { $('img').each(function(){ var $$ = $(this); // check it's not an external link if ($$.attr('src').lastIndexOf("http://", 0) !== 0) { var imgIndex = data.indexOf($$.attr('src')); // is the image in the JSON? if (imgIndex >= 0) { $$.attr('src', data[imgIndex].replace(".","@2x.")); } } }); }); } });

Only replaces images that exist

Only one additional HTTP request, that can be cached

1 additional HTTP request

Conclusion

After some investigation, it seems that technique 3 combines the best of everything, and is the one to use! How are you approaching this? Do you have a better technique?