187 lines
5.8 KiB
JavaScript
187 lines
5.8 KiB
JavaScript
|
var Class = require('../../core/class');
|
||
|
var Color = require('../../core/color');
|
||
|
var Transform = require('../../core/transform');
|
||
|
var Node = require('./node');
|
||
|
|
||
|
var genericCanvas = typeof document !== 'undefined' && document.createElement('canvas'),
|
||
|
genericContext = genericCanvas && genericCanvas.getContext && genericCanvas.getContext('2d');
|
||
|
|
||
|
function recolorImage(img, color1, color2){
|
||
|
// TODO: Fix this experimental implementation
|
||
|
color1 = Color.detach(color1);
|
||
|
color2 = Color.detach(color2);
|
||
|
var canvas = document.createElement('canvas'),
|
||
|
context = canvas.getContext('2d');
|
||
|
canvas.width = img.width;
|
||
|
canvas.height = img.height;
|
||
|
context.fillStyle = color2[0];
|
||
|
context.fillRect(0, 0, img.width, img.height);
|
||
|
context.globalCompositeOperation = 'lighter';
|
||
|
context.drawImage(img, 0, 0);
|
||
|
return canvas;
|
||
|
}
|
||
|
|
||
|
var Base = Class(Node, {
|
||
|
|
||
|
initialize: function(){
|
||
|
this._fill = null;
|
||
|
this._pendingFill = null;
|
||
|
this._fillTransform = null;
|
||
|
this._stroke = null;
|
||
|
this._strokeCap = null;
|
||
|
this._strokeDash = null;
|
||
|
this._strokeJoin = null;
|
||
|
this._strokeWidth = null;
|
||
|
},
|
||
|
|
||
|
/* styles */
|
||
|
|
||
|
_addColors: function(gradient, stops){
|
||
|
// Enumerate stops, assumes offsets are enumerated in order
|
||
|
// TODO: Sort. Chrome doesn't always enumerate in expected order but requires stops to be specified in order.
|
||
|
if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++)
|
||
|
gradient.addColorStop(i / l, new Color(stops[i]).toString());
|
||
|
else for (var offset in stops)
|
||
|
gradient.addColorStop(offset, new Color(stops[offset]).toString());
|
||
|
return gradient;
|
||
|
},
|
||
|
|
||
|
|
||
|
fill: function(color){
|
||
|
if (arguments.length > 1) return this.fillLinear(arguments);
|
||
|
if (this._pendingFill) this._pendingFill();
|
||
|
this._fill = color ? new Color(color).toString() : null;
|
||
|
return this.invalidate();
|
||
|
},
|
||
|
|
||
|
fillRadial: function(stops, focusX, focusY, radiusX, radiusY, centerX, centerY){
|
||
|
if (focusX == null) focusX = (this.left || 0) + (this.width || 0) * 0.5;
|
||
|
if (focusY == null) focusY = (this.top || 0) + (this.height || 0) * 0.5;
|
||
|
if (radiusY == null) radiusY = radiusX || (this.height * 0.5) || 0;
|
||
|
if (radiusX == null) radiusX = (this.width || 0) * 0.5;
|
||
|
if (centerX == null) centerX = focusX;
|
||
|
if (centerY == null) centerY = focusY;
|
||
|
|
||
|
centerX += centerX - focusX;
|
||
|
centerY += centerY - focusY;
|
||
|
|
||
|
if (radiusX === 0 || radiusX === '0') return this.fillLinear(stops);
|
||
|
var ys = radiusY / radiusX;
|
||
|
|
||
|
if (this._pendingFill) this._pendingFill();
|
||
|
|
||
|
var gradient = genericContext.createRadialGradient(focusX, focusY / ys, 0, centerX, centerY / ys, radiusX * 2);
|
||
|
|
||
|
// Double fill radius to simulate repeating gradient
|
||
|
if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++){
|
||
|
gradient.addColorStop(i / l / 2, new Color(stops[i]).toString());
|
||
|
gradient.addColorStop(1 - i / l / 2, new Color(stops[i]).toString());
|
||
|
} else for (var offset in stops){
|
||
|
gradient.addColorStop(offset / 2, new Color(stops[offset]).toString());
|
||
|
gradient.addColorStop(1- offset / 2, new Color(stops[offset]).toString());
|
||
|
}
|
||
|
|
||
|
this._fill = gradient;
|
||
|
this._fillTransform = new Transform(1, 0, 0, ys);
|
||
|
return this.invalidate();
|
||
|
},
|
||
|
|
||
|
fillLinear: function(stops, x1, y1, x2, y2){
|
||
|
if (arguments.length < 5){
|
||
|
var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180;
|
||
|
|
||
|
var x = Math.cos(angle), y = -Math.sin(angle),
|
||
|
l = (Math.abs(x) + Math.abs(y)) / 2,
|
||
|
w = this.width || 1, h = this.height || 1;
|
||
|
|
||
|
x *= l; y *= l;
|
||
|
|
||
|
x1 = 0.5 - x;
|
||
|
x2 = 0.5 + x;
|
||
|
y1 = 0.5 - y;
|
||
|
y2 = 0.5 + y;
|
||
|
this._fillTransform = new Transform(w, 0, 0, h);
|
||
|
} else {
|
||
|
this._fillTransform = null;
|
||
|
}
|
||
|
if (this._pendingFill) this._pendingFill();
|
||
|
var gradient = genericContext.createLinearGradient(x1, y1, x2, y2);
|
||
|
this._addColors(gradient, stops);
|
||
|
this._fill = gradient;
|
||
|
return this.invalidate();
|
||
|
},
|
||
|
|
||
|
fillImage: function(url, width, height, left, top, color1, color2){
|
||
|
if (this._pendingFill) this._pendingFill();
|
||
|
var img = url;
|
||
|
if (!(img instanceof Image)){
|
||
|
img = new Image();
|
||
|
img.src = url;
|
||
|
}
|
||
|
if (img.width && img.height){
|
||
|
return this._fillImage(img, width, height, left || 0, top || 0, color1, color2);
|
||
|
}
|
||
|
|
||
|
// Not yet loaded
|
||
|
this._fill = null;
|
||
|
var self = this,
|
||
|
callback = function(){
|
||
|
cancel();
|
||
|
self._fillImage(img, width, height, left || 0, top || 0, color1, color2);
|
||
|
},
|
||
|
cancel = function(){
|
||
|
img.removeEventListener('load', callback, false);
|
||
|
self._pendingFill = null;
|
||
|
};
|
||
|
this._pendingFill = cancel;
|
||
|
img.addEventListener('load', callback, false);
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
_fillImage: function(img, width, height, left, top, color1, color2){
|
||
|
var w = width ? width / img.width : 1,
|
||
|
h = height ? height / img.height : 1;
|
||
|
if (color1 != null) img = recolorImage(img, color1, color2);
|
||
|
this._fill = genericContext.createPattern(img, 'repeat');
|
||
|
this._fillTransform = new Transform(w, 0, 0, h, left || 0, top || 0);
|
||
|
return this.invalidate();
|
||
|
},
|
||
|
|
||
|
stroke: function(color, width, cap, join, dash){
|
||
|
this._stroke = color ? new Color(color).toString() : null;
|
||
|
this._strokeWidth = (width != null) ? width : 1;
|
||
|
this._strokeCap = (cap != null) ? cap : 'round';
|
||
|
this._strokeJoin = (join != null) ? join : 'round';
|
||
|
this._strokeDash = dash;
|
||
|
return this.invalidate();
|
||
|
},
|
||
|
|
||
|
// Rendering
|
||
|
|
||
|
element_renderTo: Node.prototype.renderTo,
|
||
|
|
||
|
renderTo: function(context, xx, yx, xy, yy, x, y){
|
||
|
var opacity = this._opacity;
|
||
|
if (opacity == null || opacity >= 1){
|
||
|
return this.renderLayerTo(context, xx, yx, xy, yy, x, y);
|
||
|
}
|
||
|
if (this._fill && this._stroke){
|
||
|
return this.element_renderTo(context, xx, yx, xy, yy, x, y);
|
||
|
}
|
||
|
context.globalAlpha = opacity;
|
||
|
var r = this.renderLayerTo(context, xx, yx, xy, yy, x, y);
|
||
|
context.globalAlpha = 1;
|
||
|
return r;
|
||
|
},
|
||
|
|
||
|
renderLayerTo: function(context, xx, yx, xy, yy, x, y){
|
||
|
context.setTransform(xx, yx, xy, yy, x, y);
|
||
|
this.renderShapeTo(context);
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
Base._genericContext = genericContext;
|
||
|
|
||
|
module.exports = Base;
|