24

I'm making a top-down shooter game that relies on the avatar always being rotated pointing to the mouse cursor. I achieve rotation like this:

//Rendering.
context.save(); //Save the context state, we're about to change it a lot.

context.translate(position[0] + picture.width/2, position[1] + picture.height/2); //Translate the context to the center of the image.
context.rotate(phi); //Rotate the context by the object's phi.
context.drawImage(picture.image, -picture.width/2, -picture.height/2); //Draw the image at the appropriate position (center of the image = [0, 0]).

context.restore(); //Get the state back.

When the phi is zero, the image is rendered in its normal quality, with sharp edges and detectable pixels. But, when I set the phi to a nonzero value (actually, when it's not 0, Pi/2, Pi, Pi+Pi/2 or 2Pi), the image looses it's sharpness and the individual pixels can't be seen anymore, because they are blurred out.

Here's a screenshot (sorry about the general bad quality of the screenshot, but I think that the difference is more than noticeable):

enter image description here

This is, well, a bit unacceptable. I can't have the images always blurred out! Why is this happening and can I solve it?

kangax
  • 38,898
  • 13
  • 99
  • 135
corazza
  • 31,222
  • 37
  • 115
  • 186
  • 3
    think about how the original pixels need to line up with a rotated version of the image.. they will not match to the pixel grid - then what should happen..? – Randy Jul 05 '12 at 20:59
  • Why doesn't that happen in non-HTML5 games? I was playing Dragon Fly later this day, and it doesn't have a problem rotating the little dragon! – corazza Jul 05 '12 at 21:00
  • It's in Java, I believe, but yes, thanks for reminding me, @Daedalus, this doesn't happen in Flash games, either. So it must be an HTML5-specific thing, I guess. And if other games **can** fit my monitor's pixel grid, and not look blurred, it's probably a software thing. – corazza Jul 05 '12 at 21:02
  • are you sure it is a single image that is being rotated? one way is to draw several images and use them at the right times - that way you are in direct control of any anti-aliasing. – Randy Jul 05 '12 at 21:04
  • Please elaborate, @Randy. And yes, I only load one instance of an image and render those. – corazza Jul 05 '12 at 21:05
  • A possible option is to rotate with CSS instead... though I fear you'll have the same problem. Well, and then your game would then be DOM based instead of `` based so scratch that idea. – potench Jul 05 '12 at 21:06
  • DOM based isn't really an option. – corazza Jul 05 '12 at 21:11
  • This could be related to image smoothing - take a look here: http://stackoverflow.com/questions/195262/can-i-turn-off-antialiasing-on-an-html-canvas-element – Mike Robinson Jul 05 '12 at 21:27
  • 2
    An option to reduce quality loss when scaling or rotating is to use vector graphics - refer to this question: http://stackoverflow.com/questions/4340040/html5-canvas-vector-graphics – potench Jul 05 '12 at 22:38

4 Answers4

17

You could try

context.imageSmoothingEnabled = false;

See docs:

context.imageSmoothingEnabled [ = value ]

Returns whether pattern fills and the drawImage() method will attempt to smooth images if they have to rescale them (as opposed to just rendering the images with "big pixels").

Can be set, to change whether images are smoothed (true) or not (false).

If you want a true pixel-art retro style effect, you'd need to manually create rotated sprite images for several angles, look up the appropriate sprite for the current value of phi, and draw it without rotation. This obviously requires a fair amount of art work!

Community
  • 1
  • 1
Martin Stone
  • 12,682
  • 2
  • 39
  • 53
3

IF you are rotating images around their center point, make sure the image itself has an even number of pixels. Once you end up on odd coordinates the image data needs to be interpolated for the target canvas. Apple has some nice documentation on translating and rotating the canvas.

So for any image, as suggested above use rounding to snap to full pixels.

context.translate(Math.floor(img.width/2), Math.floor(img.height/2));

This way every source pixel of your image will always be drawn exactly into a pixel inside the canvas and blurring does not occur. This however is only true for multiples of 90 degrees.

It seems that all browsers do, to some extend, antialiasing in image drawing so you will probably have to provide rotated images as sprites.

According to this Chromium bug report you might be lucky there if they haven't fixed it yet. Read through and you'll learn that Ian Hickson likely opposed making antialiased image drawing optional.

Torsten Walter
  • 5,614
  • 23
  • 26
1

(picture.width/2, picture.height/2) point won't always work.

(Math.floor(picture.width/2) + 0.5, Math.floor(picture.height/2) + 0.5) should help.

kirilloid
  • 14,011
  • 6
  • 38
  • 52
0

Well, actually it is something you cannot get around

If you rotate an image by a multiple of 90 degrees, your library should smart enough so that no interpolation is applied.

But as soon as you rotate an image by an angle different from a multiple of 90 degrees, you need to interpolate. As a consequence, you get that smoothing. If you are interested in the theory, you may look for a book on computer graphics or image processing.

For the concrete case of image rotation you may have a look at this paper, http://bigwww.epfl.ch/publications/unser9502.html

jpmuc
  • 1,092
  • 14
  • 30
  • I'm pretty sure that you can get around it. Maybe not using a canvas, but C++, but it's definitely possible. Ie, don't apply any interpolation and just draw everything a bit crude. Maybe a mix of these is the best... Also, it would be great if you provided some links to said literature. – corazza Jul 20 '12 at 14:33
  • @Bane How can drawing everything crude be a solution? The result would miss all the little pixel details you put into the original image. Also if you mix crude drawings with interpolation the result is still blurry (and crude). I haven't found a way to integrate c++ into javascript yet, but of course it's possible to translate c++ code to javascript code. Still there's no real way to rotate arbitrary raster images without losing pixels in c++. With vector based images it would be a lot easier. – Robert Jul 20 '12 at 23:55
  • well, I said you cannot get around it, because when rotating you need to interpolate. And those are interpolation artifacts. It has nothing to do with a particular programming language. It's the math – jpmuc Jul 24 '12 at 07:55