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/
Related
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
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>
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'm trying to scale an image that has already been draw into canvas.
This is the code:
var canvas = document.getElementById('splash-container');
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
// draw image at its original size
context.drawImage(imageObj, 0, 0);
};
imageObj.src = 'images/mine.jpeg';
// Now let's scale the image.
// something like...
imageObj.scale(0.3, 0.3);
How should I do?
You're thinking about it wrong. Once you've drawn the image onto the canvas it has no relationship to the imageObj object. Nothing you do to imageObj will affect what's already drawn. If you want to scale the image, do in the drawImage function:
drawImage(imgObj, 0, 0, imgObj.width * 0.3, imgObj.height * 0.3)
If you want to animate the scaling or are looking to achieve some other effect which requires you to draw the image at full size initially you'll have to first clear it before drawing the scaled down image.
What robertc says is correct, but if you really wanted to scale an image on a canvas after drawing it for some reason, you could just scale the whole canvas using the CSS width/height properties and that would scale the image without having to redraw it.