I noticed an interesting bug today while working on a browser-based user interface rendered with the HTML 5 <canvas> element. A little rectangle was appearing fuzzy:

nib4

Let me zoom in a bit:

nib3

Whereas it should have appeared crisp, as in this:

nib2

My first thought was that it would be due to fractional coordinates. I have years of experience with drawing APIs that force integer coordinates, so I’m used to having the fractional part of a coordinate whacked off and making up the difference when necessary in a second pass. Canvas, on the other hand, supports fractional coordinates, which I’m told is the fancy thing to do these days. (How the fraction is converted to an actual pixel is depenendent on whatever drawing system is doing the heavy lifting somewhere down the stack.) When your coordinates are fractional, you can get this kind of fuzziness.

Because the interface I’m working with involves a few layers of rendering code, ensuring that integers ruled the roost took some time. (I did this by wrapping the various canvas functions with versions that dumped their parameters before calling back to the real functions.) But after quite a bit of poking around, I found no evidence of fractional coordinates. It was around this time I saw Vlad (Mozilla’s graphics guru) walking around the office and asked for some help.

We started looking for evidence of transforms that would introduce fractional coordinates–but ultimately came up empty handed. As we went through this process, he pointed out that the <canvas> context instances are reused, so it’s a really good idea to save() and restore() when obtaining a canvas to avoid polluting the context:

var ctx = canvas.getContext("2d");
ctx.save();
// painting here
ctx.restore();

I had assumed each call to getContext() produced a fresh, stateless context, so this was welcome news indeed.

But, we didn’t find a source for the fractional coordinates. And then we noticed when right-clicking and selecting “View Image” from the context menu, the resulting image that was physically smaller than the browser window.

And that’s when we noticed that I had zoomed in a click using Firefox 3’s fancy full page zoom feature, which was causing the image the be scaled up, and the blurriness.

May you avoid a similar fate.

11 thoughts on “HTML 5 Canvas Lessons: Zooming and Reusing

  1. Thanks for the tip.

    If you don’t mind me asking, what’s the project?
    And what proportion of it is in canvas as opposed to HTML?
    Heck, while I’m at it, what browsers are you targeting for it? πŸ™‚

  2. The project targets any canvas-equipped browser, but I don’t want to spoil the surprise–I’ll talk more about it next week. πŸ™‚

  3. Hi Ben,

    I came across you post looking for “crisp canvas lines”. You describe the problem I’m facing, but zooming appears not to be the cause of my problem.

    If you like, please take a look at the following example:

    var ctx = document.getElementById(“canvas”).getContext(“2d”);
    ctx.save();
    // not crisp
    ctx.moveTo(33,10);
    ctx.lineTo(33,90);
    ctx.stroke();
    // crisp
    ctx.fillRect(66,10,1,80);
    ctx.restore();

    When I use the “default” way of drawing lines, I get the impression that two anti-aliased(?)/semi-transparent lines are drawn right next to each other, resulting in a 2px dark-grey line, where I would expect to be able to draw a crisp 1px black line.

    It almost looks like your first example and I would expect it to look like your expected example. The only way I can draw these crisp lines, is by using the fillRect method to fill a rect of width 1.

    Do you know if this behaviour is “by design” ? How do you go about drawing these crisp lines ?

    Thanks a lot in advance.

  4. Time to blame myself for not reading the documentation available: https://developer.mozilla.org/en/Canvas_tutorial/Applying_styles_and_colors#A_lineWidth_example nicely explains why this is happening and how to resolve it.

    The result boils down to:

    var ctx = document.getElementById(“canvas”).getContext(“2d”);
    ctx.save();
    // not crisp
    ctx.moveTo(25,10);
    ctx.lineTo(25,90);
    ctx.stroke();
    // one way to solve it
    ctx.fillRect(50,10,1,80);
    // the right way to solve it
    ctx.moveTo(75.5,10);
    ctx.lineTo(75.5,90);
    ctx.stroke();
    ctx.restore();

  5. I had a similar experience when I specified the canvas width with css rather than on the canvas object, making me wonder why the object on the canvas didn’t seem to be the number of pixels specified, with some fuzzy lines πŸ˜›

  6. on my ffroiex 6.0.1 it works fine! :-DI wanted to make an animated background for my blog, but maybe it’s too early. πŸ˜€

  7. And even with a little helper spreading crumbs, the Shark
    can not be outdone. For those who love cooking but hate the clean-up involved the Scrap Trap is an great tool to keep your counters and floor sparkling and uncluttered.
    You can either make your own base or use a ready-made base, and
    then add whatever ingredients and toppings that you want.

  8. Hello admin do you need unlimited content for your page
    ? What if you could copy article from other sites, make
    it pass copyscape test and publish on your blog – i
    know the right tool for you, just search in google:
    Ziakdra’s article tool

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s