corazza
corazza

Reputation: 32364

Why do images lose quality after the context has been rotated?

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?

Upvotes: 25

Views: 4119

Answers (4)

Torsten Walter
Torsten Walter

Reputation: 5782

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.

Upvotes: 3

jpmuc
jpmuc

Reputation: 1154

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

Upvotes: 0

Martin Stone
Martin Stone

Reputation: 13007

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!

Upvotes: 17

kirilloid
kirilloid

Reputation: 14304

(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.

Upvotes: 1

Related Questions