Menu
Carl Camera

PixelFill Images

As screen resolutions become denser, websites face the challenge of filling pixels without wasting bandwidth.

The Rise of Responsive Design

One of the more exciting advances in web design these past few years has been the advent and adoption of responsive design. As more modern browsers became prevalent, web designers were able to create layouts that could handle significantly different screen sizes in an elegant manner. Styles can now be defined to be invoked only when certain screen dimensions are present.

A driving force behind this trend is the explosive growth of mobile user agents. That is, mobile web browsers: iPhones, iPads, Androids, and Windows Phones. A significant chunk of our website visitors come through mobile user agents. When desktop browsers ruled the roost, we designed our sites to 800 pixel widths, then 1024 pixel widths. With the proliferation of different screen sizes and multiple orientations of those screens, we have to adapt our sites to accommodate them.

This post deals with one popular trend in this regard: scaling photographs into variable-width img tags.

The Rise of Pixel Densities

A second, more subtle change is also impacting this space. Pixel densities have increased substantially. Our stalwart Dell monitors are still hanging out at 96ppi (pixels per inch) resolutions, but our phones are pushing the boundaries of pixel densities. The first Retina® iPhones stunned the world with their display density of 326 ppi. That seems tame now. Newer phone models such as the Nokia Lumia 930 sport 1080p displays at 441 ppi.

Which brings us to the concept of a CSS pixel. Those high-density phone screens might have 640 physical pixels on their short edge, but the phone browser is reporting only 320 pixels width to CSS media queries and JavaScript libraries. One CSS pixel is being physically represented on the high-density screen as four pixels.

Simply stated, if you declare an img as 300 pixels square and place a 300×300 pixel photo into it, the phone will place it into a 600×600 pixels square. Or more.

The problem with static image solutions is that 99% of the time, we are sending more than we need over the wire.

This results in a non-optimal user experience. The screen is capable of displaying a 600×600 pixel photo but we are sending an image 75% smaller. The scaling algorithms do the best with what they're given, but the effect is a non-optimal blurring of the photo.

Enter Responsive Images

Our challenge is finding a way to serve up crisp images to visitors whose screens can handle them without penalizing visitors whose screens can not. Several different strategies are in use or proposed to address this challenge. Rather than rehash all the different strategies I'll just summarize them here:

  1. Create multiple image sizes and quality of each and every image you serve up then use additional HTML markup and/or JavaScript to determine which one to send to the visitor's browser
  2. Add a bit of JavaScript to your webpage, adjust the PHP markup and your .htaccess file and then configure your Apache server to hijack all the image requests and send a resized image.
  3. Use a plug-in or third party service to accomplish what is outlined in #1 or #2

Okay. Who's excited for any solution that requires you to create 3, 4, or 6 different sizes for each and every image on your site? I didn't think so. And even then, we would send more data across the wire than we need to. If we are limited to 1× 2× and 4× sizes, for optimal user experience, we will need to send the 4× image for a pixel density of 2.5.

The problem with static image solutions is that 99% of the time, we are sending more than we need over the wire. If we are going to fill physical pixels, we will be sending the image larger than what we need up until that singular width where it fits perfectly, then we send the next larger image until it fits perfectly.

No client-side-only solution will ever solve the filled-pixel problem because the client does not have the capability of adjusting the image size coming in over the wire.

Even then, when we send the same-sized image to a client browser, we are either wasting bandwidth for standard pixel density displays or giving the high-density display a blurred image. An optimal solution would be to send exactly the size image to fill the image for that screen at that responsive media query state.

The PixelFill Solution

No client-side-only solution will ever solve the filled-pixel problem because the client does not have the capability of adjusting the image size coming in over the wire. There must be some server-side processing to resize the image for each and every user agent request. The optimal solution is for the client to determine the exact size needed at the time of the request, and to have the server send just the right size image over the wire to precisely fill those pixels.

That's what I've done with this site using

  • An open source JavaScript snippet from Tyson Matanich to determine pixel density
  • An open source image resizer from Imazen for my IIS server to resize an image based on request parameters
  • A jQuery snippet of my own to determine image sizes and request a perfect-filled version of the image for that visitor

This solution more closely resembles the Adaptive Image / Apache solution, but it adds a bit of front-end refinement to accommodate the increased pixel densities found on modern mobile devices. It sends a precisely-sized image to fill a precisely-calculated image slot.

Setup was simple: I added three files to the bin directory of my website, added a section to the web.config file, and adjusted markup for my img tags. I'm sure there are similar resizers for other web server platforms.

Before
<img src="./files/image.jpg/>
After
<img src="ratio.png" data-src="./files/image.jpg.ashx?height={h}&width={w}&mode=crop/" >

Routing rules in IIS would allow a more elegant URL structure. The URL structure shown is the default for the Imazen resizer. This bit of jQuery will adjust the data-src attribute for the optimally-sized image

$(function () {
    $("img[data-src]").each(PixelFill);
});

function PixelFill() {
    var $this = $(this);

    var pixel_density = window.getDevicePixelRatio();
    var h = $this.height() * pixel_density;
    var w = $this.width() * pixel_density;

    var data_src = $this.attr("data-src");
    data_src = data_src.replace("{w}", Math.floor(w));
    data_src = data_src.replace("{h}", Math.floor(h));
    $this.attr("data-src", data_src);

    $this.attr("src", $this.attr("data-src"));
    $this.removeAttr("data-src");
    $this.animate({ opacity: 1 }, 1500);
}

Once the src attribute is adjusted, the Resizer will return a perfectly pixel-filling sized version of your original image custom tailored to the web visitor's display. I should also mention the importance of the ratio.png file. Since our img width is declared as 100%, the browser will take that placeholder transparent png and stretch it to accommodate the space to be filled. If ratio.png is square, the image returned will be square. If ratio.png is 16:9 then the returned image will be 16:9. On my site, the ratio is 5:3. If you need different ratios for different slots, then you'll need to create different placeholder transparent pngs.

A single large (in my case an enormous 7712×4352 two megabyte) image is used for every resizing request. The resizing is done where it belongs - on the server for optimal transmission over the wire or cell network to the end user.

Nothing is perfect

Yes, the markup is inelegant and yes image ratios need to be determined beforehand. This also does not address the Art Direction problem. Furthermore, you have to be satisfied with the quality of image resizing done for you. There is a loss of control involved. This does, however, solve the problem where you want to provide the optimal image to the visitor without bandwidth waste.

The biggest downside is the amount of processing and the sheer volume of distinct images that will be sent from your server. If your site is fully adaptive -- in other words your images could scale to hundreds of different sizes on a single browser at a single pixel density, then you could be serving up thousands of variants of each image. The number of different versions would overwhelm any attempt to cache them.

The cache problem on my site is mitigated by the responsive template that I'm using. The Gridlock template "snaps" to different column widths rather than allow pure fluid widths. So I'm thinking and hoping that the number of variants will be manageable. Perhaps a follow-up post can address that issue.

Conclusions

With every web technology there are trade-offs. PixelFill, as an image-serving solution, is heavily biased toward end-user experience. It provides the highest-quality image possible for the specific screen and image slot with little to no bandwidth waste. The trade-off is extra server-side computation to serve up the many different custom-sized images. Unlike the Apache solution above, however, PixelFill provides flexibility in choosing which images are pixel-filled and which are not. PixelFill would be appropriate for medium-sized websites where image fidelity is important - perhaps a fashion site or photography studio.

Let me know your thoughts.

Retina display® is a registered trademark of Apple, Inc.

Comments