How to deal with quality loss using canvas drawImage? - javascript

I've seen this question on Stackoverflow, but I failed to find an answer - all solutions proposed just does not work for me.
Problem: When I'm passing a base64-encoded SVG as src to image it looks as crisp as original SVG image. But if I'll take this image and use it in canvas (via context.drawImage) - it would be of a worser quality:
The question is - how can I draw svg-based image in canvas which will look like original image?
What have I tried so far. The approach described here (re-iterative downsampling) is not working for me. The other advise (to add 0.5 to the dimensions) has not saved the situation as well. Playing with imageSmoothingEnabled - still no luck.
Here's the codepen used for generating this particular screenshot:
let toBase64 = (svg) => {
let serialized = new XMLSerializer().serializeToString(svg);
let base64prefix = "data:image/svg+xml;base64,"
let enc = base64prefix + btoa(serialized);
return enc;
}
let copySvg = (svg, img) => {
let enc = toBase64(svg);
img.src = enc;
}
let copyImg = (img, canvas) => {
context = canvas.getContext("2d");
context.drawImage(img, 0, 0, 200.5, 200.5);
}
let main = () => {
let svg = document.getElementById("svg");
let img = document.getElementById("img");
let canvas = document.getElementById("canvas");
copySvg(svg, img);
copyImg(img, canvas);
}
window.onload = main;

