Limitations of Paper.js with large numbers of items

4,289 views
Skip to first unread message

Sycren

unread,
Aug 10, 2011, 5:38:48 PM8/10/11
to Paper.js
Just wondering what the limitations of the script are.. If we take the
tadpoles example and have it move rectangles around the screen it runs
faster than a complicated symbol. It can run about 500 pretty easily.
Say we add a transparent squares every 50 frames it slows down right
to a halt. 100 runs this well, 200 is ok, 300.. not so good.

Is this more a problem of using vector graphics or would canvas
perform like this in every case?

Jürg Lehni

unread,
Aug 10, 2011, 5:47:56 PM8/10/11
to pap...@googlegroups.com
What browser are you running your tests on?

Regarding transparency, are you using an item with both fill and stroke? This would require the item to be rendered to a separate canvas first and then blended over with transparency, which would explain why it's slow.

We haven't looked into optimizations too heavily yet, I'm sure there's plenty of room for things to improve still.

There's obviously a little overhead of the library, but when things run slow, usually the biggest part of the code is spent in the native canvas drawing methods.

Perhaps you could use a profiler to find out what's slowing it down?

Jürg

Peter Rietzler

unread,
Aug 11, 2011, 3:47:37 AM8/11/11
to Paper.js
Could you please explain the transparency issue in detail?

Thanks,
Peter

Peter Rietzler

unread,
Aug 11, 2011, 4:08:54 AM8/11/11
to Paper.js
We've profiled the attached example with Google Chrome. The profiler
clearly shows that JS execution of Paper.js is much slower than canvas
rendering (~ 95ms vs 2ms).

<!DOCTYPE HTML>
<html>
<head>
<title>dashboard</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/
d3.js?1.29.1"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/
d3.layout.js?1.29.1"></script>
<script type="text/javascript" src="http://paperjs.org/static/js/
paper.js?1312143495000"></script>
<script type="text/javascript" src="http://documentcloud.github.com/
underscore/underscore-min.js"></script>
<script type="text/paperscript" src="dashboard.js" canvas="canvas">
var g = new Group();
var tm = new Group();
var barchart = new Group();

g.addChild(tm);
g.addChild(barchart);

d3.json("data/flare.json", function(json) {
var height = 1000;
var width = 1000;
var color = d3.scale.category20c();

nodes = d3.layout.treemap()
.size([width / 2, height])
.sticky(true)
.value(function(d) { return d.size; })
.nodes(json);

_(nodes).each(function(d) {
// tree map
var rect = new Path.Rectangle([d.x, d.y], [d.dx - 1, d.dy - 1]);
rect.strokeColor = "black";
var gradient = new Gradient(["white", color(d.name)], "radial");
rect.fillColor = d.children ? new GradientColor(gradient,
rect.position, rect.bounds.rightCenter) : null;
tm.addChild(rect);
});

var sorted = _(nodes).sortBy(function(d) { return d.value; });
var data = _(sorted).first(100);
var max = _(data).max(function(d) { return d.value; }).value;
var barWidth = (width / 2) / data.length;
var barHeight = d3.scale.linear().domain([0, max]).range([0,
height]);

_(data).each(function(d, i) {
var rect = new Path.Rectangle([i * barWidth + width / 2, height -
barHeight(d.value)], [barWidth, barHeight(d.value)]);
rect.fillColor = color(i);
barchart.addChild(rect);
});

var text = new PointText([400, 700]);
text.characterStyle = {
fontSize: 400,
fillColor: new RGBColor(0, 0, 0, 0.5)
};

// Set the content of the text item:
text.content = '3';
g.addChild(text);


var iteration = 1;
setInterval(function() {
var data = [];
_(_.range(iteration, iteration + 100)).each(function(i) {
data.push(sorted[i % sorted.length]);
});
var max = _(data).max(function(d) { return d.value; }).value;
var barHeight = d3.scale.linear().domain([0, max]).range([0,
height]);

barchart.removeChildren();
_(data).each(function(d, i) {
var rect = new Path.Rectangle([i * barWidth + width / 2, height -
barHeight(d.value)], [barWidth, barHeight(d.value)]);
rect.strokeColor = "black";
rect.fillColor = color(i);
barchart.addChild(rect);
});
view.draw();

iteration++;
}, 100);

view.draw();
});
</script>
</head>
<body>
<canvas id="canvas" width="1000px" height="1000px"></canvas>
</body>
</html>




On Aug 10, 11:47 pm, Jürg Lehni <li...@scratchdisk.com> wrote:

Peter Rietzler

unread,
Aug 11, 2011, 4:27:33 AM8/11/11
to Paper.js
The JSON data "data/flare.json" can be downloaded here:
http://mbostock.github.com/d3/data/flare.json

Regards
Peter

On Aug 11, 10:08 am, Peter Rietzler <peter.rietz...@smarter-

Jürg Lehni

unread,
Aug 11, 2011, 5:33:01 AM8/11/11
to pap...@googlegroups.com
Are you hosting this somewhere online already? Due to cross domain references I cannot simply test this on Chrome locally.

