Zeng Cheng
Zeng Cheng

Reputation: 825

HTML5 - Canvas, drawImage() draws image blurry

I am trying to draw the following image to a canvas but it appears blurry despite defining the size of the canvas. As you can see below, the image is crisp and clear whereas on the canvas, it is blurry and pixelated.

enter image description here

and here is how it looks (the left one being the original and the right one being the drawn-on canvas and blurry.)

enter image description here

What am I doing wrong?

console.log('Hello world')

var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()

// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'
playerImg.width = 32
playerImg.height = 32

playerImg.onload = function() {
  ctx.drawImage(playerImg, 0, 0, 32, 32);
};
#canvas {
  background: #ABABAB;
  position: relative;
  height: 352px;
  width: 512px;
  z-index: 1;
}
<canvas id="canvas" height="352" width="521"></canvas>

Upvotes: 44

Views: 46214

Answers (7)

HamidReza RTM
HamidReza RTM

Reputation: 89

Just set the canvas size to the image or video size before obtaining the context from it.

const canvas = document.getElementById("canvas")

const image = document.getElementById("image")
canvas.width = image.width
canvas.height = image.height

// or

const video = document.getElementById("video")
canvas.width = video.videoWidth
canvas.height = video.videoHeight


// finally:

const context = canvas.getContext('2d')

context.drawImage(image, 0, 0, canvas.width, canvas.height)

// or

context.drawImage(video, 0, 0, canvas.width, canvas.height)

Upvotes: 0

Diniden
Diniden

Reputation: 1125

As I encountered this older post for some of my issues, here's even more additional insight to blurry images to layer atop the 'imageSmoothingEnabled' solution.

This is more specifically for the use case of monitor specific rendering and only some people will have encountered this issue if they have been trying to render retina quality graphics into their canvas with disappointing results.

Essentially, high density monitors means your canvas needs to accommodate that extra density of pixels. If you do nothing, your canvas will only render enough pixel information into its context to account for a pixel ratio of 1.

So for many modern monitors who have ratios > 1, you should change your canvas context to account for that extra information but keep your canvas the normal width and height.

To do this you simply set the rendering context width and height to: target width and height * window.devicePixelRatio.

canvas.width = target width * window.devicePixelRatio;
canvas.height = target height * window.devicePixelRatio;

Then you set the style of the canvas to size the canvas in normal dimensions:

canvas.style.width = `${target width}px`;
canvas.style.height = `${target height}px`;

Last you render the image at the maximum context size the image allows. In some cases (such as images rendering svg), you can still get a better image quality by rendering the image at pixelRatio sized dimensions:

ctx.drawImage(
    img, 0, 0, 
    img.width * window.devicePixelRatio, 
    img.height * window.devicePixelRatio
  );

So to show off this phenomenon I made a fiddle. You will NOT see a difference in canvas quality if you are on a pixelRatio monitor close to 1.

https://jsfiddle.net/ufjm50p9/2/

Upvotes: 26

aleha_84
aleha_84

Reputation: 8539

In addition to @canvas answer.

context.imageSmoothingEnabled = false;

Works perfect. But in my case changing size of canvas resetting this property back to true.

window.addEventListener('resize', function(e){
    context.imageSmoothingEnabled = false;
}, false)

Upvotes: 11

NeoGriever
NeoGriever

Reputation: 11

Simple tip: draw with .5 in x and y position. like drawImage(, 0.5, 0.5) :D There you get crisp edges :D

Upvotes: -2

Jeff Tian
Jeff Tian

Reputation: 5893

The following code works for me:

img.onload = function () {
  canvas.width = img.width;
  canvas.height = img.height;
  context.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
};
img.src = e.target.result;  // your src

Upvotes: 1

Kaiido
Kaiido

Reputation: 136698

Your problem is that your css constraints of canvas{width:512}vs the canvas property width=521will make your browser resample the whole canvas.

To avoid it, remove those css declarations.

var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()

// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'
playerImg.width = 32
playerImg.height = 32

playerImg.onload = function() {
  ctx.drawImage(playerImg, 0, 0, 32, 32);
};
#canvas {
  background: #ABABAB;
  position: relative;
  z-index: 1;
}
<canvas id="canvas" height="352" width="521"></canvas>

Also, if you were resampling the image (from 32x32 to some other size), @canvas' solution would have been the way to go.

Upvotes: 24

Canvas
Canvas

Reputation: 5897

The reason this is happening is because of Anti Aliasing. Simply set the imageSmoothingEnabled to false like so

context.imageSmoothingEnabled = false;

Here is a jsFiddle verson

jsFiddle : https://jsfiddle.net/mt8sk9cb/

var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()

// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'

playerImg.onload = function() {
  ctx.imageSmoothingEnabled = false;
  ctx.drawImage(playerImg, 0, 0, 256, 256);
};

Upvotes: 55

Related Questions