Save a reference to a html canvas path - javascript

I'm working on some code which is drawing to a canvas. One part of the code draws some lines onto the canvas. The position and colour of those lines don't change, but they often need to be redrawn because other code may have affected it (eg: drawn over the top of it).
There can be several hundred lines to draw, and in these cases, profiling shows me that it's taking ~200ms to draw, so I'm looking to optimise this somewhat.
One thing I noticed was that when drawing to the canvas, you basically are adding points to a path and then once ready, you can fill or stroke that path. Though the pixels on the canvas are out of date, if I were able to keep a reference to the path, then updating would be as simple as re-stroking the previously constructed path.
My question is: how on earth do you get a Path object?
The fill and stroke methods appear to accept a path object, and the spec defines the methods for Path, but I can't seem to find the actual Path class anywhere...
So, just to recap:
I have something like this:
function update() {
context.beginPath();
// lots of lines added to the default path...
context.moveTo(x1, y1); context.lineTo(somewhere, else);
context.moveTo(x2, y2); context.lineTo(somewhere, else);
context.stroke();
}
What I'd like is something like this:
function update() {
if (!this.path) {
this.path = new Path(); // <-- here's the magic
this.path.moveTo(x1, y2); this.path.lineTo(somewhere, else); // etc
}
this.path.stroke();
}

