From e6a9447867379026bea9b8339619afa341747024 Mon Sep 17 00:00:00 2001 From: Victor Michnowicz Date: Thu, 8 May 2014 19:50:51 -0400 Subject: [PATCH 1/5] Try and make it faster --- src/Layer.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Layer.js b/src/Layer.js index aa4489c6..edba647c 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -92,10 +92,31 @@ return null; } }, + _getImageData: function(x, y) { + var width = this.hitCanvas.width, + height = this.hitCanvas.height; + + if (width && height && !this.imageData) { + this.imageData = this.hitCanvas.context._context.getImageData(0, 0, this.hitCanvas.width, this.hitCanvas.height); + } + + if (this.imageData && typeof x === 'number' && typeof y === 'number') { + var index = (y * width ) + x; + + return [ + this.imageData.data[4 * index + 0] , // Red value + this.imageData.data[4 * index + 1], // Green value + this.imageData.data[4 * index + 2], // Blue value + this.imageData.data[4 * index + 3] // Alpha val + ]; + } + + return this.imageData || null; + }, _getIntersection: function(pos) { - var p = this.hitCanvas.context._context.getImageData(pos.x, pos.y, 1, 1).data, - p3 = p[3], - colorKey, shape; + var p = this._getImageData(pos.x, pos.y), + p3 = p[3], + colorKey, shape; // fully opaque pixel if(p3 === 255) { From 7d16ca6cca417de0edfee1f2b531dc957f1f7d97 Mon Sep 17 00:00:00 2001 From: Victor Michnowicz Date: Thu, 8 May 2014 23:27:58 -0400 Subject: [PATCH 2/5] Clear imageData after draw to hit canvas --- src/BaseLayer.js | 1 + src/Layer.js | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/BaseLayer.js b/src/BaseLayer.js index 795ba4cc..0a97d75b 100644 --- a/src/BaseLayer.js +++ b/src/BaseLayer.js @@ -47,6 +47,7 @@ clear: function(bounds) { this.getContext().clear(bounds); this.getHitCanvas().getContext().clear(bounds); + this.imageData = null; return this; }, // extend Node.prototype.setZIndex diff --git a/src/Layer.js b/src/Layer.js index edba647c..631e8786 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -93,11 +93,11 @@ } }, _getImageData: function(x, y) { - var width = this.hitCanvas.width, - height = this.hitCanvas.height; + var width = this.hitCanvas.width || 1, + height = this.hitCanvas.height || 1; - if (width && height && !this.imageData) { - this.imageData = this.hitCanvas.context._context.getImageData(0, 0, this.hitCanvas.width, this.hitCanvas.height); + if (!this.imageData) { + this.imageData = this.hitCanvas.context._context.getImageData(0, 0, width, height); } if (this.imageData && typeof x === 'number' && typeof y === 'number') { @@ -173,6 +173,7 @@ } Kinetic.Container.prototype.drawHit.call(this, canvas, top); + this.imageData = null; return this; }, /** From 3214b680c517ae9bacb0442ea81ce66211c9463a Mon Sep 17 00:00:00 2001 From: Victor Michnowicz Date: Fri, 9 May 2014 10:20:44 -0400 Subject: [PATCH 3/5] Clear cache, tabs to spaces --- src/BaseLayer.js | 1 - src/Layer.js | 50 ++++++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/BaseLayer.js b/src/BaseLayer.js index 0a97d75b..795ba4cc 100644 --- a/src/BaseLayer.js +++ b/src/BaseLayer.js @@ -47,7 +47,6 @@ clear: function(bounds) { this.getContext().clear(bounds); this.getHitCanvas().getContext().clear(bounds); - this.imageData = null; return this; }, // extend Node.prototype.setZIndex diff --git a/src/Layer.js b/src/Layer.js index 631e8786..b8eb8546 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -92,31 +92,34 @@ return null; } }, - _getImageData: function(x, y) { - var width = this.hitCanvas.width || 1, - height = this.hitCanvas.height || 1; + /** + * Get ImageData.data array for an individual pixel on the hit canvas. + * Data is cached as getImageData is an expensive method to call often. + * + * @param {Number} x + * @param {Number} x + * @returns {Array} One-dimensional array containing the data in the RGBA order, with integer values between 0 and 255 + */ + _getImageData: function(x, y) { + var width = this.hitCanvas.width || 1, + height = this.hitCanvas.height || 1, + index = (y * width ) + x; - if (!this.imageData) { - this.imageData = this.hitCanvas.context._context.getImageData(0, 0, width, height); - } + if (!this.imageData) { + this.imageData = this.hitCanvas.context._context.getImageData(0, 0, width, height); + } - if (this.imageData && typeof x === 'number' && typeof y === 'number') { - var index = (y * width ) + x; - - return [ - this.imageData.data[4 * index + 0] , // Red value - this.imageData.data[4 * index + 1], // Green value - this.imageData.data[4 * index + 2], // Blue value - this.imageData.data[4 * index + 3] // Alpha val - ]; - } - - return this.imageData || null; - }, + return [ + this.imageData.data[4 * index + 0] , // Red + this.imageData.data[4 * index + 1], // Green + this.imageData.data[4 * index + 2], // Blue + this.imageData.data[4 * index + 3] // Alpha + ]; + }, _getIntersection: function(pos) { - var p = this._getImageData(pos.x, pos.y), - p3 = p[3], - colorKey, shape; + var p = this._getImageData(pos.x, pos.y), + p3 = p[3], + colorKey, shape; // fully opaque pixel if(p3 === 255) { @@ -173,7 +176,7 @@ } Kinetic.Container.prototype.drawHit.call(this, canvas, top); - this.imageData = null; + this.imageData = null; // Clear imageData cache return this; }, /** @@ -192,6 +195,7 @@ clear: function(bounds) { this.getContext().clear(bounds); this.getHitCanvas().getContext().clear(bounds); + this.imageData = null; // Clear imageData cache return this; }, // extend Node.prototype.setVisible From 70564cc1e4928df406295f2663ffe508bc93b6ad Mon Sep 17 00:00:00 2001 From: Victor Michnowicz Date: Sat, 17 May 2014 00:06:46 -0400 Subject: [PATCH 4/5] Update clearing of getImageData cache --- src/Container.js | 3 +++ src/Layer.js | 3 +-- src/Shape.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Container.js b/src/Container.js index eeebaeea..df7a3a0a 100644 --- a/src/Container.js +++ b/src/Container.js @@ -307,6 +307,9 @@ cachedHitCanvas = cachedCanvas && cachedCanvas.hit; if (this.shouldDrawHit()) { + if (layer) { + layer.imageData = null; // Clear getImageData cache + } if (cachedHitCanvas) { this._drawCachedHitCanvas(context); } diff --git a/src/Layer.js b/src/Layer.js index b8eb8546..de4969c7 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -176,7 +176,6 @@ } Kinetic.Container.prototype.drawHit.call(this, canvas, top); - this.imageData = null; // Clear imageData cache return this; }, /** @@ -195,7 +194,7 @@ clear: function(bounds) { this.getContext().clear(bounds); this.getHitCanvas().getContext().clear(bounds); - this.imageData = null; // Clear imageData cache + this.imageData = null; // Clear getImageData cache return this; }, // extend Node.prototype.setVisible diff --git a/src/Shape.js b/src/Shape.js index d0fa738f..cc46614d 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -203,7 +203,7 @@ cachedHitCanvas = cachedCanvas && cachedCanvas.hit; if(this.shouldDrawHit()) { - + layer.imageData = null; // Clear getImageData cache if (cachedHitCanvas) { this._drawCachedHitCanvas(context); } From 1db8fec21ce2e04a0910a19ac57a060f38bc6010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B0=D0=B2=D1=80=D1=91=D0=BD=D0=BE=D0=B2=20=D0=90?= =?UTF-8?q?=D0=BD=D1=82=D0=BE=D0=BD?= Date: Sat, 17 May 2014 21:10:49 +0800 Subject: [PATCH 5/5] test + refactoring --- src/BaseLayer.js | 3 +++ src/Container.js | 2 +- src/Layer.js | 20 +++++--------- src/Shape.js | 2 +- test/unit/Layer-test.js | 58 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/BaseLayer.js b/src/BaseLayer.js index 795ba4cc..126b023e 100644 --- a/src/BaseLayer.js +++ b/src/BaseLayer.js @@ -49,6 +49,9 @@ this.getHitCanvas().getContext().clear(bounds); return this; }, + clearHitCache: function() { + this._hitImageData = undefined; + }, // extend Node.prototype.setZIndex setZIndex: function(index) { Kinetic.Node.prototype.setZIndex.call(this, index); diff --git a/src/Container.js b/src/Container.js index 9cf7e01d..4c55e869 100644 --- a/src/Container.js +++ b/src/Container.js @@ -308,7 +308,7 @@ if (this.shouldDrawHit(canvas)) { if (layer) { - layer.imageData = null; // Clear getImageData cache + layer.clearHitCache(); } if (cachedHitCanvas) { this._drawCachedHitCanvas(context); diff --git a/src/Layer.js b/src/Layer.js index 01e9b431..b01a436b 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -92,28 +92,20 @@ return null; } }, - /** - * Get ImageData.data array for an individual pixel on the hit canvas. - * Data is cached as getImageData is an expensive method to call often. - * - * @param {Number} x - * @param {Number} x - * @returns {Array} One-dimensional array containing the data in the RGBA order, with integer values between 0 and 255 - */ _getImageData: function(x, y) { var width = this.hitCanvas.width || 1, height = this.hitCanvas.height || 1, index = (y * width ) + x; - if (!this.imageData) { - this.imageData = this.hitCanvas.context._context.getImageData(0, 0, width, height); + if (!this._hitImageData) { + this._hitImageData = this.hitCanvas.context.getImageData(0, 0, width, height); } return [ - this.imageData.data[4 * index + 0] , // Red - this.imageData.data[4 * index + 1], // Green - this.imageData.data[4 * index + 2], // Blue - this.imageData.data[4 * index + 3] // Alpha + this._hitImageData.data[4 * index + 0] , // Red + this._hitImageData.data[4 * index + 1], // Green + this._hitImageData.data[4 * index + 2], // Blue + this._hitImageData.data[4 * index + 3] // Alpha ]; }, _getIntersection: function(pos) { diff --git a/src/Shape.js b/src/Shape.js index b50906cd..cfcb6049 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -210,7 +210,7 @@ if(this.shouldDrawHit(canvas)) { if (layer) { - layer.imageData = null; // Clear getImageData cache + layer.clearHitCache(); } if (cachedHitCanvas) { this._drawCachedHitCanvas(context); diff --git a/test/unit/Layer-test.js b/test/unit/Layer-test.js index 17d57d7b..4b2872a7 100644 --- a/test/unit/Layer-test.js +++ b/test/unit/Layer-test.js @@ -292,4 +292,62 @@ suite('Layer', function() { assert.equal(layer.hitGraphEnabled(), true); assert.equal(layer.shouldDrawHit(), true); }); + + // ====================================================== + test('hit graph caching', function() { + var stage = addStage(); + var layer = new Kinetic.Layer(); + var originGetImageData = layer.getHitCanvas().getContext().getImageData; + var count = 0; + layer.getHitCanvas().getContext().getImageData = function() { + count += 1; + return originGetImageData.apply(this, arguments); + }; + + stage.add(layer); + + var circle = new Kinetic.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4 + }); + layer.add(circle); + layer.draw(); + assert.equal(count, 0, 'draw should not touch getImageData'); + var top = stage.content.getBoundingClientRect().top; + stage._mousemove({ + clientX: stage.getWidth() / 2, + clientY: stage.getHeight() / 2 + top + }); + + // while mouse event we need hit canvas info + assert.equal(count, 1, 'getImageData should be called once'); + + stage._mousemove({ + clientX: stage.getWidth() / 2, + clientY: stage.getHeight() / 2 + top + 2 + }); + + assert.equal(count, 1, 'getImageData should not be called, because data is cached'); + + var group = new Kinetic.Group(); + group.cache({ + width : 1, + height : 1 + }); + layer.add(group); + + group.draw(); + + stage._mousemove({ + clientX: stage.getWidth() / 2, + clientY: stage.getHeight() / 2 + top + 2 + }); + + // after drawing group hit cache should be cleared + assert.equal(count, 2, 'while creating new cache getImageData should be called'); + }); }); \ No newline at end of file