i'm creating a web page where you dynamically draw multiple rectangles. I can draw the single object, but once I tried to draw another one, the previous one is gone away. I tried to save the state using save() and restore(), and it seems that I can't put it here. Isn't it logical that save method is put in the mouseup and restore method is called in the mousedown event? Any help or advice will be appreciated.
const canvas = document.getElementById("circle"),
ctx = canvas.getContext("2d"),
circle = {},
drag = false,
circleMade = false,
mouseMoved = false;
function draw() {
ctx.beginPath();
ctx.arc(circle.X, circle.Y, circle.radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
function mouseDown(e) {
ctx.restore();
circle.startX = e.pageX - this.offsetLeft;
circle.startY = e.pageY - this.offsetTop;
circle.X = circle.startX;
circle.Y = circle.startY;
if (!circleMade) {
circle.radius = 0;
}
drag = true;
}
function mouseUp() {
drag = false;
circleMade = true;
if (!mouseMoved) {
circle = {};
circleMade = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
mouseMoved = false;
ctx.save();
}
function mouseMove(e) {
if (drag) {
mouseMoved = true;
circle.X = e.pageX - this.offsetLeft;
circle.Y = e.pageY - this.offsetTop;
if (!circleMade) {
circle.radius = Math.sqrt(Math.pow((circle.X - circle.startX), 2) + Math.pow((circle.Y - circle.startY), 2));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
}
}
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
init();
you need to save the information about what you are drawing in a separate object, every time you make a draw to the canvas you will wipe and redraw the new object.
so when you clearRect and then draw you are clearing and then drawing a fresh one but the old ones are being left behind. A good example:
var SavedCircles = [];
var circleInfo = function()
{
this.x = 0;
this.y = 0;
this.startX = 0;
this.startY = 0;
this.radius = 0;
}
circle = {};
function draw()
{
for(var x=0;x<SavedCircles.length;x++)
{
ctx.beginPath();
ctx.arc(SavedCircles[x].X, SavedCircles[x].Y, SavedCircles[x].radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
}
function mouseDown()
{
circle = new circleInfo();
}
function mouseUp()
{
SavedCircles.push(circle);
}
function mouseMove()
{
draw();
}
so you can get rid of save and restore, also its much faster to clear a canvas simply by:
canvas.width = canvas.width;
this should help you keep all circles ever drawn. fill in the rest with your code.
Related
Im making a painting tool, and one of the feature is showing cropped image of the drawn path.
The path I have drawn(image)
For example in above the picture, the white colored path indicates what I have drawn, just like a painting tool.
Cropped image
And here is the cropped image of the path. If you look at the picture, you can see that it crops the image as if the path is closed and therefore it crops the image "area" not the path.
and here is the code
function crop({ image, points }) {
return Observable.create(observer => {
const { width, height } = getImageSize(image);
const canvas = document.createElement('canvas') as HTMLCanvasElement;
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
context.beginPath();
points.forEach(([x, y], idx) => {
if (idx === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
});
context.clip();
context.drawImage(image);
...etc
}
The crop function receives points which is consisted [x coordinate, y coordinate][ ] of the drawn path.
Is there an way to show image only the path that I've painted?
That's more what is generally called a mask then, but note that both for the current clip or for the mask you want to attain, the best is to use compositing.
Canvas context has various compositing options, allowing you to generate complex compositions, from pixels's alpha value.
const ctx = canvas.getContext('2d');
const pathes = [[]];
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';
function begin() {
canvas.width = this.width;
canvas.height = this.height;
ctx.lineWidth = 10;
addEventListener('mousemove', onmousemove);
addEventListener('mousedown', onmousedown);
addEventListener('mouseup', onmouseup);
anim();
ctx.fillText("Use your mouse to draw a path", 20,50)
}
function anim() {
requestAnimationFrame(anim);
if(dirty) draw();
dirty = false;
}
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bg, 0, 0);
ctx.beginPath();
pathes.forEach(path => {
if(!path.length) return;
ctx.moveTo(path[0].x, path[0].y);
path.forEach(pt => {
ctx.lineTo(pt.x, pt.y);
});
});
// old drawings will remain on where new drawings will be
ctx.globalCompositeOperation = 'destination-in';
ctx.stroke();
// reset
ctx.globalCompositeOperation = 'source-over';
}
function onmousemove(evt) {
if(!down) return;
const rect = canvas.getBoundingClientRect();
pathes[pathes.length - 1].push({
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
});
dirty = true;
}
function onmousedown(evt) {
down = true;
}
function onmouseup(evt) {
down = false;
pathes.push([]);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>
Don't hesitate to look at all the compositing options, various cases will require different options, for instance if you need to draw multiple paths, you may prefer to render first your paths and then keep your image only where you did already drawn, using the source-atop option:
const ctx = canvas.getContext('2d');
const pathes = [[]];
pathes[0].lineWidth = (Math.random() * 20) + 0.2;
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';
function begin() {
canvas.width = this.width;
canvas.height = this.height;
addEventListener('mousemove', onmousemove);
addEventListener('mousedown', onmousedown);
addEventListener('mouseup', onmouseup);
anim();
ctx.fillText("Use your mouse to draw a path", 20,50)
}
function anim() {
requestAnimationFrame(anim);
if(dirty) draw();
dirty = false;
}
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
pathes.forEach(path => {
if(!path.length) return;
ctx.beginPath();
ctx.lineWidth = path.lineWidth;
ctx.moveTo(path[0].x, path[0].y);
path.forEach(pt => {
ctx.lineTo(pt.x, pt.y);
});
ctx.stroke();
});
// new drawings will appear on where old drawings were
ctx.globalCompositeOperation = 'source-atop';
ctx.drawImage(bg, 0, 0);
// reset
ctx.globalCompositeOperation = 'source-over';
}
function onmousemove(evt) {
if(!down) return;
const rect = canvas.getBoundingClientRect();
pathes[pathes.length - 1].push({
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
});
dirty = true;
}
function onmousedown(evt) {
down = true;
}
function onmouseup(evt) {
down = false;
const path = [];
path.lineWidth = (Math.random() * 18) + 2;
pathes.push(path);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>
And also remember that you can very well have canvases that you won't append to the document that you can use as layers to generate really complex compositions. (drawImage() does accept a <canvas> as source).
I am trying to write a code using HTML canvas that will create a line beginning where a mousemove event occurs. The line has a defined direction and should continue extending until it is off the screen. The issue I am having is that every time I move the mouse a new line begins(this is good) but the previous line stops extending. I believe that the issue is because each new line is taking on a set of parameters with the same name as the previous line, however I am not certain that this is the issue, nor do I know how to fix it.
Here is a jsfiddle of my current code: https://jsfiddle.net/tdammon/bf8xdyzL/
I start be creating an object named mouse that takes an x and y parameter. The xbeg and ybeg will be the starting coordinates for my lines.
let canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height= window.innerHeight;
let c = canvas.getContext('2d');
let mouse ={
x:undefined,
y:undefined,
}
window.addEventListener("mousemove",function(event){
mouse.x = event.x;
mouse.y = event.y;
xbeg = mouse.x;
ybeg = mouse.y;
})
Next I create an animate function that continuously calls itself. I create a new line object which will take the xbeg and ybeg parameters for beginning points and xbeg+10 and ybeg+10 as ending point. The function then increments xbeg and ybeg. I would like this function to create new lines that do not stop extending whenever the mouse is moved.
function animate() {
requestAnimationFrame(animate);
new Line(xbeg,ybeg,xbeg+10,ybeg+10)
c.beginPath();
c.moveTo(xbeg,ybeg);
c.lineTo(xbeg+10,ybeg+10);
c.stroke();
xbeg += 1;
ybeg += 1;
}
I've added to your code an array for all your lines: let linesRy = []; and I've changed a bit your draw() function by adding this.endx++; this.endy++;
also I'm using your commented out c.clearRect(0, 0, innerWidth, innerHeight);since with every frame you redraw all the lines.
I hope this is what you need.
let linesRy = [];
let canvas = document.querySelector("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let c = canvas.getContext("2d");
let mouse = {
x: undefined,
y: undefined
};
let xbeg, ybeg;
window.addEventListener("mousemove", function(event) {
mouse.x = event.x;
mouse.y = event.y;
xbeg = mouse.x;
ybeg = mouse.y;
});
class Line {
constructor(begx, begy, endx, endy, dx, dy, slope) {
this.begx = begx;
this.begy = begy;
this.endx = endx;
this.endy = endy;
this.dx = endx - begx;
this.dy = endy - begy;
this.slope = dy / dx;
}
draw() {
this.endx++;
this.endy++;
c.beginPath();
c.moveTo(this.begx, this.begy);
c.lineTo(this.endx, this.endy);
c.stroke();
}
}
//let xend = 420;
//let yend = 220;
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
linesRy.push(new Line(xbeg, ybeg, xbeg + 10, ybeg + 10, 10, 10, 1));
linesRy.forEach(l => {
l.draw();
});
}
animate();
canvas{border:1px solid;}
<canvas></canvas>
the variable c is taken local variable
function animate() {
c = canvas.getContext('2d');
requestAnimationFrame(animate);
new Line(xbeg,ybeg,xbeg+10,ybeg+10)
c.beginPath();
c.moveTo(xbeg,ybeg);
c.lineTo(xbeg+10,ybeg+10);
c.stroke();
xbeg += 1;
ybeg += 1;
}
There are two different functions are used to draw the free drawing and line drawing on a canvas. But when a function is called other function not working properly. If the line drawing function called first, and when we press the button to draw free it also draws the line. In the opposite case the continuous line get drawn.The two function are given below.
For free drawing
function free(){
var canvas=document.getElementById('canvas');
var radius=10;
var dragging1=false;
context.lineWidth=2*radius;
var putPoint=function(e) {
if(dragging1){
context.lineTo(e.clientX,e.clientY);
context.stroke();
context.beginPath();
context.arc(e.clientX,e.clientY,radius,0,Math.PI*2);
context.fill();
context.beginPath();
context.moveTo(e.clientX,e.clientY);
}//end of if
}
var engage = function(e) {
dragging1=true;
putPoint(e);
}
var disengage = function() {
dragging1=false;
context.beginPath();
}
canvas.addEventListener('mousedown',engage);
canvas.addEventListener('mousemove',putPoint);
canvas.addEventListener('mouseup',disengage);
}
For Line Drwawing
function line(){
var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {x: x, y: y};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
drawLine(position);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
drawLine(position);
}
function init() {
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
context.strokeStyle = 'purple';
context.lineWidth = 6;
context.lineCap = 'round';
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
}
init()
}
How can i solve this? Iam stuck at this point.
In this case please create two functions that removes the event listeners in the other function. That means create to function
function removeLineListeners(){
canvas.removeEventListener('mousedown', dragStart);
canvas.removeEventListener('mousemove', drag);
canvas.removeEventListener('mouseup', dragStop);
}
and
function removeFreeListeners(){
canvas.removeEventListener('mousedown',engage,false);
canvas.removeEventListener('mousemove',putPoint,false);
canvas.removeEventListener('mouseup',disengage,false);
}
So when when add removeLineListeners() to the starting of the free and
removeFreeListeners() to the strting of Line function. This worked for me
I am making canvas drawing application and I want to implement rotation of the drawing. But when I rotate it and want to keep drawing mouse cursor isn't pointing at the pixels which are being painted.
How can I fix that?
Here is my code:
<button id="rotate">rotate right</button>
var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint = false;
var context = null;
var canvas = null;
canvas = document.getElementById('drawing');
context = canvas.getContext('2d');
rotate.onclick = function() {
context.translate(400,0);
context.rotate(90*Math.PI/180);
draw();
}
canvas.onmousedown = function(e){
paint = true;
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop,false);
draw();
}
canvas.onmousemove = function(e){
if(paint){
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
draw();
}
canvas.onmouseup = function(){
paint = false;
}
function addClick(x, y, dragging)
{
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
function redraw(){
context.clearRect(0, 0, 400, 400);
context.lineJoin = "round";
// usunieto context.lineWidth = 5;
for(var i=0; i < clickX.length; i++)
{
context.beginPath();
if(clickDrag[i]){
context.moveTo(clickX[i-1], clickY[i-1]);
}else{
context.moveTo(clickX[i], clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
}
}
Applying an transformation to the canvas remains as the canvas state. You need to save the canvas state, apply the transformation, draw, then restore the canvas state. The 2D context API provides ctx.save() and ctx.restore() to do this for you. It uses a stack to save the state so that means saves can be nested but each save must be removed with a matching restore;
ctx.save(); // push the current state onto the save stack
ctx.rotate(Math.PI/2); // rotate 90deg clockwise
// call your draw function
ctx.restore(); // pop the last saved state from the save stack
That should fix you problem.
I wrote some code that draws a semi-transparent rectangle over an image that you can draw with touch:
function drawRect() {
var canvas = document.getElementById('receipt');
var ctx = canvas.getContext('2d');
var drag = false;
var imageObj;
var rect = { };
var touch;
canvas.width = WIDTH;
canvas.height = HEIGHT;
function init() {
imageObj = new Image();
imageObj.src = 'img.jpg';
imageObj.onload = function() {
ctx.drawImage(imageObj, 0, 0);
};
canvas.addEventListener('touchstart', handleTouch, false);
canvas.addEventListener('touchmove', handleTouch, false);
canvas.addEventListener('touchleave', handleEnd, false);
canvas.addEventListener('touchend', handleEnd, false);
}
function handleTouch(event) {
if (event.targetTouches.length === 1) {
touch = event.targetTouches[0];
if (event.type == 'touchmove') {
if (drag) {
rect.w = touch.pageX - rect.startX;
rect.h = touch.pageY - rect.startY ;
draw();
}
} else {
rect.startX = touch.pageX;
rect.startY = touch.pageY;
drag = true;
}
}
}
function handleEnd(event) {
drag = false;
}
function draw() {
drawImageOnCanvas();
ctx.fillStyle = 'rgba(0, 100, 255, 0.2)';
ctx.fillRect(rect.startX, rect.startY, rect.w, rect.h);
}
function drawImageOnCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0);
}
init();
}
This works. But, now I'd like to have it so that I can capture each of the parts of the image that are in rectangles as separate images.
How can I pull this off? Because I have that redraw stuff, it deletes the previous rectangle, which makes this difficult.
A canvas can only save out the whole canvas as an image, so the trick is to create a temporary canvas the size of the region you want to save out.
One way could be to create a function which takes the image and a rectangle object as argument and returns a data-uri (or Blob) of the region:
function saveRegion(img, rect) {
var canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d");
canvas.width = rect.w;
canvas.height = rect.h;
ctx.drawImage(img, rect.startX, rect.startY, rect.w, rect.h, 0, 0, rect.w, rect.h);
return canvas.toDataURL():
}
You can pass in the original image if don't want any graphics on top, or if you draw elements on top of it just pass in the original canvas element as image source. And of course, CORS-restrictions apply.