The SVG and Image are implicitly drawn at high DPI, but you need to explicitly handle this case for canvas.
If your device has window.devicePixelRatio === 2, you'll see a much crisper image if you increase canvas size and update drawImage to match:
<!-- Change this -->
<canvas id="canvas" width="200" height="200"></canvas>
<!-- to -->
<canvas id="canvas" width="400" height="400"></canvas>
And:
// change this:
context.drawImage(img, 0, 0, 200.5, 200.5);
// to:
context.drawImage(img, 0, 0, 400, 400);
Forked codepen
For more details about window.devicePixelRatio (and canvas's backingStorePixelRatio) see the html5rocks article on High DPI Canvas

Related

canvg cannot call a class as a function

For a project I am working on I need to transform a svg to png file. In order to do so I have found multiple guides and explanations online. One of these can be found here: Exporting D3 charts to PDF
In order to transform the svg to a png they use the following code:
let canvas = document.createElement('canvas');
canvg(canvas, svg);
let imgData = canvas.toDataURL('image/png');
But I keep on getting an error when I try to implement this in my own project: "TypeError: Cannot call a class as a function". I have found multiple explanations online where they use the canvg(canvas, svg); notation. I also have read the Github documentation form Canvg and found nothing about this type of notation or an alternative way to do this.
The way I import the package into my project is as follows:
import canvg from "canvg";
This is the full code I am using to convert my d3 svg chart to a pdf:
exportToPDF() {
let svg = document.getElementsByClassName("svg")[0].innerHTML;
var canvas = document.createElement("canvas");
canvg(canvas, svg);
let imgData = canvas.toDataURL("image/png");
var doc = new jsPDF("l", "pt", [1020, 768]);
doc.addImage(imgData, "PNG", 0, 0, 1020, 768);
doc.save("svg-png-chart.pdf");
}
The error is clear, you are calling the class canvg without the new keyword.
Also, you referred to the GitHub Documentation where there is clearly write how to use it:
window.onload = () => {
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// ==== HERE YOUR FUNCTION ===
v = canvg.Canvg.fromString(ctx, '<svg width="600" height="600"><text x="50" y="50">Hello World!</text></svg>');
// Start SVG rendering with animations and mouse handling.
v.start();
};
Probably the article you read Exporting D3 charts to PDF was refering to an older API of Canvg
This should do:
exportToPDF() {
let svg = document.getElementsByClassName("svg")[0].innerHTML;
let canvas = document.createElement("canvas");
let context = canvas.getContext('2d')
let v = canvg.Canvg.fromString(context, svg);
v.start();
let imgData = canvas.toDataURL("image/png");
var doc = new jsPDF("l", "pt", [1020, 768]);
doc.addImage(imgData, "PNG", 0, 0, 1020, 768);
doc.save("svg-png-chart.pdf");
}
I have a found the solution. The fix was also thanks to DDomen. He got me on the right path. canvg.fromString(context, svg) should be used in order to get the transform the svg to a png.
But then the next problem would arise that the dimensions need to be set on the canvas in order to have the imaged being cropped when it is bigger then the default dimensions of a canvas object.
exportToPDF() {
let svgElement = document.getElementsByClassName("svg")[0];
const width = svgElement.getBoundingClientRect().width;
const height = svgElement.getBoundingClientRect().height;
let svg = svgElement.innerHTML;
let canvas = document.createElement("canvas");
let context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
let v = canvg.fromString(context, svg);
v.start();
let imgData = canvas.toDataURL("image/png");
var doc = new jsPDF("l", "pt", [1020, 768]);
doc.addImage(imgData, "PNG", 0, 0, width, height);
doc.save("svg-png-chart.pdf");
}

How to show xmlObject data (SVG) in an HTML doc? [duplicate]

Is there a default way of drawing an SVG file onto a HTML5 canvas? Google Chrome supports loading the SVG as an image (and simply using drawImage), but the developer console does warn that resource interpreted as image but transferred with MIME type image/svg+xml.
I know that a possibility would be to convert the SVG to canvas commands (like in this question), but I'm hoping that's not needed. I don't care about older browsers (so if FireFox 4 and IE 9 will support something, that's good enough).
EDIT: Dec 2019
The Path2D() constructor is supported by all major browsers now, "allowing path objects to be declared on 2D canvas surfaces".
EDIT: Nov 2014
You can now use ctx.drawImage to draw HTMLImageElements that have a .svg source in some but not all browsers (75% coverage: Chrome, IE11, and Safari work, Firefox works with some bugs, but nightly has fixed them).
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";
Live example here. You should see a green square in the canvas. The second green square on the page is the same <svg> element inserted into the DOM for reference.
You can also use the new Path2D objects to draw SVG (string) paths. In other words, you can write:
var path = new Path2D('M 100,100 h 50 v 50 h 50');
ctx.stroke(path);
Live example of that here.
Original 2010 answer:
There's nothing native that allows you to natively use SVG paths in canvas. You must convert yourself or use a library to do it for you.
I'd suggest looking in to canvg: (check homepage & demos)
canvg takes the URL to an SVG file, or the text of the SVG file, parses it in JavaScript and renders the result on Canvas.
Further to #Matyas answer: if the svg's image is also in base64, it will be drawn to the output.
Demo:
var svg = document.querySelector('svg');
var img = document.querySelector('img');
var canvas = document.querySelector('canvas');
// get svg data
var xml = new XMLSerializer().serializeToString(svg);
// make it base64
var svg64 = btoa(xml);
var b64Start = 'data:image/svg+xml;base64,';
// prepend a "header"
var image64 = b64Start + svg64;
// set it as the source of the img element
img.onload = function() {
// draw the image onto the canvas
canvas.getContext('2d').drawImage(img, 0, 0);
}
img.src = image64;
svg, img, canvas {
display: block;
}
SVG
<svg height="40" width="40">
<rect width="40" height="40" style="fill:rgb(255,0,255);" />
<image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAEX0lEQVQ4jUWUyW6cVRCFv7r3/kO3u912nNgZgESAAgGBCJgFgxhW7FkgxAbxMLwBEmIRITbsQAgxCEUiSIBAYIY4g1EmYjuDp457+Lv7n+4tFjbwAHVOnVPnlLz75ht67OhhZg/M0p6d5tD9C8SNBBs5XBJhI4uNLC4SREA0UI9yJr2c4e6QO+v3WF27w+rmNrv9Pm7hxDyHFg5yYGEOYxytuRY2SYiSCIwgRgBQIxgjEAKuZWg6R9S0SCS4qKLZElY3HC5tp7QPtmlMN7HOETUTXBJjrEGsAfgPFECsQbBIbDGJZUYgGE8ugQyPm+o0STtTuGZMnKZEjRjjLIgAirEOEQEBDQFBEFFEBWLFtVJmpENRl6hUuFanTRAlbTeZarcx0R6YNZagAdD/t5N9+QgCYAw2jrAhpjM3zaSY4OJGTDrVwEYOYw2qioigoviq5MqF31m9fg1V5fCx+zn11CLNVnufRhBrsVFE1Ihpthu4KDYYwz5YQIxFBG7duMZnH31IqHL6wwnGCLFd4pez3/DaG2/x4GNPgBhEZG/GGlxkMVFkiNMYay3Inqxed4eP33uf7Y0uu90xWkGolFAru7sZn5w5w921m3u+su8vinEO02hEWLN/ANnL2rkvv2an2yd4SCKLM0JVBsCgAYZZzrnPP0eDRzXgfaCuPHXwuEYjRgmIBlQVVLl8/hKI4fRzz3L6uWe5+PMvnHz6aa4uX+D4yYe5vXaLH86eoyoLjLF476l9oKo9pi5HWONRX8E+YznOef7Vl1h86QWurlwjbc+QpikPPfoIcZLS39pmMikp8pzae6q6oqgriqrGqS+xeLScoMYSVJlfOMTl5RXW1+5w5fJVnFGWf1/mxEMnWPppiclkTLM5RdJoUBYFZVlQ5DnZMMMV167gixKLoXXsKGqnOHnqOJ/+/CfZ+XUiZ0jTmFv5mAvf/YjEliQ2vPD8Ir6qqEcZkzt38cMRo5WruFvfL9FqpyRxQhj0qLOax5I2S08+Tu/lFiGUGOPormxwuyfMnjrGrJa88uIixeYWl776lmrzNjmw8vcG8sU7ixpHMXFsCUVg9tABjEvRgzP82j7AhbyiX5Qcv2+Bvy7dYGZ1k7efeQB/Y4PBqGBtdYvb3SFzLcfqToZc/OB1zYeBSpUwLBlvjZidmWaSB1yaYOfn6LqI/r0hyU6P+cRSlhXjbEI2zvnt7y79oqQ3qeg4g6vKjCIXehtDmi6m0UnxVnCRkPUHVNt9qkLJxgXOCYNOg34v48raPaamU2o89/KKsQ9sTSpc0JK7NwdcX8s43Ek5cnSOLC/Z2R6Rj0ra0w2W1/t0xyWn51uk2Ri1QtSO6OU5d7OSi72cQeWxKG7p/Dp//JXTy6C1Pcbc6DMpPRtjTxChEznWhwVZUCKrjCrPoPDczHLmnLBdBgZlRRWUEBR3ZKrme5TlrTGlV440Y1IrXM9qQGi6mkG5V6uza7tUIeCDElTZ1L26elX+fcH/ACJBPYTJ4X8tAAAAAElFTkSuQmCC" height="20px" width="20px" x="10" y="10"></image></svg><br/>
IMAGE
<img/><br/>
CANVAS
<canvas></canvas><br/>
You can easily draw simple svgs onto a canvas by:
Assigning the source of the svg to an image in base64 format
Drawing the image onto a canvas
Note: The only drawback of the method is that it cannot draw images embedded in the svg. (see demo)
Demonstration:
(Note that the embedded image is only visible in the svg)
var svg = document.querySelector('svg');
var img = document.querySelector('img');
var canvas = document.querySelector('canvas');
// get svg data
var xml = new XMLSerializer().serializeToString(svg);
// make it base64
var svg64 = btoa(xml);
var b64Start = 'data:image/svg+xml;base64,';
// prepend a "header"
var image64 = b64Start + svg64;
// set it as the source of the img element
img.src = image64;
// draw the image onto the canvas
canvas.getContext('2d').drawImage(img, 0, 0);
svg, img, canvas {
display: block;
}
SVG
<svg height="40">
<rect width="40" height="40" style="fill:rgb(255,0,255);" />
<image xlink:href="https://en.gravatar.com/userimage/16084558/1a38852cf33713b48da096c8dc72c338.png?size=20" height="20px" width="20px" x="10" y="10"></image>
</svg>
<hr/><br/>
IMAGE
<img/>
<hr/><br/>
CANVAS
<canvas></canvas>
<hr/><br/>
Mozilla has a simple way for drawing SVG on canvas called "Drawing DOM objects into a canvas"
As Simon says above, using drawImage shouldn't work. But, using the canvg library and:
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
ctx.drawSvg(SVG_XML_OR_PATH_TO_SVG, dx, dy, dw, dh);
This comes from the link Simon provides above, which has a number of other suggestions and points out that you want to either link to, or download canvg.js and rgbcolor.js. These allow you to manipulate and load an SVG, either via URL or using inline SVG code between svg tags, within JavaScript functions.
Something to add, to show the svg correctly in canvas element add the attributes height and width to svg root element, Eg:
<svg height="256" width="421">...</svg>
Or
// Use this if to add the attributes programmatically
const svg = document.querySelector("#your-svg");
svg.setAttribute("width", `${width}`);
svg.setAttribute("height", `${height}`);
For more details see this
As vector graphics are meant to be potentially scaled, I will offer a method I have made that is as similar to SVG as possible. This method supports:
A resizable canvas
Transparency
Hi-resolution graphics (automatically, but no pinch support yet)
Scaling of the SVG in both directions! (To do this with pixels, you will have to divide the new length by the old one)
This is done by converting the SVG to canvas functions here, then adding that to svgRed() (after changing the name of ctx to ctx2. The svgRed() function is used on startup and during pixel ratio changes (for example, increasing the zoom), but not before the canvas is scaled (in order to increase the size of the image). It converts the result into an Image, and can be called any time by ctx.drawImage(redBalloon, Math.round(Math.random() * w), Math.round(Math.random() * h)). To clear the screen, use ctx.clearRect(0, 0, w, h) to do so.
Testing this with the SVG, I found that this is many times faster, as long as the zoom is not set to large values (I discovered that a window.devicePixelRatio of 5 gives just over twice the speed as an SVG, and a window.devicePixelRatio of 1 is approximately 60 times faster).
This also has the bonus benefit of allowing many "fake SVG" items to exist simultaneously, without messing with the HTML (this is shown in the code below). If the screen is resized or scaled, you will need to render it again (completely ignored in my example).
The canvas showing the result is scaled down (in pixels) by the devicePixelRatio, so be careful when drawing items! Scaling (with ctx.scale() this canvas will result in a potentially blurry image, so be sure to account for the pixel difference!
NOTE: It seems that the browser takes a while to optimize the image after the devicePixelRatio has changed (around a second sometimes), so it may not be a good idea to spam the canvas with images immediately, as the example shows.
<!DOCTYPE html>
<html>
<head lang="en">
<title>Balloons</title>
<style>
* {
user-select: none;
-webkit-user-select: none;
}
body {
background-color: #303030;
}
</style>
</head>
<body>
<canvas id="canvas2" style="display: none" width="0" height="0"></canvas>
<canvas id="canvas"
style="position: absolute; top: 20px; left: 20px; background-color: #606060; border-radius: 25px;" width="0"
height="0"></canvas>
<script>
// disable pinches: hard to implement resizing
document.addEventListener("touchstart", function (e) {
if (e.touches.length > 1) {
e.preventDefault()
}
}, { passive: false })
document.addEventListener("touchmove", function (e) {
if (e.touches.length > 1) {
e.preventDefault()
}
}, { passive: false })
// disable trackpad zooming
document.addEventListener("wheel", e => {
if (e.ctrlKey) {
e.preventDefault()
}
}, {
passive: false
})
// This is the canvas that shows the result
const canvas = document.getElementById("canvas")
// This canvas is hidden and renders the balloon in the background
const canvas2 = document.getElementById("canvas2")
// Get contexts
const ctx = canvas.getContext("2d")
const ctx2 = canvas2.getContext("2d")
// Scale the graphic, if you want
const scaleX = 1
const scaleY = 1
// Set up parameters
var prevRatio, w, h, trueW, trueH, ratio, redBalloon
function draw() {
for (var i = 0; i < 1000; i++) {
ctx.drawImage(redBalloon, Math.round(Math.random() * w), Math.round(Math.random() * h))
}
requestAnimationFrame(draw)
}
// Updates graphics and canvas.
function updateSvg() {
var pW = trueW
var pH = trueH
trueW = window.innerWidth - 40
trueH = Math.max(window.innerHeight - 40, 0)
ratio = window.devicePixelRatio
w = trueW * ratio
h = trueH * ratio
if (trueW === 0 || trueH === 0) {
canvas.width = 0
canvas.height = 0
canvas.style.width = "0px"
canvas.style.height = "0px"
return
}
if (trueW !== pW || trueH !== pH || ratio !== prevRatio) {
canvas.width = w
canvas.height = h
canvas.style.width = trueW + "px"
canvas.style.height = trueH + "px"
if (prevRatio !== ratio) {
// Update graphic
redBalloon = svgRed()
// Set new ratio
prevRatio = ratio
}
}
}
window.onresize = updateSvg
updateSvg()
draw()
// The vector graphic (you may want to manually tweak the coordinates if they are slightly off (such as changing 25.240999999999997 to 25.241)
function svgRed() {
// Scale the hidden canvas
canvas2.width = Math.round(44 * ratio * scaleX)
canvas2.height = Math.round(65 * ratio * scaleY)
ctx2.scale(ratio * scaleX, ratio * scaleY)
// Draw the graphic
ctx2.save()
ctx2.beginPath()
ctx2.moveTo(0, 0)
ctx2.lineTo(44, 0)
ctx2.lineTo(44, 65)
ctx2.lineTo(0, 65)
ctx2.closePath()
ctx2.clip()
ctx2.strokeStyle = '#0000'
ctx2.lineCap = 'butt'
ctx2.lineJoin = 'miter'
ctx2.miterLimit = 4
ctx2.save()
ctx2.beginPath()
ctx2.moveTo(0, 0)
ctx2.lineTo(44, 0)
ctx2.lineTo(44, 65)
ctx2.lineTo(0, 65)
ctx2.closePath()
ctx2.clip()
ctx2.save()
ctx2.fillStyle = "#e02f2f"
ctx2.beginPath()
ctx2.moveTo(27, 65)
ctx2.lineTo(22.9, 61.9)
ctx2.lineTo(21.9, 61)
ctx2.lineTo(21.1, 61.6)
ctx2.lineTo(17, 65)
ctx2.lineTo(27, 65)
ctx2.closePath()
ctx2.moveTo(21.8, 61)
ctx2.lineTo(21.1, 60.5)
ctx2.bezierCurveTo(13.4, 54.2, 0, 41.5, 0, 28)
ctx2.bezierCurveTo(0, 9.3, 12.1, 0.4, 21.9, 0)
ctx2.bezierCurveTo(33.8, -0.5, 45.1, 10.6, 43.9, 28)
ctx2.bezierCurveTo(43, 40.8, 30.3, 53.6, 22.8, 60.2)
ctx2.lineTo(21.8, 61)
ctx2.fill()
ctx2.stroke()
ctx2.restore()
ctx2.save()
ctx2.fillStyle = "#f59595"
ctx2.beginPath()
ctx2.moveTo(18.5, 7)
ctx2.bezierCurveTo(15.3, 7, 5, 11.5, 5, 26.3)
ctx2.bezierCurveTo(5, 38, 16.9, 50.4, 19, 54)
ctx2.bezierCurveTo(19, 54, 9, 38, 9, 28)
ctx2.bezierCurveTo(9, 17.3, 15.3, 9.2, 18.5, 7)
ctx2.fill()
ctx2.stroke()
ctx2.restore()
ctx2.restore()
ctx2.restore()
// Save the results
var image = new Image()
image.src = canvas2.toDataURL()
return image
}
</script>
</body>
</html>
Try this:
let svg = `<svg xmlns="http://www.w3.org/2000/svg" ...`;
let blob = new Blob([svg], {type: 'image/svg+xml'});
let url = URL.createObjectURL(blob);
const ctx = canvas.getContext('2d');
canvas.width = 900;
canvas.height = 1400;
const appLogo = new Image();
appLogo.onload = () => ctx.drawImage(appLogo, 54, 387, 792, 960);
appLogo.src = url;
// let image = document.createElement('img');
// image.src = url;
// image.addEventListener('load', () => URL.revokeObjectURL(url), {once: true});
Note: Blob is not defined in Node.js file, This is code designed to run in the browser, not in Node.
More info here

Can I re-draw a canvas image with its own image?

I'm learning to draw an image with canvas and getting a problem inside this example:
let img = new Image();
img.src = 'https://image.freepik.com/free-photo/hrc-siberian-tiger-2-jpg_21253111.jpg';
let a = function () {
let c1 = document.getElementById('c1'),
c2 = document.getElementById('c2');
c1.width = c2.width = 150;
c1.height = c2.width = 150;
c1.getContext('2d').drawImage(img, 0, 0, 150, 150);
c2.getContext('2d').drawImage(img, 0, 0, 150, 150);
};
let b = function () {
let c2 = document.getElementById('c2');
c2.width = 100;
c2.height = 100;
c2.getContext('2d').drawImage(c2, 0, 0, 100, 100);
};
let c = function () {
let c1 = document.getElementById('c1'),
c3 = document.getElementById('c3');
c3.width = 100;
c3.height = 100;
c3.getContext('2d').drawImage(c1, 0, 0, 100, 100);
};
a();
b();
c();
<div>
<canvas id="c1"></canvas>
</div>
<div>
<canvas id="c2"></canvas>
</div>
<div>
<canvas id="c3"></canvas>
</div>
Inside b function. I want to re-draw (resize) its own image with another size (changing width and height from 150 to 100). But it looks like it couldn't.
Then, I've tried to make another function (c). In this function, I've used the image of canvas c1 to re-draw image of canvas c3. That's ok.
So, my question is: Cannot canvas use its own image to draw an image for itself? (or maybe I've done something wrong)
Edit: At first I thought that using an HTMLCanvasElement in the drawImage() call was an incorrect argument type. That was wrong, it's a valid argument. The actual issue was that the code was not waiting for the image to load.
You need to take a look at how you are getting your initial image data for your first canvas. I would not expect it to work because you could be drawing the image data from img before it is actually loaded. You need to attach callbacks to img to wait for it to finish loading the image and then draw that image on a canvas.
Consider my example below that includes an asynchronous way to load an image, how to draw one canvas in another, and how to draw text (just to show differences between canvases).
function loadImage(path) {
return new Promise((resolve, reject) => {
let img = new Image();
img.addEventListener("load", () => {
resolve(img);
});
img.addEventListener("error", (err) => {
reject(err);
});
img.src = path;
});
}
loadImage("https://cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg")
.then((img) => {
let c1 = document.getElementById("c1"),
c2 = document.getElementById("c2"),
c1Ctx = c1.getContext("2d"),
c2Ctx = c2.getContext("2d");
c1Ctx.drawImage(img, 0, 0, 150, 150);
c1Ctx.strokeText("I'm drawing on canvas 1", 25, 25);
c2Ctx.drawImage(c1, 25, 25, 100, 100);
c2Ctx.strokeText("I'm drawing on canvas 2", 25, 25);
})
.catch(console.error);
<canvas id="c1" width="150" height="150"></canvas>
<canvas id="c2" width="150" height="150"></canvas>
Another thing that you will likely run into since you want to be able to pull data out of your canvas is CORS issues. I point this out explicitly because the image that you are trying to draw onto your canvas is from a different domain (image.freepik.com in your example). Whenever you draw image data from another domain onto a canvas, that canvas becomes tainted an you can no longer use canvas' toBlob(), toDataURL(), or getImageData() methods. See: MDN: CORS enabled image.

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.
and here is how it looks (the left one being the original and the right one being the drawn-on canvas and blurry.)
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>
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);
};
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.
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/
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)
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
Simple tip: draw with .5 in x and y position. like drawImage(, 0.5, 0.5) :D There you get crisp edges :D

