🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Thread View: gwene.org.apache.planet
1 messages
1 total messages Started by unknown Tue, 28 Aug 2012 13:34
Serving Retina Ready Images
#3991
Author: unknown
Date: Tue, 28 Aug 2012 13:34
108 lines
12391 bytes
<div xmlns="http://www.w3.org/1999/xhtml"><p>Looking at photography on Retina-class displays is an amazing experience. To call it a game-changer might be trite, but its true. Ever since my <a href="http://duncandavidson.com/blog/2012/03/photography_on_retina">first experiment with displaying double-resolution images</a>, I’ve wanted a general solution on my site to serve up the highest appropriate resolution file for any particular photograph I display. I knew it’d be a bit of work to sort out, so I punted it for a while.</p>

<p>When the Retina MacBook Pro arrived and I saw more examples of high-resolution work and how they compared to standard-resolution, I knew it was time to get into gear. I dove in to the current state-of-the-art and started working on how to implement a solution for this on my site. After a fair bit of experimentation and a few missteps, I’m there.</p>

<p>Here’s an example that will take full advantage of resolution a Retina display, as long as it’s not an iPad in landscape orientation. (There’s an <a href="http://duncandavidson.com/blog/2012/03/webkit_retina_bug">image size limitation in WebKit on iOS5</a> that will hopefully be lifted in iOS6. For now, if you’re viewing on a Retina iPad, please view this page in portrait orientation.)</p>

<div class="photo-holder">
    <img src="http://d1me7ugkk83g18.cloudfront.net/blog/2012/08/fira--jdd-m9gjav-400.jpg"/>
</div>


<p>To display this photograph using the most appropriate resolution for your screen, I’m combining a third-party client-side JavaScript library with a few bits of backend server logic I wrote. It’s definitely not a final solution, nor is it even necessarily the best approach possible today. But it’s <em>workable</em> one and I’d like to it with you.</p>

<p>First, let’s talk about the client side.</p>

<h2>Image Sets and Picturefill</h2>

<p>The quick and easy example of how to display a double-resolution image is to create an image that’s twice as big as the web canvas space for it and then constrain it with height and width tags. Something like this in HTML:</p>

<p><code><img src="myimage_1200_x_600.jpg" width="600" height="300"></code></p>

<p>Sure, it gets the job done, but there are two immediate problems. First, it forces every browser to download the double-resolution image—which contains four times as many pixels and is typically four times as big in terms of data transfer—even if they can’t use it. Second, serving those large images is even more ridiculous for smaller clients, like phones and tablets. It demos well, but it’s a non-starter.</p>

<p>Your next step might be, like mine was, to start hacking a bit of JavaScript to try to display the right image for any particular browser. As you dig into the possibilities, however—such as emulating what Apple does with JavaScript to serve double-size images—you rapidly find yourself wishing that you could just define a set of image sizes and let the browser sort it out. Big images for Retina-class laptop displays, smaller ones for non Retina-displays, and appropriate ones for all the mobile clients out there. Something like what you can do with <a href="http://blog.cloudfour.com/safari-6-and-chrome-21-add-image-set-to-support-retina-images/">CSS background image sets</a>, but for foreground images.</p>

<p>Enter <a href="https://github.com/scottjehl/picturefill">Picturefill</a> by Scott Jehl (with <a href="https://twitter.com/grigs/status/235436079166140416">thanks to Jason Grisby for the pointer</a>). It’s a compact  bit of JavaScript which supports a syntax that mimics the <a href="http://www.w3.org/community/respimg/wiki/Picture_Element_Proposal">Picture Element Proposal</a>. In a nutshell, it uses CSS media queries to sort out the most appropriate image to use out of a set. Here’s an example of what a Picture fill image set looks like in HTML:</p>

<pre><code><div data-picture data-alt="Alt text for photo">
  <div data-src="default.jpg"></div>
  <div data-src="small.jpg" data-media="(min-width: 480px)"></div>
  <div data-src="medium.jpg" data-media="(min-width: 768px)"></div>
  <div data-src="large.jpg" data-media="(min-width: 1024px)"></div>
  <noscript><img src="default.jpg" alt="Alt text for photo"></noscript>
</div>
</code></pre>

<p>Easy enough, especially if you’re generating your HTML from code. The first child <code>div</code> contains a default image to be used and then the rest contain an image along with a media query that has to be true in order for it to be used. All you need is the right media queries for your particular needs—and you need to get them into the right order. Picturefill doesn’t evaluate the queries against each other to see which one is most appropriate. Instead, the <em>last</em> entry in the set with a media query that is true in the current display environment wins. Obvious in retrospect when you look at the Picturefill code, but I lost a few hours in the middle of the night sorting that important detail out.</p>

<p>After quite a bit of experimentation, I’m currently using this ordered cascade of queries for the <code>data-media</code> entries in my image sets:</p>

<pre><code>(min-width: 769px)
(min-width: 769px) and (-webkit-min-device-pixel-ratio: 2.0)
(max-width: 768px)
(max-width: 768px) and (-webkit-min-device-pixel-ratio: 2.0)
(max-width: 480px)
(max-width: 480px) and (-webkit-min-device-pixel-ratio: 2.0)
(max-width: 320px)
(max-width: 320px) and (-webkit-min-device-pixel-ratio: 2.0)
</code></pre>

<p>The first two queries handle any browser width or device wider than an iPad held in portrait orientation. Then, the rest handle steadily decreasing sizes of devices in both Retina and non-Retina variants. The result is that each device gets the most appropriate image file for its display. You can see an example of this if you view source on the image above. It works well and it should be easy enough to move to first-class HTML image sets whenever support for those arrive.</p>