The canvas spec calls for a Path object that is not implemented in browsers yet.
BTW, when implemented, the Path object will be useful in hit-testing when combined with context.isPointInPath(myPath); Someday...
Here's how you could create your own Path object until the browsers catch up:
Create a JS object that contains a canvas where your path strokes are drawn.
When you want to do myPath.stroke(), use myVisibleContext.drawImage(myPath.context,0,0) to "blit" the path's canvas onto your drawing canvas.
Demo: http://jsfiddle.net/m1erickson/QLJv8/
Code:
<!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; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
function Path(maxWidth,maxHeight,color,linewidth,drawingContext){
this.width=maxWidth;
this.height=maxHeight;
this.drawingCtx=drawingContext;
this.points=[]
this.canvas=document.createElement("canvas");
this.canvas.width=maxWidth;
this.canvas.height=maxHeight;
this.ctx=this.canvas.getContext("2d");
this.ctx.strokeStyle=color;
this.ctx.lineWidth=linewidth;
this.lastX;
this.lastY;
}
Path.prototype.moveTo=function(x,y){
this.lastX=x;
this.lastY=y;
}
Path.prototype.lineTo=function(x,y){
this.ctx.moveTo(this.lastX,this.lastY);
this.ctx.lineTo(x,y);
this.ctx.stroke();
this.lastX=x;
this.lastY=y;
}
Path.prototype.stroke=function(){
this.drawingCtx.drawImage(this.canvas,0,0);
}
// create a new path object
var p=new Path(300,300,"blue",2,ctx);
// set the Path's drawing commands
p.moveTo(69,91);
p.lineTo(250,150);
p.moveTo(69,208);
p.lineTo(180,54);
p.lineTo(180,245);
p.lineTo(69,91);
p.moveTo(69,208);
p.lineTo(250,150);
// draw the Path.canvas to the drawing canvas
p.stroke();
// tests...
$("#stroke").click(function(){
p.stroke();
});
$("#erase").click(function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="stroke">Path.stroke</button><br>
<button id="erase">Erase main canvas</button><br>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

Turns out, it's just that no browser supports it yet, according to this blog (dated 24th January 2013) http://www.rgraph.net/blog/2013/january/html5-canvas-path-objects.html

Their is no path support in canvas, but why don't use svg line and set its zIndex to be on top of others.

None of the canvas drawing API let you hold references to objects.
Canvas lets you draw pixels in a bitmap, not create and manipulate objects like SVG does.
If you're looking to optimize performance and you want to reuse the same path over and over again, you might want to draw it once in a separate canvas oject, and then draw that canvas into your other canvas using drawImage (which can take a canvas as argument).

Related

I want to apply filter: blur to any shape

Click on the image to hit the path.
Click the first point after hitting three or more passes to close the pass.
When the path is closed, I want to select the inside of the path and implement the function to blur the range.
Currently, nothing happens when you close the path.
I think that you are mistaken about Paper.js rendering engine.
It draws its items in the context of a <canvas> element, so all you will be able to access from developer tool is this canvas and its image data.
You will not be able to target your path and act on it using selectors which seems to be what you are trying to do.
Anyway, unfortunately, Paper.js doesn't currently support filters.
So one option could be to take advantage of the canvas context filter property (experimental) or to implement the blurring algorithm yourself.
Then, while keeping Paper.js utility for drawing, you could manage multiple canvases and do smart compositing to produce the effect that you are looking for.
Here is a fiddle demonstrating a possible implementation.
Note that for the sake of the demo, I simplified your use case but you should be able to adapt it to your case quite easily.
In this example, I use 3 different canvases:
- the bottom one is for drawing the original image
- the middle one is for drawing the blurred part
- the top one is for drawing the shape that we will use for compositing and will be hidden at the end
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Debug Paper.js</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.2/paper-core.min.js"></script>
<style>
html,
body {
margin : 0;
overflow : hidden;
height : 100%;
}
/* We position canvases on top of each other. */
canvas {
position : absolute;
top : 0;
left : 0;
width : 100vw;
height : 100vh;
}
</style>
</head>
<body>
<canvas id="bottom-canvas"></canvas>
<canvas id="middle-canvas"></canvas>
<canvas id="top-canvas"></canvas>
<script>
// Get canvases references.
const bottomCanvas = document.getElementById('bottom-canvas');
const middleCanvas = document.getElementById('middle-canvas');
const topCanvas = document.getElementById('top-canvas');
// Initialise 2 PaperScopes.
const bottomScope = new paper.PaperScope();
bottomScope.setup(bottomCanvas);
const topScope = new paper.PaperScope();
topScope.setup(topCanvas);
// For middle canvas, we need to adjust the size manually as Paper.js doesn't handle it.
middleCanvas.width = middleCanvas.offsetWidth;
middleCanvas.height = middleCanvas.offsetHeight;
// Draw the image on the bottom canvas.
new paper.Raster({
source: 'https://i.imgur.com/6N0Zwag.jpg',
crossOrigin: 'anonymous',
position: bottomScope.view.center,
parent: bottomScope.project.activeLayer,
// When image is loaded...
onLoad: function() {
// ...make it fit the whole canvas.
this.fitBounds(bottomScope.view.bounds, true);
// Draw a circle on the top canvas that represents the user drawn shape
// that we want to use for blurring.
new paper.Path.Circle({
center: topScope.view.center,
radius: 200,
fillColor: 'orange',
parent: topScope.project.activeLayer
});
// We manually call the canvas view update to make sure that everything
// is drawn before we play with image data.
bottomScope.view.update();
topScope.view.update();
// Get middle canvas context to be able to draw on it.
const middleCanvasContext = middleCanvas.getContext('2d');
// Draw the bottom canvas image on the middle canvas with the blur filter applied.
middleCanvasContext.filter = 'blur(15px)';
middleCanvasContext.drawImage(bottomCanvas, 0, 0);
// In order to see the clear part of the bottom canvas image,
// we need to remove from middle canvas what is not on top canvas.
// For that, we use "destination-in" composite operation.
middleCanvasContext.filter = 'none';
middleCanvasContext.globalCompositeOperation = 'destination-in';
middleCanvasContext.drawImage(topCanvas, 0, 0);
// Now, we need to hide the top canvas, to see the middle one below it.
topCanvas.style.display = 'none';
}
});
</script>
</body>
</html>

Context2d arc() with begin path, generates strange circle

I'm trying to write a game engine in pure javscript and canvas.
I got a strange thing today, while generating a circle with
var circle = new Circle(10, 10, 100);
The circle is weird, if I remove from the code beginPath() and closePath() and it works as it should, but then the screen is not redraw due to: http://codetheory.in/why-clearrect-might-not-be-clearing-canvas-pixels/
The "engine" code can be found here: insane96mcp.altervista.org/Invaders/script.js
I can't reproduce your weird circle from the code in your link, but...
1. You appear to have misconceptions about beginPath & endPath
beginPath starts a new path and stops drawing the previous path. It is necessary to prevent your drawings from accumulating and overwriting themselves. Without beginPath your circle will redraw (& redraw & redraw & redraw!) itself with every call to yourCircle.Draw. This constant redrawing might be causing your weird circle if you leave out beginPath.
closePath is not the counter-part to beginPath. It does not stop drawing a path. Instead it simply connects the current path position to the beginning path position with a line. Without beginPath, these extra lines created by closePath might be the cause of your jagged circle.
Some reading for you below (context.beginPath and context.closePath)
2. You're un-necessarily adding "empty" Shape objects into gameObjects in your function Shape.
3. ...And yes, as you surmise in your question, if you're not clearing the canvas between draws with clearRect then your drawings will accumulate. But one would expect the circle to be more uniformly fuzzy instead of jaggedy.
context.beginPath
context.beginPath()
Begins assembling a new set of path commands and also discards any previously assembled path.
It also moves the drawing "pen" to the top-left origin of the canvas (==coordinate[0,0]).
Although optional, you should ALWAYS start a path with beginPath
The discarding is an important and often overlooked point. If you don't begin a new path with beginPath, any previously issued path commands will automatically be redrawn.
These 2 demos both attempt to draw an "X" with one red stroke and one blue stroke.
This first demo correctly uses beginPath to start it's second red stroke. The result is that the "X" correctly has both a red and a blue stroke.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// draw a blue line
ctx.beginPath();
ctx.moveTo(30,30);
ctx.lineTo(100,100);
ctx.strokeStyle='blue';
ctx.lineWidth=3;
ctx.stroke();
// draw a red line
ctx.beginPath(); // Important to begin a new path!
ctx.moveTo(100,30);
ctx.lineTo(30,100);
ctx.strokeStyle='red';
ctx.lineWidth=3;
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>
This second demo incorrectly leaves out beginPath on the second stroke. The result is that the "X" incorrectly has both red strokes.
The second stroke() is draws the second red stroke.
But without a second beginPath, that same second stroke() also incorrectly redraws the first stroke.
Since the second stroke() is now styled as red, the first blue stroke is overwritten by an incorrectly colored red stroke.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// draw a blue line
ctx.beginPath();
ctx.moveTo(30,30);
ctx.lineTo(100,100);
ctx.strokeStyle='blue';
ctx.lineWidth=3;
ctx.stroke();
// draw a red line
// Note: The necessary 'beginPath' is missing!
ctx.moveTo(100,30);
ctx.lineTo(30,100);
ctx.strokeStyle='red';
ctx.lineWidth=3;
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>
context.closePath
context.closePath()
Draws a line from the current pen location back to the beginning path coordinate.
For example, if you draw 2 lines forming 2 legs of a triangle, closePath will "close" the triangle by drawing the third leg of the triangle from the 2nd leg's endpoint back to the first leg's starting point.
A Misconception explained!
This command's name often causes it to be misunderstood.
context.closePath is NOT an ending delimiter to context.beginPath.
Again, the closePath command draws a line -- it does not "close" a beginPath.
This example draws 2 legs of a triangle and uses closePath to complete (close?!) the triangle by drawing the third leg. What closePath is actually doing is drawing a line from the second leg's endpoint back to the first leg's starting point.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// arguments
var topVertexX=50;
var topVertexY=50;
var rightVertexX=75;
var rightVertexY=75;
var leftVertexX=25;
var leftVertexY=75;
// A set of line segments drawn to form a triangle using
// "moveTo" and multiple "lineTo" commands
ctx.beginPath();
ctx.moveTo(topVertexX,topVertexY);
ctx.lineTo(rightVertexX,rightVertexY);
ctx.lineTo(leftVertexX,leftVertexY);
// closePath draws the 3rd leg of the triangle
ctx.closePath()
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>

How to remove a drawn arch from canvas

I have been trying to print arc in the html page. How can i remove the already drawn arch from the page?. i have written the below code.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="1200" height="1000"
style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
/*ctx.beginPath();
ctx.arc(600,500,20, 0.5*Math.PI,2*Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(600,500,40, 0.5*Math.PI,2*Math.PI);
ctx.stroke();
*/
var radius=20;
for (i=0; i<10; i++)
{
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(600,500,radius, 0.5*Math.PI, 2*Math.PI);
ctx.stroke();
radius= radius+30;
}
</script>
</body>
</html>
How can i achieve this?.
Call clearRect method:
ctx.clearRect(0, 0, 1200, 1000)
The four arguments are:
axis-X of left top corner of the area to wipe
axis-Y of left top corner of the area to wipe
width of the area to wipe
height of the area to wipe
So with this method, you could wipe either the whole canvas or just a certain part of it.
If you want to remove the whole previously drawn image please take a look at the other anwers. In the comments OP made it clear that this is not what he was trying to achieve. So instead I will answer the intended question:
How do I un-stroke a path?
A bitmap is not a vector graphic. You cannot simply remove or modify things you've drawn previously. By drawing on a canvas you modify the pixel color values of its image data. If you need to undo things you have to maintain a separate data structure with the relevant data yourself.
For example you could create a copy of the image data before drawing something. Then you could return to this snapshot afterwards. HTMLCanvasElement#toDataURL returns the complete image as an url which you can use as the src of an image. Later you can draw this image on the canvas to revert all subsequent changes. HTMLCanvasElement#toBlob does about the same but it returns a blob. This might consume less memory but it's a little more inconvenient to use. The most convenient method is CanvasRenderingContext2D#getImageData. You can even use it to copy only a small part of the image. This is useful if you have a big canvas but only modify pixels in a small region.
Another way to make modifications undoable is by maintaining a detailed list of your steps. For example whenever you draw an arc you store the exact parameters as one entry in the list. steps.push({type: 'stroke', style: 'rgb(0,0,0)', shapes: [{type: 'arc', x: 600, y: 500, radius: radius, from: 0.5 * Math.PI, to: 2 * Math.PI}]}) You can remove, rearrange or modify the elements in this list any way you like and have all necessary information to draw the resulting image from scratch. Basically you've implemented just another vector graphic library.

Faded image edges on canvas using getImageData

Using getImageData, I'm trying to create a radial fade effect on an image where point(0,0) has an alpha of 0 and point(img.width, img.height) has an alpha of 0.5 sort of like this:
(The last point has an alpha of 1.0, but imagine it's 0.5.)
I've been able to create a linear alpha increase in my for loop using
imgData.data[i+3]=i/imgData.length;
but the left edge (obviously) becomes more defined as the loop iterates.
I've tried using two loops, one to create row "breaks" in the array, and the other to handle the alpha manipulation like so:
for(var j=0;j<imgData.height;j++){
for(var i=0;i<imgData.data.length;i+=4){
imgData.data[i+3] = 0 + 125 * i/imgData.data.length;
}
}
but the performance of this is really slow and it doesn't achieve the fade that I'm looking for. I'm also not sure I'm doing that properly. I also tried Faster Canvas Pixel Manipulation with Typed Arrays as outlined on the Mozilla Hacks site, but the image wasn't even drawn to the canvas using that method. (I'm using Chrome - not sure if that's the issue or if it's my code.)
Any ideas?
EDIT: Thanks to markE for the great solution - it's beautifully quick. I mentioned this in my comment to him, but here's the full code for creating a greyscale image (part of my requirements) that has the fade pictured in mark's demo.
img_width = window.innerWidth/2.5;
img_height = img_width * (9/16);
//hidden_ctx is hidden behind a background layer
var g1=hidden_ctx.createRadialGradient(500,500,550,500,500,650);
g1.addColorStop(0.00,"rgba(0,0,0,0.7)");
g1.addColorStop(0.30,"rgba(0,0,0,0.35)");
g1.addColorStop(1.00,"rgba(0,0,0,0.00)");
hidden_ctx.fillStyle=g1;
hidden_ctx.fillRect(0, 0, img_width, img_height);
hidden_ctx.globalCompositeOperation="source-in";
//custom drawImage method where selected[num] contains
//contains images & metadata
drawIt(hidden_ctx, selected[num].image[0], 0, 0, img_width, img_height);
imgData=hidden_ctx.getImageData(0, 0, img_width, img_height);
// invert colors
for (var i=0;i<imgData.data.length;i+=4)
{
grayscaled = 0.34 * imgData.data[i] + 0.5 * imgData.data[i + 1] + 0.16 * imgData.data[i + 2];
imgData.data[i]=grayscaled;
imgData.data[i+1]=grayscaled;
imgData.data[i+2]=grayscaled;
}
//places the converted image in the lower right corner where
//WIDTH/HEIGHT is window.innerWidth/window.innerHeight
ctx.putImageData(imgData, WIDTH-img_width, HEIGHT-img_height);
You can use compositing to achieve a fade effect more efficiently.
draw a radial gradient that fades to transparent in the top-left corner.
Adjust the radial gradient to meet your design needs.
set compositing to source-in which causes any subsequent draws to only appear over existing non-transparent pixels.
draw your image (the image will fade the same way your radial gradient did)
Using compositing is especially efficient if the device has a GPU because compositing will use it.
Example code and a Demo: http://jsfiddle.net/m1erickson/thqamsLk/
<!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>
canvas{ background-color: lightgray; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/reef.jpg";
function start(){
canvas.width=img.width;
canvas.height=img.height;
var g1=ctx.createRadialGradient(500,500,550,500,500,650);
g1.addColorStop(0.00,"rgba(0,0,0,1.00)");
g1.addColorStop(0.30,"rgba(0,0,0,0.75)");
g1.addColorStop(1.00,"rgba(0,0,0,0.00)");
ctx.fillStyle=g1;
ctx.fillRect(0,0,cw,ch);
ctx.globalCompositeOperation="source-in";
ctx.drawImage(img,0,0);
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Fading an image using compositing</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

To draw a line does it have use beginPath() and restore()?

I want to draw a couple of simple lines to a canvas but it will not work if I do not have beginPath() and restore() in my scripting code. Do you have to have these to draw something to the HTML 5 Canvas?
Code:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.lineWidth="5";
ctx.strokeStyle="green"; // Green path
ctx.moveTo(0,75);
ctx.lineTo(250,75);
ctx.strokeStyle="purple"; // Purple path
ctx.moveTo(50,0);
ctx.lineTo(150,130);
</script>
</body>
</html>
A canvas deals with paths.
So, as the name suggests, beginPath will starts a new path.
Each retained draw command you will call after-wise will add-up to the current path.
( retained draw command are commands that have no direct effect : moveTo, lineTo, (Curve)To, arc, rect. )
Rq1 : Using moveTo is a special command : it will begin a new sub-path, it's like releasing the pen from the paper.
Rq2 : closePath is not required, use it if you want to link the last point to the first one easily.
When using fill or stoke, the current retained path (=all the sub-path of the current path) will get stroked/filled using current transform, clipping and color setting.
Notice that there are also direct command : fill/strokeRect, fill/strokeText, drawImage, get/putImageData.
Those commands do not require to begin a new path, and do not affect the current path -they directly draw on the canvas-.
So rule is simple :
• if you use retained commands, you must use beginPath.
• If you use direct commands do not use beginPath, just send the command.
As you noticed, you can change a lot of settings on the canvas :
• you have render settings : strokeStyle / fillStyle / globalCompositeOperation / globalAlpha / lineWidth / font / textAlign/ shadow / ...
• you have transform settings that allow you to translate/scale/rotate the next draw.
• you can define clipping to avoid the draw to occur in some areas.
save() and restore() allows you not to get lost in the status of the current canvas.
Rule is : anyway you change the context in a way that could affect the render of other drawings you make, just save() before, and restore after.
example 1 :
function strokeLine ( x1, y1, x2, y2, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.moveTo(x1, y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
i used moveTo/lineTo (retained) command, so without thinking i use beginPath.
here the strokeStyle is set on each call, so no need to save context.
example 2 :
function drawImageRotated( img, x, y, rotation ) {
ctx.save();
ctx.translate(x + img.width/2, y + img.height/2); // translate to the middle of the image
ctx.rotate(rotation); // rotate context
ctx.drawImage(img, x - img.width/2, y - img.height/2 ); // draw the image in rotated context
ctx.restore();
}
here no need to use beginPath() since i use only a direct command (drawImage).
But concerning canvas status, i don't want the context to remain translated/rotated after the call. So i started by a save, and ended by a restore, so the caller keeps the same context after the call.
beginPath() is used to start drawing a contour, you will need it. Then we use stroke() to actually draw to the HTML5 Canvas visually.
ctx.beginPath(); // Start drawing
// Drawing Methods Here
ctx.stroke() // Show what we drew.
Fiddle
restore() is for changes in the state of the Canvas: rotation, scaling, skew, etc. This method reverts it to the last state of the Canvas that was saved. But it isn't used for actually drawing on our canvas, but changing it's state.

Categories