How to make an area in canvas html droppable? - javascript

How do you transfer an image of a div or file to some droppable canvas area (inside canvas)? My intention is to make a photobook.
I was doing it with div tags, but I have seen that all photobooks do it with canvas. They even send the photo as drop as canvas.
<canvas id="canvas" style="position:absolute;"></canvas>
<canvas id="canvas-encima" style="position:absolute;left:8em;top:7em;"></canvas>
<script>
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width= 1000;
canvas.height = 481;
var background = new Image();
background.src = "upload/plantilla5prueba.jpg";
background.onload = function() {
ctx.drawImage(background,0,0);
}
var canvas2 = document.getElementById("canvas-encima"),
ctx2 = canvas2.getContext("2d");
canvas2.width= 330;
canvas2.height = 280;
var image2 = new Image();
image2.src = "upload/celular.png";
image2.onload = function() {
ctx2.drawImage(image2,0,0);
;}
</script>
I believe that with the drag and drop of a blob or uploaded file to the canvas droppable, the photobook would almost be made.

In order to make an area in the canvas droppable I mark first the droppable zone. See the markDroppableZone() function. On dragenter, dragover and drop I check first if the mouse is inside the droppable zone using isPointInPath. Please see onEvent() function.
If the mouse is in path I use e.stopPropagation(); e.preventDefault();. This is preventing the opening of the dragged image in a new window.
Next on drop I procede to handle the dropped files. See the handleFiles() function.
const ctx = canvas.getContext("2d")
let cw = canvas.width;
let ch = canvas.height;
//the mouse
let m = {}
ctx.setLineDash([4]);
markDroppableZone();
ctx.stroke();
ctx.setLineDash([]);
function markDroppableZone(){
ctx.beginPath();
ctx.rect(10,10,160,160);
}
canvas.addEventListener("dragenter", dragenter, false);
canvas.addEventListener("dragover", dragover, false);
canvas.addEventListener("drop", drop, false);
function dragenter(e) {
onEvent(e);
}
function dragover(e) {
onEvent(e);
}
function drop(e) {
onEvent(e);
let data = e.dataTransfer;
let files = data.files;
// handle files
handleFiles(files);
}
function handleFiles(files){
for (var i = 0; i < files.length; i++) {
var theFile = files[i];
// check if the file is an image
var isImagen = /^image\//;
// if it's not an image continu
if (!isImagen.test(theFile.type)) {
continue;
}
var img = new Image();
img.src = window.URL.createObjectURL(theFile);
img.onload = function() {
ctx.save();
markDroppableZone();
// clip the context
ctx.clip();
// draw the image
ctx.drawImage(this, 10, 10);
ctx.restore();
window.URL.revokeObjectURL(this.src);
}
}
}
function onEvent(e){
m = oMousePos(canvas, e);
markDroppableZone();
if (ctx.isPointInPath(m.x, m.y)){
e.stopPropagation();
e.preventDefault();
}
}
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
canvas{background:#e9e9e9}
<canvas id="canvas" width="500" height="500"></canvas>

Related

JavaScript, canvas - drawn lines disappear after placing mouse over other element

my idea for the page is to load an image (done by <input type="file/>) and to draw some lines on it (those will represent spool points on welding map drawing.
I have tag in order to do that. However my current code is not working because I add canvas.addEventListener("mouseover", handler, false); event. Adding extra mouseout event doesn't solve the problem. Lines disappear when mouse proceed event over other elements in document. I tried to add arrow function, or add document.addEventListener('DOMContentLoaded') event. Both are not helpful.
Please answer how to keep lines visible as long as file is loaded.
Moreover I have some problems adding event listeners to clear and undo buttons - my variable lines = []; seems to store more data that I expected. How should I prepare this feature to undo last drawn line?
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://unpkg.com/chota">
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<h1 id="header" name="header">Spool assistant</h1>
<div>
<p>Click to draw lines</p>
</div>
<br/>
<div id="main-area">
<div id="draftsman-menu">
<input type="button" id="file-selector-button" value="Load pdf" onclick="document.querySelector('#file-selector').click();" />
<input type="file" id="file-selector" accept=".jpg, .jpeg, .png, .pdf" style="display:none;">
</div>
<div id="canvas-area">
<canvas id="canvas" width=1000 height=400></canvas>
</div>
<div id="edit-menu">
<button id="clear">Clear</button>
<button id="undo">Undo</button>
<button id="save">Save</button>
</div>
</div>
<p id="status"></p>
<img id="output" style="display:none;">
<script type="text/javascript" src="../static/js/drawing_events.js"></script>
</body>
</html>
JS
// constants and variables
const status = document.querySelector("#status");
const output = document.querySelector("#output");
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var startX, startY, mouseX, mouseY;
var isDown = false;
var lines = [];
var imageOpacity = 1;
const canvasPosition = canvas.getBoundingClientRect();
const clearButton = document.querySelector("#clear");
const undoButton = document.querySelector("#undo");
var imageData;
// start with image change event
if (window.FileList && window.File && window.FileReader) {
document.getElementById("file-selector").addEventListener("change", event => {
output.src = "";
status.textContent = "";
// allow only one file even if user selected more
const file = event.target.files[0];
// validations
if (!file.type) {
status.textContent = "Error: The File.type property does not appear to be supported on this browser.";
return;
};
if (!file.type.match("image.*")) {
status.textContent = "Error: The selected file does not appear to be an image."
return;
};
const reader = new FileReader();
reader.addEventListener("load", event => {
output.src = event.target.result;
output.onload = function() {
lines = [];
ctx.drawImage(output, 0, 0);
};
});
reader.readAsDataURL(file);
});
};
// drawing events
canvas.addEventListener("mouseover", handler, false);
function handler(event) {
var img = new Image();
img.onload = start;
img.src = output.src;
function start() {
canvas.width = canvas.width = img.width;
canvas.height = img.height;
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseUp(e);});
// redraw the image
drawTheImage(img, imageOpacity);
};
function drawLines(toX, toY) {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the image
drawTheImage(img,imageOpacity);
// redraw all previous lines
for(var i = 0; i < lines.length; i++) {
drawLine(lines[i]);
};
// draw the current line
drawLine({x1:startX,y1:startY,x2:mouseX,y2:mouseY});
};
function drawTheImage(img, opacity) {
ctx.globalAlpha = opacity;
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1.00;
};
function drawLine(line){
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.lineTo(line.x2, line.y2);
ctx.stroke();
};
function handleMouseDown(e){
e.stopPropagation();
e.preventDefault();
mouseX = parseInt(e.clientX - canvasPosition["x"] + window.scrollX);
mouseY = parseInt(e.clientY - canvasPosition["y"] + window.scrollY);
// Put your mousedown stuff here
startX = mouseX;
startY = mouseY;
isDown = true;
};
function handleMouseUp(e) {
e.stopPropagation();
e.preventDefault();
// Put your mouseup stuff here
isDown = false;
lines.push({x1:startX, y1:startY, x2:mouseX, y2:mouseY});
};
function handleMouseMove(e) {
if (!isDown) {return;}
e.stopPropagation();
e.preventDefault();
mouseX = parseInt(e.clientX - canvasPosition["x"] + window.scrollX);
mouseY = parseInt(e.clientY - canvasPosition["y"] + window.scrollY);
// Put your mousemove stuff here
drawLines(mouseX, mouseY);
};
// clearing
// TODO
// function has to be replaced with undoing or loading blank page
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
lines = []
};
function undoLine() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawTheImage(img, imageOpacity);
lines.pop();
for(var i = 0; i < lines.length; i++) {
drawLine(lines[i]);
};
};
clearButton.addEventListener("click", clearCanvas);
undoButton.addEventListener("click", undoLine);
};

