News

Welcome to End Point’s blog

Ongoing observations by End Point people

Supporting Apple Retina displays on the Web

Apple's Retina displays (on Mac desktop & laptop computers, and on iPhones and iPads) have around twice the pixel density of traditional displays. Most recent Android phones and tablets have higher-resolution screens as well.

I was recently given the task of adding support for these higher-resolution displays to our End Point company website. Our imagery had been created prior to Retina displays being commonly used, but even now many web developers still overlook supporting high-resolution screens because it hasn't been part of the website workflow before, because they aren't simple to cope with, and since most people don't notice any lack of sharpness without comparing low & high-resolution images side by side.

Most images which are not designed for Retina displays look blurry on them, like this:

The higher-resolution image is on the left, and the lower-resolution image is on the right.

Now, to solve this problem, you need to serve a larger, higher quality image to Retina displays. There are several different ways to do this. I'll cover a few ways to do it, and explain how I implemented it for our site.

Retina.js

As I was researching ways to implement support for Retina displays, I found that a popular suggestion is the JavaScript library Retina.js. Retina.js automatically detects Retina screens, and then for each image on the page, it checks the web server for a Retina image version under the same name with @2x before the suffix. For example, when fetching the image background.jpg on a Retina-capable system, it would afterward look for background@2x.jpg and serve that if it's available.

Retina.js makes it relatively painless to deal with serving Retina images to the correct people, but it has a couple of large problems. First, it fetches and replaces the Retina image after the default image, serving both the normal and Retina images to Retina users, greatly increasing download size and time.

Second, Retina.js does not use the correct image if the browser window is moved from a Retina display to a non-Retina display or vice versa when using multiple monitors. For example, if an image is loaded on a standard 1080p monitor and then the browser is moved to a Retina display, it will show the incorrect, low-res image.

Using CSS for background images

Doesn't the "modern web" have a way to handle this natively in HTML & CSS? For sites using CSS background images, CSS media queries will do the trick:

@media only screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .icon {
    background-image: url(icon@2x.png);
    background-size: 20px 20px;
  }
}

But this method only works with CSS background images, so for our site and a lot of other sites, it will only be useful for a small number of images.

Take a look at this CSS-Tricks page for some excellent examples of Retina (and other higher-res display) support.

Server-side checks for Retina images

A very efficient way to handle all types of images is to have the browser JavaScript set a cookie that tells the web server whether to serve Retina or standard images. That will keep data transfer to a minimum, with a minimum of trickery required in the browser. You'll still need to create an extra Retina-resolution image for every standard image on the server. And you'll need to have a dynamic web process run for every image served. The Retina Images open source PHP program shows how to do this.

Why we didn't use these methods

There is one reason common to all of these methods which made us decide against them: All of them require you to maintain multiple versions of each image. This ends up taking a lot of time and effort. It also means your content distribution network (CDN) or other HTTP caches will have twice as many image files to load and cache, increasing cache misses and data transfer. It also uses more disk space, which isn't a big problem for the small number of images on our website, but on an ecommerce website with many thousands of images, it adds up quickly.

We would feel compelled to have the separate images if it were necessary if the Retina images were much larger and slow down the browsing experience for non-Retina users for no purpose. But instead we decided on the following solution that we saw others describe.

Serving Retina images to everybody (how we did it)

We read that you can serve Retina images to everyone, but we immediately thought that wouldn't work out well. We were sure that the Retina images would be several times larger than the normal images, wasting a ton of bandwidth for anyone not using a Retina screen. We were very pleasantly surprised to find out that this wasn't the case at all.

After testing on a few images, I found I could get Retina images within 2-3 KB of the normal images while keeping the visual fidelity, by dropping the JPEG compression rate. How? Because the images were being displayed at a smaller size than they were, the compression artifacts weren't nearly as noticeable.

These are the total file sizes for each image on our team page:

Retina  Normal  Filename
 10K    9.3K    adam_spangenthal.jpg
 13K     13K    adam_vollrath.jpg
 12K     11K    benjamin_goldstein.jpg
7.6K    4.2K    bianca_rodrigues.jpg
 14K     13K    brian_buchalter.jpg
 13K     15K    brian_gadoury.jpg
7.5K    8.0K    brian_zenone.jpg
9.8K    6.6K    bryan_berry.jpg
 12K     11K    carl_bailey.jpg
