diff --git a/src/Context.js b/src/Context.js index ae3f568a..418c5a76 100644 --- a/src/Context.js +++ b/src/Context.js @@ -519,7 +519,7 @@ dashArray = shape.getDashArray(), strokeScaleEnabled = shape.getStrokeScaleEnabled(); - if(stroke || strokeWidth) { + if(shape.hasStroke()) { if (!strokeScaleEnabled) { this.save(); this.setTransform(1, 0, 0, 1, 0, 0); diff --git a/src/Shape.js b/src/Shape.js index 7de8c506..4391e8a9 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -78,16 +78,24 @@ return this._getCache(HAS_SHADOW, this._hasShadow); }, _hasShadow: function() { - return (this.getShadowOpacity() !== 0 && !!(this.getShadowColor() || this.getShadowBlur() || this.getShadowOffsetX() || this.getShadowOffsetY())); + return this.getShadowEnabled() && (this.getShadowOpacity() !== 0 && !!(this.getShadowColor() || this.getShadowBlur() || this.getShadowOffsetX() || this.getShadowOffsetY())); }, /** - * returns whether or not a fill is present + * returns whether or not the shape will be filled * @method * @memberof Kinetic.Shape.prototype */ hasFill: function() { return !!(this.getFill() || this.getFillPatternImage() || this.getFillLinearGradientColorStops() || this.getFillRadialGradientColorStops()); }, + /** + * returns whether or not the shape will be stroked + * @method + * @memberof Kinetic.Shape.prototype + */ + hasStroke: function() { + return !!(this.getStroke() || this.getStrokeWidth()); + }, _get: function(selector) { return this.className === selector || this.nodeType === selector ? [this] : []; }, @@ -210,17 +218,18 @@ delete Kinetic.shapes[this.colorKey]; return this; }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke(); + }, drawScene: function(can) { var canvas = can || this.getLayer().getCanvas(), context = canvas.getContext(), drawFunc = this.getDrawFunc(), - applyShadow = this.hasShadow() && this.getShadowEnabled(), - applyOpacity = this.getAbsoluteOpacity() !== 1, + hasShadow = this.hasShadow(), stage, bufferCanvas, bufferContext; - if(drawFunc && this.isVisible()) { - // buffer canvas is needed to apply shadows or opacity - if (applyShadow || applyOpacity) { + if(drawFunc && this.isVisible()) { + if (this._useBufferCanvas()) { stage = this.getStage(); bufferCanvas = stage.bufferCanvas; bufferContext = bufferCanvas.getContext(); @@ -232,27 +241,32 @@ bufferContext.restore(); context.save(); - if (applyShadow) { + if (hasShadow) { context.save(); context._applyShadow(this); context.drawImage(bufferCanvas._canvas, 0, 0); context.restore(); } - if (applyOpacity) { - context._applyOpacity(this); - } - + context._applyOpacity(this); context.drawImage(bufferCanvas._canvas, 0, 0); context.restore(); } // if buffer canvas is not needed else { context.save(); - context._applyOpacity(this); - context._applyLineJoin(this); - context._applyAncestorTransforms(this); + context._applyLineJoin(this); + context._applyAncestorTransforms(this); + + if (hasShadow) { + context.save(); + context._applyShadow(this); drawFunc.call(this, context); + context.restore(); + } + + context._applyOpacity(this); + drawFunc.call(this, context); context.restore(); } } diff --git a/src/shapes/Image.js b/src/shapes/Image.js index d346f1b9..bec26df8 100644 --- a/src/shapes/Image.js +++ b/src/shapes/Image.js @@ -40,6 +40,9 @@ Kinetic.Shape.call(this, config); this.className = IMAGE; }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke(); + }, drawFunc: function(context) { var width = this.getWidth(), height = this.getHeight(), diff --git a/src/shapes/Sprite.js b/src/shapes/Sprite.js index 1dda2ecd..de308d42 100644 --- a/src/shapes/Sprite.js +++ b/src/shapes/Sprite.js @@ -103,6 +103,9 @@ context.closePath(); context.fillShape(this); }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke(); + }, /** * start sprite animation * @method diff --git a/test/unit/Shape-test.js b/test/unit/Shape-test.js index c8bcab0a..5c01d7dc 100644 --- a/test/unit/Shape-test.js +++ b/test/unit/Shape-test.js @@ -388,5 +388,99 @@ suite('Shape-test', function() { }); + // ====================================================== + test('fill with shadow and opacity', function(){ + var stage = addStage(); + var layer = new Kinetic.Layer(); + + var rect = new Kinetic.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: 10, + shadowOpacity: 0.5 + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getX(), 100); + assert.equal(rect.getY(), 50); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + + assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.25;shadowColor=black;shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();'); + + }); + + // ====================================================== + test('stroke with shadow and opacity', function(){ + var stage = addStage(); + + var layer = new Kinetic.Layer(); + + var rect = new Kinetic.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + stroke: 'red', + strokeWidth: 20, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: 10, + shadowOpacity: 0.5 + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getX(), 100); + assert.equal(rect.getY(), 50); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.25;shadowColor=black;shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();'); + }); + + // ====================================================== + test('fill and stroke with shadow and opacity', function(){ + var stage = addStage(); + + var layer = new Kinetic.Layer(); + + var rect = new Kinetic.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'red', + strokeWidth: 20, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: 10, + shadowOpacity: 0.5, + draggable: true + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getX(), 100); + assert.equal(rect.getY(), 50); + + var trace = layer.getContext().getTrace(); + assert.equal(trace, 'clearRect(0,0,578,200);save();save();globalAlpha=0.25;shadowColor=black;shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;drawImage([object HTMLCanvasElement],0,0);restore();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0);restore();'); + + }); }); \ No newline at end of file diff --git a/test/unit/shapes/Image-test.js b/test/unit/shapes/Image-test.js index 03dcebbc..784e3f6a 100644 --- a/test/unit/shapes/Image-test.js +++ b/test/unit/shapes/Image-test.js @@ -210,12 +210,13 @@ suite('Image', function(){ draggable: true, shadowColor: 'black', shadowBlur: 10, - shadowOffset: [20, 20], + shadowOffset: 20, shadowOpacity: 0.2 }); // override color key with black - lion.colorKey = '000000'; + lion.colorKey = '#000000'; + Kinetic.shapes['#000000'] = lion; layer.add(lion); @@ -227,6 +228,10 @@ suite('Image', function(){ //console.log(trace); assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,40);drawImage([object HTMLImageElement],0,0,144,139);beginPath();rect(0,0,144,139);closePath();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,200,40);drawImage([object HTMLImageElement],0,0,144,139);beginPath();rect(0,0,144,139);closePath();restore();'); + var hitTrace = layer.hitCanvas.getContext().getTrace(); + //console.log(hitTrace); + assert.equal(hitTrace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,40);drawImage([object HTMLImageElement],0,0,144,139);beginPath();rect(0,0,144,139);closePath();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,200,40);drawImage([object HTMLImageElement],0,0,144,139);beginPath();rect(0,0,144,139);closePath();restore();'); + done(); }); @@ -303,4 +308,75 @@ suite('Image', function(){ }); + // ====================================================== + test('image with opacity and shadow', function(done) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + + var layer = new Kinetic.Layer(); + darth = new Kinetic.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: [50, 30], + draggable: true, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOpacity: 0.5, + shadowOffset: 20 + }); + + layer.add(darth); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);save();globalAlpha=0.25;shadowColor=black;shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,100);closePath();drawImage([object HTMLImageElement],0,0,100,100);restore();globalAlpha=0.5;beginPath();rect(0,0,100,100);closePath();drawImage([object HTMLImageElement],0,0,100,100);restore();'); + + done(); + + }; + imageObj.src = 'assets/darth-vader.jpg'; + }); + + // ====================================================== + test('image with stroke, opacity and shadow', function(done) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + + var layer = new Kinetic.Layer(); + darth = new Kinetic.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: [50, 30], + draggable: true, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOpacity: 0.5, + shadowOffset: 20, + stroke: 'red', + strokeWidth: 20 + }); + + layer.add(darth); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal(trace, 'clearRect(0,0,578,200);save();save();globalAlpha=0.25;shadowColor=black;shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object HTMLCanvasElement],0,0);restore();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0);restore();'); + + done(); + + }; + imageObj.src = 'assets/darth-vader.jpg'; + }); }); \ No newline at end of file diff --git a/test/unit/shapes/Line-test.js b/test/unit/shapes/Line-test.js index ac62b0b2..b716d7bf 100644 --- a/test/unit/shapes/Line-test.js +++ b/test/unit/shapes/Line-test.js @@ -155,9 +155,9 @@ suite('Line', function() { stage.add(layer); - var relaxedTrace = layer.getContext().getTrace(true); - //console.log(relaxedTrace); - assert.equal(relaxedTrace, 'clearRect();save();save();globalAlpha;shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();drawImage();restore();'); + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal(trace, 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);save();globalAlpha=0.5;shadowColor=black;shadowBlur=20;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=20;strokeStyle=blue;stroke();restore();beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=20;strokeStyle=blue;stroke();restore();'); }); }); \ No newline at end of file