Amino 1.1 Released and Retina Ready
I am happy to announce the 1.1 release of Amino, my open source JavaScript graphics library, is ready today. All tests are passing. The new site and docs are up. (Generated by a new tool that I'll describe later). Downloads activate! With the iPad 3 coming any day now I thought it would be good to take a look at what I've done to make Amino Retina Ready (™). Even if you don't have a retina screen it will improve your apps.
The Problem
But first, let's back track and talk for a second about how Canvas works. Canvas isn't really a part of the DOM. To the web browser it just looks like a rectangular image that can be updated. It's just pixels. This low level access is powerful but comes with some tradeoffs. Since the rest of the browser just sees a Canvas element as an image it will scale the canvas as an image as well. If the user zooms the page then the canvas 'image' will be scaled up, resulting in pixelation. This is also a problem with Apple's hi-dpi retina screens. Though they have double the resolution of their non-retina counterparts they still report the same screen sizes. Essentially everything on the page is given a 2x zoom, so your canvas isn't taking advantage of the full pixel density. (This isn't strictly true, but bear with me for a second). Finally, if you scale the canvas area directly using CSS transforms or a proportional size (like
width:50%
) then the canvas may change size over the lifetime of the page, again giving the user zoomed in pixels. So how can we deal with this?
The Solution
Simple: we check if the real size of the canvas is different than the specified size. If it is then we can update the canvas size directly to match what is really on screen. The code looks like this:
if(canvas.width != canvas.clientWidth) { canvas.width = canvas.clientWidth; }
To deal with a retina display we just scale an extra 2x by checking for
window.devicePixelRatio
== 2. To tell when the user has changed the page by zooming or resizing we could hook up all sorts of event listeners, but I prefer to simply check on repaints since most things I do are animated. Of course we have to set the canvas height as well, which brings up the question: how should we scale things? If the canvas is uniformly scaled then you can calculate a ratio and multiply the height by that. If the canvas is *not* uniformly scaled, say because the width is set to a percentage but the height is not, then you can automatically scale to fit, or stretch it to fill the new size. In the end I found only a few combinations to actually be useful:
- Do nothing. Don't adjust the canvas size or scaling at all.
- Resize the canvas but don't mess with scaling. This essentially turns the canvas into a resizable rectangle, leaving it up to the content to adjust as desired.
- Uniformly scale the content to fit.
To handle all of this I gave Amino two properties:
Canvas.autoSize
and
Canvas.autoScale
. autoSize controls whether the canvas should adapt at all. autoScale controls if it will rescale the content. Amino will handle all of the math and detect retina displays. All you have to do is choose which option you want. I haven't tested this on IE yet (I still need to get the new Win 8 preview) but I have tested this on FireFox, Chrome, Safari and Mobile Safari on an iPhone 4S. Check out
to see it in action. Be sure to check out the new
Posted March 5th, 2012
Tagged: amino