<p>Good so far, but the last thing I wanted to do was write all those image sets by hand and export at least eight different sizes of each photograph. That seems like a quick way to madness. Luckily, the server bits I added take care of that for me.</p>

<h2>On the Server Side</h2>

<p>To support the Picturefill image sets, I added two bits of functionality to the code that runs on this site’s server. The first is a tag generator which generates image sets based on a combination of the dimensions of the image file on the server and the constraints of my layout and its behavior in different device window sizes. The second is a image controller that serves up dynamically resized images on demand.</p>

<p>The tag generator is nothing magical. In fact, right now, its a messy nasty pile of conditionals that does a bit of math to sort things out and it’s at least two generations from being something I’d show in public. But the code isn’t really important. The important thing is that the generator is free to select any image size it wants to, up to the maximum image size available to the server and just write out image URLs of the form:</p>

<p><code>/path/(originalname)--jdd-(hash)-(width).jpg</code></p>

<p>For example, the set of URLs for the photograph above include the following generated from the original <code>/blog/2012/08/fira.jpg</code> file:</p>

<pre><code>http://d1me7ugkk83g18.cloudfront.net/blog/2012/08/fira--jdd-m9gj9x-1800.jpg
http://d1me7ugkk83g18.cloudfront.net/blog/2012/08/fira--jdd-m9gj9x-1500.jpg
http://d1me7ugkk83g18.cloudfront.net/blog/2012/08/fira--jdd-m9gj9x-920.jpg
</code></pre>

<p>Here are what the extra bits in the file name do:</p>

<ol>
<li>The <code>--jdd-</code> part is a hint that the URL path contains the next two significant bits of information. It’s really not necessarily and I could improve the my image controller’s path parsing to eliminate the need for it, but I’ve left it in for now.</li>
<li>The hash, <code>m9gj9x</code> is a base36 encoding of the file’s last modified time. It’s purpose is to allow caching of the images by caches, such as <a href="http://aws.amazon.com/cloudfront/">Amazon CloudFront</a>, with a super long expiration time. It’s similar in form to the MD5 hashes that the <a href="http://guides.rubyonrails.org/asset_pipeline.html">Rails asset pipeline</a> uses, but shorter and simpler to compute. For you Ruby folk, I’m simply doing a <code>img.modified_at.to_i.to_s(36)</code>.</li>
<li>The width is, straightforwardly enough, the width of the resized image. Why just the width? I don’t change the aspect ratio of photographs when their resized. Therefore, the resized height can easily be computed from the original image dimensions. No need to include it here.</li>
</ol>


<p>When a request comes back to my server with one of these paths, my image controller grabs the original image file, resizes it to the width and computed height requested. Once resized, the controller then serves it up with all the cache control headers set and an expiration date far in the future.</p>

<h2>The Future</h2>

<p>For now, this is working fairly well for me and I’m continuing to fine tune things—along with working through other bits of layout as I make my site more responsive to various browsers. But, one thing I’ve really noticed I don’t like about the Polyfill syntax is that using two different media queries to satisfy the Retina/non-Retina display case is annoyingly verbose. Thankfully, the latest versions of the <a href="http://www.w3.org/community/respimg/wiki/Picture_Element_Proposal">Picture Element Proposal</a> have noticed this. Here’s the latest proposed syntax:</p>

<pre><code><picture>
  <source srcset="small-1.jpg 1x, small-2.jpg 2x">
  <source media="(min-width: 18em)" srcset="med-1.jpg 1x, med-2.jpg 2x">
  <source media="(min-width: 45em)" srcset="large-1.jpg 1x, large-2.jpg 2x">
  <img src="small-1.jpg">
</picture>
</code></pre>

<p>That’s just about perfect in my book. My only concern is that the <code>srcset</code> params may get a bit convoluted with long image URLs. But that’s pretty minor really, and with a tag generator making the markup, it isn’t really all that important to me.</p>

<p>The <code>picture</code> element isn’t the only proposal on the table, it seems. There’s also a <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset"><code>srcset</code> attribute proposal for the <code>img</code> tag</a> as part of the WhatWG Embedded Content standard that’s considerably more terse. Here’s an example:</p>

<pre><code><img src="image.jpg" srcset="med-1.jpg 1x, med-2.jpg 2x, small-2.jpg 2w 320w">
</code></pre>

<p>I’m not sure what the relationship is between these two proposals or the groups behind them, but either way it goes, I hope that we start seeing official implementation of a markup standard for this shortly. Until then, however, thanks to Scott Jehl’s work, I’ve got a solution that works and I’m quite happy about that.</p>

<h3>Notes</h3>

<ol>
<li>I could modify my server source code to emit progressive JPEGs to work around the <a href="http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html%23//apple_ref/doc/uid/TP40006482-SW15">iOS5 image size limitation</a>, but with iOS6 coming out shortly and the fact that a progressive JPG takes more compute time (and power) to decode, I’m going to punt on that for now. More on this in my <a href="http://duncandavidson.com/blog/2012/03/retina_web_thoughts">post on whether or not to use progresive JPEGs on Retina displays</a>.</li>
<li>If you’re a photographer and haven’t seen photographs on a Retina-class display, make it a priority to do so. While you might not need, or even want, a Retina MacBook Pro right now—and might not until Photoshop is Retina-aware—you need to know where the state-of-the-art of displaying photographs on the web is going and be prepared to meet up with it in the near future.</li>
</ol></div>

<p><a href="http://duncandavidson.com/blog/2012/08/retina_ready">Link</a>
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads