user29574732
user29574732

Reputation:

Pixel image blurs on rotation, JS Canvas

When drawing an image with rotation on a JS canvas the image is blurring. See attached images.

Blurred, with rotation: blurred

No blur, not rotated: not blurred

I have set imageSmoothingEnabled to false and image-rendering to pixelated.

Render code:

fg.save();
fg.translate(gun.x - gun.frame.w / 2, gun.y - gun.frame.h / 2);
fg.rotate(gun.a);
if(mouse.x < (player.x + player.width / 2)) {
    fg.scale(1, -1);
}
fg.drawImage(spriteSheet, gun.frame.x, gun.frame.y, gun.frame.w, gun.frame.h, 0, 0, gun.frame.w, gun.frame.h);
fg.restore();

Please no answers saying it isn't possible. It would be much more helpful having an alternative rendering method suggested.

Upvotes: 1

Views: 52

Answers (1)

Blindman67
Blindman67

Reputation: 54099

Add a transparent edge

Even when smoothing is off and canvas image is pixelated, the canvas 2D renderer will still anti-alias the edge of an image rendered using ctx.drawImage. This is true even if the image is part of a sprite sheet and you use the overload drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

Any visible pixels that touch the edge will be aliased. This cannot be disabled.

The solution is to add a transparent boundary around each sprite and draw them including the transparent boundary.

This will ensure no visible pixels are touching the edge of the rendered image.

Image source https://stackoverflow.com/questions/79426201/pixel-image-blurs-on-rotation-js-canvas#

Image shows sprites as defined in the example snippet below.

Red rectangles no transparent padding and green rectangles padded with a one-pixel transparent boundary.

Example

The code below renders the gun sprite onto a zoomed in canvas (8 times)

On the...

  • Left there is no padding.
  • Right the gun is rendered with a one-pixel transparent boundary.

const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;

const sprites = {  // ax, ay are anchors as fraction of sprite size
    // Without transparent padding
    gun: {x: 19, y: 1, w: 16, h: 8, ax: 0.375,  ay: 0.5},   
    cowboy: {x: 1, y: 1, w: 16, h: 16, ax: 0.5,  ay: 1},
    
    // With transparent padding
    gunPad: {x: 18, y: 0, w: 18, h: 10, ax: 0.375,  ay: 0.5},   
    cowboyPad: {x: 0, y: 0, w: 18, h: 18, ax: 0.5,  ay: 1},
};


// draws sprite with anchor at x, y and rotated around anchor
function drawSprite(sprSheet, spr, x, y, rotate) {
    const ax = Math.cos(rotate);
    const ay = Math.sin(rotate);
    ctx.setTransform(ax, ay, -ay, ax, x, y);
    const cx = spr.ax * spr.w;
    const cy = spr.ay * spr.h;
    ctx.drawImage(sprSheet, spr.x, spr.y, spr.w, spr.h, -cx, -cy, spr.w, spr.h);
}
function drawGuns() {
    const ang = Math.atan2(-1, 2);
    drawSprite(spriteSheet, sprites.gun, 8, 8, ang);
    drawSprite(spriteSheet, sprites.gunPad, 28, 8, ang);
}

// Image source https://stackoverflow.com/questions/79426201/pixel-image-blurs-on-rotation-js-canvas#
// Copy of your cowboy and gun sprite with padding
const spriteSheet = new Image();
spriteSheet.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAASCAYAAAAzI3woAAAAAXNSR0IArs4c6QAAAXlJREFUSEvNVrFKBEEMzZw/YGmnYqNcq+BniBbqh1jJISJX+SeC4p9YHdprYWkh2Agrb+Et2VxmJnNc4TZ3OzN5eclLMpvknz3J43N1cNbp9fvXx6TX8B6Nw2KV7IC7BAyA6fSztzs9+ZKn501ZLLYEa3jHczfflxIpYDAIbVciQ8yBkAWhMUloMBrTRu8xIB0Egio98DEipEFgyAzlQOCM5/DfZot4dBSVd5BMA1CiEghl4FlNyCND+SK1N5IsqjfIemRmh+fd3s7HUH8laXMBj4q6pSMAaCO+Obrodrffe1+elBHpXELIFItSg3CNNWQJ3R5fdj/fv5ImIpONJPOXh/B4oB+X0Oz6ra96/Nru4p6XoUgGameyc8hr91zt1Jy07C9liHJ57V9r9xbH1aLWExqHvRliu7A2sVch2GcIZFgvdqp60uFM9BppJTUQYvSIOjepvbtt3VkaDUZ2Tm0e6ds/Mn1bshT6/LCA6yah8ZsHV0u0q5z9A9xzCSK+FV/aAAAAAElFTkSuQmCC";
spriteSheet.addEventListener("load", drawGuns);
canvas {
    width: 384px;
    height: 128px;
    image-rendering: pixelated;
}
<canvas id="canvas" width="48" height="16"></canvas>

Alternative

If the rotated image is still too low quality, you should consider hand drawing the rotated image. Personally, I find hand drawn rotation more esthetically pleasing than machine rotated.

Upvotes: 2

Related Questions