High DPI Websites (Part 1)
One area of Web design that is going to become more important in the coming years is high DPI. For those of us working on WebKit, this will also become an issue for WebKit applications and for Dashboard widgets.
What is DPI?
DPI stands for “dots per inch” and refers to the number of pixels of your display that can fit within an inch. For example a MacBook Pro has a 1440×900 resolution on a 15 inch screen. Screens exist for laptops, however, that have the same physical size (15 inches) but that cram many more pixels into the same amount of space. For example my Dell XPS laptop has a 1920×1200 resolution.
Why does this matter?
Consider a Web page that is designed for an 800×600 resolution. Let’s say we render this Web page such that the pixels specified in CSS (and in img tags and such on the page) map to one pixel on your screen.
On a screen with 1920×1200 resolution the Web site is going to be tiny, taking up < 50% of the screen’s width and half the screen’s height. In terms of absolute size, the text will be much smaller and harder to read.
Now this may not be a huge problem yet, but as displays cram more and more pixels into the same amount of space, if a Web browser (or any other application for that matter) naively continues to say that one pixel according to the app’s concept of pixels is the same as one pixel on the screen, then eventually you have text and images so small that they’re impossible to view easily.
How do you solve this problem?
The natural way to solve this “high DPI” problem is to automatically magnify content so that it remains readable and easily viewable by the user. It’s not enough of course to simply pick a pleasing default, since the preferences of individuals may vary widely. An eagle-eyed developer may enjoy being able to have many open windows crammed into the same amount of space, but many of us would like our apps to remain more or less the same size and don’t want to have to squint to read text.
The full solution to this problem therefore is to allow your user interface to scale, with the scale factor being configurable by the user. This means that Web content has to be zoomable, with the entire page properly scaling based off the magnification chosen by the user.
What the heck is a CSS px anyway?
Most Web site authors have traditionally thought of a CSS pixel as a device pixel. However as we enter this new high DPI world where the entire UI may be magnified, a CSS pixel can end up being multiple pixels on screen.
For example if I set a zoom magnifcation of 2x, then 1 CSS pixel would actually be represented by a 2×2 square of device pixels.
This is why a pixel in CSS is referred to as a relative unit, because it is a unit whose value is relative to the viewing device (e.g., your screen).
CSS 2.1 describes how a the px unit should be rescaled as needed.
http://www.w3.org/TR/CSS21/syndata.html#length-units
What’s wrong with zooming?
Zooming an existing Web page so that it can be more easily viewed has a number of immediate benefits. Text remains readable. Images don’t become so tiny that they can’t be viewed.
Doing naive zooming, however, will result in a Web site that – when scaled – looks much worse. (Try looking at what happens to images in Internet Explorer for Windows when you change the OS DPI setting from 96 to 120 for example.) Several factors come into play here.
For example, with text you don’t want or need to “zoom” it. In other words, you aren’t going to take the actual pixels for each character and scale them like you’d scale an image. Instead you simply use a larger font size. This will allow text to have a higher level of detail on high DPI displays and ultimately look more and more like the text you might see in a printed book.
For images, you first and foremost need a good scaling algorithm. You’d like for the image to look about as good as it did on a lower DPI display when rendered at the same physical size. However, the problem with scaling of existing images is that all you’ve done is maintained the status quo, when instead you could be designing a Web site that looks even better on these higher DPI displays.
How can I make images look better?
Consider a common Web site example: the use of images to do UI elements like buttons with rounded corners and fancy backgrounds. Let’s say the Web designer uses a 50×50 pixel image for the button. The rounded corners and background may look reasonably nice on a lower DPI display and even continue to look nice when the image is scaled by 2x but rendered at the same physical size on a higher DPI display.
What if you could use a 200×200 image instead? Or, even better, what if you used an image format that hadn’t hard-coded all of its pixel information in the first place? The use of either a higher resolution image (with more detail) or of a scalable image format allows for the creation of images that would look better when rendered on the higher DPI display.
Enter SVG
Safari actually supports PDF as an image format (the hands of the clock Dashboard widget are an example of this). However other browsers do not support this format. The agreed-upon standard for scalable graphics on the Web is SVG.
SVG stands for Scalable Vector Graphics and is an XML language for describing two-dimensional images as vector graphics. Describing graphics in this fashion allows for the creation of images that will look better on high DPI displays when rendered at the same physical size.
Our goal with WebKit is to make SVG a first-class image format, so that it can be used anywhere you might use a PNG, a GIF or a JPG. In other words, all of the following should be possible:
<img src="tiger.svg"/> div { background-image: url(tiger.svg) } li { list-style-image: url(bullet.svg) }
Our current thinking regarding SVG images used this way is that they would be non-interactive (in other words you can’t hit test elements inside the SVG’s DOM). It’s debatable whether or not script execution should be allowed when SVG is used this way.
These are some issues we’d like to hammer out, since we view this use of SVG as being very different from SVG included explicitly in a compound XHTML document or included via the use of an <iframe> or <frame> element (where scripting and interactivity would be supported).
Size Matters
In addition to supporting scalable image formats like SVG, we want to make it possible for Web designers to continue to use image formats they are familiar with (like PNG, JPG and GIF), but give them the capability to conditionally include higher resolution artwork.
The idea behind this approach is that a much higher-resolution image can be specified and then either used only if the resolution is detected to be high enough, or downscaled on lower DPI displays.
In order for this approach to be viable, every place where images can be used today must support being able to specify a size in CSS pixels so that the higher resolution artwork can render with more detail in the same amount of space. (This will become clear with the examples that follow.)
In addition we would like these approaches to degrade gracefully in browsers that don’t support high DPI Web sites yet.
Let’s go over each of the places images can be used today.
The img Element
The img element already supports specifying explicit sizes, and so today you can specify a width and height and if an image is larger it will be downscaled. In a high DPI system where 1 CSS pixel != 1 device pixel, more detail can then be rendered.
In other words how you scale an image is based off comparing device pixels and not CSS pixels. For example, if you have a zoom factor of 2, an img tag that specifies a width and height of 100 CSS pixels, and an image whose intrinsic size is 200×200
device pixels, then that image is not scaled, since 100×2 = 200. However on a lower DPI display that might not have any zoom factor set, the 200×200 image would be properly downscaled to 100×100.
If no CSS size is specified for the element, then the size of the element in CSS pixels is simply the image’s size in device pixels. This will result in the image obeying the zoom.
This approach degrades gracefully, with the only tradeoff being that the higher resolution artwork would be slower to load on low DPI displays that couldn’t render all the detail anyway.
Backgrounds
For backgrounds the problem is that you need to be able to specify the size of a background tile in CSS pixels. CSS3 has a new property that we now support in WebKit called background-size. This property allows you to specify the size of a tile in CSS pixels, and thus enables backgrounds to support higher DPI artwork as well.
If no tile size is specified (as is the case on the Web today), then the size used is the image’s intrinsic size in CSS pixels. Existing background images on the Web will then obey the zoom automatically.
However once you can specify a tile size in CSS pixels, then the way the image scales can then be done using device pixels. Using the background-size property inside the background shorthand can allow for a degradable approach that won’t mess up in other browsers.
For example, let’s say you have an image tiger-low.png that is 100×100 and an image tiger-high.png that is 200×200. Here’s an example of how you might make a CSS declaration that can use the low-res image for browsers that don’t understand background-size and the higher-resolution image for browsers that do.
div { background: url(tiger-low.png); background: url(tiger-high.png) (100px 100px); }
In the above example, both declarations result in a tile that is the same size in CSS pixels, but on a high DPI machine with a zoom factor of 2, you will be able to see all of the additional detail of the higher resolution image.
Browsers that don’t understand background-size specified in the shorthand will throw out the entire second declaration. Browsers that do understand it will overwrite the previous background declaration.
List Bullets
As with backgrounds the trouble with list bullets using images is that you have no way of specifying the size of the list bullet in CSS2. Luckily CSS3 has a solution for this problem as well.
The ::marker pseudo-element can be used to style a list bullet. We plan to add support for this pseudo-element to provide much more control over the images used by bullets.
Once you can specify the size of the marker, then the same rules apply as in the previous examples.
li { list-style-image: url(bullet-low.png); } li::marker { content: url(bullet-high.png); width:10px; height:10px; }
In the above example, let’s say that bullet-low.png is 10×10 pixels and bullet-high.png is 20×20 pixels. With this approach, only if the browser understands the CSS3 list marker pseudo-element, the image will be replaced by a higher-resolution version, and thus more detail will be shown when zooming on a high dpi display.
Border Images
Safari supports the CSS3 border-image property. This property essentially already works, since in the places where tiling is used, the tiles get scaled to match the widths of the borders.
The only open issue right now is tiling in the center, since right now the spec states that the center tile is not scaled. Hopefully some heuristic will be chosen that will scale the center tiles based off the border widths (e.g., using the left/top border widths). This issue can be worked around by using border-image only to render the border and using background with background-size to tile high-resolution artwork in the center.
Conditional Inclusion
The above approaches allow you to go ahead and mingle low-res and high-res rules, but this approach can get somewhat cluttered. In addition the approach only works for two different images. What if you want to offer more than 2 versions of your artwork, e.g., low/medium/high images?
Our proposed solution for this problem is to extend CSS Media Queries with a new media query feature, the CSS pixel scaling factor.
Media queries allow a Web site author to write rules that should only be matched conditionally based off features of the device (like the viewport width/height, the screen dimensions, the screen’s DPI, etc.). Unfortunately media queries do not include the ability to query based off the zoom factor. This feature is necessary in order to really understand what’s going to happen with images.
We plan to add a new feature, device-pixel-ratio, that can be queried to find out how a CSS pixel relates to a device pixel. Min and max versions of the feature can be supported as well.
You can then construct queries like so:
<link rel="stylesheet" media="screen and min-device-pixel-ratio: 2" href="highres.css"/>
With CSS3 media queries you can then build Web sites with completely different CSS files based off the pixel-ratio of CSS pixels to device pixels, including higher res artwork as necessary.
This approach also degrades gracefully, since you can specify the lowres CSS file and then higher res CSS files inside media queries that will be ignored by browsers that don’t understand them.
Feedback
This is a preliminary proposal for how to make great high DPI Web sites in such a way that the higher resolution images can be incorporated cleanly into an existing design.
We welcome your feedback (either in this blog or to webkit-dev@opendarwin.org) and are hopeful that other browser vendors will also get involved in this discussion so that concrete standards in this area can be hammered out.