Unexpected behavior of cached shapes
Hi,
I'm trying to upgrade KineticJS 4.5 to 5.1 and ran into some issues with the new caching mechanism. Caching is now the only way to use filters on images, but it has some serious bugs. Symptoms are numerous, but from what I've learned so far they are caused by two design flaws.
-
Shape transformations are applied twice
If a shape had some transformations defined (like scaling, rotation) before a call to
cachethen these will be applied twice - once when the cache is created and again when the cache is drawn on layer. This includes transformations passed as parameters to the shape constructor. -
Cache canvas by default is not large enough to hold the entire transformed shape
You can apply transformations, add borders or shadows to make the shape unfit for it's cache canvas. In the case of shapes like Circle or Ellipse, which have their origin in the center of the shape you don't even have to add anything - cache canvas will fit only one quarter of the shape.
Technically the
cachemethod acceptsx,y,width,heightarguments to go around this, but then the positioning of the resulting cached shape has to be altered to draw the shape in the same spot as it's non-cached counterpart.
Examples
I have created examples with three versions of the same code - stage with no caching, stage where cache is called on a shape before it is transformed in any way (this also forced removal of scale or rotation parameters from shape constructors) and a stage where cache is called on a shape just before the layer is drawn.
Here is the first fiddle: http://jsfiddle.net/yD2f6/1/
Without caching we see an image that is squashed vertically, stretched horizontally and has borders. With caching before transformations we can see the borders are gone, because it didn't fit the cache canvas. When cache is called after transformations the scaling is done twice and you can see how bottom border is visible again, because the resulting image is shorter than the cache canvas and there is some room left for it.
Another fiddle: http://jsfiddle.net/q5Dqj/1/
Here I've added rotation and you can see how the bottom image is clipped, because rotated it didn't fit the cache canvas. It's also malformed, because of the second rotation that happened when drawing cache canvas on layer.
In this fiddle you can see how only a quarter of a circle is cached: http://jsfiddle.net/Zram9/1/
This is caused by circle's center origin. In the last fiddle I have tried to overcome this using custom cache parameters. I managed to cover the entire circle, but now I would need to change the shape's position to make it match the original.
http://jsfiddle.net/2Rjr7/1/
I didn't get as far as to play with groups caching. I bet that would be fun.
I tried coming up with a pull request, but it's no trivial fix. Maybe you'll need to rethink the design?
Usage of this caching mechanism (and thus image filters) in it's current state is severely limited. User would expect this to be a drop in optimizer - call cache and save some computation, but in fact you need to meticulously create a virtual canvas with really quirky syntax (gone are many useful constructor parameters). Maybe it would be better to drop caching as a method of a Node and instead introduce caching layers. This is what we used to do, right? If we needed something optimized we were creating a layer, saving it toImage and using wherever. Current implementation does something similar, but I think in a less transparent way.
Cheers!
I'm a total newbie to kineticjs but I came across the quarter circle caching issue raised above... in the interest of time I'm turning off caching for this shape since I don't expect it to be a real performance issue for me...