Loading video frames into javascript in google colab

I wish to present a video, frame by frame with the possibility of flipping between frames in google colab. I also wish to mark some rectangles and get the x,y,w,h of each rectangle.
As far as I could understand, the only way to click the output and gather data in google Collab is via a javascript call, which I am new to. I have split my video into a list called frames and written the following python\javascript code for my task:
def get_fxy_val(base64imgs):
js = Javascript('''
async function showImage(base64) {
var canvas = document.createElement('canvas');
var rect = {};
var drag = false;
var finish = false;
var clicks = [];
var img = document.createElement('img');
var frame = 0
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
img = new Image();
img.onload = function () {
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
};
img.src = 'data:image/jpeg;base64,'+base64[frame];
var prev = document.createElement('button');
prev.innerHTML = '<';
prev.onclick = function() {
frame = frame -1;
if(frame<0){
frame = 0;
}
else{
// console.log(frame)
img.src = 'data:image/jpeg;base64,'+base64[frame];
};
};
document.body.appendChild(prev);
var next = document.createElement('button');
next.innerHTML = '>';
next.onclick = function() {
frame = frame+1;
if (frame>base64.length-1) {
// console.log(frame)
frame = base64.length-1
}
else{
img.src = 'data:image/jpeg;base64,'+base64[frame];
}
};
document.body.appendChild(next);
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
function mouseDown(e) {
rect.startX = e.pageX - this.offsetLeft;
rect.startY = e.pageY - this.offsetTop;
drag = true;
}
function mouseUp() {
drag = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
if (rect.w<0){
rect.startX = rect.startX + rect.w
rect.w = rect.w*(-1)
}
if (rect.h<0){
rect.startY = rect.startY + rect.h
rect.h = rect.h*(-1)
}
ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}
}
var saver = document.createElement('button');
saver.innerHTML = 'save rectangle';
saver.onclick = function() {
clicks.push([frame, rect.startX, rect.startY, rect.w, rect.h]);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
};
document.body.appendChild(saver);
var finisher = document.createElement('button');
finisher.innerHTML = 'Done!';
finisher.onclick = function() {
};
document.body.appendChild(finisher);
await new Promise((resolve) => finisher.onclick = resolve);
document.body.removeChild(finisher)
document.body.removeChild(saver)
document.body.removeChild(next)
document.body.removeChild(prev)
document.body.removeChild(canvas)
return clicks
}
''')
display(js)
imgs = ["'"+b64img+"'" for b64img in base64imgs]
imgs = '[' + ','.join(imgs) +']'
data = eval_js(f"showImage({imgs})")
return data
I call it using:
base64imgs = [base64.b64encode(cv2.imencode('.jpg', img)[1]).decode() for img in frames]
data = get_fxy_val(base64imgs)
Everything works well for a small number of images, however for the full video Collab crushes and I cannot manage to find any logs to verify what is going on.
Two thoughts I had, which I do not know how to implement:
Reduce the images' memory size somehow. I cannot harm the aspect ratio so downsampling is not a possibility.
Load images directly from memory inside the javascript code. I have no idea how to implement this or if it is even possible.
Can I implement any of these? Is there another approach?
B.T.W, my video has images with size h,w,c = (1080, 1920, 3).

