Thursday, August 25, 2016

HTML5 Canvas DrawImage Artifacts

I'm working on an HTML5 canvas game that requires smooth scrolling of images across the screen. The coordinates of the images are real numbers, not integers. The canvas can handle real number coordinates just fine and antialiases everything to make it look smooth. The problem is that it also sometimes produces some weird artifacts to be drawn.

What do I mean by artifacts? Artifacts are pixels that are drawn that you don't expect. In my case, for example, I have an image that has a significant amount of transparent pixels. When it's drawn at particular locations thin gray lines get drawn in some of the space where it should be transparent. It's not a lot, but it's noticeable.

So what can you do about it? There are a couple things, but they have their disadvantages. For one thing you can convert all of your coordinates to integers. One way to do this is with a little JavaScript trick; binary "or" the value with 0.

var x = 1.67;
x = x | 0; // x is now 1

That eliminates the artifacts because the images are drawn on whole pixel boundaries, so there's no need for the canvas to smooth out the image. The problem is that it makes your animations really choppy, especially if you have a lot of images scrolling next to each other.

Another thing I tried was to use Math.round() instead of just lopping off the fraction. I figured that would reduce the amount of choppiness. Unfortunately it didn't make much of a difference.

var x = 1.67;
x = Math.round(x); // x is now 2

But wait, there's one more. The canvas context has an imageSmoothingEnabled property you can set to true or false. By default it's true. I found that when you set it to false it's basically the same as using Math.round() above. It forces your images to be drawn on whole pixel boundaries. Which also produces choppiness.

context.imageSmoothingEnabled = false;
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;

So now we're right back where we started. In the end I decided not to use any of these methods and just stick with the occasional artifact being drawn. It looks crappy, but it only seems to happen at certain screen locations. In my case having smooth animations was far more important than the occasional artifact.

I think in most cases the choppiness wouldn't be such an issue and one of these techniques would work. It's up to you to determine what looks best. I would go with using imageSmoothingEnabled property because then you don't have to add any extra code to convert to an integer. Let the system do the work for you.

Code hard!