The following creates an unexpected clip path. Is it because of the
movement from the end of one arc to the start of the next? The circles turned out to be ellipses, which was also unexpected.
var canvas3=document.getElementById("drawing3");
var ct=canvas3.getContext("2d");
ct.beginPath();
ct.arc(50,80,30,0,Math.PI*2,false);
ct.arc(120,40,60,0,Math.PI*2,true);
ct.arc(180,40,30,0,Math.PI*2,true);
ct.arc(240,40,30,0,Math.PI*2,true);
ct.clip();
ct.fillStyle="yellow";
ct.fillRect(10,10,400,200);
ct.fillStyle="magenta";
ct.font = "40px sans-serif";
ct.fillText("what's this?",30,70);
By the way, does a clip area always have to start with beginPath()?
Why ellipses instead of arcs
Although you don't include your CSS, canvas drawings become deformed when the canvas element is resized with CSS. The default size of html5 canvas is 300x150 so adjusting its size with CSS will stretch or squish subsequent drawings unless the CSS resizes the canvas exactly in proportion to its original 2:1 proporitions.
So, if you want to resize the canvas do the resizing by changing the element's size directly:
var canvas3=document.getElementById("drawing3");
canvas3.width=400;
canvas3.height=300;
Why beginPath is useful (essential!)
A context.clip always acts upon the last set of path commands and a set of path commands should be started with context.beginPath. So yes, a clip should always start with context.beginPath.
Why is beginPath useful (always use it!): If you issue multiple sets of path commands without starting each set with beginPath, then each new set will also execute all previous sets. Your path will just become one long drawing rather than having distinct drawings.
Fixing the inter-connected circles
context.arc is a canvas path drawing command.
Path commands will always connect (with a line) all drawings that occur between context.beginPath and context.stroke or context.fill.
But you can command the path to "pick up the pencil and move it to the next circle center" using context.moveTo. This will prevent your circles from becoming inter-connected.
// pick up the pencil and move it to the next arc's centerpoint
ct.moveTo(x,y);
// and draw the arc around the centerpoint
ct.arc(x,y,r,0,Math.PI*2,false);
Here's your code refactored to "pick up the pencil" between arcs:
var canvas3=document.getElementById("drawing3");
var ct=canvas3.getContext("2d");
ct.beginPath();
arcAtXY(50,80,30);
arcAtXY(120,40,60);
arcAtXY(180,40,30);
arcAtXY(240,40,30);
ct.clip();
ct.fillStyle="yellow";
ct.fillRect(10,10,400,200);
ct.fillStyle="magenta";
ct.font = "40px sans-serif";
ct.fillText("what's this?",30,70);
function arcAtXY(x,y,r){
ct.moveTo(x,y);
ct.arc(x,y,r,0,Math.PI*2,false);
}
canvas{border:1px solid red;}
<canvas id="drawing3" width=300 height=300></canvas>
An additional thought not directly related to your design
If you want to stroke the circles instead of filling them, you would move to the next circle's perimeter rather than its centerpoint. Moving to the centerpoint would cause an automatic line to draw from the centerpoint to the perimeter.
function arcAtXY(x,y,r){
ct.moveTo(x+r,y);
ct.arc(x,y,r,0,Math.PI*2,false);
}
Related
I am working on a real time canvas drawing webapp using socket.io, node.js, and p5.js. I am having trouble creating a smooth line when the mouse is dragged. If the mouse is dragged too fast there is a trail of empty space in between each ellipse. The end goal here is to create a smooth path. Here are the things I have tried so far:
Attempt 1:
noStroke();
fill(lineColor[0],lineColor[1],lineColor[2]);
ellipse(mouseX, mouseY, lineThickness, lineThickness);
Attempt 2:
strokeWeight(lineThickness);
line(mouseX,mouseY);
stroke(lineColor[0],lineColor[1],lineColor[2]);
Here is a picture of what the issue looks like:
canvas drawing incomplete path image
any feedback is welcome; thanks!
Kevin's answer is great because it will be more efficient to draw lines instead of many ellipses. You should also look into:
beginShape()/endShape()
bezierVertex()
curveVertex()
curvePoint()
The above should help you draw a smooth path and setting a thicker stroke will looks as it many filled ellipses are connected forming the path.
If for some reason you do want to draw many ellipses, you can interpolate position when the mouse move faster and create gaps to fill those gaps.
For more information and p5.js example, check out this answer:
Processing: Draw vector instead of pixels
You could use the pmouseX and pmouseY variables, which hold the previous position of the cursor. Use that to draw a line from the previous position to the current position to fill in the blank space between mouse events.
From the reference:
// Move the mouse across the canvas to leave a trail
function setup() {
//slow down the frameRate to make it more visible
frameRate(10);
}
function draw() {
background(244, 248, 252);
line(mouseX, mouseY, pmouseX, pmouseY);
print(pmouseX + " -> " + mouseX);
}
<script src="https://github.com/processing/p5.js/releases/download/0.5.11/p5.js"></script>
I have a canvas <canvas></canvas> that displays a graphic of an unclean window. I need to be able to 'clean' the window with my cursor, displaying another image behind it. Just as this website uses their brushes: http://mudcu.be/sketchpad/ but rather than adding to the page, I need to remove content to display the image behind it.
Here's an example of the before and after 'rubbing out':
http://www.titaniumwebdesigns.com/images/forums/before.png http://www.titaniumwebdesigns.com/images/forums/after.png
See this complete DEMO
globalCompositeOperation is one of the most nice features in canvas api.
To achieve the desired effect, I use multiple canvas layers and globalCompositeOperation.
In this solution we have 3 layers:
Layer 1 - bottom Layer 2 - middle
Layer 3 - top
Middle and Top layers are static and only the middle layer is dynamically cleared using globalCompositeOperation.
middleCtx.globalCompositeOperation = "xor";
With globalCompositeOperation = "xor", the brush is drawn over the layer and clears the portion of canvas where it was drawn.
The final result is:
UPDATE:
To verify if the window is fully cleaned I create a hidden canvas with the same size of the others layers and drawn a black rectangle on it. When dragging the mouse over the canvas the layer 2 (middle) is cleared with a circle with transparent gradient color and now we also draw over the hidden canvas a circle with white color (might be any color different of black).
So on, we just verify the pixels of the hidden canvas and if there is no black pixels, the window is cleaned.
To get the image data we need to use something like:
imageData = context.getImageData(x, y, width, height)
and then get the pixels from the image data:
pixels = imageData.data;
The requestAnimationFrame routine is used for performance reason, because getImageData might be slow. The major change in the code is put the brush action inside an animation frame when dragging the mouse instead of do that action in each mouse move event. This allows the processor to have more time to do the pixel data verification.
Here is the modified fiddle with pixel data verification and an alert when the window is cleaned:
jsFiddle link
If you have a canvas where you have drawn a blurred image into it once, you should be able to create that effect by creating a "brush" image (an image containing a semi-transparent circle, with soft edges), and then draw that image in the canvas at the mouse coordinate using:
canvasContext.globalCompositeOperation = "destination-out";
https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
As soon as you have drawn the blurred image to the canvas, you just need to call the line above once and all drawn images after will use the specified composite operation.
I am trying to achieve grass like bend on a straight line created by using Cubic bezier curve.I have created a straight line using Bezier curve and now i want to bend it from top.But the problem I am facing is I am not able to do it dynamically.The moment I use mousemove for creating the bend,series of curves appear giving the canvas a paint like look which is somthing I dont want.I just want a single curved line.My question is how do I clear the previous lines that have been drawn and restore the latest bended curve??
My CODE:here is my code if you want to have a look at it
Create two canvases stacked on top of each other:
The first one containing the static content, the other only the dynamic content.
This way you will only need to be concerned about clearing the area the grass was drawn in (and this is much faster too as there is no need to redraw static all the time).
Place the two canvases inside a div which have position set to relative, then place the canvases using position set to absolute.
Now you can make a function to draw the static content (you will need to redraw it if the browser window gets resized etc.).
Record the area the grass is drawn within and clear only this (for each grass) in the next frame.
If this last is too complicated, just use a simple clear on the "dynamic" canvas:
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
This will also preserve transparency of this canvas, or "layer" if you like.
Use requestAnimationFrame to do the actual animation.
var isPlaying = true;
function animate() {
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
drawGrass(); //function to draw the actual grass
if (isPlaying) requestAnimationFrame(animate);
}
animate(); // start
The isPlaying is a flag you can toggle with a button on screen or something similar, to be able to stop the animation. See the link for requestAnimationFrame on how to make this cross-browser as this is still a "young" implementation.
dynCtx and dynCanvas can of course be called what you want.
You need to erase the current contents of the canvas before redrawing your updated "grass". This would involve redrawing everything else that is "static" on the canvas. For example:
function redraw() {
// Erase the current contents
ctx.fillStyle = 'white';
ctx.fill(0, 0, canvas.width, canvas.height);
// Draw the stuff that does not change from frame to frame
drawStaticContent();
// Draw the dynamic content
drawGrass();
}
In your mousemove event, update your grass curve information, then 'redraw'.
I'm trying to implement pixel perfect collision detection in my canvas game, however I can't seem to get the pixel information from my sprites.
I need the x and y values for each pixel of the sprite, and from what I've read I use the getImageData() method to do that.
However, this doesn't work:
this.sprite = new Image();
this.sprite.src = 'img/player.png';
console.log(this.sprite.getImageData());
Am I maybe using the wrong type of sprite? Because I get this error in the console:
Uncaught TypeError: Object # has no method
'getImageData'
Here’s how to use a sprite’s pixel data to do pixel-perfect hit-testing
First, draw your sprite normally on your visible canvas.
Create a red-masked copy of the sprite on a hidden canvas. This copy is the exact size as the sprite but contains only transparent or red pixels.
Track the visible sprite’s bounding box. When the bounding box is clicked calculate the X/Y of the mouseclick in relation to the sprite’s bounding box (not in relation to the canvas).
Then, refer to the red-masked sprite and see if the corresponding pixel at that X/Y is red or transparent. If the pixel is red, you have a pixel-perfect hit. If the pixel is transparent, no hit.
In this illustration, assume the blue dots are the X/Y click position. Since the corresponding X/Y pixel in the red-masked canvas is “red”, this is a HIT.
Here is code to create a red-masked sprite. I don’t show the code for hit-testing here, but if you try and can’t code the hit-test, I’ll take the time to code up the hit-testing too.
Important note: to get this code to run, you must avoid the cross-domain security restriction. Be sure your image source is on your local domain, otherwise you will run into a cross domain security violation and the masking image will not be drawn…So you can't do this for your sprite source: http://otherDomain.com/picture.jpg!
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid blue;}
</style>
<script>
$(function(){
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
var img=new Image();
img.onload=function(){
ctx.drawImage(this,100,25);
// make a red-masked copy of just the sprite
// on a separate canvas
var canvasCopy=document.getElementById("canvasCopy");
var ctxCopy=canvasCopy.getContext("2d");
canvasCopy.width=this.width;
canvasCopy.height=this.height;
ctxCopy.drawImage(img,0,0);
// make a red-masked copy of the sprite on a separate canvas
var imgData=ctxCopy.getImageData(0,0,c.width,c.height);
for (var i=0;i<imgData.data.length;i+=4)
{
if(imgData.data[i+3]>0){
imgData.data[i]=255;
imgData.data[i+1]=0;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
}
}
ctxCopy.putImageData(imgData,0,0);
}
img.src = "houseIcon.png";
}); // end $(function(){});
</script>
</head>
<body>
<p>Original sprite drawn on canvas at XY:100/25</p>
<canvas id="canvas" width="400" height="300"></canvas>
<p>Red-masked on canvas used for hit-testing</p>
<canvas id="canvasCopy" width="300" height="300"></canvas>
</body>
</html>
To do pixel-perfect collision testing between 2 sprites, you would:
Create a red-masked canvas for both sprite#1 and sprite#2.
First test if the bounding boxes of the 2 sprites are colliding. If the bounding boxes are not colliding, the 2 sprites are not colliding, so you can stop the hit-test here.
If the 2 sprites are possibly colliding using the bounding boxes test, create a third canvas for the collision test.
You’re going to use canvas’s compositing method to test for a collision between sprite#1 and sprite#2 by drawing both sprite#1 and sprite#2 onto the third canvas. Using compositing, only the COLLIDING pixels of the 2 sprites will be drawn on the third canvas.
Here’s how Compositing with “destination-in” works: The existing canvas content is kept where both the new shape (sprite#2) and existing shape (sprite#1) content overlap. Everything else is made transparent.
So…
Draw sprite#1 in its transformed state into the third canvas (transforms could be move, rotate, scale, skew--anything!).
Set the globalCompositeOperation to destination-in.
context.globalCompositeOperation = 'destination-over';
Draw sprite#2 in its transformed state into the third canvas.
After this draw, the third canvas contains only the colliding portions of sprite#1 and sprite#2
Test each pixel in the third canvas for non-transparent pixels. If you find any non-transparent pixels, the 2 sprites ARE COLLIDING.
Depending on what action you want to take upon collision, you might just bail out when you find the first colliding pixel.
I am writing a colouring game for small children, where I have a black and white image shown on a canvas initially, and as the user moves the drawing tool (mouse) over the canvas, the black and white surface gets over-painted with the colour information from the corresponding coloured image.
In particular, on every mouse move I need to copy a circular area from the coloured image to my canvas. The edge of the circle should be a little blurry to better immitate the qualities of a real drawing tool.
The question is how to accomplish this?
One way I see is to use a clipping region, but this approach does not let me have blurry edges. Or does it?
So I was thinking about using an alpha mask to do that and copy only pixels that correspond to the pixels in the mask that have non zero alpha. Is it feasible?
My suggestion is to have your drawable canvas in front of the coloured image you wish to reveal. (You could use your coloured image as a CSS background image for the canvas.)
Initially have the canvas containing the black and white image with 100% opacity. Then, when you draw, actually erase the contents of the canvas to show the image behind.
Like this:
var pos_x, pos_y, circle_radius; // initialise these appropriately
context.globalCompositeOperation = 'destination-out';
context.fillStyle = "rgba(0,0,0, 1.0)";
// And "draw" a circle (actually erase it to reveal the background image)
context.beginPath();
context.arc(pos_x, pos_y, circle_radius, 0, Math.PI*2);
context.fill();
I would probably use multiple clipping regions with varying alpha (one dab for each) to mimic the effect you are after. Render the low opacity one first (paste using drawImage) and render the rest after that till you reach alpha=1.0.
Have you considered using radial gradients that go from an opaque color to a fully transparent one?
Here is a demo from Mozilla. The circles are drawn the way you need. - https://developer.mozilla.org/samples/canvas-tutorial/4_10_canvas_radialgradient.html