konva/src/Canvas.js
2013-02-12 00:20:24 -08:00

382 lines
14 KiB
JavaScript

(function() {
/**
* Canvas Renderer constructor
* @constructor
* @param {Number} width
* @param {Number} height
*/
Kinetic.Canvas = function(width, height) {
this.width = width;
this.height = height;
this.element = document.createElement('canvas');
this.context = this.element.getContext('2d');
this.setSize(width || 0, height || 0);
};
// 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;
Kinetic.Canvas.pixelRatio = devicePixelRatio / backingStoreRatio;
Kinetic.Canvas.prototype = {
/**
* clear canvas
* @name clear
* @methodOf Kinetic.Canvas.prototype
*/
clear: function() {
var context = this.getContext();
var el = this.getElement();
context.clearRect(0, 0, el.width, el.height);
},
/**
* get canvas element
* @name getElement
* @methodOf Kinetic.Canvas.prototype
*/
getElement: function() {
return this.element;
},
/**
* get canvas context
* @name getContext
* @methodOf Kinetic.Canvas.prototype
*/
getContext: function() {
return this.context;
},
/**
* set width
* @name setWidth
* @methodOf Kinetic.Canvas.prototype
* @param {Number} width
*/
setWidth: function(width) {
this.width = width;
// take into account pixel ratio
this.element.width = width * Kinetic.Canvas.pixelRatio;
this.element.style.width = width + 'px';
},
/**
* set height
* @name setHeight
* @methodOf Kinetic.Canvas.prototype
* @param {Number} height
*/
setHeight: function(height) {
this.height = height;
// take into account pixel ratio
this.element.height = height * Kinetic.Canvas.pixelRatio;
this.element.style.height = height + 'px';
},
/**
* get width
* @name getWidth
* @methodOf Kinetic.Canvas.prototype
*/
getWidth: function() {
return this.width;
},
/**
* get height
* @name getHeight
* @methodOf Kinetic.Canvas.prototype
*/
getHeight: function() {
return this.height;
},
/**
* set size
* @name setSize
* @methodOf Kinetic.Canvas.prototype
* @param {Number} width
* @param {Number} height
*/
setSize: function(width, height) {
this.setWidth(width);
this.setHeight(height);
},
/**
* to data url
* @name toDataURL
* @methodOf 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.Global.warn('Unable to get data URL. ' + e.message)
return '';
}
}
},
/**
* fill shape
* @name fill
* @methodOf Kinetic.Canvas.prototype
* @param {Kinetic.Shape} shape
*/
fill: function(shape) {
if(shape.getFillEnabled()) {
this._fill(shape);
}
},
/**
* stroke shape
* @name stroke
* @methodOf 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.
* @name fillStroke
* @methodOf 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
* @name applyShadow
* @methodOf 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;
node._eachAncestorReverse(function(no) {
var t = no.getTransform(), m = t.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}, true);
}
};
Kinetic.SceneCanvas = function(width, height) {
Kinetic.Canvas.call(this, width, height);
};
Kinetic.SceneCanvas.prototype = {
setWidth: function(width) {
this.width = width;
this.element.width = width;
this.element.style.width = width + 'px';
},
setHeight: function(height) {
this.height = height;
this.element.height = height;
this.element.style.height = height + 'px';
},
_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);
// 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, fill = shape.getFill(), fillPatternImage = shape.getFillPatternImage(), fillLinearGradientStartPoint = shape.getFillLinearGradientStartPoint(), fillRadialGradientStartPoint = shape.getFillRadialGradientStartPoint(), fillPriority = shape.getFillPriority();
context.save();
if(!skipShadow && shape.hasShadow()) {
this._applyShadow(shape);
}
// priority fills
if(fill && fillPriority === 'color') {
this._fillColor(shape);
}
else if(fillPatternImage && fillPriority === 'pattern') {
this._fillPattern(shape);
}
else if(fillLinearGradientStartPoint && fillPriority === 'linear-gradient') {
this._fillLinearGradient(shape);
}
else if(fillRadialGradientStartPoint && fillPriority === 'radial-gradient') {
this._fillRadialGradient(shape);
}
// now just try and fill with whatever is available
else if(fill) {
this._fillColor(shape);
}
else if(fillPatternImage) {
this._fillPattern(shape);
}
else if(fillLinearGradientStartPoint) {
this._fillLinearGradient(shape);
}
else if(fillRadialGradientStartPoint) {
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();
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.Global.extend(Kinetic.SceneCanvas, Kinetic.Canvas);
Kinetic.HitCanvas = function(width, height) {
Kinetic.Canvas.call(this, width, height);
};
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.Global.extend(Kinetic.HitCanvas, Kinetic.Canvas);
})();