I have a drawn image in canvas with context.drawImage() and I want to select that image to move it with drag and drop in the same canvas (same as MS Paint selection tool). How can I code that in javascript?
function crop(xStart,yStart,xLast,yLast){
canvas.width = xLast - xStart;
canvas.height = yLast - yStart;
context.clearRect(0, 0, canvas.width,canvas.height); drawFromAux(xStart,yStart,xLast,yLast,0,0);
return canvas.toDataURL();
}
// img is my original image
function select(xStart,yStart,xLast,yLast){
selection.src = crop(xStart,yStart,xLast,yLast);
selection.draggable = "true";
context.clearRect(0, 0, canvas.width,canvas.height);
canvas.width = img.width;
canvas.height = img.height;
context.clearRect(0, 0, canvas.width,canvas.height);
context.drawImage(img, 0, 0);
context.clearRect(xStart, yStart, xLast - xStart,yLast - yStart);
context.drawImage(selection,0,0);
}
Using Canvas.js and Pointer.js that should not be that hard.
Things to do:
create image object containing with x, y and raw image
render it to a canvas
listen for mouse moves and check if mouse button is pressed
simple point collision detection between mouse and image on canvas to be able to "select it" and drag it
Loading Pointer.js and Canvas.js:
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Pointer.js"></script>
1) Creating an image object is not very hard:
const image = {
image: new Image(),
x: canvas.width / 2 - image.width / 2, // centered in canvas
y: canvas.height / 2 - image.height / 2 // centered in canvas
};
image.image.src = ' <url> ';
2) Render that image to the canvas (using Canvas.js)
const canvas = new Canvas('my-canvas', 500, 500).start();
canvas.on('draw', function ( renderer ) {
renderer.drawImage(image.image, image.x, image.y);
});
3) Listening for mouse moves and moving the image (using Pointer.js)
const pointer = new Pointer( canvas.element );
let moveImage = false;
pointer.on('move', function ( event ) {
if( moveImage ) {
image.x += (event.x - pointer.getMoveHistory(-2).x);
image.y += (event.y - pointer.getMoveHistory(-2).y);
}
});
4) Pointer collision detection (using Pointer.js)
pointer.on('down', function () {
moveImage = pointer.touches({ x: image.x, y: image.y, width: image.image.width, height: image.image.height });
});
pointer.on('up', function () {
moveImage = false;
});
JSFiddle: https://jsfiddle.net/GustavGenberg/3h39b9h1/
Hope this helps you :) !
Related
I have a situation where I need to get the full ImageData inside the canvas even after panning the image.
Thanks in Advance
Example in the fiddle: https://jsfiddle.net/z46cvm9h/
<canvas id="canvas"></canvas>
<button onclick="showImage()">
Show panned Image
</button>
<img id="image" src='' />
var context = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 300;
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var pan = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
function draw() {
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(pan.offset.x, pan.offset.y);
context.beginPath();
context.rect(50, 50, 100, 100);
context.fillStyle = 'skyblue';
context.fill();
context.beginPath();
context.arc(350, 250, 50, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
canvas.addEventListener("mouseleave", endPan);
canvas.addEventListener("mouseup", endPan);
function startPan(e) {
canvas.addEventListener("mousemove", trackMouse);
canvas.addEventListener("mousemove", draw);
pan.start.x = e.clientX;
pan.start.y = e.clientY;
}
function endPan(e) {
canvas.removeEventListener("mousemove", trackMouse);
pan.start.x = null;
pan.start.y = null;
global.offset.x = pan.offset.x;
global.offset.y = pan.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - pan.start.x;
var offsetY = e.clientY - pan.start.y;
pan.offset.x = global.offset.x + offsetX;
pan.offset.y = global.offset.y + offsetY;
}
function showImage(){
document.getElementById('image').src = canvas.toDataURL();
}
PS: JSFiddle is for demonstration, I can't put the exact scenario into JSFiddle. In the application, I pan the image and then use the mouse to draw something on the canvas using the mousedown and mousemove events. On the mouseup event, I need to have the ImageData with the full image I drew, but when using getImageData, I am only getting the image appearing on the canvas. The panned image is cropped to the canvas size.
As #Blindman67 suggested, you can make a second canvas.
Create an object to store the furthest away points in your image.
const imagePadding = {
top: 0,
right: 0,
bottom: 0,
left: 0
}
top shall be the highest point of the drawing, right shall be the point furthest to the right, and so on. For demonstration purposes, the padding is set equal to how far you have panned in each direction. You will want to set it equal to the shapes/points of the drawing that are furthest in each direction (up, right, down, and left--those are the directions I am talking about).
function panImage(e) {
pan.x += e.movementX
pan.y += e.movementY
drawPreview()
imagePadding.top = Math.max(imagePadding.top, pan.y)
imagePadding.left = Math.max(imagePadding.left, pan.x)
imagePadding.bottom = Math.max(imagePadding.bottom, -pan.y)
imagePadding.right = Math.max(imagePadding.right, -pan.x)
}
To draw the full image, you can make a function like this:
function drawFinalImage() {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const pannedImage = document.getElementById('image')
canvas.width = previewCanvas.width + imagePadding.left + imagePadding.right
canvas.height = previewCanvas.height + imagePadding.top + imagePadding.bottom
ctx.translate(imagePadding.left, imagePadding.top)
drawShapes(ctx)
pannedImage.src = canvas.toDataURL()
}
See the JSFiddle for a demo as well as how you can implement the code. Let me know if there is anything I should explain better.
You can use JSFiddle Console to adjust the padding. Type imagePadding.top = 200, press enter, and click the canvas to update the final image.
I want to have an image follow the mouse around the canvas, which is fairly easy, but the catch is that I want my canvas to change with screen resolution (it is set using CSS to be 70vw).
When the resolution decreases and the window becomes smaller this means that using a normal method of using clientX doesn't work.
My code so far is this:
var mouseX = e.clientX/document.documentElement.clientWidth * 1920;
var mouseY = e.clientY/document.documentElement.clientHeight * 943;
This tries to convert the users clientX into the value it would be on a 1920x1080 monitor.
However, this isn't really accurate and doesn't work very well on even 1920x1080 monitors. Any help would be appreciated.
You can't scale the canvas using CSS in the way that you think. A canvas is basically a more advanced image. Scaling the canvas via CSS just stretches the canvas the same way an image would stretch. To change the canvas height and width, you need to change it's height and width attributes in the tag or via code. This will physically change the canvas to the size that you want without scaling and/or stretching.
That being said, we can use this to watch for window size changes and resize the canvas when the window changes.
window.addEventListener('resize', e => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
})
With some basic math, we can calculate what a 70% width would be, it would be done like this
window.addEventListener('resize', e => {
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight
})
The next thing we need to do is get the local position of the mouse on the canvas, which can be done using mousePosition - canvasOffset like this
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
When all is said and done, we end up with something like this (To see it in action press run then click on Full Page and you will see the canvas resize):
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// Set the inital height and width of the canvas
canvas.width = window.innerWidth
canvas.height = window.innerHeight
canvas.addEventListener('mousemove', e => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Get the local x/y coordinates of the mouse on the canvas
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
// Draw a dot where the mouse is
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
})
// Update the height and width when the window size changes
window.addEventListener('resize', e => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
})
body {
padding: 0;
margin: 0;
}
canvas {
background-color: black;
display: block;
}
<canvas></canvas>
In this example below, we use a canvas that is 70% the width and height of the screen and center it with CSS. However, we never touch the height/width with css because it will mess up the canvas' coordinate system. This part is done with JavaScript.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// Set the inital height and width of the canvas
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight * 0.7
canvas.addEventListener('mousemove', e => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Get the local x/y coordinates of the mouse on the canvas
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
// Draw a dot where the mouse is
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
})
// Update the height and width when the window size changes
window.addEventListener('resize', e => {
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight * 0.7
})
body {
padding: 0;
margin: 0;
}
canvas {
background-color: black;
display: block;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin: auto;
}
<canvas></canvas>
I took my snippet from my answer to create a full screen canvas.
I added this for mouse movement:
let User = { x: 0, y: 0 };
//controles if the mouse is moving
window.addEventListener(
"mousemove",
e => {
User.x = e.clientX;
User.y = e.clientY;
},
false
);
Uncomment: cvs.ctx.drawImage(image, User.x, User.y); in the ShowImage() function to draw an image at the mouse x and y position.
Mind to replace the path of the image source: image.src = "Your/Path/To/Image.png";
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* Sets the canvas properties.
* #param {object} Cvs Give the html canvas Id.
* #param {boolean} Fullscreen Change the canvas fullscreen default false.
* #param {string} Dimension Change the canvas dimension default "2d".
* #return {object}
*/
function NewCanvas(cvs, fullscreen, dimension) {
if (!dimension) dimension = "2d";
var ctx = cvs.getContext(dimension);
if (fullscreen) {
cvs.style.position = "fixed";
cvs.style.left = cvs.x = 0;
cvs.style.top = cvs.y = 0;
} else {
var rect = cvs.getBoundingClientRect();
cvs.x = rect.left;
cvs.y = rect.top;
}
cvs.ctx = ctx;
cvs.dimension = dimension;
cvs.fullscreen = fullscreen;
return cvs;
}
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* Updates the canvas width and hight.
* #param {object} Cvs NewCanvas() object.
* #param {boolean} Clear Change the canvas clear default true.
*/
function UpdateCvs(cvs) {
if (cvs.fullscreen) {
//if the width is not the same resize the canvas width
if (window.innerWidth != cvs.width) {
cvs.width = window.innerWidth;
}
//if the height is not the same resize the canvas height
if (window.innerHeight != cvs.height) {
cvs.height = window.innerHeight;
}
} else {
let rect = cvs.getBoundingClientRect();
cvs.x = rect.left;
cvs.y = rect.top;
}
}
function ClearCvs(cvs) {
if (cvs.dimension == "2d")
// set fillRect to clearRect to clear all of the canvas
// fillRect is used here to show the full canvas
cvs.ctx.fillRect(0, 0, cvs.width, cvs.height);
}
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* get html element by id.
* #param {string} id give the html element id.
* #return {object} document.getElementById(id);
*/
function GetId(id) { return document.getElementById(id) }
// To create your canvas object.
var canvas = NewCanvas(GetId("yourCanvasId"), true);
// If you want to update your canvas size use this:
window.addEventListener("resize", function() {
UpdateCvs(canvas);
});
let User = { x: 0, y: 0 };
//controles if the mouse is moving
window.addEventListener(
"mousemove",
e => {
User.x = e.clientX;
User.y = e.clientY;
},
false
);
// Set it to current width
UpdateCvs(canvas);
ClearCvs(canvas);
// create an image
let image = new Image();
image.src = "Your/Path/To/Image.png";
function ShowImage(cvs) {
// Use this line to draw your image.
// cvs.ctx.drawImage(image, User.x, User.y);
// Shows where your image will be drawn.
cvs.ctx.clearRect(User.x, User.y, 100, 100);
}
function Update() {
ClearCvs(canvas);
ShowImage(canvas);
// keeps it looping
window.requestAnimationFrame(Update)
}
// Init the loop
Update();
<canvas id="yourCanvasId"></canvas>
I have an image to be drawn on a canvas with its coordinate. e.g;
var data = {
x: 100, y: 100, // the coord when the image drawn
src: imguri,
scale: 1.6 // the scale when the image drawn
}
and zoom function like below;
var scale = 1.6, width = canvas.width, height = canvas.height
function zoom(positiveOrNegative) {
scale += positiveOrNegative * .2
canvas.width = width * scale
canvas.height = height * scale
loadImage()
}
function loadImage() {
var img = new Image()
img.src = data.src;
img.onload = function() { context.drawImage(img, data.x, data.y) }
}
https://jsfiddle.net/bbuv53u6/
how do I resize and re-position the image to look like it's been zoomed in/out when the canvas is resized?
Use this methods for get a position values .
If you put just
var posX = 10
This variable is fixed but if you dont use variable use method :
VIEW.W(2) // this is 2% from window width .
In this case you no need for any calculation on resize.
Also your example have no zoom effect, you just resize canvas tag element.
Procedures for zoom :
//context.save();
context.translate( x , y );
context.scale(scale, scale);
//context.restore();
Heres object :
var VIEW = {
W : function(per){
if (typeof per === 'undefined'){
return window.innerWidth;
}else{
return window.innerWidth/100 * per;
}
},
H : function(per){
if (typeof per === 'undefined'){
return window.innerHeight;
}
else{
return window.innerHeight/100 * per;
}
},
ASPECT : function(){
return window.innerWidth / window.innerHeight;
},
};
Bonus link :
Zooming with canvas
I'm trying to use the clip() function in canvas to create this effect, as pictured: there is a background image, and when your mouse hover on it, part of the image is shown. I got it to work as a circle, but I want this gradient effect you see the picture. How do I achieve that?
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/normalize.css">
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/style.css">
</head>
<body>
<canvas id="canvas" width="2000" height="1200"></canvas>
<script>
var can = document.getElementById('canvas');
var ctx = can.getContext('2d');
can.addEventListener('mousemove', function(e) {
var mouse = getMouse(e, can);
redraw(mouse);
}, false);
function redraw(mouse) {
console.log('a');
can.width = can.width;
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.rect(0,0,2000,1200);
ctx.arc(mouse.x, mouse.y, 200, 0, Math.PI*2, true)
ctx.clip();
ctx.fillRect(0,0,2000,1200);
}
var img = new Image();
img.onload = function() {
redraw({x: 0, y: 0})
}
img.src = 'http://placekitten.com/2000/1000';
function getMouse(e, canvas) {
var element = canvas,
offsetX = 0,
offsetY = 0,
mx, my;
// Compute the total offset. It's possible to cache this if you want
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {
x: mx,
y: my
};
}
</script>
USING a RADIAL gradient
There are many ways to do that but the simplest is a gradient with an alpha.
First you need to define the size of the circle you wish to show.
var cirRadius = 300;
Then the location (canvas coordinates) where this circle will be centered
var posX = 100;
var posY = 100;
Now define the rgb colour
var RGB = [0,0,0] ; // black
Then an array of alpha values to define what is transparent
var alphas = [0,0,0.2,0.5,1]; // zero is transparent;
Now all you do is render the background image
// assume ctx is context and image is loaded
ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); // fill the canvas
Then create the gradient with it centered at the position you want and the second circle at the radius you want. The first 3 numbers define the center and radius of the start of the gradient, the last 3 define the center and radius of the end
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
Now add the colour stops using the CSS color string rgba(255,255,255,1) where the last is the alpha value from 0 to 1.
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
or for legacy browsers that do not support arrow functions or template strings
var i,len = alphas.length;
for(i = 0; i < len; i++){
grad.addColorStop(i / (len - 1), "rgba(" + RGB[0] + "," + RGB[1] + "," + RGB[2] + "," + alphas[i] + ")");
}
Then set the fill style to the gradient
ctx.fillStyle = grad;
then just fill a rectangle covering the image
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);
And you are done.
By setting the position with via a mouse event and then doing the above steps 60times a second using window.requestAnimationFrame you can get the effect you are looking for in real time.
Here is an example
// create a full screen canvas
var canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 10;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
// var to hold context
var ctx;
// load an image
var image = new Image();
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
// add resize event
var resize = function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
// add mouse event. Because it is full screen no need to bother with offsets
var mouse = function(event){
posX = event.clientX;
posY = event.clientY;
}
// incase the canvas size is changed
window.addEventListener("resize",resize);
// listen to the mouse move
canvas.addEventListener("mousemove",mouse)
// Call resize as that gets our context
resize();
// define the gradient
var cirRadius = 300;
var posX = 100; // this will be set by the mouse
var posY = 100;
var RGB = [0,0,0] ; // black any values from 0 to 255
var alphas = [0,0,0.2,0.5,0.9,0.95,1]; // zero is transparent one is not
// the update function
var update = function(){
if(ctx){ // make sure all is in order..
if(image.complete){ // draw the image when it is ready
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}else{ // while waiting for image clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
}
// create gradient
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
// add colour stops
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
// set fill style to gradient
ctx.fillStyle = grad;
// render that gradient
ctx.fillRect(0,0,canvas.width,canvas.height);
}
requestAnimationFrame(update); // keep doing it till cows come home.
}
// start it all happening;
requestAnimationFrame(update);
I'm drawing a large canvas image as a background, the image is larger that the window size. I'm wondering if theres a way for me to center the image to fit on full screen. If so, how? this is what I'm doing:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
drawStuff();
}
resizeCanvas();
function drawStuff() {
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 69, 50);
};
imageObj.src = '/resources/img/bg.png';
}
Here is an optional way of centering an image to canvas not using transform (also see note below):
imageObj.onload = function() {
var x = (canvas.width - this.width ) * 0.5, // this = image loaded
y = (canvas.height - this.height) * 0.5;
ctx.drawImage(this, x, y);
};
Since the image is larger than the canvas x and y will be negative in this case, which is perfectly fine. If the image was smaller it would work just as fine too. If you do the drawing outside the load handler you would of course need to use imageObj instead of this.
NOTE: The way you have set up your resize handler is not the best way to handle image repositioning - you should only load the image once, then reuse that object. As resizing typically creates a number of events it would trigger an equal number of image reloads.
For this to work properly you could do something like this instead:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
imageObj = new Image(); // declare globally
imageObj.onload = function() {
// now set up handler when image is actually loaded
// - else drawImage will fail (width, height is not available and no data)
window.addEventListener('resize', resizeCanvas, false);
// initial call to draw image first time
resizeCanvas();
};
imageObj.src = '/resources/img/bg.png';
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
drawStuff();
}
function drawStuff() {
var x = (canvas.width - imageObj.width ) * 0.5,
y = (canvas.height - imageObj.height) * 0.5;
ctx.drawImage(imageObj, x, y);
}
It's not perfect as the resize event queue will still be large and may lag - there are solutions for this too, for example this one (with minor modifications).
Here's how to center the image on the canvas. Any vertical or horizontal overflow will be off-canvas:
imageObj.onload = function() {
ctx.translate(canvas.width/2,canvas.height/2);
ctx.drawImage(imageObj,-imageObj.width/2,-imageObj.height/2);
ctx.translate(-canvas.width/2,-canvas.height/2);
};
Good luck with your project!
If you are adding image after uploading then you can use this, it works for me :)
var image_center_width = (canvas.width - img.width) / 2;
var image_center_height = (canvas.height - img.height) / 2;
img.set({
left : image_center_width,
top : image_center_height,
});
canvas.add(img)