How to get click event on images which is placed on canvas - javascript

I have create program to display multiple images on canvas. Now I want to get that images data using mouse click event which is placed on canvas.
My JavaScript cod is -
var canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
canvas.width = 720;
canvas.height = 480;
//I got images data in one array
var imageobj = new Array();
for (var d=0;d<calloutImageArray.length;d++)
{
imageobj[d] = new Image();
(function(d)
imageobj[d].src = sitePath+"ATOM/chapter1/book/"+calloutImageArray[d];
imageobj[d].onload = function()
{
ctx.drawImage(imageobj[d], calloutImageArrayX[d], calloutImageArrayY[d],calloutImageArrayW[d], calloutImageArrayH[d]);
};
})(d);
}

Fairly easily done:
Listen for mousedown events using canvas.addEventListener
On mousedown, check if the mouse is inside any image.
Get the image data for the image under the mouse.
Example code:
function handleMousedown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
var mouseX=e.clientX-BBoffsetX;
var mouseY=e.clientY-BBoffsetY;
// loop through each image and see if mouse is under
var hit=-1;
for(var i=0;i<imageobj.length;i++){
var x=calloutImageArrayX[i];
var y=calloutImageArrayY[i];
var w=calloutImageArrayW[i];
var h=calloutImageArrayH[i];
if(mouseX>=x && mouseX<=x+w && mouseY>=y && mouseY<=y+h){
hit=i;
}
}
// you clicked the image with index==hit
// so get its image data
if(hit>=0){
var imageData=ctx.getImageData(
calloutImageArrayX[hit],
calloutImageArrayY[hit],
calloutImageArrayW[hit],
calloutImageArrayH[hit]);
// now do your thing with the imageData!
}
}
Example code and a Demo:
var $results=document.getElementById('results');
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var BB,BBoffsetX,BBoffsetY;
function setBB(){
BB=canvas.getBoundingClientRect();
BBoffsetX=BB.left;
BBoffsetY=BB.top;
}
setBB();
window.onscroll=function(e){ setBB(); }
var imageobj=[];
var calloutImageArrayX=[10,125,10,125];
var calloutImageArrayY=[10,10,150,150];
var calloutImageArrayW=[];
var calloutImageArrayH=[];
// put the paths to your images in imageURLs[]
var imageURLs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/character3.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/character2.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/character1.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stack1/avatar.png");
// the loaded images will be placed in imageobj[]
var imagesOK=0;
startLoadingAllImages(imagesAreNowLoaded);
// Create a new Image() for each item in imageURLs[]
// When all images are loaded, run the callback (==imagesAreNowLoaded)
function startLoadingAllImages(callback){
// iterate through the imageURLs array and create new images for each
for (var i=0; i<imageURLs.length; i++) {
// create a new image an push it into the imageobj[] array
var img = new Image();
img.crossOrigin='anonymous';
// Important! By pushing (saving) this img into imageobj[],
// we make sure the img variable is free to
// take on the next value in the loop.
imageobj.push(img);
// when this image loads, call this img.onload
img.onload = function(){
// this img loaded, increment the image counter
imagesOK++;
// if we've loaded all images, call the callback
if (imagesOK>=imageURLs.length ) {
callback();
}
};
// notify if there's an error
img.onerror=function(){alert("image load failed");}
// set img properties
img.src = imageURLs[i];
}
}
// All the images are now loaded
// Do drawImage & fillText
function imagesAreNowLoaded(){
// the imageobj[] array now holds fully loaded images
// the imageobj[] are in the same order as imageURLs[]
// add widths & heights to the appropriate arrays
for(var i=0;i<imageobj.length;i++){
calloutImageArrayW.push(imageobj[i].width);
calloutImageArrayH.push(imageobj[i].height);
}
// listen for mousedown events
canvas.onmousedown=handleMousedown;
draw();
}
function draw(){
ctx.clearRect(0,0,cw,ch);
for(var d=0;d<imageobj.length;d++){
ctx.drawImage(imageobj[d],
calloutImageArrayX[d], calloutImageArrayY[d],
calloutImageArrayW[d], calloutImageArrayH[d]);
}
}
function handleMousedown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
var mouseX=e.clientX-BBoffsetX;
var mouseY=e.clientY-BBoffsetY;
// loop through each image and see if mouse is under
var hit=-1;
for(var i=0;i<imageobj.length;i++){
var x=calloutImageArrayX[i];
var y=calloutImageArrayY[i];
var w=calloutImageArrayW[i];
var h=calloutImageArrayH[i];
if(mouseX>=x && mouseX<=x+w && mouseY>=y && mouseY<=y+h){
hit=i;
}
}
// you clicked the image with index==hit
// so get its image data
if(hit>=0){
var imageData=ctx.getImageData(
calloutImageArrayX[hit],
calloutImageArrayY[hit],
calloutImageArrayW[hit],
calloutImageArrayH[hit]);
var pos;
switch(hit){
case 0:pos='top-left';break;
case 1:pos='top-right';break;
case 2:pos='bottom-left';break;
case 3:pos='bottomRight';break;
}
$results.innerHTML='You clicked the '+pos+' image.<br>This imageData has this length: '+imageData.data.length;
}
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4 id="results">Click on an image</h4>
<canvas id="canvas" width=300 height=300></canvas>

