I am trying to draw parallel lines on a canvas. With one of the lines being fixed. The user inputs the distance between two lines and hence the second line is positioned accordingly. I am new to JavaScript. Please help how should I change the position of second line with user input.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
console.log('myCanvas');
//Fixed Line
ctx.beginPath();
ctx.moveTo(50,200);
ctx.lineTo(300,200);
ctx.strokeStyle='white';
ctx.stroke();
//moving line
ctx.beginPath();
ctx.moveTo(50,250);
ctx.lineTo(300,250);
ctx.strokeStyle='white';
ctx.stroke();
Parallel line
To draw a line parallel to an existing line.
Get the vector from start to end of the line.
Use that vector to get the length of the line
Divide the offset distance by the length of the line to get offset scale
Scale the line vector by the offset scale
Add the scaled vector to the ends of the line and draw.
See example function drawLine
Get input
To get a value from an input element use the elements value property.
To get the value when it changes, add an event listener using the elements addEventListener function. . Do not assign a listener directly to the elements event property eg Avoid doing myElement.oninput = ()=> {/* ... code */};
There are a variety of input events. You can use one or more according to your needs. In this case there are two events change and input. See example.
change fires when the user commits a change to the value
input fires when there is any change to the input value
Always assign an input value a meaningfully value, do not leave it empty if empty has no meaning.
Example
const ctx = myCanvas.getContext("2d");
const myLine = {
from: {x: 50, y: 50},
to: {x: 150, y: 200},
style: {strokeStyle: "#000", lineWidth: 2}
};
distanceElement.addEventListener("input", inputEvent);
var lineOffset = distanceElement.value;
drawLines();
function inputEvent(e) {
lineOffset = e.target.value;
drawLines();
}
function drawLines() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawLine(myLine);
lineOffset !== 0 && drawLine(myLine, lineOffset);
}
function drawLine(line, offset = 0) {
var [ox, oy] = [0, 0];
Object.assign(ctx, line.style);
if (offset) {
const [dx, dy] = [line.from.x - line.to.x, line.from.y - line.to.y];
const scale = offset / (dx * dx + dy * dy) ** 0.5;
[ox, oy] = [-dy * scale, dx * scale];
}
ctx.beginPath();
ctx.lineTo(ox + line.from.x, oy + line.from.y);
ctx.lineTo(ox + line.to.x, oy + line.to.y);
ctx.stroke();
}
input {display:block;}
<label for="distanceElement">Line offset distance:</label>
<input id="distanceElement" placeholder="Enter Distance" type="number" value="0">
<canvas id="myCanvas" width="250" height="250"></canvas>
Edit:
While this answer will work for some people, this answer works better for more situations (notably, it supports diagonals) and has a more thorough explanation of what is going on.
Old Answer:
You can use the oninput event to run every time the input is typed in. Here is an example:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var input = document.getElementById("distance");
input.oninput = ()=>{
// clear
ctx.clearRect(0, 0, c.width, c.height);
// draw fixed line
ctx.beginPath();
ctx.moveTo(50,0);
ctx.lineTo(50,200);
ctx.strokeStyle='black';
ctx.stroke();
let value = parseFloat(input.value);
// draw moved line
ctx.beginPath();
ctx.moveTo(50+value,0);
ctx.lineTo(50+value,250);
ctx.strokeStyle='black';
ctx.stroke();
}
input {
display:block;
}
<canvas id="myCanvas" width="150" height="150"></canvas>
<input id="distance" placeholder="Enter Distance Here (in px)" type="number">
Related
I want to create a striped pattern with HTML5 canvas, where the thickness of lines for the striped pattern should be configurable using the property lineWidth.
After I read this answer, I understood that for coord x,y from moveTo()/lineTo(), I need to add like 2.5 for the ctx.lineWidth =5 or maybe create a formula based on thickness like this example. But I can't figure out how to change the values of those coordinates so the pattern remains striped like on the right, not random like in left
Below is my code. How should I calculate the coordonates x,y?
function createStrippedPattern(color) {
const pattern = document.createElement('canvas');
// create a 10x10 px canvas for the pattern's base shape
pattern.width = 10;
pattern.height = 10;
// get the context for drawing
const context = pattern.getContext('2d');
context.strokeStyle = color;
context.lineWidth = 5;
// draw 1st line of the shape
context.beginPath();
context.moveTo(2, 0);
context.lineTo(10, 8);
context.stroke();
// draw 2st line of the shape
context.beginPath();
context.moveTo(0, 8);
context.lineTo(2, 10);
context.stroke();
return context.createPattern(pattern, 'repeat');
};
function fillWithPattern(targetCanvas, patternCanvas) {
const ctx = targetCanvas.getContext('2d', {
antialias: false,
depth: false
});
const width = targetCanvas.width;
const height = targetCanvas.height;
ctx.fillStyle = patternCanvas;
ctx.fillRect(0, 0, width, height);
return targetCanvas;
}
fillWithPattern(
document.getElementById("targetCanvas"),
createStrippedPattern("red")
);
<canvas id="targetCanvas" width=30 height=30></canvas>
Code logic problems
The size of the pattern needs to match the slope of the line. That size must be expanded to allow for a set spacing between the lines.
Your code has a fixed size that does not match the slope of either of the lines you draw.
The lines you draw are both in different directions. You will never get them to create a repeatable pattern.
The code you have given is too ambiguous for me to understand what you wish to achieve thus the example adds some constraints that considers my best guess at your requirements.
Tileable striped pattern
The function in the example below creates a striped repeatable (tilded) pattern.
The function createStripedPattern(lineWidth, spacing, slope, color) requires 4 arguments.
lineWidth width of the line to draw
spacing distance between lines. Eg if lineWidth is 5 and spacing is 10 then the space between the lines is the same width as the line.
slope The slope of the line eg 45 degree slope is 1. I have only tested value >= 1 and am not sure if it will work below 1.
Nor have I tested very large slopes. The point of the example is to show how to draw the line on the pattern to repeat without holes.
color Color of line to draw.
The function works by creating a canvas that will fit the constraints given by the arguments. It then draws a line from the top left to bottom right corners. This leaves a gap in the repeating pattern at the top right and bottom left corners.
To fill the missing pixels two more lines are drawn. One through the top right corner and the other through the bottom left.
Note you could also just copy the canvas onto itself (offset to the corners) to fill the missing corner pixels. For pixel art type patterns this may be preferable.
Note that canvas sizes are integer values and lines are rendered at sub pixel accuracy. For very small input values there will be artifact as the relative error between the canvas (integer) pixel size and required (floating point) size grows larger
Example
The example contains the function to create the pattern as outlined above and then renders some examples.
The first canvas has inset patterns with each pattern increasing the line width will keeping the spacing and slope constant.
The second canvas just fills with a fixed lineWidth as 4, spacing as 8 and a slope of 3
function createAARotatedPattern(lineWidth, spacing, ang, color) {
const can = document.createElement('canvas');
const w = can.width = 2;
const h = can.height = spacing;
const ctx = can.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0, 0, 2, lineWidth);
const pat = ctx.createPattern(can, 'repeat');
const xAx = Math.cos(ang);
const xAy = Math.sin(ang);
pat.setTransform(new DOMMatrix([xAx, xAy, -xAy, xAx, 0, 0]));
return pat;
}
function createStripedPattern(lineWidth, spacing, slope, color) {
const can = document.createElement('canvas');
const len = Math.hypot(1, slope);
const w = can.width = 1 / len + spacing + 0.5 | 0; // round to nearest pixel
const h = can.height = slope / len + spacing * slope + 0.5 | 0;
const ctx = can.getContext('2d');
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
// Line through top left and bottom right corners
ctx.moveTo(0, 0);
ctx.lineTo(w, h);
// Line through top right corner to add missing pixels
ctx.moveTo(0, -h);
ctx.lineTo(w * 2, h);
// Line through bottom left corner to add missing pixels
ctx.moveTo(-w, 0);
ctx.lineTo(w, h * 2);
ctx.stroke();
return ctx.createPattern(can, 'repeat');
};
function fillWithPattern(canvas, pattern, inset = 0) {
const ctx = canvas.getContext('2d');
ctx.clearRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
ctx.fillStyle = pattern;
ctx.fillRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
return canvas;
}
fillWithPattern(targetCanvas, createStripedPattern(2, 6, 2, "#000"));
fillWithPattern(targetCanvas, createStripedPattern(3, 6, 2, "#000"), 50);
fillWithPattern(targetCanvas, createStripedPattern(4, 6, 2, "#000"), 100);
fillWithPattern(targetCanvas1, createStripedPattern(4, 8, 3, "#000"));
var y = 0;
var ang = 0;
const ctx = targetCanvas2.getContext('2d');
while (y < targetCanvas2.height) {
ctx.fillStyle = createAARotatedPattern(2, 5, ang, "#000");
ctx.fillRect(0, y, targetCanvas2.width, 34);
y += 40;
ang += 2 * Math.PI / (targetCanvas2.height / 40);
}
<canvas id="targetCanvas" width="300" height="300"></canvas>
<canvas id="targetCanvas1" width="300" height="300"></canvas>
<canvas id="targetCanvas2" width="300" height="600"></canvas>
Update
The above example now includes a second method createAARotatedPattern(lineWidth, spacing, ang, color) that uses the pattern transform. ang replaces slope from the original function and represents the angle of the pattern in radians.
It works by drawing the pattern aligned to the x axis and then rotates the pattern via a DOMMatrix.
It will create a pattern at any angle, though personally the quality can at times be less than the first method.
The example has a 3 canvas with strips showing the pattern drawn at various angles. (Note you do not have to recreate the pattern to change the angle)
I have been given the following task, but I am getting errors that can be seen when the code snippet is run. I would like some help figuring out what exactly I am doing wrong.
Basically, I need to draw a circle, make it so that it moves and changes the direction/color when touching the walls of the screen.
Task: create a Circle class with the following properties:
x - the initial value of the coordinate x
y is the initial value of the y coordinate
radius - values โโof width and height
color - fill color Describe the methods:
draw () - marks off on the screen an element that is described by the given properties
setColor (newColor) - Changes the fill color to newColor
move ({x = 0, y = 0}) - moves the captured object by the vector (x, y) - each time period (for example, 100 ms) changes (adds \ subtracts)
to the values โโx and y, respectively. When a circle collides with any
edge of the screen it is necessary to realize its mirror reflection
(change the value of the corresponding coordinate of the vector on the
opposite of the value of the sign, and call this method with the new
vector) and generate the collision event, collision, which is captured
at the document level.Hang on this event a handler that will change
the color of the pouring of the circle into another (random) value.
Movement occurs until the stop method is called.
stop () - stops the circle movement
If the Escape button on the keyboard was pressed, the movement should stop.
I created a canvas and set the frame to move. I drew a circle and tried to move it using setInterval(), but it seems like I'm losing the context.
let c = document.getElementById("mycanvas");
let ctx = c.getContext("2d");
let xinc = 1;
let yinc = 1;
class Circle {
constructor(xpos, ypos, radius, color) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.color = color;
}
draw() {
ctx.beginPath();
ctx.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
}
move(xpos, ypos) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
this.draw();
xpos += xinc;
ypos += yinc;
console.log(xpos, ypos);
if ((this.xpos > c.width - this.radius) || (this.xpos < 0 + this.radius)) {
xinc = -xinc;
}
if ((this.ypos > c.height - this.radius) || (this.ypos < 0 + this.radius)) {
yinc = -yinc;
}
setInterval(this.move, 10);
//this.draw();
}
}
let circle = new Circle(200, 300, 50, "red");
circle.draw();
circle.move(200, 300);
<canvas id="mycanvas" width="1335" height="650" style="border: 1px solid"> </canvas>
I am just starting to learn events and DOMs, please help me correctly implement this task
You are passing this.move to setInterval with no context - just a function, with no this to call it in. You can pass in this.move.bind(this) to create a bound function. You can also do it once in the constructor: this.move = this.move.bind(this).
Also, the call to beginPath in move seems unnecessary.
I need to use several canvas-es with different values (see data-percent) with same reusable code block but "animation" makes it a little bit tricky. Im not sure how to make it reusable. Copy-pasting the same code over and over again is obviously a wrong move, I usually avoid it at any cost.
First thing is obviously to remove id and use class instead, then I could select all the canvas-es:
<canvas class="circle-thingy" width="120" height="120" data-percent="75"></canvas>
<canvas class="circle-thingy" width="120" height="120" data-percent="23"></canvas>
<canvas class="circle-thingy" width="120" height="120" data-percent="89"></canvas>
var allCircles = document.getElementsByClassName('circle-thingy');
But now comes the trickier part.. How about canvas JavaScript code? There's probably a very easy solution but I can't see it! Terrible time to quit smoking I guess (as always), brain is like shut down.
What I tried: for loop with allCircles list. Problem is that I cannot use setInterval and clearTimeout with this approach. Dynamic variable names? How do I reference them later?
Here's my code with a single circle, try it.
// Get canvas context
var ctx = document.getElementById('my-circle').getContext('2d');
// Current percent
var currentPercent = 0;
// Canvas north (close enough)
var start = 4.72;
// Dimensions
var cWidth = ctx.canvas.width;
var cHeight = ctx.canvas.height;
// Desired percent -> comes from canvas data-* attribute
var finalPercent = ctx.canvas.getAttribute('data-percent');
var diff;
function circle() {
diff = ((currentPercent / 100) * Math.PI * 2 * 10).toFixed(2);
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.lineWidth = 3;
// Bottom circle (grey)
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.arc(60, 60, 55, 0, 2 * Math.PI);
ctx.stroke();
// Percent text
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
ctx.font="900 10px arial";
ctx.fillText(currentPercent + '%', cWidth * 0.5, cHeight * 0.5 + 2, cWidth);
// Upper circle (blue)
ctx.strokeStyle = '#0095ff';
ctx.beginPath();
ctx.arc(60, 60, 55, start, diff / 10 + start);
ctx.stroke();
// If has desired percent -> stop
if( currentPercent >= finalPercent) {
clearTimeout(myCircle);
}
currentPercent++;
}
var myCircle = setInterval(circle, 20);
<canvas id="my-circle" width="120" height="120" data-percent="75"></canvas>
Feel free to use this code snippet in your own projects.
You can use bind to solve this.
Create a helper function that will start animation for given canvas:
function animateCircle(canvas) {
var scope = {
ctx: canvas.getContext('2d')
// other properties, like currentPercent, finalPercent, etc
};
scope.interval = setInterval(circle.bind(scope), 20);
}
Change your circle function to refer variables from this instead of global ones:
function circle() {
// your old code with corresponding changes
// e.g.
var ctx = this.ctx; // references corresponding scope.ctx
// or
this.currentPercent++; // references corresponding scope.currentPercent
}
Working JSFiddle, if something is not clear.
I have two canvas elements and need them to be resized on buttons click.
<div class="sDetails"><div>
<div id="canvasDiv" style="width: 310px;"><canvas id="canvasGraph"></canvas></div></div>
<div class="kDetails"><div><div>
<div id="canvasDiv" style="width: 310px; height: 240px;"><canvas id="canvasGraph"></canvas></div></div>
and the script:
var sketch;var sketch_sl;var onPaint;var canvas=null;var ctx=null;var tmp_ctx=null;
function drawCanvas(div) {
canvas = document.querySelector(div + " #canvasGraph");
ctx = canvas.getContext('2d');
sketch = document.querySelector(div + " #canvasDiv");
sketch_sl = getComputedStyle(sketch);
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
tmp_canvas = document.createElement('canvas');
tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = canvas.width;
tmp_canvas.height = canvas.height;
sketch.appendChild(tmp_canvas);
the redraw function:
// here I must redraw my lines resized 2 times ( *cScale ) where cScale=2 or =1
function drawScales(ctx, canvas)
ctx.strokeStyle = 'green';
ctx.fillStyle = 'green';
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(0, canvas.height);
scaleStep = 24*cScale;
for some reason it works really bad, old positions stay.
Is there a way to completely delete the whole canvas and append it or redraw it completely?
I tried canvas.width=canvas.width, tried ctx.clearRect(0, 0, canvas.width, canvas.height);tmp_ctx.clearRect(0, 0, canvas.width, canvas.height);, tried $(".sDetails #canvasGraph")[0].reset();
logically, drawCanvas(".sDetails");drawLines(ctx, canvas); should redraw it from scratch but it will not.
Resize the canvas element's width & height and use context.scale to redraw the original drawings at their newly scaled size.
Resizing the canvas element will automatically clear all drawings off the canvas.
Resizing will also automatically reset all context properties back to their default values.
Using context.scale is useful because then the canvas will automatically rescale the original drawings to fit on the newly sized canvas.
Important: Canvas will not automatically redraw the original drawings...you must re-issue the original drawing commands.
Illustration with 2 canvases at same size (their sizes are controlled by range controls)
Illustration with left canvas resized larger
Illustration with right canvas resized larger
Here's example code and a Demo. This demo uses range elements to control the resizing, but you can also do the resizing+redrawing inside window.onresize
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
var canvas2=document.getElementById("canvas2");
var ctx2=canvas2.getContext("2d");
var originalWidth=canvas1.width;
var originalHeight=canvas1.height;
var scale1=1;
var scale2=1;
$myslider1=$('#myslider1');
$myslider1.attr({min:50,max:200}).val(100);
$myslider1.on('input change',function(){
var scale=parseInt($(this).val())/100;
scale1=scale;
redraw(ctx1,scale);
});
$myslider2=$('#myslider2');
$myslider2.attr({min:50,max:200}).val(100);
$myslider2.on('input change',function(){
var scale=parseInt($(this).val())/100;
scale2=scale;
redraw(ctx2,scale);
});
draw(ctx1);
draw(ctx2);
function redraw(ctx,scale){
// Resizing the canvas will clear all drawings off the canvas
// Resizing will also automatically clear the context
// of all its current values and set default context values
ctx.canvas.width=originalWidth*scale;
ctx.canvas.height=originalHeight*scale;
// context.scale will scale the original drawings to fit on
// the newly resized canvas
ctx.scale(scale,scale);
draw(ctx);
// always clean up! Reverse the scale
ctx.scale(-scale,-scale);
}
function draw(ctx){
// note: context.scale causes canvas to do all the rescaling
// math for us, so we can always just draw using the
// original sizes and x,y coordinates
ctx.beginPath();
ctx.moveTo(150,50);
ctx.lineTo(250,150);
ctx.lineTo(50,150);
ctx.closePath();
ctx.stroke();
ctx.fillStyle='skyblue';
ctx.beginPath();
ctx.arc(150,50,20,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(250,150,20,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();;
ctx.arc(50,150,20,0,Math.PI*2);
ctx.fill();
ctx.stroke();
}
$("#canvas1, #canvas2").mousemove(function(e){handleMouseMove(e);});
var $mouse=$('#mouse');
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
var bb=e.target.getBoundingClientRect();
mouseX=parseInt(e.clientX-bb.left);
mouseY=parseInt(e.clientY-bb.top);
if(e.target.id=='canvas1'){
$mouse.text('Mouse1: '+mouseX/scale1+' / '+mouseY/scale1+' (scale:'+scale1+')');
}else{
$mouse.text('Mouse2: '+mouseX/scale2+' / '+mouseY/scale2+' (scale:'+scale2+')');
}
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div>Resize left canvas</div>
<input id=myslider1 type=range><br>
<div>Resize right canvas</div>
<input id=myslider2 type=range><br>
<h4 id=mouse>Mouse coordinates:</h4>
<canvas id="canvas1" width=300 height=300></canvas>
<canvas id="canvas2" width=300 height=300></canvas>
If you need scale-independent positions you could use normalized values ([0, 1]) instead and use the size of canvas as the scale factor. This way you can scale and store values without too much concern about the actual target size.
You would also be able to use the mouse positions almost as is and normalize by just dividing them on canvas size.
For example:
When rendering, a point of (1,1) will always draw in lower-right corner as you would do (1 * canvas.width, 1 * canvas.height).
When you store a point you would use the mouse position and divide it on the canvas dimension, for example, if I click in the lower right corner of a canvas of size 400x200, the points would be 400/400 = 1, 200/200 = 1.
Note that width and height would be exclusive (ie. width-1 etc.), but for sake of simplicity...
Example
In this example you can start with any size of the canvas, draw points which are normalized, change size of canvas and have the points redrawn proportionally relative to the original position.
var rng = document.querySelector("input"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
points = [];
// change canvas size and redraw all points
rng.onchange = function() {
c.width = +this.value;
render();
};
// add a new normalized point to array
c.onclick = function(e) {
var r = this.getBoundingClientRect(), // to adjust mouse position
x = e.clientX - r.left,
y = e.clientY - r.top;
points.push({
x: x / c.width, // normalize value to range [0, 1]
y: y / c.height
}); // store point
render(); // redraw (for demo)
};
function render() {
ctx.clearRect(0, 0, c.width, c.height); // clear canvas
ctx.beginPath(); // clear path
for(var i = 0, p; p = points[i]; i++) { // draw points as fixed-size circles
var x = p.x * c.width, // normalized to absolute values
y = p.y * c.height;
ctx.moveTo(x + 5, y);
ctx.arc(x, y, 5, 0, 6.28);
ctx.closePath();
}
ctx.stroke();
}
canvas {background:#ddd}
<h3>Click on canvas to add points, then resize</h3>
<label>Width: <input type="range" min=50 max=600 value=300></label><br>
<canvas></canvas>
I decided to use a scale variable to resize my scales. I resize the canvas canvas.width *= 2; and then I redraw my scales.
var scaleStep;
and use add it into the code: ctx.lineTo(12*24*cScale+12, canvas.height-24); where the scaling needs to be done.
The scaleStep is 2 when maximizing the canvas and 1 when returning to the original size.
I'm working on concept maps application, which has a set of nodes and links. I have connected the links to nodes using the center of the node as reference. Since I have nodes with different size and shapes, it is not advisable to draw arrow-head for the link by specifying height or width of the shape. My approach is to draw a link, starting from one node, pixel by pixel till the next node is reached(here the nodes are of different color from that of the background), then by accessing the pixel value, I want to be able to decide the point of intersection of link and the node, which is actually the co-ordinate for drawing the arrow-head.
It would be great, if I could get some help with this.
Sample Code:
http://jsfiddle.net/9tUQP/4/
Here the green squares are nodes and the line starting from left square and entering into the right square is the link. I want the arrow-head to be drawn at the point of intersection of link and the right square.
I've created an example that does this. I use Bresenham's Line Algorithm to walk the line of whole canvas pixels and check the alpha at each point; whenever it crosses a 'threshold' point I record that as a candidate. I then use the first and last such points to draw an arrow (with properly-rotated arrowhead).
Here's the example: http://phrogz.net/tmp/canvas_shape_edge_arrows.html
Refresh the example to see a new random test case. It 'fails' if you have another 'shape' already overlapping one of the end points. One way to solve this would be to draw your shapes first to a blank canvas and then copy the result (drawImage) to the final canvas.
For Stack Overflow posterity (in case my site is down) here's the relevant code:
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>HTML5 Canvas Shape Edge Detection (for Arrow)</title>
<style type="text/css">
body { background:#eee; margin:2em 4em; text-align:center; }
canvas { background:#fff; border:1px solid #666 }
</style>
</head><body>
<canvas width="800" height="600"></canvas>
<script type="text/javascript">
var ctx = document.querySelector('canvas').getContext('2d');
for (var i=0;i<20;++i) randomCircle(ctx,'#999');
var start = randomDiamond(ctx,'#060');
var end = randomDiamond(ctx,'#600');
ctx.lineWidth = 2;
ctx.fillStyle = ctx.strokeStyle = '#099';
arrow(ctx,start,end,10);
function arrow(ctx,p1,p2,size){
ctx.save();
var points = edges(ctx,p1,p2);
if (points.length < 2) return
p1 = points[0], p2=points[points.length-1];
// Rotate the context to point along the path
var dx = p2.x-p1.x, dy=p2.y-p1.y, len=Math.sqrt(dx*dx+dy*dy);
ctx.translate(p2.x,p2.y);
ctx.rotate(Math.atan2(dy,dx));
// line
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-len,0);
ctx.closePath();
ctx.stroke();
// arrowhead
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-size,-size);
ctx.lineTo(-size, size);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Find all transparent/opaque transitions between two points
// Uses http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
function edges(ctx,p1,p2,cutoff){
if (!cutoff) cutoff = 220; // alpha threshold
var dx = Math.abs(p2.x - p1.x), dy = Math.abs(p2.y - p1.y),
sx = p2.x > p1.x ? 1 : -1, sy = p2.y > p1.y ? 1 : -1;
var x0 = Math.min(p1.x,p2.x), y0=Math.min(p1.y,p2.y);
var pixels = ctx.getImageData(x0,y0,dx+1,dy+1).data;
var hits=[], over=null;
for (x=p1.x,y=p1.y,e=dx-dy; x!=p2.x||y!=p2.y;){
var alpha = pixels[((y-y0)*(dx+1)+x-x0)*4 + 3];
if (over!=null && (over ? alpha<cutoff : alpha>=cutoff)){
hits.push({x:x,y:y});
}
var e2 = 2*e;
if (e2 > -dy){ e-=dy; x+=sx }
if (e2 < dx){ e+=dx; y+=sy }
over = alpha>=cutoff;
}
return hits;
}
function randomDiamond(ctx,color){
var x = Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
y = Math.round(Math.random()*(ctx.canvas.height - 100) + 50);
ctx.save();
ctx.fillStyle = color;
ctx.translate(x,y);
ctx.rotate(Math.random() * Math.PI);
var scale = Math.random()*0.8 + 0.4;
ctx.scale(scale,scale);
ctx.lineWidth = 5/scale;
ctx.fillRect(-50,-50,100,100);
ctx.strokeRect(-50,-50,100,100);
ctx.restore();
return {x:x,y:y};
}
function randomCircle(ctx,color){
ctx.save();
ctx.beginPath();
ctx.arc(
Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
Math.round(Math.random()*(ctx.canvas.height - 100) + 50),
Math.random()*20 + 10,
0, Math.PI * 2, false
);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
</script>
</body></html>