Request: Add persistence / avoid clearing the canvas at each frame / redraw only changed items

464 views
Skip to first unread message

Arthur Masson

unread,
Feb 26, 2014, 9:44:47 AM2/26/14
to pap...@googlegroups.com
Hello,

I have used paper.js for a while now, and I am really stuck with the fact that the canvas is always cleared.

The user might want to clear it differently for trails effects (fade out the last frame with a ctx.fillStyle = 'rgba(0,0,0,.1)' & ctx.fillRect(0,0,width,height) ), or for static classical drawing applications (the user always adds new things on top of old ones, no need to redraw previous items).

I'm developing  Romanesco and there are some performances issues since Paper.js redraws everything at each frame. 

Instead, it would be better to draw only the last items that were changed (and possibly the items on top of them).

To do that, one would need to add a persistence attribute to the View class:
  • persistence = 0: the canvas is totally cleared, and everything is redrawn at each frames,
  • 0<persistence<1: the canvas is fade out depending on the persistence amount and only changed items are redrawn (and possibly items on top of them, this could be a performance option? ),
  • persistence = 1: canvas is never cleared,
like so in the 'update' function of the CanvasView class:  

if (this._persistence>0 || this._persistence<1)
{
var alpha =  1.0-this._persistence;
var temp = ctx.fillStyle;
ctx.fillStyle = 'rgba(0,0,0,' + alpha + ')';
ctx.fillRect(0,0, size.width + 1, size.height + 1);
ctx.fillStyle = temp;
}
else if (this._persistence<=0)
{
ctx.clearRect(0, 0, size.width + 1, size.height + 1);
}

And add an hasChanged attribute to the Item class which would be set to true in the 'change' event, and to false in the 'draw' function (after the test to check if item needs to be drawn. This test would of cours check if hasChanged is true). 

I tried to make a pull request on github, but you I am stuck with the Item class: 'this' is a layer in the draw function of the Item.js file... So this._hasChanged is different in the 'change' event and in the 'draw' function.

I gave up this pull request, since I think that someone more experienced with paper.js could make this small change fairly easily.

Thanks !
Arthur


Jürg Lehni

unread,
Feb 26, 2014, 10:29:29 AM2/26/14
to pap...@googlegroups.com
So if persistence is 0 then all items are redrawn and if it's something else than 0 only the ones that change are redrawn? Why would that be? This would still mess with the fade each time you're simply moving a segment.

I know we need to address the performance issues caused by redrawing the whole scene each time, but the idea I have is to allow a multi-canvas mode where the simple assumption is made that each Layer has its own canvas element and these are layered on top of each other through the browser's DOM.

If you know an item doesn't change you simply move it to a Layer in which you don't modify any items anymore, and you use a separate layer for all the constantly changing things. That way things would automatically be buffered for you, and use the browser's fast rendering pipeline which can be quite a bit faster than blitting canvas pixels in JS through context.putImageData(), unfortunately.

But I am weary to add fading effects since I want this library to be backend agnostic, so I can only add things that can easily be added in SVG / WebGL as well at a later point.

But maybe the fading is something you could then achieve through CSS, since each layer's canvas element will then be a separate layer that won't change?

J

--
You received this message because you are subscribed to the Google Groups "Paper.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to paperjs+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Arthur Masson

unread,
Feb 27, 2014, 6:10:21 PM2/27/14
to pap...@googlegroups.com
Hi ! Thanks for your quick answer :-)

Ok, with two layers, one for changing items and another for still items, it should work.

Actually, the only thing really important is to be able to control how the view is refreshed. 

In other words, the trick (for tails and drawing apps) is just to avoid clearing everything at each frame, but drawing a black transparent rectangle on the whole screen instead. In this way, the previous frame fades slightly, (and so did the ones before). This is done by the 10 lines of the 'update' function of CanvasView that I gave in my previous message (like in this example, line 114).

In addition, one can avoid redrawing unchanged items, just by adding a simple "changed" flag, (or by using two layers one for changing items and another for static ones).

Here is another example that I made the other day, using the same technique in webgl.
I made this from the three.js example.

I have been working a bit for art performances, installations, vj-ing sets, and I use this trailing effect all the time, since it is a very good way to give life to simple particles or geometries. Moreover, most basic drawing apps work by adding color to a static layer, without always drawing everything.

So that is why, in my opinion, this request seems easy to implement, and necessary :-)

Again, that should work with two (or more?) layers, and this is the only way to do it with SVG. I don't know if it's the simplest method for WebGL and Canvas, but it wont be too complicated anyway.

Thanks

Arthur