Combine two images in one canvas

I am using this code to display data of two canvas into third canvas but it is not working.
I am saving the two canvas data using localstorage and passing it to third canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//First canvas data
var img1 = loadImage(localStorage.getItem('cbdata'), main);
//Second canvas data
var img2 = loadImage(localStorage.getItem('cbdata1'), main);
var imagesLoaded = 0;
function main() {
imagesLoaded += 1;
if (imagesLoaded == 2) {
// composite now
ctx.drawImage(img1, 0, 0);
ctx.globalAlpha = 0.5;
ctx.drawImage(img2, 0, 0);
}
}
function loadImage(src, onload) {
var img = new Image();
img.onload = onload;
img.src = src;
return img;
}
Your code works. Check that the content of localStorage.getItem is non-empty. And I also slightly modified display of the images by changing ctx.drawImage commands:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//First canvas data
var img1 = loadImage('http://ep01.epimg.net/elpais/imagenes/2015/10/21/ciencia/1445444934_907402_1445781076_portada_normal.jpg', main);
//Second canvas data
var img2 = loadImage('http://ep01.epimg.net/economia/imagenes/2015/10/22/actualidad/1445508003_507635_1445508894_portada_normal.jpg', main);
var imagesLoaded = 0;
function main() {
imagesLoaded += 1;
if (imagesLoaded == 2) {
// composite now
ctx.drawImage(img1, 0, 0, 100, 100);
ctx.globalAlpha = 0.5;
ctx.drawImage(img2, 100, 0, 100, 100);
}
}
function loadImage(src, onload) {
console.log('loadImage', src);
var img = new Image();
img.onload = onload;
img.src = src;
return img;
}
https://jsfiddle.net/4Le4g8ta/

draw on image using canvas

I have a picture which which is loaded like this:
<img src="map/racetrack.jpg" id="map">
And here is my drawing function:
this.drawRoute = function(){
var pos = [];
var canvas = $('<canvas/>')[0];
var img = $('#map')[0];
var ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
$(window).on('mousedown', function(e){
pos.push({
x: e.clientX,
y: e.clientY
});
});
if (positions.length > 1) {
ctx.beginPath();
ctx.moveTo(pos[0].x, pos[0].y);
for (var i = 1; i < pos.length; i++) {
ctx.lineTo(pos[i].x, pos[i].y);
}
ctx.stroke();
}
}
But nothing is drawn on the picture. I can't see where the mistake is. I know usually I'm supposed to use <canvas> element, but I only need this particular part done in canvas, nothing else.
Here's the fiddle
Well a few things first you have no canvas element. Secondly I didnt see where you were calling the draw portion.
Live Demo
What I ended up doing was adding a canvas element, hiding the image, and then every time you draw it draws the image to the canvas, and then draws the points over it. This should hopefully be enough to get you started.
var pos = [];
var canvas = $('#canvas')[0];
var img = $('#map')[0];
var ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
function render() {
ctx.drawImage(img, 0, 0);
if (pos.length > 1) {
ctx.beginPath();
ctx.moveTo(pos[0].x, pos[0].y);
for (var i = 1; i < pos.length; i++) {
ctx.lineTo(pos[i].x, pos[i].y);
}
ctx.stroke();
}
}
$(window).on('mousedown', function (e) {
pos.push({
x: e.clientX,
y: e.clientY
});
render();
});
render();