Copy Canvas Image into a Frame

As starting point I have a image on my canvas:
var canvas = $('canvas')[0];
var ctx=canvas.getContext("2d");
var img=document.getElementById("scream");
ctx.drawImage(img,0,0,240,297);
Now I would like to add a frame to this Image on the same canvas. For this:
I first replace the image with the Frame.
Next I try to copy the old canvas content inside of this frame
I try to do this like that:
var frame = document.getElementById("frame");
var ctx = $('canvas')[0].getContext("2d");
var dst = ctx.canvas;
ctx.drawImage(frame,0,0, 240, 297);
ctx.drawImage(dst, 40, 40);
But the code is not working because it copies the frame again to the canvas instead of the image: Here you can see a demo: https://jsfiddle.net/35mxfsv0/
What do I wrong? What do I have to change? Thanks
As you just assign the original canvas to dst, when you draw anything on that canvas, the dst will also changed (because they're pointing to same canvas).
So you have to assign a new canvas to dst, and draw the image from origin to dst, then draw the frame on origin, and last, draw dst's image to origin.
window.onload = function() { // Make it safe that window.onload ensures all images loaded.
var canvas = $('canvas')[0];
var ctx=canvas.getContext("2d");
var img=document.getElementById("scream");
ctx.drawImage(img,0,0,240,297);
//START OF MY CODE
var frame = document.getElementById("frame");
// Comment out as we demo in same scope, ctx is already have what we want.
//var ctx = $('canvas')[0].getContext("2d");
// Create a new canvas, and draw the image on the origin canvas to it.
var dst = document.createElement('canvas');
dst.width = canvas.width;
dst.height = canvas.height;
var dstCtx = dst.getContext('2d');
dstCtx.drawImage(canvas, 0, 0);
// Above is what you suppose the canvas should do.
//var dst = ctx.canvas;
// Draw frame
ctx.drawImage(frame,0,0, 240, 297);
// As your frame is not an opacity one, you have to copy to specific position.
ctx.drawImage(dst, 0, 0, dst.width, dst.height, 10, 10, 220, 277);
}
img{display:none}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<canvas id="myCanvas" width="240" height="297" style="border:1px solid #d3d3d3;">
</canvas>
<img src="http://www.w3schools.com/tags/img_the_scream.jpg" id="scream"/>
<img src="http://www.wpclipart.com/page_frames/picture_frames/wood_picture_frame.jpg" id="frame"/>
However, if you still have the access to that scream image, you can just draw frame first, then draw image on it:
var canvas = $('canvas')[0];
var ctx=canvas.getContext("2d");
var img=document.getElementById("scream");
var frame = document.getElementById("frame");
ctx.drawImage(frame,0,0, 240, 297);
// 10, 10 seems to fit the frame.
ctx.drawImage(img, 10, 10);
Please change id of your html and js file.

Categories