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
Related
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
First of all I've looked through all the similar questions and tried multiple times to get this to work but have had no luck. I'm trying to take a signature that I capture via a canvas element on either a desktop or mobile device and resize it. Because the device sizes are different, so is my canvas for each device. Before I save the signature image, I want to resize it so that all my saved images are the same size.
Here is the code I currently have.
resize(){
var image = new Image();
var t = this;
image.onload = function(){
image.width = '100px';
image.height = 'auto';
t.canvas.width = image.width;
t.canvas.height = image.height;
t.ctx.drawImage(image, 0,0);
console.log(t.canvas.toDataURL());
return t.canvas.toDataURL();
}
image.src = this.canvas.toDataURL("image/png");
}
This code doesn't produce any errors, however after I "resize" the image, canvas.toDataURL(); returns "data:," rather than the image. Before I attempt the resize, I am able to grab a valid png image from this.canvas.toDataURL();.
I also tried changing the style width and height but this causes my canvas to reset which causes my image to disappear.
this.canvas.style.width = 100;
this.canvas.style.height = 100;
Any help would be appreciated.
UPDATE
I've put together a Codepen here that shows the original image on the left and on the right you will see that same image that I have attempted to resize with JavaScript. I added an orange background for readability purposes only.
Codepen
The only problem I could spot in your codepen is this line
context.drawImage(img,200,150);
The values 200 and 150 specify the position on the canvas where the image should be drawn. This essentially draws it ouside the visible area.
If you change it to
context.drawImage(img, 0, 0, img.width, img.height);
it works as the second pair of numbers is the clipping area.
Here's an example:
var canvas = document.getElementById("can");
var context = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
canvas.width = 200;
canvas.height = 150;
img.width = 200;
img.height = 150;
context.drawImage(img, 0, 0, img.width, img.height);
}
img.src = document.getElementById("theImg").src;
canvas {
background: orange;
}
<img id="theImg" src="data:image/png;base64,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">
<canvas id="can"></canvas>
I have this example of a canvas as a div background (I forked it from another example, don't remember where I found it):
http://jsfiddle.net/sevku/6jace59t/18/
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var divHeight = document.getElementById('canvas').clientHeight;
var divWidth = document.getElementById('canvas').clientWidth;
function assignToDiv(){ // this kind of function you are looking for
dataUrl = canvas.toDataURL();
document.getElementsByTagName('div')[0].style.background='url('+dataUrl+')'
}
function draw() { // replace with your logic
ctx.fillStyle = "rgb(100, 250, 100)";
ctx.fillRect (10, 10, divWidth-20, divHeight-20);
}
draw()
assignToDiv()
My problem; If I put the dimensions of the div 300 x 150, the canvas does what it is supposed to do. But if I change the dimensions, the canvas is supposed to adapt to the div dimensions. What did I do wrong that this doesn't happen?
PS: I'm a beginner, so please forgive me stupid questions.
It's because if you don't give canvas width and height, it's default to 300x150, so after you get the width and height from div, you should use them to set your canvas' dimensions as well.
Another point worth notice is that you use div.style.background property to set the background image, however, as there's many background related properties (e.g: background-repeat in your jsfiddle, background-position, background-size...), the background can set all of them at once.
When you use div.style.background='url('+dataUrl+')';. It overrides all other background-related properties to initial.
If you want to preserve those properties, you may either reset them after you set style.background, or you can use div.style.backgroundImage to change the background image without affect other background related properties.
jsfiddle
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var divHeight = document.getElementById('canvas').clientHeight;
var divWidth = document.getElementById('canvas').clientWidth;
// VVVV After you get WxH, set the canvas's dimension too.
canvas.width = divWidth;
canvas.height = divWidth;
var div1 = document.getElementById('canvas');
var div2 = document.getElementById('canvas2');
function assignToDiv(div){ // this kind of function you are looking for
var dataUrl = canvas.toDataURL();
div.style.background='url('+dataUrl+')'; // This line will overwrite your background settings.
div.style.backgroundRepeat = 'repeat-x'; // Use this to set background related properties after above.
}
function assignToDivAlt(div){ // this kind of function you are looking for
var dataUrl = canvas.toDataURL();
div.style.backgroundImage = 'url('+dataUrl+')'; // Only set the background-image would have same effect.
}
function draw() { // replace with your logic
ctx.fillStyle = "rgb(100, 250, 100)";
// If you don't set WH, then the canvas would be 300x150, and those
// you drawed but out of boundary are clipped.
ctx.fillRect (10, 10, divWidth-20, divHeight-20);
}
draw()
assignToDiv(div1);
assignToDiv(div2);
canvas {display:none;}
div {
width:600px;
height:550px;
border:1px solid grey;
background-repeat:repeat-x;
}
<canvas></canvas>
<div id="canvas"></div>
<div id="canvas2"></div>
I'm trying to get an image to be displayed on an HTML5 Canvas while keeping the aspect ratio and having the image reduced (if larger than the canvas). I've look around at some answers but seem to be missing something and wondered if you guys can help.
I'm using the function "calculateAspectRatioFit" suggested by one of the Stackoverflow answers, but it seems to not resize the image for me, as it did in the answer - so I may be doing something wrong :)
Here's my code:
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
var rtnWidth = srcWidth*ratio;
var rtnHeight = srcHeight*ratio;
return { width: rtnWidth, height: rtnHeight };
}
var canvasImage = new Image();
canvasImage.src = "http://www.greenwallpaper.org/backgrounds/simply-green-502085.jpeg";
var ctx = this.getContext('2d');
var parentWidth = self._widgetSize[0];
var parentHeight = self._widgetSize[1];
canvasImage.onload = function() {
var imgSize = calculateAspectRatioFit(canvasImage.width, canvasImage.height, parentWidth, parentHeight);
ctx.clearRect(0, 0, parentWidth, parentHeight);
ctx.drawImage(canvasImage, 0, 0,imgSize.width, imgSize.height);
};
The image is displayed but is larger than the HTML5 Canvas. What I am after is to have the image the same width as the Canvas, and if the height is larger than the height of the canvas then it overflows and is hidden...I just want to fill the width and keep the aspect ratio.
Can anyone help point out what I am missing?
Appreciate your help :)
---Update 30/11---
I've just added the answer to my code and I get the following: (this is the 2nd answer)
var canvasImage = new Image();
canvasImage.src = "http://www.greenwallpaper.org/backgrounds/simply-green-502085.jpeg";
var ctx = this.getContext('2d');
canvasImage.onload = scaleAndDraw(this, ctx, canvasImage);
function scaleAndDraw(canvas, ctx, srcImage) {
// Image is 2560x1600 - so we need to scale down to canvas size...
// does this code actually 'scale' the image? Image of result suggests it doesn't.
var aspect = getAspect(canvas, srcImage);
var canvasWidth = (srcImage.width * aspect);
var canvasHeight = (srcImage.height * aspect);
ctx.drawImage(srcImage, 0, 0, canvasWidth|0, canvasHeight|0);
}
function getAspect(canvas, image) {
return canvas.size[0] / image.width;
}
So the code for displaying the image works, but the image is keeping it's dimensions and is not resizing to the dimensions of the canvas.
Hopefully the image will help you see the problem I am having. Larger images do not seem to rescale to fit in the canvas while keeping aspect ratio.
Any thoughts? :)
Your code works for me as long as I supply sane parentWidth,parentHeight values:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
var rtnWidth = srcWidth*ratio;
var rtnHeight = srcHeight*ratio;
return { width: rtnWidth, height: rtnHeight };
}
var parentWidth = 100; //self._widgetSize[0];
var parentHeight = 50; //self._widgetSize[1];
var canvasImage = new Image();
canvasImage.onload = function() {
var imgSize = calculateAspectRatioFit(canvasImage.width, canvasImage.height, parentWidth, parentHeight);
ctx.clearRect(0, 0, parentWidth, parentHeight);
ctx.drawImage(canvasImage,30,30,imgSize.width, imgSize.height);
};
canvasImage.src = "http://www.greenwallpaper.org/backgrounds/simply-green-502085.jpeg";
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
I'm trying to resize some images with canvas but I'm clueless on how to smoothen them.
On photoshop, browsers etc.. there are a few algorithms they use (e.g. bicubic, bilinear) but I don't know if these are built into canvas or not.
Here's my fiddle: http://jsfiddle.net/EWupT/
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);
The first one is a normal resized image tag, and the second one is canvas. Notice how the canvas one is not as smooth. How can I achieve 'smoothness'?
You can use down-stepping to achieve better results. Most browsers seem to use linear interpolation rather than bi-cubic when resizing images.
(Update There has been added a quality property to the specs, imageSmoothingQuality which is currently available in Chrome only.)
Unless one chooses no smoothing or nearest neighbor the browser will always interpolate the image after down-scaling it as this function as a low-pass filter to avoid aliasing.
Bi-linear uses 2x2 pixels to do the interpolation while bi-cubic uses 4x4 so by doing it in steps you can get close to bi-cubic result while using bi-linear interpolation as seen in the resulting images.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function () {
// set size proportional to image
canvas.height = canvas.width * (img.height / img.width);
// step 1 - resize to 50%
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
// step 3, resize to final size
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>
Depending on how drastic your resize is you can might skip step 2 if the difference is less.
In the demo you can see the new result is now much similar to the image element.
Since Trung Le Nguyen Nhat's fiddle isn't correct at all
(it just uses the original image in the last step)
I wrote my own general fiddle with performance comparison:
FIDDLE
Basically it's:
img.onload = function() {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d"),
oc = document.createElement('canvas'),
octx = oc.getContext('2d');
canvas.width = width; // destination canvas size
canvas.height = canvas.width * img.height / img.width;
var cur = {
width: Math.floor(img.width * 0.5),
height: Math.floor(img.height * 0.5)
}
oc.width = cur.width;
oc.height = cur.height;
octx.drawImage(img, 0, 0, cur.width, cur.height);
while (cur.width * 0.5 > width) {
cur = {
width: Math.floor(cur.width * 0.5),
height: Math.floor(cur.height * 0.5)
};
octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
}
ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
I created a reusable Angular service to handle high quality resizing of images / canvases for anyone who's interested: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983
The service includes two solutions because they both have their own pros / cons. The lanczos convolution approach is higher quality at the cost of being slower, whereas the step-wise downscaling approach produces reasonably antialiased results and is significantly faster.
Example usage:
angular.module('demo').controller('ExampleCtrl', function (imageService) {
// EXAMPLE USAGE
// NOTE: it's bad practice to access the DOM inside a controller,
// but this is just to show the example usage.
// resize by lanczos-sinc filter
imageService.resize($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
// resize by stepping down image size in increments of 2x
imageService.resizeStep($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
})
While some of those code-snippets are short and working, they aren't trivial to follow and understand.
As i am not a fan of "copy-paste" from stack-overflow, i would like developers to understand the code they are push into they software, hope you'll find the below useful.
DEMO: Resizing images with JS and HTML Canvas Demo fiddler.
You may find 3 different methods to do this resize, that will help you understand how the code is working and why.
https://jsfiddle.net/1b68eLdr/93089/
Full code of both demo, and TypeScript method that you may want to use in your code, can be found in the GitHub project.
https://github.com/eyalc4/ts-image-resizer
This is the final code:
export class ImageTools {
base64ResizedImage: string = null;
constructor() {
}
ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
let img = new Image();
img.src = base64image;
img.onload = () => {
// Check if the image require resize at all
if(img.height <= height && img.width <= width) {
this.base64ResizedImage = base64image;
// TODO: Call method to do something with the resize image
}
else {
// Make sure the width and height preserve the original aspect ratio and adjust if needed
if(img.height > img.width) {
width = Math.floor(height * (img.width / img.height));
}
else {
height = Math.floor(width * (img.height / img.width));
}
let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
let resizingCanvasContext = resizingCanvas.getContext("2d");
// Start with original image size
resizingCanvas.width = img.width;
resizingCanvas.height = img.height;
// Draw the original image on the (temp) resizing canvas
resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);
let curImageDimensions = {
width: Math.floor(img.width),
height: Math.floor(img.height)
};
let halfImageDimensions = {
width: null,
height: null
};
// Quickly reduce the dize by 50% each time in few iterations until the size is less then
// 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
// created with direct reduction of very big image to small image
while (curImageDimensions.width * 0.5 > width) {
// Reduce the resizing canvas by half and refresh the image
halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);
resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, halfImageDimensions.width, halfImageDimensions.height);
curImageDimensions.width = halfImageDimensions.width;
curImageDimensions.height = halfImageDimensions.height;
}
// Now do final resize for the resizingCanvas to meet the dimension requirments
// directly to the output canvas, that will output the final image
let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
let outputCanvasContext = outputCanvas.getContext("2d");
outputCanvas.width = width;
outputCanvas.height = height;
outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, width, height);
// output the canvas pixels as an image. params: format, quality
this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);
// TODO: Call method to do something with the resize image
}
};
}}
I don't understand why nobody is suggesting createImageBitmap.
createImageBitmap(
document.getElementById('image'),
{ resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap =>
document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);
works beautifully (assuming you set ids for image and canvas).
I created a library that allows you to downstep any percentage while keeping all the color data.
https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js
That file you can include in the browser. The results will look like photoshop or image magick, preserving all the color data, averaging pixels, rather than taking nearby ones and dropping others. It doesn't use a formula to guess the averages, it takes the exact average.
Based on K3N answer, I rewrite code generally for anyone wants
var oc = document.createElement('canvas'), octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0);
while (oc.width * 0.5 > width) {
oc.width *= 0.5;
oc.height *= 0.5;
octx.drawImage(oc, 0, 0, oc.width, oc.height);
}
oc.width = width;
oc.height = oc.width * img.height / img.width;
octx.drawImage(img, 0, 0, oc.width, oc.height);
UPDATE JSFIDDLE DEMO
Here is my ONLINE DEMO
I solved this by using scale for canvas and the image quality in my case becomes really good.
So first I scale the content inside of canvas:
ctx.scale(2, 2)
And then scale out the canvas tag with css:
#myCanvas { transform: scale(0.5); }
export const resizeImage = (imageFile, size = 80) => {
let resolver = ()=>{};
let reader = new FileReader();
reader.onload = function (e) {
let img = document.createElement("img");
img.onload = function (event) {
// Dynamically create a canvas element
let canvas = document.createElement("canvas");
canvas.width=size;
canvas.height=size;
// let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
// Actual resizing
ctx.drawImage(img, 0, 0, size, size);
// Show resized image in preview element
let dataurl = canvas.toDataURL(imageFile.type);
resolver(dataurl);
}
img.src = e.target.result;
}
reader.readAsDataURL(imageFile);
return new Promise((resolve, reject) => {
resolver = resolve;
})
};
I wrote small js-utility to crop and resize image on front-end. Here is link on GitHub project. Also you can get blob from final image to send it.
import imageSqResizer from './image-square-resizer.js'
let resizer = new imageSqResizer(
'image-input',
300,
(dataUrl) =>
document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);
//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Here is my code, which I hope may be useful for someone out there in the SO community:
You can include your target image dimension as a param in your script call. That will be the result value of your image width or height, whichever is bigger. The smaller dimension is resized keeping your image aspect ratio unchanged. You can also hard-code your default target size in the script.
You can easily change the script to suit your specific needs, such as the image type you want (default is "image/png") for an output and decide in how many steps percentwise you want to resize your image for a finer result (see const percentStep in code).
const ResizeImage = ( _ => {
const MAX_LENGTH = 260; // default target size of largest dimension, either witdth or height
const percentStep = .3; // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();
const doResize = (callback, maxLength) => {
// abort with error if image has a dimension equal to zero
if(image.width == 0 || image.height == 0) {
return {blob: null, error: "either image width or height was zero "};
}
// use caller dimension or default length if none provided
const length = maxLength == null ? MAX_LENGTH : maxLength;
canvas.width = image.width;
canvas.height = image.height;
canvasContext.drawImage(image, 0, 0, image.width, image.height);
// if image size already within target size, just copy and return blob
if(image.width <= length && image.height <= length) {
canvas.toBlob( blob => {
callback({ blob: blob, error: null });
}, "image/png", 1);
return;
}
var startDim = Math.max(image.width, image.height);
var startSmallerDim = Math.min(image.width, image.height);
// gap to decrease in size until we reach the target size,
// be it by decreasing the image width or height,
// whichever is largest
const gap = startDim - length;
// step length of each resizing iteration
const step = parseInt(percentStep*gap);
// no. of iterations
var nSteps = 0;
if(step == 0) {
step = 1;
} else {
nSteps = parseInt(gap/step);
}
// length of last additional resizing step, if needed
const lastStep = gap % step;
// aspect ratio = value by which we'll multiply the smaller dimension
// in order to keep the aspect ratio unchanged in each iteration
const ratio = startSmallerDim/startDim;
var newDim; // calculated new length for the bigger dimension of the image, be it image width or height
var smallerDim; // length along the smaller dimension of the image, width or height
for(var i = 0; i < nSteps; i++) {
// decrease longest dimension one step in pixels
newDim = startDim - step;
// decrease shortest dimension proportionally, so as to keep aspect ratio
smallerDim = parseInt(ratio*newDim);
// assign calculated vars to their corresponding canvas dimension, width or height
if(image.width > image.height) {
[canvas.width, canvas.height] = [newDim, smallerDim];
} else {
[canvas.width, canvas.height] = [smallerDim, newDim];
}
// draw image one step smaller
canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// cycle var startDim for new loop
startDim = newDim;
}
// do last missing resizing step to finally reach target image size
if(lastStep > 0) {
if(image.width > image.height) {
[canvas.width, canvas.height] = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
} else {
[canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
}
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
}
// send blob to caller
canvas.toBlob( blob => {
callback({blob: blob, error: null});
}, "image/png", 1);
};
const resize = async (imgSrc, callback, maxLength) => {
image.src = imgSrc;
image.onload = _ => {
doResize(callback, maxLength);
};
};
return { resize: resize }
})();
Usage:
ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
if(imageObject.error != null) {
// handle errors here
console.log(imageObject.error);
return;
}
// do whatever you want with the blob, like assinging it to
// an img element, or uploading it to a database
// ...
document.querySelector("#my-image").src = imageObject.blob;
// ...
}, 300);