Files
konva/src/Canvas.js

453 lines
15 KiB
JavaScript

(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');
// set inline styles
this.element.style.padding = 0;
this.element.style.margin = 0;
this.element.style.border = 0;
this.element.style.background = 'transparent';
this.element.style.position = 'absolute';
this.element.style.top = 0;
this.element.style.left = 0;
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(err) {
Kinetic.Util.warn('Unable to get data URL. ' + err.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 m = node.getAbsoluteTransform().getMatrix();
this.context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
},
_clip: function(container) {
var context = this.getContext(),
clipX = container.getClipX() || 0,
clipY = container.getClipY() || 0,
clipWidth = container.getClipWidth(),
clipHeight = container.getClipHeight();
context.save();
this._applyAncestorTransforms(container);
context.beginPath();
context.rect(clipX, clipY, clipWidth, clipHeight);
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.lineWidth = strokeWidth || 2;
context.strokeStyle = shape.colorKey;
shape._strokeFuncHit(context);
}
}
};
Kinetic.Util.extend(Kinetic.HitCanvas, Kinetic.Canvas);
})();