Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2012-602
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 602
$eventShortId
Shortened ID of event: wwdc12
$year
Year of session: 2012
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Session 602] Delivering ...

WWDC12 • Session 602

Delivering Web Content on High Resolution Displays

Safari and Web • iOS, OS X • 58:14

Your web content should always look sharp, regardless of the display it is viewed on. Come and see how to deliver pixel-perfect web interfaces on high resolution displays for books, websites, and apps on iOS and OS X. Learn different ways to fetch and display high-resolution image assets only when they're needed and see how to use technologies like CSS and SVG to create scalable graphics that can replace traditional images. Discover advantages of different optimization techniques and gain a deeper understanding of the right approach for your web content.

Speakers: Beth Dakin, Dean Jackson

Unlisted on Apple Developer site

Downloads from Apple

HD Video (367.6 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi, everyone. Welcome to session 602, Delivering Web Content on High Resolution Displays. I'm Beth Dakin, and I'm an engineer on the Safari and WebKit team. Remember when monitors use to look like this? It really wasn't so long ago. Everything was grainy, and you could literally count pixels without squinting or leaning into the screen. Oh, those were the days.

Okay, not really. These are the days. Screen resolutions got better and better with incremental improvements to cinema displays, better and better resolutions to our notebook displays over the years. But we've really reached the pinnacle of this technology with the retina displays. If you've watched any of the videos on Apple.com about how these are made, then you know that they're designed to have pixels so small and so close together that the human eye is not capable of distinguishing these pixels. The result is a display that's a huge improvement over displays made only a year ago, and it's just light years away from that CRT.

Text is so crisp, it's like you're reading a book on real paper. Apps and games come alive. And browsing the web is a cinematic experience. Well, most of the time. But often you'll find yourself on a web page like this one, where that photorealistic illusion is broken by this fuzzy low-resolution image taking up such a prominent space on the page. This is a moment that really disrupts an otherwise amazing experience. Today, I'm going to teach you how to fix this and make your web content look perfect on retina displays.

Safari on the Mac and on iOS and many, many other apps on those platforms are powered by WebKit. Most of you are probably at least a little familiar with WebKit, but in case you're not, it's the web rendering engine that powers Safari on all platforms. It's open source, and it's responsible for loading, parsing, interpreting, rendering, and managing web content in Safari and in many, many other apps.

WebKit actually does a great job on these retina displays. Everything that WebKit draws from scratch looks razor sharp out of the box. So things like text, form controls, other things like borders. Anything that WebKit draws itself just looks amazing. Other things like SVG graphics. But there are certain elements that are kind of like little black boxes to a web browser. It doesn't draw them from scratch. It displays them as they are provided. And the images are just like that. WebKit can only do as good a job as the original source material. So in other words, WebKit can't make a low-resolution image look like a high-resolution image.

Today we'll cover four main topics. First, I want to give you the foundation to understand what's going on at the software level on these retina displays so that you can understand the problem with images. Then I'll teach you how to banish those blurry images. Next, we'll discuss the Canvas element.

Canvas is a great way to add dynamic graphics to your websites, apps, and digital books. Underneath the hood, it's just implemented with a backing store that's a bitmap, like an image. So you do have to take care to make sure you're creating a high-resolution backing store when you're targeting a retina display.

Finally, I hope that this talk inspires all of you to go out and generate and integrate new versions of all of the artwork that you use in your web content. But if that seems like a whole lot of work because you have a whole lot of images, then I'm going to remind you about some great technologies that you can often use in place of images and make WebKit do all of that heavy lifting for you and make things automatically look perfect on retina displays.

Okay, so let's begin with that foundation of what's happening on these retina displays at the software level. I want to cover two topics here. I want to tell you about the software scale factor, and I want to tell you why images require special attention. So for many years, as screen resolutions improved just through these incremental improvements, it was a common thing that you would notice that UI would get a little bit smaller each time.

And it makes sense if you had a UI element that took up, say, 100 pixels, then as those pixels on the device got smaller and closer together, the amount of space that 100 pixels took up would be smaller. But on a retina display, the pixels are way smaller and way closer together. This is a way more significant improvement than we've ever seen before.

For every pixel that you have on an iPad 2, there are four in the same place on the new iPad. So if we continued to let those 100-pixel UI elements continue to take up just 100 of these physical pixels, things would be impossibly small. But as you know, things are not impossibly small. And that's because at the most basic level graphics context on the system, there's a scale factor of two in place on all current retina displays. So the whole user interface is being scaled up by a factor of two. On Mac OS X, this is a per-window concept.

So if you have a retina MacBook Pro hooked up to an external display that's just a normal display, then the display on your retina MacBook Pro will have that scale factor built in, and the external display will just have a normal scale factor of one, kind of like not scaling at all. And as you drag windows between the two displays, they'll update automatically to either have that scale factor or not have it.

So a whole UI scaled up by a factor of two. And that's great for native apps. But what about your web apps where you've meticulously laid things out according to pixels? Well, it turns out they're going to be just fine. CSS was designed with a future in mind where pixels vary across different devices. So when you talk about one pixel or one PX in your style sheets, you're not talking about one piece of phosphorus soldered to silicon. You're talking about a relative unit of measurement in the web page.

Now, that may be surprising because historically on the Mac, we did happen to map one CSS pixel to one pixel on the device. So it would be easy to conflate the two. But even then, that was only some of the time. The equivalency obviously doesn't continue to hold once you start zooming in. So when you pinch to zoom or double tap on a Mac with a trackpad or an iOS device, the web page appears to get bigger because underneath the hood, there's a scale factor applied to all of those CSS units.

Beth Dakin, Dean Jackson So really, you're already used to this world where CSS pixels are relative units in the web page. And you've seen for yourself that this doesn't break layouts or anything, that it all works. Beth Dakin, Dean Jackson So not only that, but on iOS, by default, we actually scale all web pages down to fit in the viewport. So let me explain viewport just for a minute. I think this is a pretty good example to illustrate what's happening. I have this div here with a width of 320 pixels.

Beth Dakin, Dean Jackson So if you're familiar with the iPhone screen size, you may know that it's 320 by 480. So you may expect this div to fill the screen in mobile Safari horizontally when I load this snippet of HTML in the browser. Beth Dakin, Dean Jackson But as you can see, it does not fill the screen horizontally. It takes up only about a third of the width. And the reason for that is viewport.

Most websites were designed with big, resizable browser windows in mind. And so Mobile Safari assumes a width of 980 pixels and then scales that down to fit within the actual screen size of the device, 320 pixels. And you end up with this crazy fractional scale factor. And again, this is all viewport scaling. So if you had retina scaling in place, that'd be a separate thing. It's a crazy number. Don't need to worry about it.

[Transcript missing]

Okay, so that's the software scale factor, and you'll hear me and many other people refer to this by many other names, and I apologize if that's confusing, but display scale factor, device scale factor. If you're over in AppKit land, you might hear backing scale factor. You'll hear me say device pixel ratio. All of these things refer to the same thing. It's referring to that number two on a retina display where the whole UI is scaled up by two, and on a standard display, it would just be one, whatever that scale factor is.

Okay, so now I want to talk about images, but to do that, I actually want to step back first and talk about why other elements look great on retina displays. So consider this simple div, 10-pixel by 10-pixel div, on a standard display, so a normal MacBook Pro like the one you have in front of you. And this is that sort of idyllic old-school environment where one CSS pixel did happen to correspond directly to one pixel on the device.

If I were to draw a grid over this div representing the CSS pixels, it would look like this: 10 by 10 pixels, 10 in each direction. And again, because this is that old-school environment, if I were to draw a grid representing the device pixels, it would be identical.

But now let's consider the same div on a retina MacBook Pro. The CSS pixels would look the same. Again, they're relative units, so these things don't change between the non-retina and retina case. But the device pixels have doubled underneath that. And this is what makes everything that WebKit draws look so sharp. We can take advantage of those extra pixels and make the curves on text, like in the letter S or the letter B, look as smooth as silk.

Now let's talk about images. So really we're talking about raster images or bitmap images here, things like pings, tiffs, JPEGs. We're not talking about vector images, which are different. But all raster images have an intrinsic size. And this image has 400 by 250 pixels in it. It's a certain number of pixels. So, quick side note here. Photoshop encourages this idea that you can somehow control the resolution independence of an image file if you set its DPI, the dots per inch, in other words.

This is not really a good thing to think about in terms of images, and it doesn't really make sense. This adds some metadata to the file, but it doesn't actually add any additional pixel data. So, It just doesn't make sense to think of DPI in terms of images because how tightly packed those pixels will be depends entirely on the display that's viewing the image and it depends entirely on the container you choose to put the image inside. This bit of metadata can't really control that.

So it's best not to consider that when you're thinking about creating high-resolution images. Just think about the number of pixels in the image, the width and the height. So that's what we'll do here. This image has 400 by 250 pixels in it and it will look best if it's displayed using the exact same number of pixels on the display.

This is something we're going to keep coming back to so I really want to emphasize it. This image will look best if it's displayed using the same number of device pixels as the actual image file has available. So if I were to add this image to my website, I would probably write some HTML that looks like this.

And here's what that would look like on a standard non-retina display. And here I've set up a viewport so there's no additional viewport scaling happening here. So I have a one-to-one correspondence between all of my CSS pixels and my device pixels. Each of my image pixels gets to render into exactly one device pixel.

We're seeing the image in full fidelity and its full glory. So just to recap that, we have a certain CSS size, and our display is providing the exact same number of actual physical device pixels within that CSS size. And that happens to be the exact number of pixels we have available in our image as well.

Now let's consider this same HTML snippet on a retina display. Here the image looks blurry. And the reason for that is that we have the same CSS size as before, but the number of device pixels underneath that have actually quadrupled. And we still have only 400 by 250 image pixels to work with. So the underlying graphics library is forced to try to scale this image up. It's filling in the details that it doesn't know somewhat randomly. Unknown details are colored in, and the result is an image that looks fuzzy.

So now I want to teach you how to fix this problem. So that takes us right into the next section. The basic idea here is that you want to take all of your existing images, and you want to generate new versions that are twice the size, twice the number of pixels.

And then you want to declare what you have available. And then depending on the technique that you choose to use, either WebKit will choose the best image for the display or you'll explicitly tell WebKit which image to use. And you'll end up with something that looks great on a retina display.

We'll discuss three techniques for achieving this. First, I'll tell you about CSS sizing and then querying for device scale. And finally, I'll tell you about a new option we have in WebKit called Image Set. We'll get to that in just a few minutes. For each of these techniques, we'll grade them in three categories. We'll see how they are with performance. By performance, I mean memory performance, also speed performance, and even graphical performance, how things look. We'll discuss how simple it is to integrate this technique into existing web content. And finally, we'll discuss if the technique works across different browsers.

Okay, so let's begin with CSS sizing. To do that, I want to jump right back into that example we were just considering, where we have an image that's 400 by 250 pixels, but we have four times the number of device pixels to fill. So the way to integrate the CSS sizing technique here is really just to leave everything the same, but change the URL of the SRC attribute to point to a high-resolution version of the image that actually has 800 by 500 pixels in it.

So the key to this is that I have not changed the width and height attributes, because I still want this image to take up the same amount of space relative to other elements on the page. But I've provided an image resource with enough data to fill all the device pixels. that are actually available within that space.

So there are a number of other ways to integrate images into web content besides just using the IMG element, and CSS sizing is something you can do for any of those different ways. Most similar to the IMG element is just using the CSS content property. So here you can provide content that's an image using the URL function, and then I'm just going to use the width and height properties to set the size of that image, very similar to the width and height attributes on the previous screen, which were also supplied in CSS Pixels, I should note. That's the units here. And then for background images, you can size the image using the background size property. If you have an image that repeats, then the background size represents the size of each tile in the repeat.

Finally, you can do this for border image as well. It's a little bit trickier with border image because you have to use two properties in conjunction with one another to get the sizing right. On top of that, they're expressed in different units. You use border width, which is expressed in CSS pixels. That makes it very similar to width and height and background size. Anything expressed in CSS pixels is not going to change whether it's a 1x image or a 2x image for a retina display. These things will remain the same.

However, border image slices, these units are not CSS pixels. These are pixels in the actual image file. They're in the image's coordinate space. So if the actual image changes in size, then the values of the slices will also change. We'll come back to this in a few minutes when we're talking about media queries.

Okay, so you should always give your images a size in CSS. And in fact, you'll need to do that in order to be successful with the other two techniques I'm going to tell you about in a minute with querying or image set. But the idea of doing only that as a technique to integrate high-resolution images is that you always use that high-resolution image resource.

So let's grade this technique of always using high-resolution images as a way to make your web content retina savvy. How is it with performance? Well, it's pretty bad with performance. It's really bad for performance, actually. When you use this technique, you'll be sending those really large image files to all of your users, even your users without retina displays. So this is going to create really long download times for users on older devices. Those images are going to take up a lot more space and memory. Retina devices can handle that.

They're built to handle large image files, so the memory hits not as bad there. But a user on an iPhone 3GS having a big image file loaded into memory, or many of them, could be a real burden. On top of that, you won't be delivering a full fidelity image experience to all of your users either.

Your retina users will be getting that full fidelity experience, but everyone on an older display, they'll be kind of experiencing the opposite of the image scaling up. The image here will be scaled down to fit in a much smaller space because the image will have way more pixel data than the device will have available to display.

So the graphics library will have to choose which pixels to leave out. It's not a full fidelity experience. The result is not nearly as bad as when you scale up an image, but it's still not perfect. However, this technique is very simple and it works across all browsers, so it does still have some merits.

Okay, so you may remember earlier I was telling you about the software scale factor that's in place on all Retina devices, scale factor of two. So with this technique of querying for the device scale, you use a technique to query for exactly what that device scale factor or software scale factor is, and then you choose which images to load based on the result.

And the first way to do this is with media queries, which have been around for a while. Media queries are a way to provide or to define CSS rules that will apply conditionally based on characteristics of the device viewing the content. So here I've created two media query blocks, one to specify styles that I want to apply when the content is viewed on a screen, and a separate block to specify different styles that I want to apply when the content is printed. This is a long-standing way to use media queries.

They've also become popular in recent years as a way to target styles toward iPhone or iPad. And the most common way to do that is to query for the device width, which is in CSS pixels, I should note. So here I have one block looking for that device width of 320, so it's targeting iPhone, and then targeting iPad with a device width of 768.

And now you can query for that device scale factor as well. So here I've set up two different media query blocks. My first one is looking for a device pixel ratio of one. This means all standard non-retina displays. This is a display where that software scale factor is just one. And in my second media query, I'm looking for a minimum device pixel ratio of two. So this will apply to all current retina devices and any future devices that happen to have an even higher software scale factor.

The way that people usually use this, in the case for targeting retina or non-retina displays, is to put most styles up in a common style block. Because most things aren't going to change for the retina or non-retina version of your site. You really just need to change those image URLs.

So I even have my background size up here, because that CSS Pixel, and it's not going to change. And then down in the media queries, I'm just defining my normal sized image and my 2x image for the two different types of devices that I'm targeting. So there are a number of cool things about media queries.

First of all, the CSS engine won't even evaluate the query if it doesn't apply to the device viewing the content. So you'll always only download the image targeted for the device. You don't have to worry about downloading both images. You're always going to get the correct image for that device.

On top of that, media queries will update dynamically if that device scale factor changes. So in other words, if you have that Retina MacBook Pro in a multi-monitor scenario where one monitor has a 1x scale factor, because it's a standard old display, and the other monitor is the built-in display, which has the 2x scale factor, your media queries will re-evaluate as you drag the windows between those two displays. So you'll always get the image that's targeted to the display that the content is on.

A less verbose way to write this code would be to write it like this. Here I'm putting the 1x image URL up in the common style block, and then I'm overriding it with a media query for retina displays. And you still don't have to worry about both images being downloaded here. You'll override that before any downloading happens. So you're still getting the image targeted for the display.

Another benefit here is that this will actually target any displays that have a device scale factor between 1 and 2. And all current Apple devices have a device scale factor of 1 or 2. But you never know with future devices or devices from other manufacturers. So here I have, again, the image URL for the 1x image in the common style block.

I have to make sure, though, that I write that code in that order. If I accidentally were to put the media query block first, then I wouldn't end up getting that retina targeted image on my retina display because I would end up overriding it later with my normal 1x image. So this is just something to be a little bit careful with when you're using media queries.

I mentioned border image earlier. I just want to bring it up again here quickly because of the complication with slices. So here I again have a common style block. I can put the border width here. That won't change. But then I will need to redefine two pieces of information instead of just the URL for the image in my media query. I need to redefine the slices as well because, again, these are in terms of the image's coordinate space. So if the image changes, the slice values will change as well. Just something to be aware of.

It's a pretty common technique on websites with many images to load them in JavaScript. And if that's something that you do, then you can still query for what this device scale factor is and choose which images to load based on the result. So here I'm evaluating a CSS media query in JavaScript.

And then I'm deciding whether I want the retina version of my image or the regular one. I could also use the window.devicePixelRatio property. This will directly return whatever the current software scale factor is. So again, it will return one on all standard displays and two on all retina displays.

One of the cool things that I mentioned about CSS media queries is that they will update dynamically if the scale factor changes, like in a multi-monitor scenario. So to hook into that goodness if you're doing this through JavaScript, you have to be careful and make sure that you add this event listener for match media for this device pixel ratio changing. If you do this, then you can also make sure that the images will update dynamically.

Okay, so let's grade this technique. So this is great for performance. I mentioned a lot of this as we were going through how to use media queries, but these are great for performance. You always only get the image that's targeted for the device. They'll update dynamically so you can always provide a full fidelity experience. They work really well.

However, this is not a super simple technique, also for reasons that I mentioned as we were going through. You have to be careful when you're using CSS media queries that everything's in the right order. That can be a little bit error-prone. You have to be careful with border image, that you redefine the slices. You have to sort of think about that and grok that they're in different coordinates than CSS pixels. And if you're doing it through JavaScript, you have to remember to implement that event listener. There are a lot of things to keep straight, admittedly.

It is, however, a cross-browser solution. I was using the -webkit prefix on those slides, but this media query is also implemented in Firefox and Opera with the -moz and -o prefixes, respectively. Opera has a slightly different syntax that you'll have to look up online. But overall, this is a perfectly good technique to target all of the major browsers that you could be using on your Retina MacBook Pro.

Okay, so now I would like to tell you about ImageSet. This is a new CSS function that we came up with here at Apple for a more elegant solution to the high-resolution image problem. We've proposed it to the CSS Working Group, and it's currently scheduled to be a part of the Images Level 4 module.

So this slide should look familiar. This is a media query. Here I'm using a media query to set up a background image. And you may recall all of the reasons why this is great for performance. You're always getting the image that's targeted for the device. The image will update dynamically for a multi-monitor scenario.

Well, to get all of that goodness with much less code and much less error-proneness, you can just use the new WebKit image set function. So everywhere in your style sheets where you're currently using the URL function to point to an image, you can instead use the WebKit image set function to list multiple URLs, as many as you like.

And for each URL, you'll provide an associated scale factor. This tips WebKit off to know what that image is appropriate for and how big it is. It doesn't have to download the image to know what its size is. It can see from your scale factor the 2x image must be twice the size.

In this way, things will update dynamically. You'll only ever download what's targeted to the device. This is new in Safari 6 and Safari on iOS 6 as well. This also greatly simplifies border image, where you had to think about that slice information before. Now, in addition to just taking up less space with the URLs, you also only have to define the slices once.

You can just define them in terms of the 1x image, and then WebKit will extrapolate from there, based on the scale factors that you've provided, what the slice values should be for the higher resolution versions of the image. Okay, so let's grade this new image set function on our little chart here. This is great for performance. It has all those same performance characteristics of media queries.

It's also very simple. You're just replacing the URL function anywhere that you're using it with the WebKit image set function, providing a tiny bit more information. Sadly, at this time, it's not cross-browser. It's only implemented in WebKit. However, we are taking this through the standardization route, so we anticipate other browsers picking up on this and implementing it. So we anticipate this X turning into a checkmark. So looking at this graph, it may still be difficult to determine when you should use which of these techniques which is right for you and your content. So let's talk about that.

So you may recall CSS sizing has all of these performance issues, but it's still an OK technique to consider if you have a small number of local images. Local meaning they're already on the device, so if you're an app or a book. You're still going to pay that memory cost, which is why we recommend really only a small number of images here.

Media queries are great for many images, and they're great for websites in addition to apps and books since they have a really good cross-browser story. ImageSet is great for any number of images as well, and it's great if you're targeting WebKit. It's actually OK even if you're a website that wants to work in many browsers because CSS degrades so gracefully.

So other browsers would just ignore this rule, and you would still get your high-resolution images into any WebKit-based browsers. Okay, so hopefully now you all have a full understanding of what you need to do to integrate high-resolution artwork into your websites. Now I'd like to talk about Canvas, and to do that, I would like to invite Dean Jackson, my third favorite colleague, up to the stage.

This time last year I was ninth, so... Okay, so the example that Beth showed before, it was pretty clear to demonstrate the significant difference in quality between a photograph and the extremely crisp text that was displayed alongside it. And then Beth gave you a number of techniques to banish those blurry images. But it turns out that photographs, in fact, aren't always the types of images that are in most need of improvement for written display. In fact, HTML Canvas is a great example of something that needs a little bit more attention.

So let's examine why. So here I have a photograph of a three-headed alien from the planet Anthropologie. And actually, this photograph is many times bigger than the size of this projector, which is 19-20 across. But the photograph still looks good. In fact, even when I scale it down 50% again, it still looks great. You can pick up a lot of detail. When I zoom it up to show it at one-to-one pixel ratio, here we focus on the vicious teeth of this creature. You can see there's a lot more detail in the photograph than was shown when it was scaled down.

Now I'm going to replace it by an image that's the exact same region of the photograph, but in this time I'm only going to use 256 pixels -- sorry, 240 pixels across and see if you can notice the difference. Full resolution, and there's the one. So here you've got something that's eight times -- basically scaled up eight times. And it still looks okay.

And that's because your brain and your eyes are pretty good at fooling you into sort of de-blurring the image itself. Now when we take that photograph and overlay it with an admittedly fake UI for a photo editing application, we'll see that it's these types of synthetic or dynamically generated images that are in most need of scaling.

So here the photograph still looks fine, but if I zoom in on the UI elements, you can see starting to see, like, the chunks of text -- in this case, the text being drawn directly into the image. The icons in particular looked pretty terrible at high resolution. And here is a good example where the straight lines have become these sort of stair steps, and in fact the anti-aliasing of the horizontal lines is starting to look awkward because the gaps between the lines have been interpolated.

So that brings us to Canvas. These are the types of images we want to talk about. So what is Canvas? It's a technique that was invented by Apple a few years ago to allow you to dynamically generate images in JavaScript in the browser. Since then, it's become one of the most popular features in HTML5. People use it for maps and decorations and games and charts and all sorts of things. And Apple uses it for a few things in Dashboard, for example, the stocks widget and the flight widget and the clock.

So our goal for this part is to show you how to create your Canvas to be as best as possible on a retina display. What do we got to cover? So first we have to describe how the Canvas actually works on a retina display. Then we're going to talk about how to improve your Canvas.

And lastly, we're going to just show, in most cases, you don't need to change your code, but there are some cases where you need to update your code. So let's start with how does Canvas behave on a retina display. And we'll start, in fact, by describing how a regular image works. So Canvas doesn't have an image element. It typically starts with a referenced file somewhere on the web or on a disk if you're a native app. And this file is a JPEG or a PNG.

And the browser's got to read that file and decode it into a rectangular array of pixels. Each pixel represents a color. When it comes time to draw, it takes that rectangular array and basically puts it into another rectangular region on the screen. And sometimes it has to squish or stretch that array, making up pixels, as Beth said, taking an educated guess. Canvas is, in fact, exactly the same, except instead of coming from an image file, we have something called a backing store.

So it's actually JavaScript that's got to tell the browser what the rectangular array of pixels is going to be sized at. In this case, I've just said 400 by 250. And it starts off blank, so we need to write some more JavaScript that's going to set the color of every one of those pixels. And there's a whole bunch of commands to do that, and basically you end up with the same rectangular array. And from that point on, it's exactly the same. When we come to draw, we take that and squish it and stretch it into the page. age.

The goal, as with images for Canvas, is to always have the same number of pixels in your backing store or your image pixels as you do in the device pixels when you actually display it on the screen. So let's talk about what happens on a normal resolution display.

So let's imagine I've got a canvas that's sized at 400 by 250 CSS pixels. Now, on a non-retina display, without this viewport scale or without the user zooming, that's going to cover 400 by 250 pixels on the display, and unsurprisingly, this means you want a canvas size of 400 by 250 pixels. So this bit of code is really simple. We're going to come back to it a few times. This is the easy case. I start with some global variables that define my width and height in CSS pixels.

Then I go to get a reference to my canvas element. And the next I want to set the width and height of the canvas element to what the global variables were. Now, this is important. It's not actually changing the size of the canvas on the page. It's telling the browser how many pixels it needs in its backing store. So in this case, it's going to create this rectangular array of 400 by 250 pixels, which is exactly what we wanted.

When we come to draw, canvas has a whole set of commands for doing drawing. I'm not going to cover them. But in this case, we just want to keep this one in mind as an example. I want to just draw a square in the page that's covering 30 CSS pixels by 30 CSS pixels. That's it. So that was the easy case. Let's talk about a retina case. We'll start with retina on OS X because retina on iPad is like just so eight weeks ago. Now, the canvas size is exactly the same, 400 by 250 pixels.

But when we display that, as Beth mentioned, we're using a technique called pixel doubling. So in fact, it's going to cover 800 by 500 pixels. And because it's scaling that original size up, it's going to look blurry unless we actually allocate 800 by 500 pixels in our backing store.

Once we do that, we actually get the nice output at the right size in the page in our canvas. So on OS X, We want, again, 400 by 250 pixel-sized canvas to end up with an 800 by 500 backing store. And the good news is, on OS X, you don't actually have to do anything.

We're doing the work for you. So the system's going to detect that I'm on a Retina display, and we wanted all the web pages out there in the world to look better on these new MacBook Pros without any code change. So behind the scenes, WebKit is actually going to say, okay, Retina display, I'm going to allocate twice as many backing store pixels as the user asked for and produce the better display.

So, in fact, our code hasn't changed at all. Still got the global variables, 400 by 250, but down here, when we actually request the width and height exactly the same as before, the system's going to say, well, they asked for 400, but I know they really want 800 to make it look good.

We now got a bigger backing store. Well, why don't we have to change our drawing code because otherwise things will come-- will appear very small. It turns out we actually take care of that for you automatically. So before we start drawing, we apply a scale factor to all your commands so that everything's going to come out twice the size, drawn with the best quality into the backing store. So again, here, our 30 by 30 square is, in fact, going to cover 60 by 60 pixels in the backing store.

So Canvas on OS X means with the exact same code on a normal display, you get to get the output. With no changes, you get a much better quality on the retina display, and that's fantastic. But you probably know that there's a couple hundred million devices out there with existing retina displays, and that's iOS. And the story's a little bit different there, so we're going to go through this now.

So you're probably sick of this slide, but again, 400 by 250 pixel canvas. We want it to be 800 by 500 backing store pixels. But on retina on iOS, the system isn't going to automatically double the backing store for you. So why did we make this decision? There's basically two reasons. One is the very nature of these devices is that the web content isn't always viewed at the one-to-one scale.

Typically, there's a viewport or the user is pinched to zoom or panned around. And it's quite often that the size you've actually decided to render your page, it's not exactly how it's going to be displayed on the user's device. The other thing is that memory use and CPU performance and battery life is much more precious on these devices.

So we wanted to make sure that we didn't automatically double things and use up the resources. And it's up to you, the developer, to choose how big you want your backing store. So in this case, I have to manually double my backing store. I have to detect that I'm on a retina.

I have to detect that I'm on a retina display in order to decide whether I need to double my backing store. And I do this with the property device pixel ratio that Beth mentioned before. So this is the scaling factor between normal pixels and the retina pixels. So it's one on a normal device and two on another device. So if the device pixel ratio is undefined to one, you're on a non-retina device.

If it's two, then you're on a retina device. So this allows me to write this little bit of code, and I'm going to come back to it a few times, which is effectively telling me how much I want to scale my canvas by or my backing store canvas by and how much I want to scale my back-to-back store canvas by. So in this case, it's a bit more complicated than it needs to be, but you'll get the point that if my device pixel ratio is around, then that's the scale factor I use.

So, now the code is just slightly different. I've added -- that's the same and I've added a global variable which is my backing scale. I've used the function just on the previous slide to work out the scale factor, and then down below when I allocate my canvas, I'm actually going to use that multiplying factor -- or use that factor to multiply the width and height that I ask for my backing store.

So, in this case, it would be two by 400 or two by 250, 800 by 500 canvas, we've got what we want. But there's a problem, and some of you might have picked up what the problem is. If I run this exact code on OS X, what's going to happen is that I'm now specifically asking for an 800 by 500 backing store, and the system is going to say, well, I'm on a Retina device, I'll auto double it for you and actually give you 1600 by 1000, which is going to be four times bigger than you wanted, in fact, 16 times bigger than what you originally asked for.

So you need to be very careful that if you're on a system that's manually doubling the backing store size for you, you need to detect that. So how do we detect it? There's another property, which is the WebKit Backing Store Pixel Ratio. So whereas the device pixel ratio told you the multiplying factor between a normal pixel and the display pixel, the WebKit Backing Store Pixel Ratio tells you the multiplying factor between how many pixels you asked for in your backing store and how many it's actually going to give you.

If the back-and-stall pixel ratio is undefined or 1, it would be undefined on a browser that doesn't support this property, you're on a system that does not auto-double, so that could be a non-retina device or an iOS device. And if the WebKit back-and-stall pixel ratio is 2, then you're on a system that auto-doubles whatever you request, an OS X retina device.

So this is getting a bit complicated, so we'll just draw it as a flow chart. The first question you're going to ask is, am I on a retina device? If I'm not on a retina device, then I don't have to do anything. No scale needed. If I am on a retina device, then the next question I have to ask is, is the system going to auto-double the pixels for me? If it's not, then I have to manually double the pixels. I'm on an iOS retina device. If it does auto-double the pixels, then I, again, don't have to do anything, but I'm on a retina OS X device.

So we flip this question around to actually ask, what scaling factor do I need to apply? The answer is 1, 2, 1, and 1, we don't have to do anything, and in fact, they're the same. So the really only case we need to worry about is this. Am I on a retina device? Yes. And is the system going to auto-double the pixels for me? No. In which case, multiply by 2. So I'm going to update my code that I had before, and again, there's many ways you can write this.

This is the way I chose it. If the device pixel ratio is greater than 1, I'm on retina. Or if the context WebKit back-install pixel ratio is less than 2, I'm not auto-doubling. Then return the scaling factor that the screen is using. I'm going to use this function again in the same code.

Instead of -- I'm going to declare the variable initially to be nothing, and down below, I need to actually have the context to decide whether it's going to auto-double. So here I set the global variable to be the scale factor, which will be 2 on an iOS retina device and 1 elsewhere. And now I can just use this same code, so I'll get the right result on an OS X device.

Now we're in a situation where some devices actually have a doubled backing store, some have an automatically doubled backing store, and the drawing is taken care of, so I have to take care of the fact where I'm actually manually drawing into a doubled backing store, otherwise everything's going to appear really small and up in the top left-hand corner. So I do this first in my, this is my entry point to the draw function. I'm going to save the state of the drawing, the context as it is right now. I'm going to apply the scale factor that I calculated before.

If this is one, of course, it's not going to do anything, so it's fine. If it's two, it's going to make all the drawing operations from here on in twice as big, which is exactly what we want. And as I exit, I reset the sale so it's not accumulating every time I come into this function.

So there's some things to consider. The first is you are actually going to use more memory, four times as much memory. And Beth mentioned why this is important on some devices. The drawing performance, you're drawing to four times as many pixels, which is going to be a little bit slower.

So it's really up to you to decide or to determine. You might have the best idea of what scale the user is actually going to view their canvas at. It might not be one-to-one. If you think they're never going to zoom in on this element, you might not need a retina canvas.

There are some code and API changes I'll get to at the moment. So where are we in summary? We always want to pick the one-to-one, the same number of image pixels as device pixels. You have to check whether you're on Retina, in which case you're going to need to double your back-end store. And then if the system is going to automatically double the back-end store, you have to do it manually. If the system isn't, you have to do it manually.

So in our progress chart, we actually were having so much fun, we skipped past one and now we've covered two as well. So the last thing to cover is what are the actual code changes? And there are three to cover. So there's this very handy function called ability in Canvas to be able to access the pixels directly. In most cases, you just use the JavaScript draw functions to draw shapes or whatever. In this case, you might want to access the pixels and do something like increase the brightness or set every other pixel to red or something crazy like that.

So the idea is given a Canvas element, you give a rectangle, a rectangle, x, y, width, height, and it's going to extract that rectangle and give you a variable in your JavaScript, which is the array of pixels there are. And you can iterate over that array and set the color. Put image data does the same thing. It takes that array after you've possibly manipulated it and puts it back into a region of the Canvas.

Now, it's important to note, even on a retina display, that these parameters to the functions are actually in CSS pixels. So what this means, if you ask, for example, say, for a 30x30 region on an automatically doubled backing store, it's actually going to take a 60x60 pixel region and squish it down into the 30x30 pixels and give it back to you. So the image is sort of being compressed.

In the same way, when you write a 30x30 pixel image into put image data, the system's going to take that and stretch it out, effectively making it blurry before it's putting it into the canvas. And the reason we did this is if you look at most of the code on the Internet, when people call get image data XY width height, they normally follow it up with something like 4i equals 0 to width times height, do something with those pixels. So if we actually gave back more pixels than they'd asked for, then all that code would break and we'd effectively destroy the Internet.

So what happens if you really want access to the pixels? We've added two functions. So the first, the top ones are the ones we're already familiar with. The parameters are in CSS pixels. If you want actual access to whatever the backing store has, use these new methods, WebKit Get Image Data HD and WebKit Put Image Data HD.

It's important for that to remember that you need to be aware of what region you are. If you actually want the 30 by 30 region on an auto-double scale, you need to make sure you ask for 60 by 60. The second thing to consider is when you're drawing images into a canvas. It's a pretty common thing to do, but now we're in a situation where we don't necessarily know how big the canvas is that we're drawing into.

You should always, as Beth mentioned before with CSS, you should always specify width and height for your images. In fact, there's one type of form of the draw image command in Canvas which seems really convenient where you just say, here's my image in my XY position, and it's going to put it into the canvas. Let's say you're drawing one canvas into another. We don't actually know how many pixels that image is going to cover, so it's really important to know exactly what the dimensions of the output is going to be.

The last change is another convenient function, which is the ability to take a canvas and extract it as an image file. And this is useful if, say, you've got an application that's drawing onto the canvas and you want to post the image up to your server. So given a canvas, you call this function toDataURL, and it takes parameters of an image type, but normally we just call it directly. And it returns this string of gobbledygook, which is actually, for example, in this case, a ping image encoded as text.

Now, like the original Get Image Data functions, this function works in CSS pixels. So if you are on an auto-double display, you've got to get the CSS pixels you requested, not the full size of the back-end store. So the three things to consider that you might need to look for if your retina canvas isn't quite working is new methods for extracting the exact pixels, if you need that.

Always specify your width and height, and be careful when you're using 2Data URL. And with that, we've finished the canvas. I'm going to invite Beth back. Beth obviously doesn't know I've found her secret stash of cookies, and I'm still third in the ranking. Thanks. Thank you. Thank you, Dean. That was great.

Okay, so as I'm sure you all know, images are really an important part of creating an immersive web experience. And we understand that, too, and that's why we've been talking at you for 45 minutes now about how to make sure your images look fabulous on retina displays. But in many cases, web technologies are sophisticated enough that you can use them in place of images.

Make WebKit do all of the work to make sure it looks great on retina displays. So I'm going to quickly take you through three scenarios where you can do just that. First, I'll talk about making image-free glossy buttons. And then we'll talk about letting text be text. And finally, we'll discuss SVG.

So, on my little Hawaiian vacation blog here, I have this button, and this is just a simple input type equals button, but if I wanted to style this to give it a little bit more of that aloha spirit, I would probably make it look like this. And it's a very common technique on websites when styling a form control like this to use image assets. And if that's what you do and what you want to continue doing, then I've taught you everything that you need to know about how to integrate high-resolution image assets.

But image assets are really a bit brittle for such a small UI element like this one. If I wanted to change it to make it a green button, then I'd have to go change all those image files, the low-resolution ones and the high-resolution ones. Having image files for such a small element is like a little extra burden on the memory as well. So wouldn't it be great if I didn't need them? Well, I don't.

And I made this button with CSS. Let me show you what I did. So I started with a div, and I gave it some really basic styles with height, a border, and a size. So I started with a div, and I gave it some really basic styles with height, a border, and a size.

Purple background color, white text centered the text. All of this is really basic. But now I'm going to show you what I did to make this really come alive. First, I rounded the corners with border radius. And then I gave the button a little bit of extra depth using shadow as a text shadow and a box shadow on the button itself.

And finally, to really make that button come alive with that fully lickable appearance, I gave it a background image that's a WebKit-generated gradient. That's all I really need for this beautiful button. It's worth noting that you can use this technique to make buttons that look exactly like the system buttons on iOS. So this is a little web app mockup of the Notes app, and this button is created in exactly the same way.

Okay, now let's jump to text. So you may have noticed that I also have this lovely masthead on my blog. And you may have noticed that it didn't get blurry when we looked at my blog on the retina device, unlike the image that's on this page, which did get blurry. And you may have expected it to get blurry because many people make mastheads like this with images because of the kind of design control that they provide for such an important UI element on my page. Well, this didn't get blurry because it's just text.

What I've done is that I have a font that I have the ability to use. All fonts have licenses associated with them, so you do have to make sure you can use a font in this way before you go ahead and do it. And then I've just embedded it in my CSS so that I can refer to it later. It's very simple.

I've just used the at font face rule, given it a font family name. I could call it anything. And then I'm using the SRC property to point to where I have that font file stored. And then I can just refer back to that font family name any time I want to, the same way I would refer.

I can refer to any other font family name. The result is something that looks great at all sizes, so it scales really, really well. On top of that, it's more accessible, more searchable, and more editable than what I had before. Okay. Finally, let's discuss SVG. So SVG is a way, it's like a set of instructions for defining an image. So it's entirely instruction-based instead of being pixel-based like other image formats.

That can sometimes limit the use case. So SVG is a way to use a lot of different interfaces for SVG, but it still has tons of applications. So rather than talk about it, I'd rather show it to you. I'd like to bring someone who knows a thing or two about SVG back to the stage.

Okay, so as Beth mentioned, SVG is an image format. It's actually an image format, it's a text format as well, very similar to HTML. And the techniques Beth showed before about replacing a chunk of -- sorry, collection of images with a chunk of CSS, I've done the same thing here where instead of replacing a chunk of images I'm just referencing a single SVG file. This is Beth's Hawaiian button. And you can see that it actually looks fine at any resolution. If I pull up the inspector -- I might have to make the window a little bit bigger.

So here's my div. I'll swap to the style panel and collapse this one. And you can see down here that my background image is just -- let me zoom in. Nope. You can see the background image is just one SVG file. If I open that SVG file in its own window, it's a little easier to see. So the cool thing here is that the browser or web kit is actually deciding what resolution it needs to draw the image, and then creating the size -- you know, the rectangular array of pixels and just rendering the SVG instructions directly into that.

Now I can actually edit this file. It's pretty easy if you're familiar with HTML. You can see that really this one image is made up of three rectangles, one with a gradient. So I can do something like move my cursor up here. And change the-- well, let's do this one. Let's change the black stroke to be something like orange.

Oops. If I could type and save. I'll just reload the page here. And we've got the orange thing. So that's pretty easy. So you can see that actually SVG is a great format to use and it's going to apply to a whole bunch of use cases and you're free to use it wherever you need to.

So, Beth? Well, okay, this is pretty cool, Dean. But, you know, I just showed everybody how to build this in CSS, so can you, like, do something different? Right, okay. So what do you have in mind here? I don't know, just something with a little more pizzazz. Okay. Maybe something wavy. Believe it or not, I have something I prepared earlier. So in this case, I've got a little bit more complex SVG. What I'm going to do here is paste this element in.

[Transcript missing]

Okay, so here I've integrated some SVG right into my HTML. So I have an HTML file, and I'm using an HTML input element, and I'm decorating it using SVG. So I have this really cool focus ring that also scales really well, and I can type into this file. So as I demonstrated, and Beth also demonstrated, that SVG is a great image format for some cases. There are some things you should be aware of.

It's not particularly well suited to when you're going to display the image at a very low size, or very small size, or very low resolution. And you're probably familiar with this type of thing when you think back to 8-bit arcade games where an artist wanted to have exact control over every pixel. And vector artwork doesn't always give you that. Designers often call this sort of like pixel fitting or something like that. But with that aside, you might find that SVG is a really handy image format to use for all your multi-resolution needs. Thank you, Beth.

Beth Dakin, Dean Jackson Thanks, Dean. Beth Dakin, Dean Jackson Okay. Beth Dakin, Dean Jackson So you should really consider some of these image alternatives. They're a win on so many fronts. You get all of that great retina goodness that WebKit provides out of the box. On top of that, you'll often use less memory, create something that's more accessible, that's easier to edit down the line. It's a great thing to consider these other technologies.

Okay, so that's everything we have for you today. I just want to leave you with a few of our key points. Most things on Retina displays are going to look great as is. Images do need a little bit of special attention, and you want to make sure the number of pixels in your image is equal to the number of pixels that will be available on the device, and that's the way you'll deliver the best experience.

You want to always give images a width and a height. And finally, remember to consider those image alternatives. I can't wait to see all of the beautiful websites, apps, and digital books that you all create using these technologies. If you have any additional questions, you can contact our evangelist, Vicki Murley.

There are a number of related sessions here. A couple of them have already happened, and if you didn't see them, I encourage you to check out the videos when they're released. And we still have a few more sessions later today and tomorrow that I strongly encourage you to attend. Have a great conference.