6.9K     15K    dave_jenkins.jpg
 13K     13K    david_christensen.jpg
7.7K     21K    emanuele_calo.jpg
 16K     16K    erika_hamby.jpg
 13K     11K    gerard_drazba.jpg
 14K     14K    greg_davidson.jpg
 14K     12K    greg_sabino_mullane.jpg
 14K     15K    jeff_boes.jpg
 14K     12K    jon_jensen.jpg
 13K     12K    josh_ausborne.jpg
 13K     14K    josh_tolley.jpg
 13K     11K    josh_williams.jpg
8.9K    9.5K    kamil_ciemniewski.jpg
 13K     21K    kent_krenrich.jpg
 15K     12K    kiel_christofferson.jpg
9.9K     11K    kirk_harr.jpg
7.7K     13K    marco_manchego.jpg
 12K     13K    marina_lohova.jpg
 14K     11K    mark_johnson.jpg
7.3K     13K    matt_galvin.jpg
 15K     12K    matt_vollrath.jpg
6.6K     14K    miguel_alatorre.jpg
 13K     14K    mike_farmer.jpg
7.1K     19K    neil_elliott.jpg
9.9K    9.0K    patrick_lewis.jpg
 13K    5.6K    phin_jensen.jpg
 12K     14K    richard_templet.jpg
 12K    9.9K    rick_peltzman.jpg
 14K     13K    ron_phipps.jpg
9.7K     14K    selvakumar_arumugam.jpg
9.3K     15K    spencer_christensen.jpg
 12K     12K    steph_skardal.jpg
 15K     18K    steve_yoman.jpg
6.7K     15K    szymon_guz.jpg
7.5K    6.8K    tim_case.jpg
 15K     21K    tim_christofferson.jpg
9.3K     12K    will_plaut.jpg
 12K     14K    wojciech_ziniewicz.jpg
 12K    9.9K    zed_jensen.jpg

TOTALS

Retina: 549.4K
Normal: 608.8K

This is where I found the biggest, and best, surprise. The cumulative size of the Retina image files was less than that of the original images. So now we have support for Retina displays, making our website look nice on modern screens, while actually using less data transfer. We don't need JavaScript, cookies, or any extra server-side trickery to do this. And best of all, we don't have to maintain a separate set of Retina images.

Once you've seen the difference in quality on a Retina screen or a new Android phone, you'll wonder how you ever were able to tolerate the lower-resolution images. And at least for our selection of JPEG images, there's not even a file size penalty to pay!

Reference reading

7 comments:

Trevor Kjorlien said...

The solution seems to work well for @2x resolutions, but now that the iPhone 6 Plus (and others) effectively have @3x resolution, can the same technique be used?

Would you just increase the size of your image to 3x the size of non-Retina and do the same image compression?

Would the file size then be drastically increased, as not seen in @2x?

Phin Jensen said...

Trevor, that's a good point, and something we were planning on looking into.

After trying it on the same image I used in the blog post, I've found some interesting results.

17K gsm@2x.jpg
7.2K gsm@2x.webp
15K gsm@3x.jpg
9.4K gsm@3x.webp

With the JPEGs, I was able to make the image even smaller, as I was a able to drastically lower the quality (25 in the GIMP), but because the image is larger, it still looks good.

With the WebP file, I bumped the quality down (from 70 to 50), and it still looks fine. The file is slightly bigger, but there was still room to lower the quality.

Hope this answered your question.

friendshipchrch said...

In your post you say, "After testing on a few images, I found I could get Retina images within 2-3 KB of the normal images while keeping the visual fidelity, by dropping the JPEG compression rate."

How do you manipulate the jpeg compression rate? We are using Photoshop.

Steph Skardal said...

In Photoshop, there is a "Save for Web" option that you can use instead of "Save As" that allows you to toggle compression via Image options.

Rainer Reitz said...

Thanks. What exactly have you done? JPEG, 50%, is there a general rule or image by image?

Phin Jensen said...

I chose 50% as a compression rate after manually trying various rates with a few images on our site. It's not a rule set in stone, I just found that it works for the majority of the images. If I decide that an image looks bad, I'll manually change its quality.

Paul Howson said...

When serving retina images as img tag, how do you scale the image to 50%? Do you do that on a case by case basis?

PH