Arthur Masson

unread,
Feb 28, 2014, 11:33:03 AM2/28/14
to pap...@googlegroups.com
Two more things:

  • With two or more layers, how would you manage shapes which are static most of the time, but still changes sometime? This case would be easier to handle with a 'changed' flag for each item, right?
  • Why 'this' is of type 'Layer' and not 'Item' in the 'draw' function of Item.js? If I get the answer I could implement the request.
Arthur

Jürg Lehni

unread,
Mar 1, 2014, 12:42:58 PM3/1/14
to pap...@googlegroups.com
On Feb 28, 2014, at 17:33 , Arthur Masson <arth...@gmail.com> wrote:

Two more things:

  • With two or more layers, how would you manage shapes which are static most of the time, but still changes sometime? This case would be easier to handle with a 'changed' flag for each item, right?
If an item changes then the whole layer that it is part of will have to be redrawn.
  • Why 'this' is of type 'Layer' and not 'Item' in the 'draw' function of Item.js? If I get the answer I could implement the request.
Layer extends Group, which extends Item. They all share the implementation of Item#draw(), which calls the _draw() method on `this`. _draw() is overridden by many of the subclasses to do the actual drawing.

 If 'this' points to a Layer inside Item#draw() then it's because the item being drawn is a layer. The Group#_draw() method as used by Layer then calls #draw() on all the children.

I hope this makes sense.

But before you create a pull request, let's make sure that what you have in mind is something I'm willing to merge. I'm not convinced that fade effects should be part of paper.js (but I understand of course why they would be useful to you). We can't accommodate all special cases in the core of the library, otherwise maintaining the code will quickly become complicated.

J

Arthur Masson

unread,
Mar 3, 2014, 9:28:55 AM3/3/14
to pap...@googlegroups.com
Ok, I implemented the request here, but I didn't pull it yet.

Basically it consists in:

View.js line 31:
this._persistence = 0;


CanvasView.js line 97:
if (!clear && this._persistence>0 && this._persistence<1)
{
var alpha =  1.0-this._persistence;
var temp = ctx.fillStyle;
ctx.fillStyle = 'rgba(0,0,0,' + alpha + ')';
ctx.fillRect(0,0, size.width + 1, size.height + 1);
ctx.fillStyle = temp;
}
else if (clear || this._persistence<=0)
{
ctx.clearRect(0, 0, size.width + 1, size.height + 1);
}
if (clear)
{
layers = this._project.layers
for (var i = 0, l = layers.length; i < l; i++)
layers[i].needsUpdate();
}

Item.js line 51:
_hasChanged: true,

 line 217:
this._hasChanged = true;

var nextParent = cacheParent;
while(nextParent)
{
nextParent._hasChanged = true;
nextParent = nextParent._parent;
}
line 268:
/**
* Item needs update: set _hasChanged flag to true, notify every children as well 
*/
needsUpdate: function() {
this._hasChanged = true;
if (this._children) {
for (var i = 0, l = this._children.length; i < l; i++)
this._children[i].needsUpdate();
}
},

line 3450:
if (!this._visible || this._opacity === 0 || this._hasChanged === false)
return;

if(this._symbol)
this._symbol._definition._hasChanged = true;

if(this._project && this._project.view && this._project.view._persistence > 0)
this._hasChanged = false;

You can check two examples: the tail effect and the performance benchmark (click to toggle visibility of modified clones, press 'space' to toggle persistence).
You can find the code here, under drawings.

There should be no change to port it on OpenGL (same thing with glClear() ), and to port it to SVG we should copy the current image to another one, fade it if necessary and draw the new SVG on top.

I hope you like it and find it usefull, I can pull request whenever you give me the green light.

Arthur

Arthur Masson

unread,
Mar 4, 2014, 4:52:39 AM3/4/14
to pap...@googlegroups.com
Note that for now, when persistence is greater than 0, the user has to explicitly refresh the shapes he wants to redraw (with the Item.needsUpdate() and view.update(true) functions), so documentation should be updated.

It should be possible to implement an auto-update mode which redraw only shapes that are necessary to update: one can check the intersecting shapes (or bounding box to keep good performances) to flag them as changed, and recursively recheck the need of update on the new neighbors. However the number of shapes to redraw can change drastically so performances gain will not be constant (sometimes it might be faster to just redraw everything without considering which shapes are close to the newly updated area), and probably others problems will arise (there is no way to redraw the canvas as it was before if persistence>0 and objects have moved in the updated areas). So for now I think it's wise to let the user manage their canvas as they want to.

Arthur
Reply all
Reply to author
Forward
0 new messages