javascripthtml5-canvasimage-quality

Disable auto-scaling of images in JavaScript Canvas


I've made a basic snake game, and I'm trying to improve one minor detail.

You see, when I place the apple image [or leaf image because I had an SVG image for that handy], the canvas scales the image and makes it quite big.

Here is my code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>
      Basic Snake Game
    </title>
  </head>
  <body>
    <svg style="width:50px;height:50px;"><polygon points="10,10 10,14 10.5,17 12,23 14,30 16,34 18,36 22,37 25,37 25,30 24.5,28 24,27 23.5,26 11,10" style="fill:lime;stroke:green;stroke-width:1;" /><line x1="10" y1="10" x2="25" y2="37" style="stroke:green;stroke-width:1;" /></svg>
    <script>
      var html = document.querySelector('body');
      html.innerHTML += '<p></p><br/><input type="button" onclick="heading -= 90" value="Left 90*" style="font-size:20pt;"/><input type="button" onclick="heading += 90" value="Right 90*" style="font-size:20pt;"/><canvas style="width:1000px;height:700px;border:3px solid red;"></canvas>';
      var canvas = document.querySelector('canvas');
      var ctx = canvas.getContext('2d');
      var img = document.querySelector('svg');
      var xml = new XMLSerializer().serializeToString(img);
      img = new Image();
      img.src = 'data:image/svg+xml;base64,' + btoa(xml);
      ctx.fillStyle = 'black';
      ctx.fillRect(0, 0, 2000, 1400);
      var width = 15;
      var speed = 1;
      var heading = 0;
      var pos = [15, 10];
      var leafPos;
      leafPos = [Math.floor(Math.random() * 250), Math.floor(Math.random() * 75)];
      ctx.fillRect(30, 20, width, 10);
      var main = setInterval(function() {
        ctx.fillStyle = 'black';
        ctx.fillRect(0, 0, 2000, 1400);
        ctx.fillStyle = '00ff00';
        if(heading == -90) {
          heading = 270;
        } if(heading == 360) {
          heading = 0;
        } if(heading == 0) {
          pos[0] += speed;
        } if(heading == 180) {
          pos[0] -= speed;
        } if(heading == 0 || heading == 180) {
          ctx.fillRect(pos[0], pos[1], width, 5);
        } if(heading == 270) {
          pos[1] -= speed;
        } if(heading == 90) {
          pos[1] += speed;
        } if(heading == 270 || heading == 90) {
          ctx.fillRect(pos[0], pos[1], 5, width);
        }

        if((Math.abs(leafPos[0] - pos[0]) + Math.abs(leafPos[1] - pos[1])) <= 15) {
          speed  += 0.5;
          width ++;
          leafPos = [Math.floor(Math.random() * 250), Math.floor(Math.random() * 75)];
        }
        ctx.drawImage(img, leafPos[0], leafPos[1]);                 // The line of interest
        document.querySelector('p').innerHTML = (speed - 1) *10;
      }, 50);
    </script>
  </body>
</html>

The original SVG image is at the top for reference.

To remedy this, I tried putting ctx.scale(0.5, 0.5) before the main interval and doubling all x, y, width, and height values EXCEPT that of our line of interest. This worked, but the image quality was compromised quite badly. I also tried editing the line of interest by scaling its width and height:

ctx.drawImage(img, leafPos[0], leafPos[1], img.width /2, img.height /2)

However, this produced the same result.

Is there some way to disable automatic scaling of images to avoid this altogether?

OR

Is there some way to resize images without reducing image quality?

Any help is appreciated.


Solution

  • Instead of setting the canvas size on the style:
    <canvas style="width:1000px;height:700px;border:3px solid red;"></canvas>'

    You should set it like this:
    <canvas width=1000 height=700 style="border:3px solid red;"></canvas>

    See working sample below

    var canvas = document.querySelector('canvas');
    var ctx = canvas.getContext('2d');
    var img = document.querySelector('svg');
    var xml = new XMLSerializer().serializeToString(img);
    img = new Image();
    img.src = 'data:image/svg+xml;base64,' + btoa(xml);
    
    function draw() {
      ctx.fillStyle = 'black';
      ctx.fillRect(0, 0, 2000, 1400);
      ctx.fillStyle = '00ff00';
      ctx.drawImage(img, 20, 20);
    }
    
    setInterval(draw, 50);
    <svg style="width:50px;height:50px;">
    <polygon points="10,10 10,14 10.5,17 12,23 14,30 16,34 18,36 22,37 25,37 25,30 24.5,28 24,27 23.5,26 11,10" style="fill:lime;stroke:green;stroke-width:1;" />
    <line x1="10" y1="10" x2="25" y2="37" style="stroke:green;stroke-width:1;" />
    </svg>
    
    <canvas width=1000 height=700 style="border:3px solid red;"></canvas>