A canvas into which I would like to load a Base64 string that represents a png image but just displays a blank white box

I have a canvas into which I would like to load a Base64 string that represents a png image. However, the following code just displays a blank white box and I am baffled as to why. When I look at the data in the canvas, it looks identical to a canvas that gets its data from a FileReader object (also below). Any help deducing this issue is greatly appreciated!
This code shows a white canvas:
html
<canvas id="canvas" width="114" height="114" style="z-index: 999999; display: none; padding-left: 50px"></canvas>
javascript
var websiteIconData = $('#ctl00_ContentPlaceHolder1_websiteIcon');
if (websiteIconData.val() != '') {
var canvas = document.getElementById('canvas');
var ctx = document.getElementById('canvas').getContext('2d');
var loadedImg = new Image();
loadedImg.onload = function () {
ctx.drawImage(this, 0, 0);
Debugger.log(ctx);
};
loadedImg.src = websiteIconData.val();
canvas.style.display = 'block';
}
This code shows the image:
$('#loadWebsiteIcon').on({
change: function (ev) {
var reader = new FileReader();
reader.onload = function (e) {
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function () {
var MAX_WIDTH = 114;
var MAX_HEIGHT = 114;
var width = img.width;
var height = img.height;
if (width > MAX_WIDTH) {
width = MAX_WIDTH;
}
if (height > MAX_HEIGHT) {
height = MAX_HEIGHT;
}
ctx.drawImage(img, 0, 0, width, height);
for (var i = 0; i <= document.images.length; i++) {
}
Debugger.log(ctx);
};
img.src = e.target.result;
}
draw();
};
reader.readAsDataURL(ev.target.files[0]);
var canvas = document.getElementById('canvas');
canvas.style.display = 'block';
var imgData = canvas.toDataURL();
$('#ctl00_ContentPlaceHolder1_websiteIcon').val(imgData);
Debugger.log(imgData);
}
});
Be careful how Base64 is parsed. If it is parsed for email, it will insert a character ever 76 lines by default. Most Base64 encoders have an option to turn this off. I am looking at MIME::Base64
From that document :
The returned encoded string is broken into lines of no more than 76 characters each and it >will end with $eol unless it is empty.
where $eol was one of the arguments. In the case of this module, setting it to an empty string would prevent the base64 from being broken up.
It turns out the issue had little to do with the canvas API or the Base64 encoding. It was more an issue of the canvas.toDataURL(); function getting called before the image had been loaded in the canvas. Moving this line to within the img.onload function seemed to do the trick. Below is the correct code:
function initImageChange() {
//this is will be a string in Base64, in this case it is an <input type="text">... tag
var imageData = $('#imageData');
//this is a canvas tag in on the page
var canvas = document.getElementById('canvas');
//load the image from the imageData var if it exists
if (imageData.val() != '') {
var ctx2=canvas.getContext('2d');
var loadedImg = new Image();
loadedImg.onload = function() {
ctx2.drawImage(this, 0, 0);
};
loadedImg.src = imageData.val();
canvas.style.display = 'block';
}
//this is a function that loads an image from a file onto the canvas and
//sends the Base64 representation of this image to the text input tag. This
//could fairly easily be send directly to a post request or saved some other way
$('#loadImage').on({
change: function (ev) {
var ctx = document.getElementById('canvas').getContext('2d');
var dataURL = '';
var reader = new FileReader();
reader.onload = function (e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
function draw() {
var img = new Image();
img.onload = function () {
var MAX_WIDTH = 130;
var MAX_HEIGHT = 110;
var width = img.width;
var height = img.height;
if (width > MAX_WIDTH) {
width = MAX_WIDTH;
}
if (height > MAX_HEIGHT) {
height = MAX_HEIGHT;
}
ctx.drawImage(img, 0, 0, width, height);
dataURL = canvas.toDataURL();
$('#ctl00_ContentPlaceHolder1_websiteIcon').val(dataURL);
ctx.restore();
};
img.src = e.target.result;
}
draw();
};
reader.readAsDataURL(ev.target.files[0]);
canvas.style.display = 'block';
}
});
}

Categories

Resources