Thursday, September 20, 2012

Spriting Images on High DPI ("Retina") Displays

This post assumes you're already familiar with the concept of image spriting, and are just getting into this same concept on high DPI displays, like Apple's Retina® displays.


It's actually very easy to do sprites that can work across both regular and retina displays as long as you follow these basic tips:
  1. Develop your low DPI sprite image first, and don't try to combine your high DPI and low DPI graphics into the same image. Trust me, I've tried and cried.
  2. Blow up your low DPI sprite image to 2x it's normal size, and save this into a new file. (I recommend using "nearest neighbor" rather than any of the other types of the resizing algorithms, so that you get hard edges as it makes the next step easier.)
  3. Generate your high DPI graphics however you intend to do that. Each graphic should be EXACTLY 2x the size of the original low DPI graphic when you're finished with it, even if it means pushing pixels around to get things just right.
  4. In a new layer on top of the original double-sized low DPI graphics, start overlaying your new high DPI graphics so that everything lines up perfectly over the low DPI versions. EXACTLY. When finished, hide your low DPI layer, and save your high DPI image in whatever format you prefer. I follow Apple's iOS convention of using the same file name with @2x appended (before the file extension). So my low DPI might be named sprite.png, and my high DPI will be named sprite@2x.png.
  5. Establish your baseline CSS like this example below. The 50px and 100px shown is the width and height of your image, respectively. NOTE: Your high DPI CSS will inherit this same exact background size. You should NOT double this or change it in any way for high DPI.

    .sprite
    {
        background-image: url(/images/sprite.png);
        background-size: 50px 100px;
    }
  6. For any element you want to be sprited, you'd specify the class you defined above. AND in addition, you would specify a separate class that you can define in CSS to position the background image to fit properly. In the example below, I have a clock icon that is 10px wide and 10px tall, and a stop sign that is 20px wide and 10px tall. In my sprite, my clock is oriented at the top/left of the image, and my stop sign is 10 pixels BELOW it, thus the offset given in the background-position for the stop sign has the X at 0, and the Y down at -10px.

    .clock
    {
        height: 10px;
        width: 10px;
        background-position: 0 0;
    }

    .stopsign
    {
        height: 10px;
        width: 20px;
        background-position: 0 -10px;
    }

  7. Now your table is set. To turn on high DPI support, all you would need to do is specify your new background-image. All the other styles defined above will be inherited, and are exactly the same in high DPI, because the browser treats all of the measurements as virtual pixels, which it will double internally. So my high DPI css for the examples above would be:

    @media
    only screen and (-webkit-min-device-pixel-ratio : 1.5),
    only screen and (min-device-pixel-ratio : 1.5)
    {
        .sprite
        {
            background-image: url(/images/sprite@2x.png);
        }
    }
That's it! If you run into any alignment problems between your low and high DPI views, you probably didn't follow one of the steps above correctly. Make sure your high DPI sprite image is exactly doubled in every way, including the placement of each icon graphic within the sprite. Make sure you have not accidentally changed the background-position, height, width, or background-size in your high DPI css area, as it is not necessary and will instead cause you problems!