Fade out effect for line in HTML5 canvas with background image - javascript

I'm currently working on an HTML canvas example that fades out a line after it is drawn on a canvas, though my question is: how do I attain this while using a background image?
Live Demo (current code in a link at the bottom of this post)
CSS:
canvas {
background-image: url("https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2018/02/PluralsightandSO.jpg");
background-size: 100% 100%;
}
JavaScript:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0;
canvas.width = canvas.height = 600;
canvas.onmousedown = function (e) {
if (!painting) {
painting = true;
} else {
painting = false;
}
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmousemove = function (e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(mouseX, mouseY);
ctx.stroke();
lastX = mouseX;
lastY = mouseY;
}
}
function fadeOut() {
ctx.fillStyle = "rgba(255,255,255,0.3)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
setTimeout(fadeOut,100);
}
fadeOut();
I realize fillRect is filling the entire canvas with the fillStyle so I assumed I could add the image to the canvas instead, but as you can tell, the animation is not the same by using this code instead:
var image = new Image();
image.src = "https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2018/02/PluralsightandSO.jpg"
ctx.drawImage(image, 0, 0);
Current Live Demo (after changes described above) - it's too rigid and doesn't have as long of a tail
Any thoughts?

You're drawing a 30% transparent square to get the 'fading' effect:
ctx.fillStyle = "rgba(255,255,255,0.3)";
So you need to draw the PNG transparently as well
canvas.onmousemove = function (e) {
if (painting) {
// set line alpha to 1
ctx.globalAlpha = 1.0;
// your paint code here
}
}
function fadeOut() {
var image = new Image();
// set image alpha to 0.3
ctx.globalAlpha = 0.3;
// your draw image code here
}
By the way, you don't need to create the new image every time!
Example

You need to specify width and height:
ctx.drawImage(image, 0, 0, 600, 600);

Related

I'm making a drawing based web-app and I'm using HTML Canvas to handle the drawing, however the drawing is offset to the right a lot?

const map = document.getElementById('map')
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const grid = document.getElementById('grid')
function resize() {
canvas.width = map.offsetWidth
canvas.height = map.offsetHeight
ctx.width = map.offsetWidth
ctx.height = map.offsetHeight
}
resize();
grid.appendChild(canvas)
canvas.style.gridColumn = 2
canvas.style.gridRow = 1
let pos = { x: 0, y: 0 };
window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);
function setPosition(e) {
pos.x = e.clientX;
pos.y = e.clientY;
}
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = '#c0392b';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
Heres the code that generates the canvas relative to the size of a picture and allows the user to draw on the canvas. I've looked over another StackOverflow post with the same problem but no relevant answers. I know that the cause of the problem is that the canvas is stretched from it's standard proportion of 300 x 150 and is drawing at the correct position mathematically but not physically. How do I fix this?

How to crop canvas image by path, not area

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).

Drawing on Canvas with image

I have a video element playing a mp4 video.
When clicking the screenshot button, it draws the current video frame on the canvas. I would then like to draw on top of the canvas.
My problem is that I can't get the cursor to align with the drawing coordinates. I have a suspicion that this is because of the way I'm setting the height and the width of the canvas to be able to draw the video frame correctly.
Minimal repro:
https://codepen.io/stiba/pen/KEBRdy
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var video = document.getElementById('vid');
canvas.addEventListener('mousedown', mouseDown);
canvas.addEventListener('mouseup', mouseUp);
canvas.addEventListener('mousemove', mouseMove);
var isDrawing = false;
var prevX = 0;
var prevY = 0;
var currX = 0;
var currY = 0;
function buttonClick() {
console.log('Clicked!');
var height = video.videoHeight;
var width = video.videoWidth;
canvas.width = width;
canvas.height = height;
ctx.fillRect(0, 0, width, height);
ctx.drawImage(video, 0, 0, width, height);
}
function setCoordinates(e) {
prevX = currX;
prevY = currY;
var boundingClientRect = e.target.getBoundingClientRect();
var left = boundingClientRect.left;
var top = boundingClientRect.top;
currX = e.clientX - left;
currY = e.clientY - top;
}
function mouseDown(e) {
setCoordinates(e);
isDrawing = true;
}
function mouseUp(e) {
isDrawing = false;
}
function mouseMove(e) {
if (!isDrawing) {
return;
}
setCoordinates(e);
draw();
}
function draw() {
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.stroke();
ctx.closePath();
}
Removing the css styles from your canvas will fix the issue. If you need your canvas to be larger use canvas.width and canvas.height properties. Using css to control your canvas size will only increase the html element size but not the drawing surface(pixels).
css to be removed:
flex: 1;
width: 100%;