Profiling on WebKit showed other results, I'm not sure you're reading the profiler correctly?

Regards,

Jürg

Jürg Lehni

unread,
Aug 11, 2011, 6:11:03 AM8/11/11
to pap...@googlegroups.com
The issue is with how canvas handles stroking and filling separately.

We wanted opacity set to something below 1.0 not to make the fill appear behind the stroke (mimicking the way it's handled in Illustrator and thus Scriptographer), so the solution was to draw everything without opacity into a separate canvas and then render the image with opacity into the main canvas. This is a bottleneck indeed.

Alternatives could be to convert stroke to non-overlapping fill once we implement a path stroker / outliner and see how that performs.

We're open to other suggestions.

Jürg

Peter Rietzler

unread,
Aug 12, 2011, 8:29:12 AM8/12/11
to Paper.js
We've switched to Safari (5.1, Mac/Lion) to dig into some details of
our example. We are just evaluating how fast a Canvas based object
model could be - so we're not charging paper.js of being slow - this
is just feedback that may be useful to you.

The number 95ms vs. 2ms in our last example was simply dead wrong,
because the canvas operations are not included in the browsers "paint
time". Instead, they are included in the javascript execution time.

Our example turned out to divide into three main areas - each of them
approximately eating up 1/3 of the time.

1) browser releated stuff, like repainting etc...
2) creation of the paper.js object model
3) paper.js repaint (incl. canvas operations)

ad 2) removing 100 paths (rects) of a group and recreate another 100
paths (rects) takes 33% of overall time. That could be worth
investigating. We've had another example were we converted rather
complex SVG paths to paper.js paths and also observed that the
creation of the object model took pretty much time.

ad 3) ~1/3 of the time spend in paper.js redraw are canvas operations,
2/3 is javascript execution.

There was another (Javascript, not paper.js) finding that made me
curious enough to try out something that seemed absolutely irrelevant
regarding performance - with an interesting outcome: The method isZero
(line 708) called within drawSegment takes 4,5% of total execution
time. That made me curious because the method does nothing but access
two properties which leads to the execution of two accessor functions.
So, I've inlined the accessors (return this._x == 0 && this._y ==
0; ... changed without further investigation for correctness!) and
the execution time immediately dropped to 0.8%.



On Aug 10, 11:47 pm, Jürg Lehni <li...@scratchdisk.com> wrote:

Jürg Lehni

unread,
Aug 12, 2011, 10:29:08 AM8/12/11
to pap...@googlegroups.com
Hi Peter,

The observation about isZero() is a good one. For Segments we override Point with SegmentPoint, so we can intercept changes to its coordinates (x & y) through getters / setters. The price we pay is slightly slower performance in property access, which is why in most SegmentPoint specific code we use its internal properties _x & _y directly. The internal isZero() calls are one place where we forgot to do so, and it does indeed show. I shall fix that. There might be other places too.

BTW, the inlining of _x & _y will not work for instances of Point, only for SegmentPoint. In the base class Point, x & y are real properties, not accessors, and _x & _y are not there.

The other observation about DOM creation being 'slow' is not a surprise. It was never intended to be used that way, and we haven't optimised the Path.* constructors or even the DOM hierarchy methods for speed yet. If you're creating and destroying the DOM on each frame, why not just use Canvas drawing operations directly? The performance improvements should be rather big.

Instead of destroying and recreating the rects, you might want to try scaling them in place? You can either modify their segments, or just set their bounds to the new dimensions.

To clarify: The motivation for creating Paper.js was to provide a well abstracted vector graphics toolset that facilitates the creation and manipulation of vector shapes very much in a way that for example a drawing application like InkScape or Illustrator would require. We provide the complicated math to calculate path lengths, bounding boxes including stroke styles, dashing, etc. It wasn't the plan to be a high performance drawing toolkit for shapes that are created and destroyed on each frame.

Even the fact that it seemed so well suited for animations came as quite a surprise to us. We only realised this once we ported the foundations of Scriptographer over.

Jürg

Jürg Lehni

unread,
Aug 12, 2011, 10:33:09 AM8/12/11
to pap...@googlegroups.com
PS:

From my tests it seemed that 25% of the time are spent in some underscore.js code. Did you look into that?

On 12 Aug 2011, at 14:29, Peter Rietzler wrote:

Sycren

unread,
Aug 12, 2011, 11:17:18 AM8/12/11
to Paper.js
I am using the latest version of google chrome.

With regards to transparency, will there be any changes to increase
the performance?
I think adding and removing is not the largest performance loss.

Another thing, Quad trees would be very efficient for things like the
hit test and in other areas.
I am adding this to my code now, perhaps it could be added to paper.js

I will try and make an example and send you a link.

Jürg Lehni

unread,
Aug 12, 2011, 4:55:32 PM8/12/11
to pap...@googlegroups.com
We are considering offering an alternative backend that uses SVG instead of Canvas. On Chrome, SVG now appears to perform better for a lot of cases than Canvas, so the situation has changed substantially since we started working on Paper.js in February. One advantage of SVG might be better transparency handling.