Take my advice and go with something like fabricJS , it will make your life very easy. In that you can easily have multiple objects on the canvas and also perform functions (Scale,rotate,pan) on them separately.

Related

HTML Canvas - passing a background image to Canvas while it's also passing base64 data

I'm currently trying to do some Javascript work in Laserfiche forms which requires me to save the base64 data for an image in a separate text area, and feed that data back into the image to allow the canvas to be turned into an image in which I can save into the system.
The issue is I'm trying to have a background image in which the user can draw on (in this case, a vehicle that they can draw a circle on to indicate where the damage is). I am using sketch.js to allow the drawing part of the task.
From what I've read is that the background CSS cannot be saved into the canvas. That's fine but how do I pass the background image when I'm already grabbing the base64 data and passing that back into my canvas?
The saveimage class belongs to the text area and the imagefinish belong to the checkbox that they mark when the image is ready
html
<div class="demo" id="colors_demo">
<div class="tools">
Marker
Eraser
Download
</div>
<canvas id="colors_sketch" width="750" height="500" style="border:2px solid #000000 ; background: url(http://localhost/forms/img/vanImage.jpg"></canvas>
</div>
<input name="Field11" id="Field11-0" type="checkbox" value="isLocked" vo="e" data-parsley-class-handler="#Field11" data-parsley-errors-container="#Field11" data-parsley-multiple="Field11">
<textarea id="Field13" name="Field13" aria-labelledby="Field13" class="cf-medium" rows="3" vo="e" data-parsley-id="28"></textarea>
Javascript
//submitted form
if ($('[name=IsLocked]').val() == 'True') {
var myCanvas = document.getElementById('colors_sketch');
var ctx = myCanvas.getContext('2d');
var img = new Image;
img.onload = function() {
ctx.drawImage(this, 0, 0, myCanvas.width, myCanvas.height);
}
img.src = $('.saveimage .cf-field').text();
}
else {
//fill form
//$.getScript('http://localhost/Forms/js/sketch.js', function() {
$.getScript('https://rawgit.com/intridea/sketch.js/gh-pages/lib/sketch.js', function() {
//script is loaded and executed put your dependent JS here
$.each(['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#000', '#fff'], function() {
$('#colors_demo .tools').append("<a href='#colors_sketch' data-color='" + this + "' style='width: 10px; background: " + this + ";'></a> ");
});
$.each([3, 5, 10, 15], function() {
$('#colors_demo .tools').append("<a href='#colors_sketch' data-size='" + this + "' style='background: #ccc'>" + this + "</a> ");
});
//$('#colors_sketch').sketch();
$('#colors_sketch').sketch({defaultColor: "#000"});
});
$('.imagefinish input').change(function(){
if(this.checked) {
var myCanvas = document.getElementById('colors_sketch');
$('.saveimage textarea').val(myCanvas.toDataURL());
}
});
}
I was able to add an image by adding a variable for my image path
var image = 'http://localhost/forms/img/vanImage.jpg'
and I also added the two lines to my onload for the style of "myCanvas". I have a feeling that this solution will only work because of how Laserfiches forms software works but the marked answer is also correct.
img.onload = function() {
ctx.drawImage(this, 0, 0, myCanvas.width, myCanvas.height);
myCanvas.style.backgroundRepeat = "no-repeat";
myCanvas.style.backgroundImage = 'url('+image+')'
}
img.src = $('.saveimage .cf-field').text();
}
Load the background image on page load, or an appropriate time and when the client is ready, draw the background onto the canvas behind the user content using ctx.globalCompositeOperation = "destination-over";
Create an image to hold the background.
const backgroundImage = new Image();
backgroundImage.src = "http://localhost/forms/img/vanImage.jpg";
You need to ensure that the image has loaded when the client clicks ready, incase it has not loaded you can set a callback that will call back the ready function if needed.
var backgroundLoadedCallback = null;
backgroundImage.onload = function(){
if( typeof backgroundLoadedCallback === "function"){
backgroundLoadedCallback();
}
}
Then create the canvas -> textarea function
function canvasToTextarea(){
const myCanvas = document.getElementById('colors_sketch');
const ctx = myCanvas.getContext("2d");
ctx.globalCompositeOperation = "destination-over"; // make sure the background go under the drawn pixels
// draw and fit background to the canvas
ctx.drawImage(backgroundImage,0,0,ctx.canvas.width,ctx.canvas.height);
// then convert to URL for textarea
$('.saveimage textarea').val(myCanvas.toDataURL());
}
In the checked function
$('.imagefinish input').change(function(){
if(this.checked) {
if(backgroundImage.complete) { // image has loaded so all good
canvasToTextarea(); // put canvas data URL to textarea
} else { // background has not yet loaded (or could be an error you will have to deal with that as well
// set the callback to the canvasToTextarea function
backgroundLoadedCallback = canvasToTextarea;
// when the background has loaded the canvas and background
// will be moved to the textarea as a data URL
}
}
});
Or modify sketch.js
Below is the draw function from sketch.js (and it`s very old school)
Sketch.prototype.redraw = function() {
var sketch;
this.el.width = this.canvas.width();
this.context = this.el.getContext('2d');
sketch = this;
$.each(this.actions, function() {
if (this.tool) {
return $.sketch.tools[this.tool].draw.call(sketch, this);
}
});
if (this.painting && this.action) {
return $.sketch.tools[this.action.tool].draw.call(sketch, this.action);
}
};
Just replace it with the following. You dont need to modify the sketch.js file justy overwrite the redraw Prototype
In your code load the background and set the new redraw
const backgroundImage = new Image();
backgroundImage.src = "http://localhost/forms/img/vanImage.jpg";
// replace Sketch.js redraw function
Sketch.prototype.redraw = function(){
var sketch;
// dont need the next line use clear instead
// this.el.width = this.canvas.width();
const ctx = this.context = this.el.getContext('2d');
// clear canvas
this.context.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// If backgroundimage complete draw it first
if(backgroundImage && backgroundImage.complete){
ctx.drawImage(backgroundImage,0,0,ctx.canvas.width,ctx.canvas.height);
}
// back to the original code. :P
sketch = this;
$.each(this.actions, function() {
if (this.tool) {
return $.sketch.tools[this.tool].draw.call(sketch, this);
}
});
if (this.painting && this.action) {
return $.sketch.tools[this.action.tool].draw.call(sketch, this.action);
}
}

Images on first load don't show on canvas

I want to pass a list of images and draw them each one for canvas.
My view.py:
def myview(request):
...
lista=Myobject.objects.filter(tipo=mytipo)
numero_oggetti = range(len(lista))
lista_formattata=[]
for elem in lista:
lista_formattata.append('/media/'+str(elem.myfield))
context_dict['lista']=lista_formattata
context_dict['numero_oggetti']=numero_oggetti
return render(request, 'mytemplate.html', context_dict)
My template.html:
<script>
<!--
window.onpageshow = function() {
myfunction({{lista|safe}});
};
-->
</script>
{% for c in numero_oggetti %}
<canvas id='componenti_canvas{{ c }}' width='60' height='75' style="border:1px solid #000000;">
Your browser does not support the HTML5 canvas tag.
</canvas>
{% endfor %}
My script.js:
function myfunction(lista) {
lista=lista
for (i=0; i<lista.length; i++) {
var canvas = document.getElementById('componenti_canvas'+i);
var ctx = canvas.getContext("2d");
var base = new Image();
base.src = lista[i];
ctx.scale(0.5,0.5);
ctx.drawImage(base, 0, 0);
};
};
This code works but sometimes the images show, sometimes don't (all of them or no one). When I load the page they don't show, when I re-load the page they show up. If I wait some minutes and re-load they don't show again.
I'm using firefox and in the console log when say GET image_name.png HTTP/1.0 200 they don't show (sometimes they are in cache, sometimes not... it don't make difference), when it don't say nothing they show.
I tried:
-setTimeout
-call the list with an ajax request with cache: false, async: false
-base.onload, like that:
base.onload = function(){
ctx.scale(0.5,0.5);
ctx.drawImage(base, 0, 0);
}
but or the images don't show never or they show in this way. I can give details, of course I can have done errors.
Edit: in the comment say to use onload.
My script.js:
function myfunction(lista) {
lista=lista
for (i=0; i<lista.length; i++) {
var canvas = document.getElementById('componenti_canvas'+i);
var ctx = canvas.getContext("2d");
var base = new Image();
base.onload = function() {
ctx.drawImage(base, 0, 0);
};
base.src = lista[i];
ctx.scale(0.5,0.5);
};
};
It draws only the last image on the last canvas (I have many canvas and I draw an image for each).
This will not work because you keep overwriting the image for every iteration of the loop. There is only one variable called base, it can only hold one image so all the ones before it are lost.
function myfunction(lista) {
lista=lista
for (i=0; i<lista.length; i++) {
var canvas = document.getElementById('componenti_canvas'+i);
var ctx = canvas.getContext("2d");
var base = new Image(); // second and more loops you over write base
base.onload = function() {
ctx.drawImage(base, 0, 0); // when it load only the last image is in base
// there is only one var called base so it
// can not hold more than one image
};
base.src = lista[i];
ctx.scale(0.5,0.5);
};
};
Use a function to wrap all the required vars so that you create a unique set for each image.
function myfunction(lista) {
lista.forEach((name,i)=>{ // each time the call back is called a
// new set of variables are created to
// store each unique image.
var base = new Image();
base.src = name;
base.onload = function() { ctx.drawImage(base, 0, 0); };
var canvas = document.getElementById('componenti_canvas'+i);
var ctx = canvas.getContext("2d");
ctx.scale(0.5,0.5);
});
}

Firefox drawing blank Canvas when restoring from saved base64

I am working with a single canvas that allows the user to click on a window pane in a window image. The idea is to show where the user has clicked. The image will then be modified (by drawing a grill on the window) and then saved to in JPEG. I am saving the canvas image prior to the click function because I don't want the selection box to show in the final image. However, Firefox often displays a blank canvas when restoring the canvas where IE and Chrome do not. This works perfectly in Chrome and IE. Any suggestions? Does Firefox have a problem with toDataURL()? Maybe some async issue going on here? I am also aware that saving a canvas in this fashion is memory intensive and there may be a better way to do this but I'm working with what I have.
Code:
/**
* Restores canvas from drawingView.canvasRestorePoint if there are any restores saved
*/
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.src = drawingView.canvasRestorePoint.pop();
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
}
},
/**
* Pushes canvas into drawingView.canvasRestorePoint
*/
saveCanvas:function()
{
var canvas = document.getElementById("drawPop.canvasOne");
var urlData = canvas.toDataURL();
drawingView.canvasRestorePoint.push(urlData);
},
EXAMPLE OF USE:
readGrillInputs:function()
{
var glassNum = ir.get("drawPop.grillGlassNum").value;
var panelNum = ir.get("drawPop.grillPanelNum").value;
drawingView.restoreCanvas();
drawEngine.drawGrill(glassNum, panelNum,null);
drawingView.saveCanvas();
},
sortClick:function(event)
{
..... //Sorts where user has clicked and generates panel/glass num
.....
drawingView.showClick(panelNum, glassNum);
},
showClick:function(panelNum, glassNum)
{
var glass = item.panels[panelNum].glasses[glassNum];
var c = drawEngine.context;
drawingView.restoreCanvas();
drawingView.saveCanvas();
c.strokeStyle = "red";
c.strokeRect(glass.x, glass.y, glass.w, glass.h);
},
By just looking at the code setting the img.src is an async action to retrieve the image, so when you try to draw it 2 lines later to the canvas, it probably hasn't been loaded yet (having it in cache will make it return fast enough that it might work).
You should instead use an img.onload function to draw the image when it has loaded.
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.onload = function() {
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
};
img.src = drawingView.canvasRestorePoint.pop();
}
},

Are there any alternatives for using toDataURL in Chrome?

The main problem is that I have to work with many images. And I can't use crossOrigin Attribute for all of them.
My code looks like this:
<script>
var c=document.getElementById('example');
var ctx=c.getContext('2d');
var LeftHand = new Image();
LeftHand.id="imq1";
var RightHand = new Image();
RightHand.id="imq2";
var Body = new Image();
Body.id="imq6";
boyBody.src = "https://getout-s3.s3.amazonaws.com/baseBody/boy-02.png";
LeftHand.src = "https://getout-s3.s3.amazonaws.com/NK4XtQvkZ4MGctZf_.hand(unisex)_13.png ";
RightHand.src = "https://getout-s3.s3.amazonaws.com/OPdFPcU2sORgNmTy_.hand(unisex)_9.png ";
Body.src = "https://getout-s3.s3.amazonaws.com/HRZqrTYSdJXGedgX_.Body_(m)7.png ";
boyBody.onload = function() {
ctx.drawImage(boyBody, 0, 0, boyBody.width/2, boyBody.height/2);
ctx.drawImage(LeftHand, (899 - LeftHand.width/2)/2, (867 - LeftHand.height/2)/2, LeftHand.width/2, LeftHand.height/2);
ctx.drawImage(Underwear, (599 - Underwear.width/2)/2, (845 - Underwear.height/2)/2, Underwear.width/2, Underwear.height/2);
ctx.drawImage(Body, (599 - Body.width/2)/2, (557 - Body.height/2)/2, Body.width/2, Body.height/2);
var img = c.toDataURL("image/png");
document.write('<img src="' + img + '" />');
};
</script>
Browsers don't let programmers export cross-domain content for very good security reasons. Your private banking info is cross-domain content that you don't want give to thieves using the canvas as an export device.
Therefore, context.toDataURL is disabled immediately after you drawImage a cross-domain image onto canvas. The same disabling is true for context.getImageData. (context.getImageData is another way to export canvas content).
To allow exporting the canvas content to your users, you will have to host all the images on the same domain as your webpage.
BTW, you must give all your elements time to load before drawing them. Here's an image loader that loads all your images in advance and then calls start() when all the images are fully loaded. Put your ctx.drawImage's in start().
// put the paths to your images in imageURLs[]
var imageURLs=[];
imageURLs.push("https://getout-s3.s3.amazonaws.com/baseBody/boy-02.png");
imageURLs.push("https://getout-s3.s3.amazonaws.com/NK4XtQvkZ4MGctZf_.hand(unisex)_13.png");
// ...etc, for all images
// the loaded images will be placed in imgs[]
var imgs=[];
var imagesOK=0;
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// the imgs[] array now holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
}

Is there an onload function for html canvas?

My goal is to create an array of imageData from a series of png images.
In order to do this I use a for loop to go through the images, put them in the canvas and then retrieve and store the created imageData in an array.
The problem is that the image does not have enough time to be loaded in the canvas.
My solution for now is to have a timer leave enough time but it's horrible.
I can't seem to find any solution to this problem. Is there a way to have an "onload" function for the canvas?
for(var i=1;i<50;i++){
(function(i){
setTimeout(function(){
return function(){
var newpath = path+i+".png"; //path to the image
baliseimg.onload = function(){ //baliseimg : html image tag
ctxt.drawImage(baliseimg,0,0);
ctxt.fillText(i, 50, 50); //just to stamp the image number on the imagedata
imgs[i-1]=ctxt.getImageData(0,0,512,512);
}
baliseimg.src=newpath;
}();
},100*i); //100ms works, limit seems to be at 40
}(i));
}
Thank you for your time.
No, according to http://www.w3schools.com/jsref/event_onload.asp
But you still can use img elements' onload in order to do canvas manipulation. This article may help you.
Bonne chance !
Here's code to preload all your images.
When they are fully loaded the start() function is called.
// image loader
var imageURLs=[];
var imagesOK=0;
var imgs=[];
// put the paths to your images in imageURLs[]
imageURLs.push("yourImage1.png");
imageURLs.push("yourImage2.png");
imageURLs.push("yourImage3.png");
imageURLs.push("yourImage4.png");
imageURLs.push("yourImage5.png");
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// the imgs[] array holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
// for example, draw the images on the canvas
var nextX=10;
for(var i=0;i<imgs.length;i++){
context.drawImage(imgs[i],nextX,10);
nextX+=imgs[i].width;
}
}

Categories

Resources