How to rotate canvas in the right way

I have an image which I want to crop.
Thus, I created canvas as overlay with a darker background and then on mouse move I draw an rect on that canvas and in that rect I clear the dark background. That looks as follows:
Thus the area without background will be cropped.
My code is as follows:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
var deg = 0;
var image_rotate_angle = 90;
document.getElementById('right').addEventListener("click", rotateRight, false);
document.getElementById('left').addEventListener("click", rotateLeft, false);
function rotateRight(){
deg = deg + image_rotate_angle;
img.style.transform = "rotate(" + deg + "deg)";
canvas.style.transform = "rotate(" + deg + "deg)";
}
function rotateLeft(){
deg = deg - image_rotate_angle;
img.style.transform = "rotate(" + deg + "deg)";
canvas.style.transform = "rotate(" + deg + "deg)";
}
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.clearRect(rect.startX, rect.startY, rect.w, rect.h);
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1
};
drag = true;
}
function mouseUp() { drag = false; update = true; }
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="margin-bottom: 15px;">
<button id="left">Rotate Reft</button>
<button id="right">Rotate Right</button>
</div>
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
I have also two buttons which I use to rotate the image. And there I have the problem. The solution is maybe obvious but I do not see it.
If I draw a the rectangle and then rotate the image, I rotate the image and the canvas also, and then rectangle stays at the same place after rotating. That is what I want and that works fine.
The problem is if I first rotate the image and then try to draw a rectangle. Then the rectangle isn't drawn as it should be, it goes in the opposite direction.
Here is the fiddle.
Any idea how to solve it?
You could probably just get the maths to find where the new x and y will lay, based on your image's size, but I guess it will be more manageable to actually draw everything on your canvas, and rotate its transformation matrix.
But this implies some changes in your code:
You would have to draw the image with compositing (or with 'even-odd' fill-rule, like I already shown you).
You would also have to make your drawCanvas function a bit more complicated in order to rotate your hole only when needed (when the user clicked the "rotate" buttons).
var canvas = document.getElementById('canvas');
var img = new Image();
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var angle = 0;
document.getElementById('right').addEventListener("click", rotateRight, false);
document.getElementById('left').addEventListener("click", rotateLeft, false);
function rotateRight() {
angle += Math.PI / 2;
drawCanvas(true); // let know we're from rotate function
}
function rotateLeft() {
angle -= Math.PI / 2;
drawCanvas(true);
}
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas(false); // don't rotate the rectangle here
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect() {
ctx.clearRect(rect.startX, rect.startY, rect.w, rect.h);
}
// clears canvas sets filters and draws rectangles
function drawCanvas(fromRotate) {
// reset the transformation matrix to its defaults
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// for the rectangle, rotate only if we are using previously set rect
if (fromRotate) {
// Move to center
ctx.setTransform(1, 0, 0, 1, canvas.width / 2, canvas.height / 2);
// rotate by the current angle
ctx.rotate(angle);
// move back to what is now top right corner
ctx.translate(-canvas.width / 2, -canvas.height / 2);
}
drawRect();
// draw the image behind the overlay
ctx.globalCompositeOperation = 'destination-over';
// rotate the whole context every time for the image
ctx.setTransform(1, 0, 0, 1, canvas.width / 2, canvas.height / 2);
// rotate by the current angle
ctx.rotate(angle);
// draw the image, from center
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX: e.offsetX,
startY: e.offsetY,
w: 1,
h: 1
};
drag = true;
}
function mouseUp() {
drag = false;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.offsetX - this.offsetLeft) - rect.startX;
rect.h = (e.offsetY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
img.src = 'https://www.html5canvastutorials.com/demos/assets/darth-vader.jpg';
<div style="margin-bottom: 15px;">
<button id="left">Rotate Reft</button>
<button id="right">Rotate Right</button>
</div>
<div style="position: relative; overflow: hidden;display:inline-block;">
<canvas id="canvas"></canvas>
</div>

How can I capture multiple images out of one in the <canvas>?

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.

Categories