For Canvas, one solution could be to tolerate the mixing of fills and strokes. This could be a flag per project perhaps? I don't think there will be another solution on the short term.

As for the Quad trees, how easy is it to keep them up to date with moving items? And what do they reference? The bounds of the item, or just the center? I'm very interested in this work, and we're happy to integrate code if it makes sense.

Jürg

Jürg Lehni

unread,
Aug 12, 2011, 5:02:47 PM8/12/11
to Jürg Lehni, pap...@googlegroups.com
Just to make sure to not have given the wrong impression: There are many other situations where Canvas still outperforms SVG (and SVG on Chrome), e.g. on Safari 5.1. And there are other situations where inline SVG is not available, e.g. iOS. So the idea of an alternative backend would simply be to offer a choice over whatever works better for a given situation, not to replace Canvas.

Jürg

Sycren

unread,
Aug 12, 2011, 5:13:53 PM8/12/11
to Paper.js
Heres a good example of quadtrees with canvas:
http://www.mikechambers.com/blog/2011/03/21/javascript-quadtree-implementation/
Looking at the code on github (MIT licence), much of it seems that it
could slot nicely into paper.js although I am unsure where exactly..

He uses easel.js for drawing. Code:
https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree

Mixing of fills and strokes? Not quite sure what you mean there?

I have been using this:

var subtilin = new Path.Circle([0,0], 40);
a = new RGBColor(1,0,0,0.05);
subtilin.fillColor = a;

No stroke.

I love paper.js, its the best canvas library out there! In fact my
project is for a masters thesis in bioinformatics. Paper.js is
powering a complex simulation of bacteria communicating with each
other, which is why performance is quite important.

Jürg Lehni

unread,
Aug 13, 2011, 3:07:15 AM8/13/11
to pap...@googlegroups.com
About QuadTrees:

I doubt that this is the solution, since it requires the creation of a full tree from scratch on each frame. This would be extreme overkill if for example all we executed was one hit-test.

What we would need is a structure that could be updated for the changed items only without rebuilding the whole thing. This could then be kept up-to-date for us behind the scenes, without the user even needing to know it's there.

If anybody has suggestions?

Transparency:

If there's only a fill, the drawing should already happen at the optimal speed. Perhaps you could put an example up online for me to test?

Sycren

unread,
Aug 13, 2011, 8:30:54 AM8/13/11
to Paper.js
Here is a modified version of your flocking tadpole example:
http://deeplogic.info/project/squares/

About Quadtrees:

It would be very useful if there were large numbers of objects on the
screen and you were using some kind of collision detection against all
of them. For example, in the tadpole demo you compare one tadpole
against all other tadpoles in a for loop for cohesion, alignment,
separation. If quad trees were used then one tadpole would only be
tested against a group taking the complexity of the algorithm from
3n^2 to something a lot lower.

An alternative perhaps would be to use a quadtree like implementation
were instead of dividing the view every frame, we split the view into
a number of similar sized rectangular groups and sort each item on the
first frame and check if an object passes from one group to another on
every other frame.

On transparency, if there is only fill and it is running already at
optimal speed, are there any more possible optimisations for paper.js
to allow it to perform better?

On Aug 13, 8:07 am, Jürg Lehni <li...@scratchdisk.com> wrote:
> About QuadTrees:
>
> I doubt that this is the solution, since it requires the creation of a full tree from scratch on each frame. This would be extreme overkill if for example all we executed was one hit-test.
>
> What we would need is a structure that could be updated for the changed items only without rebuilding the whole thing. This could then be kept up-to-date for us behind the scenes, without the user even needing to know it's there.
>
> If anybody has suggestions?
>
> Transparency:
>
> If there's only a fill, the drawing should already happen at the optimal speed. Perhaps you could put an example up online for me to test?
>
> On 12 Aug 2011, at 23:13, Sycren wrote:
>
>
>
>
>
>
>
> > Heres a good example of quadtrees with canvas:
> >http://www.mikechambers.com/blog/2011/03/21/javascript-quadtree-imple...
> > Looking at the code on github (MIT licence), much of it seems that it
> > could slot nicely into paper.js although I am unsure where exactly..
>
> > He uses easel.js for drawing. Code:
> >https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript...

Sam Mateosian

unread,
Jan 30, 2013, 11:02:24 AM1/30/13
to pap...@googlegroups.com, Jürg Lehni
What's the status of the SVG backer?  I would LOVE this - specifically for iOS retina display and performance.  But what's that about no inline svg in iOS?  I've done jquery svg experiments in iOS and it renders as inline svg - works fine.  Maybe this post is just older than the svg support?

Loving paper.js in general - I've been building a real time collaborative doodle app in paper.js and meteor - super fun project (for me) http://thread.meteor.com/ - seems like a few other people on this group trying to do the same thing.

Best,
Sam
Reply all
Reply to author
Forward
0 new messages