(function() { // calculate pixel ratio var canvas = document.createElement('canvas'), context = canvas.getContext('2d'), devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1, _pixelRatio = devicePixelRatio / backingStoreRatio; /** * Canvas Renderer constructor * @constructor * @abstract * @memberof Kinetic * @param {Number} width * @param {Number} height */ Kinetic.Canvas = function(config) { this.init(config); }; Kinetic.Canvas.prototype = { init: function(config) { config = config || {}; var width = config.width || 0, height = config.height || 0, pixelRatio = config.pixelRatio || _pixelRatio, contextType = config.contextType || '2d'; this.pixelRatio = pixelRatio; this.element = document.createElement('canvas'); this.element.style.padding = 0; this.element.style.margin = 0; this.element.style.border = 0; this.element.style.background = 'transparent'; this.context = this.element.getContext(contextType); this.setSize(width, height); }, /** * get canvas element * @method * @memberof Kinetic.Canvas.prototype */ getElement: function() { return this.element; }, /** * get canvas context * @method * @memberof Kinetic.Canvas.prototype */ getContext: function() { return this.context; }, /** * set width * @method * @memberof Kinetic.Canvas.prototype * @param {Number} width */ setWidth: function(width) { // take into account pixel ratio this.width = this.element.width = width * this.pixelRatio; this.element.style.width = width + 'px'; }, /** * set height * @method * @memberof Kinetic.Canvas.prototype * @param {Number} height */ setHeight: function(height) { // take into account pixel ratio this.height = this.element.height = height * this.pixelRatio; this.element.style.height = height + 'px'; }, /** * get width * @method * @memberof Kinetic.Canvas.prototype */ getWidth: function() { return this.width; }, /** * get height * @method * @memberof Kinetic.Canvas.prototype */ getHeight: function() { return this.height; }, /** * set size * @method * @memberof Kinetic.Canvas.prototype * @param {Number} width * @param {Number} height */ setSize: function(width, height) { this.setWidth(width); this.setHeight(height); }, /** * clear canvas * @method * @memberof Kinetic.Canvas.prototype */ clear: function() { var context = this.getContext(); var el = this.getElement(); context.clearRect(0, 0, this.getWidth(), this.getHeight()); }, /** * to data url * @method * @memberof Kinetic.Canvas.prototype * @param {String} mimeType * @param {Number} quality between 0 and 1 for jpg mime types */ toDataURL: function(mimeType, quality) { try { // If this call fails (due to browser bug, like in Firefox 3.6), // then revert to previous no-parameter image/png behavior return this.element.toDataURL(mimeType, quality); } catch(e) { try { return this.element.toDataURL(); } catch(e) { Kinetic.Util.warn('Unable to get data URL. ' + e.message) return ''; } } }, /** * fill shape * @method * @memberof Kinetic.Canvas.prototype * @param {Kinetic.Shape} shape */ fill: function(shape) { if(shape.getFillEnabled()) { this._fill(shape); } }, /** * stroke shape * @method * @memberof Kinetic.Canvas.prototype * @param {Kinetic.Shape} shape */ stroke: function(shape) { if(shape.getStrokeEnabled()) { this._stroke(shape); } }, /** * fill, stroke, and apply shadows * will only be applied to either the fill or stroke.  Fill * is given priority over stroke. * @method * @memberof Kinetic.Canvas.prototype * @param {Kinetic.Shape} shape */ fillStroke: function(shape) { var fillEnabled = shape.getFillEnabled(); if(fillEnabled) { this._fill(shape); } if(shape.getStrokeEnabled()) { this._stroke(shape, shape.hasShadow() && shape.hasFill() && fillEnabled); } }, /** * apply shadow * @method * @memberof Kinetic.Canvas.prototype * @param {Kinetic.Shape} shape * @param {Function} drawFunc */ applyShadow: function(shape, drawFunc) { var context = this.context; context.save(); this._applyShadow(shape); drawFunc(); context.restore(); drawFunc(); }, _applyLineCap: function(shape) { var lineCap = shape.getLineCap(); if(lineCap) { this.context.lineCap = lineCap; } }, _applyOpacity: function(shape) { var absOpacity = shape.getAbsoluteOpacity(); if(absOpacity !== 1) { this.context.globalAlpha = absOpacity; } }, _applyLineJoin: function(shape) { var lineJoin = shape.getLineJoin(); if(lineJoin) { this.context.lineJoin = lineJoin; } }, _applyAncestorTransforms: function(node) { var context = this.context, t, m; node._eachAncestorReverse(function(no) { t = no.getTransform(true); m = t.getMatrix(); context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); }, true); }, _clip: function(container) { var context = this.getContext(); context.save(); this._applyAncestorTransforms(container); context.beginPath(); container.getClipFunc()(this); context.clip(); context.setTransform(1, 0, 0, 1, 0, 0); } }; Kinetic.SceneCanvas = function(config) { Kinetic.Canvas.call(this, config); }; Kinetic.SceneCanvas.prototype = { setWidth: function(width) { var pixelRatio = this.pixelRatio; Kinetic.Canvas.prototype.setWidth.call(this, width); this.context.scale(pixelRatio, pixelRatio); }, setHeight: function(height) { var pixelRatio = this.pixelRatio; Kinetic.Canvas.prototype.setHeight.call(this, height); this.context.scale(pixelRatio, pixelRatio); }, _fillColor: function(shape) { var context = this.context, fill = shape.getFill(); context.fillStyle = fill; shape._fillFunc(context); }, _fillPattern: function(shape) { var context = this.context, fillPatternImage = shape.getFillPatternImage(), fillPatternX = shape.getFillPatternX(), fillPatternY = shape.getFillPatternY(), fillPatternScale = shape.getFillPatternScale(), fillPatternRotation = shape.getFillPatternRotation(), fillPatternOffset = shape.getFillPatternOffset(), fillPatternRepeat = shape.getFillPatternRepeat(); if(fillPatternX || fillPatternY) { context.translate(fillPatternX || 0, fillPatternY || 0); } if(fillPatternRotation) { context.rotate(fillPatternRotation); } if(fillPatternScale) { context.scale(fillPatternScale.x, fillPatternScale.y); } if(fillPatternOffset) { context.translate(-1 * fillPatternOffset.x, -1 * fillPatternOffset.y); } context.fillStyle = context.createPattern(fillPatternImage, fillPatternRepeat || 'repeat'); context.fill(); }, _fillLinearGradient: function(shape) { var context = this.context, start = shape.getFillLinearGradientStartPoint(), end = shape.getFillLinearGradientEndPoint(), colorStops = shape.getFillLinearGradientColorStops(), grd = context.createLinearGradient(start.x, start.y, end.x, end.y); if (colorStops) { // build color stops for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } context.fillStyle = grd; context.fill(); } }, _fillRadialGradient: function(shape) { var context = this.context, start = shape.getFillRadialGradientStartPoint(), end = shape.getFillRadialGradientEndPoint(), startRadius = shape.getFillRadialGradientStartRadius(), endRadius = shape.getFillRadialGradientEndRadius(), colorStops = shape.getFillRadialGradientColorStops(), grd = context.createRadialGradient(start.x, start.y, startRadius, end.x, end.y, endRadius); // build color stops for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } context.fillStyle = grd; context.fill(); }, _fill: function(shape, skipShadow) { var context = this.context, hasColor = shape.getFill(), hasPattern = shape.getFillPatternImage(), hasLinearGradient = shape.getFillLinearGradientColorStops(), hasRadialGradient = shape.getFillRadialGradientColorStops(), fillPriority = shape.getFillPriority(); context.save(); if(!skipShadow && shape.hasShadow()) { this._applyShadow(shape); } // priority fills if(hasColor && fillPriority === 'color') { this._fillColor(shape); } else if(hasPattern && fillPriority === 'pattern') { this._fillPattern(shape); } else if(hasLinearGradient && fillPriority === 'linear-gradient') { this._fillLinearGradient(shape); } else if(hasRadialGradient && fillPriority === 'radial-gradient') { this._fillRadialGradient(shape); } // now just try and fill with whatever is available else if(hasColor) { this._fillColor(shape); } else if(hasPattern) { this._fillPattern(shape); } else if(hasLinearGradient) { this._fillLinearGradient(shape); } else if(hasRadialGradient) { this._fillRadialGradient(shape); } context.restore(); if(!skipShadow && shape.hasShadow()) { this._fill(shape, true); } }, _stroke: function(shape, skipShadow) { var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(), dashArray = shape.getDashArray(); if(stroke || strokeWidth) { context.save(); if (!shape.getStrokeScaleEnabled()) { context.setTransform(1, 0, 0, 1, 0, 0); } this._applyLineCap(shape); if(dashArray && shape.getDashArrayEnabled()) { if(context.setLineDash) { context.setLineDash(dashArray); } else if('mozDash' in context) { context.mozDash = dashArray; } else if('webkitLineDash' in context) { context.webkitLineDash = dashArray; } } if(!skipShadow && shape.hasShadow()) { this._applyShadow(shape); } context.lineWidth = strokeWidth || 2; context.strokeStyle = stroke || 'black'; shape._strokeFunc(context); context.restore(); if(!skipShadow && shape.hasShadow()) { this._stroke(shape, true); } } }, _applyShadow: function(shape) { var context = this.context; if(shape.hasShadow() && shape.getShadowEnabled()) { var aa = shape.getAbsoluteOpacity(); // defaults var color = shape.getShadowColor() || 'black'; var blur = shape.getShadowBlur() || 5; var offset = shape.getShadowOffset() || { x: 0, y: 0 }; if(shape.getShadowOpacity()) { context.globalAlpha = shape.getShadowOpacity() * aa; } context.shadowColor = color; context.shadowBlur = blur; context.shadowOffsetX = offset.x; context.shadowOffsetY = offset.y; } } }; Kinetic.Util.extend(Kinetic.SceneCanvas, Kinetic.Canvas); Kinetic.HitCanvas = function(config) { Kinetic.Canvas.call(this, config); }; Kinetic.HitCanvas.prototype = { _fill: function(shape) { var context = this.context; context.save(); context.fillStyle = shape.colorKey; shape._fillFuncHit(context); context.restore(); }, _stroke: function(shape) { var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(); if(stroke || strokeWidth) { this._applyLineCap(shape); context.save(); context.lineWidth = strokeWidth || 2; context.strokeStyle = shape.colorKey; shape._strokeFuncHit(context); context.restore(); } } }; Kinetic.Util.extend(Kinetic.HitCanvas, Kinetic.Canvas); })();