i'm looking for a way to erase drawing lines in canvas without erasing my background image.
when i tried using white color i get white line on the image.
i thought about making transparent line but i don't think that's possible.
on saving the canvas with toDataURL() i want to save the drawing with the background.
this is how i set background image:
var background = new Image();
background.src = "pizza.png";
background.onload = function(){
context.drawImage(background,0,0);
}
You can "erase" with Compositing, but it's not recommended for lines
You can use compositing to "erase" a previously drawn line by redrawing it with globalCompositeOperation='destination-out'. This causes the previous line to become transparent pixels. Then you can reapply the background image over just the transparent pixels using globalCompositeOperation='destination-over'.
But there's a problem with lines. The problem with "erasing" lines is that canvas will automatically apply anti-aliasing to every line. That added anti-aliasing is difficult to remove.
The better (more typical) way of erasing canvas lines drawn over a background is to redraw everything:
Save all line definitions in an array (eg lines[]),
To remove line(s), remove their definitions from the lines[] array,
Erase the whole canvas,
Redraw the background,
Redraw all lines still in the lines[] array.
Here's annotated code and a demo:
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// save line definitions
var lines=[
{x0:75,y0:50,x1:200,y1:100,color:'red'},
{x0:75,y0:75,x1:200,y1:100,color:'green'},
{x0:75,y0:100,x1:200,y1:100,color:'blue'},
{x0:65,y0:50,x1:65,y1:200,color:'green'},
]
// load the background
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/bk.png";
function start(){
// draw the first time
draw();
// remove green lines on button click
$('#go').click(function(){ remove('green'); });
}
function draw(){
// clear canvas
ctx.clearRect(0,0,cw,ch);
// redraw background
ctx.drawImage(img,0,0);
// redraw all lines still in lines[]
ctx.lineWidth=3;
for(var i=0;i<lines.length;i++){
var l=lines[i];
ctx.beginPath();
ctx.moveTo(l.x0,l.y0);
ctx.lineTo(l.x1,l.y1);
ctx.strokeStyle=l.color;
ctx.stroke();
}
}
function remove(color){
for(var i=lines.length-1;i>=0;i--){
if(lines[i].color==color){lines.splice(i,1);}
}
draw();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=go>Remove green lines</button>
<br>
<canvas id="canvas" width=300 height=300></canvas>
Related
Still very much a newbie to coding, so please be gentle :)
I'm hoping someone might be able to help how to use Paper.js on a second canvas after the first one has been executed?
I'm trying to use x2 canvas elements:
Canvas 1 - to capture a html5 video image still and convert to base64 (tick :-) = done)
Canvas 2 - Use the base64 image and perform the 'Working with Rasters to find the colors of pixels' and convert to circle paths (boo = fail :-( )
Something like this:
The code:
<script src="https://cdn.jsdelivr.net/npm/hls.js#latest"></script>
<video id="video" preload="auto" muted="" playsinline="" width="580" src="blob:https://www.georgefisher.co.uk/78e3a45c-ae07-4ea5-af56-45a5ed9cf1b0"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://camsecure.co/HLS/georgefisher.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
video.play()
</script>
<br>
<button onclick="capture()">Capture</button>
<br>
<canvas id="canvas" style="overflow:auto">
</canvas>
<canvas id="canvas2" resize>
<img src="" id="myImg"/></canvas>
var resultb64="";
function capture() {
var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
canvas.width = video.videoWidth/4;
canvas.height = video.videoHeight/4;
canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth/4, video.videoHeight/4);
resultb64=canvas.toDataURL();
document.querySelector("#myImg").src = canvas.toDataURL();
}
/*Paper JS Setup for working in CodePen */
/* ====================== *
* 0. Initiate Canvas *
* ====================== */
// expose paperjs classes into global scope
paper.install(window);
// Only executed our code once the DOM is ready.
window.onload = function() {
// bind paper to the canvas
paper.setup('canvas2');
// paper.activate();
// Get a reference to the canvas object
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
// console.log(ctx, image);
// ctx.drawImage(image, 0, 0);
// return;
// }
// Create a raster item using the image id='' tag
var image = document.querySelector('img');
var raster = new Raster(image);
// Hide the raster:
raster.visible = false;
// The size of our grid cells:
var gridSize = 15;
// Space the cells by 120%:
var spacing = 1
;
// As the web is asynchronous, we need to wait for the raster to load before we can perform any operation on its pixels.
raster.onLoad = function() {
// Since the example image we're using is much too large, and therefore has way too many pixels, lets downsize it to 40 pixels wide and 30 pixels high:
raster.size = new Size(40, 30);
for (var y = 0; y < raster.height; y++) {
for(var x = 0; x < raster.width; x++) {
// Get the color of the pixel:
var color = raster.getPixel(x, y);
// Create a circle shaped path:
var path = new Path.Circle({
center: new Point(x, y).multiply(gridSize),
radius: gridSize / 2 / spacing,
});
// Set the fill color of the path to the color
// of the pixel:
path.fillColor = color;
}
}
// Move the active layer to the center of the view, so all the created paths in it appear centered.
project.activeLayer.position = view.center;
}
}
I've tried giving the second canvas a different Id="canvas2" and referencing that, which I can see in the console. However, nothing appears in the second canvas and the paper.js script doesn't seem to execute, can someone help me understand why?
Please see also see link to the fiddle below:
https://jsfiddle.net/jmnes/o4Lpkfs6/1/
Alternatives method.
You don't need to capture the video, you don't need to capture the pixels using paper.js and raster. You don't need to find the color of each circle and draw it.
All these methods are slow, complex, and power hungry.
You can create a mask and mask out the circles, with the colors drawn from a smaller canvas with a res that matches the number off circles.
How to
Add one (main canvas) canvas to the DOM. This will display the result
Create 2 offscreen canvas.
One (color canvas) has the same resolution as the circles you want to display. Eg if you have 30 by 40 circle the canvas res should be 30 by 40
One (mask canvas) is the circle mask. It is the same resolution as the main canvas. Draw the circles all in one color on this canvas.
Then rendering once a frame
Draw the video on the color canvas to fit.
Turn off smoothing on the main canvas eg ctxMain.imageSmoothingEnabled = false
Draw the color canvas onto the main canvas to fit.
This will draw a color square at each circle position. ctx.drawImage(colorCanvas, 0, 0, mainCanvas.width, mainCanvas.height)
Set composite operation "destination-in" eg ctxMain.globalCompositeOperation = "destination-in"
Draw the mask canvas (canvas with circles on it) onto the main canvas. This will remove pixels outside each circle.
Restore default composite operation for the main canvas ctxMain.globalCompositeOperation = "source-over"
All done for a real-time FX on almost any device.
The above methods is the fastest way to render the effect you are after using the 2D API
How do I put the canvas on top of my website, with position: absolute, so that my animation happens on top of my regular website. Now when I animate, the canvas background becomes white so I can't see my website. BUT I want to see both my website and the animation that is lying on top of it.?
My end goal is to have tiny circles follow my fingers when I touch the screen on a mobile phone, but before I can achieve that, I have to know that I can animate on top of other elements first. At least, that is what I think at the moment.
Please help :)
Use ctx.clearRect() for your animation. I also added background-color:transparent just in case, but it works without it.
var ctx = canvas.getContext('2d');
canvas.height = innerHeight;
canvas.width = innerWidth;
function rad(deg){
return deg*Math.PI/180;
}
var t = 360;
function loop(){
requestAnimationFrame(loop);
ctx.clearRect(0,0,innerHeight,innerWidth);
ctx.beginPath();
ctx.arc(100+50*Math.sin(rad(t)),100+50*Math.cos(rad(t)),40+20*Math.sin(rad(t)),0,Math.PI*2);
ctx.fillStyle='rgba(255,255,20,0.8)';
ctx.strokeStyle='red';
ctx.lineWidth = 5;
ctx.fill();
ctx.stroke();
t=t>0?t-5:360;
}
loop();
canvas{
position:absolute;
background-color:transparent;
}
<canvas id=canvas></canvas>
<h1>
Your website.
</h1>
<h2>
Words words words.
</h2>
words words words words
Is it possible to move an image behind a mask without moving the mask itself ? I am looking for an operation which allow to move an image behind a mask and it should be accurate and smooth.
The best answer I am looking is to mask an Kinetic.Image object. Kinetic.Image is draggable and need to worry about it's movement. Please let me know if it's really possible to mask Kinetic.Image Object ?
A Demo: http://jsfiddle.net/m1erickson/u28MS/
Use a Kinetic.Shape to get access to the canvas context and then create a clipping region.
Create a new Kinetic.Shape
Define your non-rectangular path in the shape
Call clip() to restrict drawing to that path.
Draw the image into the clipping region.
Give the image x & y properties so that the image can be draw
Here's what that looks like in code:
// create a Kinetic.Shape which gives you access
// to a context to draw on
clippingShape = new Kinetic.Shape({
sceneFunc: function(context) {
// define your path here
// context.beginPath(); ...
// make your path a clipping region
context.clip();
// draw the image inside the clipping region
// img.x & img.y are offsets which can be used
// to "drag" the image around the clipping region
context.drawImage(img,img.x,img.y);
// KineticJS specific context method
context.fillStrokeShape(this);
},
stroke: 'black',
strokeWidth: 4,
listening:false
});
Listen for mouse events on the stage to cause the image to reposition when drawn in the Shape.
In mousedown: Save the mouse position and set a flag indicating the drag has begun.
In mousemove: Calc how much the mouse has moved and offset the image's x/y by that distance.
In mouseup: clear the dragging flag since the drag is over.
The mouse event handlers look like this:
var isdown=false;
stage.getContent().onmousedown=function(e){
var pos=stage.getPointerPosition();
img.lastX=parseInt(pos.x);
img.lastY=parseInt(pos.y);
isdown=true;
};
stage.getContent().onmouseup=function(e){
isdown=false;
};
stage.getContent().onmousemove=function(e){
if(!isdown){return;}
var pos=stage.getPointerPosition();
var mouseX=parseInt(pos.x);
var mouseY=parseInt(pos.y);
var dx=mouseX-img.lastX;
var dy=mouseY-img.lastY;
img.lastX=mouseX;
img.lastY=mouseY;
img.x+=dx;
img.y+=dy;
layer.draw();
};
[ Previous version of answer -- replaced with new answer above after questioners commments ]
This kind of clipping is traditionally done with a foreground image that contains a transparent "viewport" which lets the user see a portion of the background image beneath.
A Demo: http://jsfiddle.net/m1erickson/2f9yu/
Create a draggable background image on a bottom layer:
// create a background layer
var bottomLayer=new Kinetic.Layer();
stage.add(bottomLayer);
// put a draggable image on the background layer
var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
bottomLayer.add(city);
bottomLayer.draw();
Create a non-draggable foreground image on a top layer.
The top image has a transparent "viewport".
Important: the top layer does not listen for events, so dragging moves the bottom image, not the top image.
// create a top layer that does not respond to mouse events
// any mouse events will filter down to the background image
// this enables the background to be dragged even while behind the top image
var topLayer=new Kinetic.Layer({listening:false,});
stage.add(topLayer);
// create a top image with transparent pixels
// used as a viewport to see a portion of the bottom image
var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
topLayer.add(mirror);
topLayer.draw();
Example code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
var bottomLayer=new Kinetic.Layer();
stage.add(bottomLayer);
var topLayer=new Kinetic.Layer({listening:false,});
stage.add(topLayer);
var loadedCount=0;
//
var bk=new Image();
bk.onload=start;
bk.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/desert1.jpg";
//
var viewport=new Image();
viewport.onload=start;
viewport.src="https://dl.dropboxusercontent.com/u/139992952/multple/car4.png";
function start(){
if(++loadedCount<2){return;}
var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
bottomLayer.add(city);
bottomLayer.draw();
var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
topLayer.add(mirror);
topLayer.draw();
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag to move the background image in the mirror</h4>
<div id="container"></div>
</body>
</html>
How can I draw a canvas rectangle with fill color AND 4 different border colors on each side?
You can add a new method to canvas.context that draws your multi-colored rectangle.
You define a new method on the canvas.context through its prototype:
CanvasRenderingContext2D.prototype.myNewMethod = function(){ ... };
Inside the new method, you can use any context drawing commands to draw your desired shape.
Note that inside myNewMethod, “this” refers to the canvas.context, so you draw like this:
this.lineTo(x,y) // not context.lineTo
Your fancy rectangle is a fairly straightforward drawing, except for mitered side strokes.
Each side stroke is drawn as a filled trapezoid:
function trapezoid(context,color,x1,y1,x2,y2,x3,y3,x4,y4){
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x4,y4);
context.closePath();
context.fillStyle=color;
context.fill();
}
Your fancy new rectangle method (rainbowRect) is called just like context.fillRect.
context.rainbowRect(100,50,100,50,"gold","red","blue","green","purple");
Here is a complete example:
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// Add a rainbowRect function to the context prototype
// This method is used alone like context.fillRect
// This method is not used within a context.beginPath
// NOTE: this addition must always be run before it is used in code
CanvasRenderingContext2D.prototype.rainbowRect = function (x,y,w,h,fillColor,tColor,rColor,bColor,lColor){
// use existing fillStyle if fillStyle is not supplied
fillColor=fillColor||this.fillStyle;
// use existing strokeStyle if any strokeStyle is not supplied
var ss=this.strokeStyle;
tColor=tColor||ss;
rColor=rColor||ss;
bColor=bColor||ss;
lColor=lColor||ss;
// context will be modified, so save it
this.save();
// miter the lines
this.lineJoin="miter";
// helper function: draws one side's trapezoidal "stroke"
function trapezoid(context,color,x1,y1,x2,y2,x3,y3,x4,y4){
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x4,y4);
context.closePath();
context.fillStyle=color;
context.fill();
}
// context lines are always drawn half-in/half-out
// so context.lineWidth/2 is used a lot
var lw=this.lineWidth/2;
// shortcut vars for boundaries
var L=x-lw;
var R=x+lw;
var T=y-lw;
var B=y+lw;
// top
trapezoid(this,tColor, L,T, R+w,T, L+w,B, R,B );
// right
trapezoid(this,rColor, R+w,T, R+w,B+h, L+w,T+h, L+w,B );
// bottom
trapezoid(this,bColor, R+w,B+h, L,B+h, R,T+h, L+w,T+h );
// left
trapezoid(this,lColor, L,B+h, L,T, R,B, R,T+h );
// fill
this.fillStyle=fillColor;
this.fillRect(x,y,w,h);
// be kind -- always rewind (old vhs reference!)
this.restore();
// don't let this path leak
this.beginPath();
// chain
return(this);
};
// testing
ctx.lineWidth=20;
ctx.rainbowRect(100,50,100,50,"gold","red","blue","green","purple");
}); // end $(function(){});
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width=300 height=150></canvas>
Unfortunately, you can't draw borders of a rectangle just like you do with css.
You can use stroke() method, but it draws "borders" just with one color for every side.
So, I guess, you can draw borders manually by drawing lines near a rectangle.
I am trying to draw circles on a canvas filled with portions from an image. Imagine clicking on a white canvas and where the user clicked reveal a portion of a photo.
I have found ways to draw 1 circle, but can not succeed using this to draw multiples. If I repeat the action with other coordinates the drawing is not happening.
function start_drawing(){
ctx.beginPath();
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0,0,canvas.width,canvas.height);//fill the background. color is default black
ctx.arc(mouse.x,mouse.y,45,0,6.28,false);//draw the circle
ctx.arc(mouse.x+100,mouse.y+100,45,0,6.28,false);
ctx.clip();//call the clip method so the next render is clipped in last path
ctx.drawImage(img,0,0);
ctx.closePath();
}
Any idea on how this can be achieved ?
Thank you.
Later Edit (The entire exact code used)
<!DOCTYPE HTML>
<html>
<head>
<script>
window.onload=function(){
var canvas = document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
var mouse={x:0,y:0} //make an object to hold mouse position
canvas.onmousemove=function(e){mouse={x:e.pageX-this.offsetLeft,y:e.pageY-this.offsetTop};} //update the mouse when the canvas is moved over
var img=new Image();
img.src="bmw_gina.jpg";
setInterval( start_drawing ,100);// set the animation into motion
ctx.beginPath();
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0,0,canvas.width,canvas.height);//fill the background. color is default black
ctx.closePath();
function start_drawing(){
//ctx.save();
ctx.beginPath();
ctx.arc(mouse.x,mouse.y,45,0,6.28,false);//draw the circle
ctx.clip();//call the clip method so the next render is clipped in last path
ctx.drawImage(img,0,0);
ctx.closePath();
//ctx.restore();
}
}
</script>
</head>
<body>
<canvas id="myCanvas" width="1003" height="914"></canvas>
</body>
</html>
There are two issues I can see with yor code:
The first is that start_drawing is clearing the canvas every execution. So for each mouse click (I assume that start_drawing is called on mouse click) the circle is drawn but the canvas is cleared before that.
the other is that You need to call BeginPath and closePath for each clipping region you want to create. so your code should look something like that:
function start_drawing(){
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0,0,canvas.width,canvas.height);//fill the background. color is default black
ctx.beginPath();
ctx.arc(mouse.x,mouse.y,45,0,6.28,false);//draw the circle
ctx.clip();//call the clip method so the next render is clipped in last path
ctx.closePath();
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.arc(mouse.x+100,mouse.y+100,45,0,6.28,false);
ctx.clip();//call the clip method so the next render is clipped in last path
ctx.closePath();
ctx.drawImage(img2,0,0);
}
Update
Well apparently, the trick to reset the clipping region is to reset the canvas. This can be achieved by re setting it's width.
There you go: http://jsfiddle.net/qCg9N/5/