/////////////////////////////////////////////////////////////////////// // Shape /////////////////////////////////////////////////////////////////////// /** * Shape constructor. Shapes are used to objectify drawing bits of a KineticJS * application * @constructor * @augments Kinetic.Node * @param {Object} config * @config {String|Object} [fill] can be a string color, a linear gradient object, a radial * gradient object, or a pattern object. * @config {String} [stroke] stroke color * @config {Number} [strokeWidth] stroke width * @config {String} [lineJoin] line join can be "miter", "round", or "bevel". The default * is "miter" * @config {Object} [shadow] shadow object * @config {String} [detectionType] shape detection type. Can be "path" or "pixel". * The default is "path" because it performs better */ Kinetic.Shape = Kinetic.Node.extend({ init: function(config) { this.setDefaultAttrs({ detectionType: 'path' }); this.nodeType = 'Shape'; this.appliedShadow = false; // call super constructor this._super(config); }, /** * get canvas context tied to the layer * @name getContext * @methodOf Kinetic.Shape.prototype */ getContext: function() { return this.getLayer().getContext(); }, /** * get canvas tied to the layer * @name getCanvas * @methodOf Kinetic.Shape.prototype */ getCanvas: function() { return this.getLayer().getCanvas(); }, /** * helper method to stroke the shape and apply * shadows if needed * @name stroke * @methodOf Kinetic.Shape.prototype */ stroke: function(context) { var go = Kinetic.Global; var appliedShadow = false; if(this.attrs.stroke || this.attrs.strokeWidth) { context.save(); if(this.attrs.shadow && !this.appliedShadow) { appliedShadow = this._applyShadow(context); } var stroke = this.attrs.stroke ? this.attrs.stroke : 'black'; var strokeWidth = this.attrs.strokeWidth ? this.attrs.strokeWidth : 2; context.lineWidth = strokeWidth; context.strokeStyle = stroke; context.stroke(context); context.restore(); } if(appliedShadow) { this.stroke(context); } }, /** * helper method to fill the shape with a color, linear gradient, * radial gradient, or pattern, and also apply shadows if needed * @name fill * @methodOf Kinetic.Shape.prototype * */ fill: function(context) { var appliedShadow = false; var fill = this.attrs.fill; if(fill) { context.save(); if(this.attrs.shadow && !this.appliedShadow) { appliedShadow = this._applyShadow(context); } var s = fill.start; var e = fill.end; var f = null; // color fill if( typeof fill == 'string') { f = this.attrs.fill; context.fillStyle = f; context.fill(context); } // pattern else if(fill.image) { var repeat = !fill.repeat ? 'repeat' : fill.repeat; f = context.createPattern(fill.image, repeat); context.save(); if(fill.scale) { context.scale(fill.scale.x, fill.scale.y); } if(fill.offset) { context.translate(fill.offset.x, fill.offset.y); } context.fillStyle = f; context.fill(context); context.restore(); } // linear gradient else if(!s.radius && !e.radius) { var grd = context.createLinearGradient(s.x, s.y, e.x, e.y); var colorStops = fill.colorStops; // build color stops for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } f = grd; context.fillStyle = f; context.fill(context); } // radial gradient else if((s.radius || s.radius === 0) && (e.radius || e.radius === 0)) { var grd = context.createRadialGradient(s.x, s.y, s.radius, e.x, e.y, e.radius); var colorStops = fill.colorStops; // build color stops for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } f = grd; context.fillStyle = f; context.fill(context); } else { f = 'black'; context.fillStyle = f; context.fill(context); } context.restore(); } if(appliedShadow) { this.fill(context); } }, /** * helper method to fill text and appy shadows if needed * @param {String} text * @name fillText * @methodOf Kinetic.Shape.prototype */ fillText: function(context, text) { var appliedShadow = false; if(this.attrs.textFill) { context.save(); if(this.attrs.shadow && !this.appliedShadow) { appliedShadow = this._applyShadow(context); } context.fillStyle = this.attrs.textFill; context.fillText(text, 0, 0); context.restore(); } if(appliedShadow) { this.fillText(contex, text, 0, 0); } }, /** * helper method to stroke text and apply shadows * if needed * @name strokeText * @methodOf Kinetic.Shape.prototype * @param {String} text */ strokeText: function(context, text) { var appliedShadow = false; if(this.attrs.textStroke || this.attrs.textStrokeWidth) { context.save(); if(this.attrs.shadow && !this.appliedShadow) { appliedShadow = this._applyShadow(context); } // defaults var textStroke = this.attrs.textStroke ? this.attrs.textStroke : 'black'; var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2; context.lineWidth = textStrokeWidth; context.strokeStyle = textStroke; context.strokeText(text, 0, 0); context.restore(); } if(appliedShadow) { this.strokeText(context, text, 0, 0); } }, /** * helper method to draw an image and apply * a shadow if neede * @name drawImage * @methodOf Kinetic.Shape.prototype */ drawImage: function() { var appliedShadow = false; var context = arguments[0]; context.save(); var a = Array.prototype.slice.call(arguments); if(a.length === 6 || a.length === 10) { if(this.attrs.shadow && !this.appliedShadow) { appliedShadow = this._applyShadow(context); } if(a.length === 6) { context.drawImage(a[1], a[2], a[3], a[4], a[5]); } else { context.drawImage(a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } } context.restore(); if(appliedShadow) { this.drawImage.apply(this, a); } }, /** * helper method to set the line join of a shape * based on the lineJoin property * @name applyLineJoin * @methodOf Kinetic.Shape.prototype */ applyLineJoin: function(context) { if(this.attrs.lineJoin) { context.lineJoin = this.attrs.lineJoin; } }, /** * apply shadow. return true if shadow was applied * and false if it was not */ _applyShadow: function(context) { var s = this.attrs.shadow; if(s) { var aa = this.getAbsoluteAlpha(); // defaults var color = s.color ? s.color : 'black'; var blur = s.blur ? s.blur : 5; var offset = s.offset ? s.offset : { x: 0, y: 0 }; if(s.alpha) { context.globalAlpha = s.alpha * aa; } context.shadowColor = color; context.shadowBlur = blur; context.shadowOffsetX = offset.x; context.shadowOffsetY = offset.y; this.appliedShadow = true; return true; } return false; }, /** * determines if point is in the shape * @param {Object|Array} point point can be an object containing * an x and y property, or it can be an array with two elements * in which the first element is the x component and the second * element is the y component */ intersects: function() { var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)); var stage = this.getStage(); // path detection if(this.attrs.detectionType === 'path') { var pathCanvas = stage.pathCanvas; var pathCanvasContext = pathCanvas.getContext(); this._draw(pathCanvas); return pathCanvasContext.isPointInPath(pos.x, pos.y); } // pixel detection if(this.imageData) { var w = stage.attrs.width; var alpha = this.imageData.data[((w * pos.y) + pos.x) * 4 + 3]; return (alpha); } // default return false; }, _draw: function(canvas) { if(this.attrs.drawFunc) { var stage = this.getStage(); var context = canvas.getContext(); var family = []; var parent = this.parent; family.unshift(this); while(parent) { family.unshift(parent); parent = parent.parent; } context.save(); for(var n = 0; n < family.length; n++) { var node = family[n]; var t = node.getTransform(); var m = t.getMatrix(); context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } /* * pre styles include alpha, linejoin */ var absAlpha = this.getAbsoluteAlpha(); if(absAlpha !== 1) { context.globalAlpha = absAlpha; } this.applyLineJoin(context); // draw the shape this.appliedShadow = false; this.attrs.drawFunc.call(this, canvas.getContext()); context.restore(); } } }); // add getters and setters Kinetic.Node.addGettersSetters(Kinetic.Shape, ['fill', 'stroke', 'lineJoin', 'strokeWidth', 'shadow', 'drawFunc', 'filter']); /** * set fill which can be a color, linear gradient object, * radial gradient object, or pattern object * @name setFill * @methodOf Kinetic.Shape.prototype * @param {String|Object} fill */ /** * set stroke color * @name setStroke * @methodOf Kinetic.Shape.prototype * @param {String} stroke */ /** * set line join * @name setLineJoin * @methodOf Kinetic.Shape.prototype * @param {String} lineJoin. Can be miter, round, or bevel. The * default is miter */ /** * set stroke width * @name setStrokeWidth * @methodOf Kinetic.Shape.prototype * @param {Number} strokeWidth */ /** * set shadow object * @name setShadow * @methodOf Kinetic.Shape.prototype * @param {Object} config */ /** * set draw function * @name setDrawFunc * @methodOf Kinetic.Shape.prototype * @param {Function} drawFunc drawing function */ /** * get fill * @name getFill * @methodOf Kinetic.Shape.prototype */ /** * get stroke color * @name getStroke * @methodOf Kinetic.Shape.prototype */ /** * get line join * @name getLineJoin * @methodOf Kinetic.Shape.prototype */ /** * get stroke width * @name getStrokeWidth * @methodOf Kinetic.Shape.prototype */ /** * get shadow object * @name getShadow * @methodOf Kinetic.Shape.prototype */ /** * get draw function * @name getDrawFunc * @methodOf Kinetic.Shape.prototype */