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.
Related
In general we can convert the HTML elements to string and then we can insert it into DOM later when needed. Similarly, I want to convert the "CANVAS" element to string along with its context properties.
In the following example, I am getting the string value of span tag with outerHTML property. Likewise I want to get the "CANVAS"element along with context properties.
Is there any method or property for this support?
Example code snippets:
var sp=document.createElement("span");
sp.innerHTML = "E2"
var e2 = sp.outerHTML;
$("#test1").append(e2);
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var cn = c.outerHTML;
$("#test2").append(cn);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test1">
<span>E1</span>
</div>
<div id="test2">
</div>
Seems like you already know how to get dom properties of the canvas object.
Now you only need "context" infos (image data as I understand it)
You can get the image data as a base64 string like this:
function CreateDrawing(canvasId) {
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
}
function GetDrawingAsString(canvasId) {
let canvas = document.getElementById(canvasId);
let pngUrl = canvas.toDataURL(); // PNG is the default
// or as jpeg for eg
// var jpegUrl = canvas.toDataURL("image/jpeg");
return pngUrl;
}
function ReuseCanvasString(canvasId, url) {
let img = new Image();
img.onload = () => {
// Note: here img.naturalHeight & img.naturalWidth will be your original canvas size
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
}
img.src = url;
}
// Create something
CreateDrawing("mycanvas");
// save the image data somewhere
var url = GetDrawingAsString("mycanvas");
// re use it later
ReuseCanvasString("replicate", url);
<canvas id="mycanvas"></canvas>
<canvas id="replicate"></canvas>
In short no!
You should realize the difference between a standard DOM-element and a canvas-element:
A created DOM-element is part of the mark-up language that can be viewed and changed.
In the canvas a vector image is drawn based upon the rules created in script. These rules are not stored in the element as text but as the image and can't be subtracted from the canvas element.
However there are other possibilities. We can get the variables from the ctx-object. But no info about coordinates:
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var ctxInfo = [];
for (ctxKey in ctx)
{
if (Object.prototype.toString.call(ctx[ctxKey]) !== "[object Function]" )
{
ctxInfo.push(ctxKey + " : " + ctx[ctxKey]);
}
}
console.log(ctxInfo);
To transfer from one canvas to the other I would keep a list (array or object) of instructions and write a generic function that applies them.
canvasObject = [["beginPath"], ["moveTo", 20, 20], ["lineTo", 100, 20], ["arcTo", 150, 20, 150, 70, 50], ["lineTo", 150, 120], ["stroke"]];
function createCanvas(cnvsObj)
{
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
cnvsObj.forEach(function(element){
//loop through instructions
ctx[element[0]].apply(ctx, element.slice(1));
});
return c;
}
var a = createCanvas(canvasObject);
document.body.appendChild(a);
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
I am using cropper JS to crop my image. I am able to get width and height of my canvas, however, need to know co-ordinates (X and Y) of cropped image.
Here is my code-
(function () {
//getting ratio
function getImageRatio(sourceCanvas) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var width = sourceCanvas.width;
var height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI);
context.strokeStyle = 'rgba(0,0,0,0)';
context.stroke();
context.clip();
context.drawImage(sourceCanvas, 0, 0, width, height);
return canvas;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var button = document.getElementById('button');
var result = document.getElementById('result');
var croppable = false;
var cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 1,
built: function () {
croppable = true;
}
});
button.onclick = function () {
var croppedCanvas;
var roundedCanvas;
var roundedImage;
if (!croppable) {
return;
}
// Crop
croppedCanvas = cropper.getCroppedCanvas();
console.log(getImageRatio(croppedCanvas));
};
});
})();
Any idea, how to get coordinate of cropped image, so that I can crop this image by PHP.
So, if i understand properly, you are looking for the method getData.
It will return these properties as the documentation says:
x: the offset left of the cropped area
y: the offset top of the cropped area
width: the width of the cropped area
height: the height of the cropped area
rotate: the rotated degrees of the image
scaleX: the scaling factor to apply on the abscissa of the image
scaleY: the scaling factor to apply on the ordinate of the image
Although, i suggest taking a look at this section of the doc:
https://github.com/fengyuanchen/cropper#getcroppedcanvasoptions
Specifically at this part:
After then, you can display the canvas as an image directly, or use HTMLCanvasElement.toDataURL to get a Data URL, or use HTMLCanvasElement.toBlob to get a blob and upload it to server with FormData if the browser supports these APIs.
If you cannot use the toBlob method because some browser limitation (you can always add a polyfill), you can get the image using toDataUrl over the canvas (IE9+). And send it to the backend service already cropped. For this you are going to need to do something like this:
var formData = new FormData();
formData.append('image', canvas.toDataURL());
You can check the formData browser support in caniuse
Hope you find it helpful!
I am learning javascript and I came to a point where I created an image like this:
function placeThePawn(){
base_image = new Image();
base_image.src = 'pawns/pawn_red.png';
base_image.onload = function(){
ctx.drawImage(base_image, 50, 50 , 32 , 32);
}
}
[I searched and read few tutorial to learn how to move the pawn]
Now I made a function to move the pawn (image) like this:
function moveThePawn() {
base_image.style.left = parseInt(base_image.style.left) + 100 + 'px';
}
Somehow it doesn't seem to work.
I want to know how can I implement this (move the pawn) and would also want to know why this is not working!
You will have to call the function movethePawn();
Have this in your <body onkeyup="moveSelection(event)">
and this in your script
function moveSelection(event) {
if(event.keyCode === 37) {
movethePawn();
}
}
EDIT 1 :
You may need to construct this image as an element in your DOM and have that element with document.getElementbyId and move it left. It won't work directly with an Image object
This code creates your pawn, and moves it after two seconds using only your functions (and a clearRect to remove the old icon)
var base_image;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function placeThePawn(){
base_image = new Image();
base_image.src = 'http://placehold.it/200x200';
base_image.onload = function(){
ctx.drawImage(base_image, 50, 50 , 32 , 32);
}
}
function moveThePawn() {
ctx.clearRect(0,0,200,100);
ctx.drawImage(base_image, 150, 50 , 32 , 32);
}
placeThePawn();
setTimeout(moveThePawn, 2000);
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;">
</canvas>
This is a better example of how to do this:
var base_image;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var clearMyCanvas = function(){ctx.clearRect(0,0,1000,1000)};
var pawn = {
x:50,
y:50,
h:32,
w:32,
render: function placeThePawn(){
base_image = new Image();
base_image.src = 'http://placehold.it/200x200';
base_image.onload = function(){
ctx.drawImage(base_image, pawn.x, pawn.y , pawn.w , pawn.h);
}
}
}
pawn.render();
// redraw all the things you want on your next screen here.
setTimeout(function(){
// clear canvas
clearMyCanvas();
// perform logics
pawn.x += 100;
// redraw
pawn.render()
}, 2000);
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;">
</canvas>
I think you are mixing up the html5 canvas element (as seen in ctx.drawImage(base_image, 50, 50 , 32 , 32);) with simple html and css.
this line: base_image.style.left = parseInt(base_image.style.left) + 100 + 'px'; is attempting to change the css style property of an element saved in a variable called base_image. But there is no such element.
What you need to change is not the style of base_image, but the canvas context ctx where base_image has been drawn.
function moveThePawn() {
ctx.drawImage(base_image, 50, 50 , 32 , (32+100));//add the 100 pixels to the canvas rendering of the image
}
I have a number of images within #mycontainer like:
<div id="mycontainer">
<img src="http://localhost:8080/images/my-image.png" />
…
</div>
I need to convert those into B/W. Pretty common task but I didn't find any solution for this that would just work for me — there is some problem with the actions execution.
What I have now is the following:
function grayscale(src) {
var ctx = document.createElement('canvas').getContext('2d'),
imgObj = new Image(),
pixels, i, n, gs, url;
// wait until the image has been loaded
imgObj.onload = function () {
ctx.canvas.width = this.width;
ctx.canvas.height = this.height;
ctx.drawImage(this, 0, 0);
pixels = ctx.getImageData(0, 0, this.width, this.height);
for (i = 0, n = pixels.data.length; i < n; i += 4) {
gs = pixels.data[i] * 0.3 + pixels.data[i+1] * 0.59 + pixels.data[i+2] * 0.11;
pixels.data[i] = gs; // red
pixels.data[i+1] = gs; // green
pixels.data[i+2] = gs; // blue
}
ctx.putImageData(pixels, 0, 0);
};
imgObj.src = src;
return ctx.canvas.toDataURL('image/png');
}
In general the actions are:
I supply src of an image to process,
wait for the image to be fully loaded,
draw the image on the canvas, convert the pixels and put the converted pixels back on the canvas
then I want the data URL of the resulting image from the canvas to be returned.
Right now, when in Developer Tools I am tring something like:
c = $('#mycontainer').find('img')[0];
grayscale(c.src);
I get back data URL of a fully transparent default 300px x 150px canvas as if that imgObj.onload() doesn't exist in the script at all.
Can anybody point me to a mistake here please?
Quick answer: Since you're using jQuery, you might look at the jQuery desaturate plugin, which might do what you need.
Longer answer in reference to your code - imgObj.onload is an asynchronous callback function, so it won't have executed by the time you reach your return statement. You'll need to execute any code that requires the post-onload data URL from inside the onload callback. One way to do this would be to have grayscale take a callback argument:
function grayscale(src, callback) {
// ... snip ...
// wait until the image has been loaded
imgObj.onload = function () {
// ... snip ...
ctx.putImageData(pixels, 0, 0);
// now fire the callback
callback(ctx.canvas.toDataURL('image/png'));
};
imgObj.src = src;
}
c = $('#mycontainer').find('img')[0];
grayscale(c.src, function(dataUrl) {
// further stuff with grayscale dataUrl
});