finished up new Scene and Hit Renderers. added new textShadow attrs for more flexibility. Added new fillStroke() method which encapsulates shadow application logic

This commit is contained in:
Eric Rowell
2012-11-23 14:54:32 -08:00
parent 144e95ad42
commit 318d03feb7
16 changed files with 308 additions and 291 deletions

View File

@@ -98,49 +98,6 @@ Kinetic.Shape = (function() {
getCanvas: function() {
return this.getLayer().getCanvas();
},
/**
* helper method to stroke the shape
* @name stroke
* @methodOf Kinetic.Shape.prototype
*/
stroke: function(context, stroke, strokeWidth, shadow) {
if(context.type === 'scene') {
return this._strokeScene(context, stroke, strokeWidth, shadow);
}
else if(context.type === 'hit') {
return this._strokeHit(context, strokeWidth);
}
return false;
},
_strokeScene: function(context, stroke, strokeWidth, shadow) {
if(stroke || strokeWidth) {
context.save();
var appliedShadow = this._applyShadow(context, shadow);
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
if(appliedShadow) {
if(shadow.opacity) {
this._strokeScene(context, stroke);
}
return true;
}
}
return false;
},
_strokeHit: function(context, strokeWidth) {
var stroke = this.colorKey;
if(stroke || strokeWidth) {
context.save();
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
}
return false;
},
_getFillType: function(fill) {
var type = Kinetic.Type;
if(!fill) {
@@ -163,111 +120,36 @@ Kinetic.Shape = (function() {
}
},
/**
* helper method to fill and stroke shape based on fill and stroke properties,
* and also automatically handle shadows, line joins, and line caps
* @name render
* @methodOf Kinetic.Shape.prototype
*/
render: function(context) {
this.applyLineJoin(context, this.getLineJoin());
this.applyLineCap(context, this.getLineCap());
if(context.type === 'scene') {
this.applyOpacity(context);
}
var appliedShadow = this.fill(context, this.getFill(), this.getShadow());
this.stroke(context, this.getStroke(), this.getStrokeWidth(), appliedShadow ? null : this.getShadow());
},
/**
* helper method to fill the shape
* fill current path
* @name fill
* @methodOf Kinetic.Shape.prototype
*/
fill: function(context, fill, shadow) {
if(context.type === 'scene') {
return this._fillScene(context, fill, shadow);
}
else if(context.type === 'hit') {
return this._fillHit(context);
}
return false;
},
_fillScene: function(context, fill, shadow) {
var fillType = this._getFillType(fill);
if(fill) {
context.save();
var appliedShadow = this._applyShadow(context, shadow);
var s = fill.start;
var e = fill.end;
// color fill
switch(fillType) {
case 'COLOR':
context.fillStyle = fill;
context.fill(context);
break;
case 'PATTERN':
var repeat = !fill.repeat ? 'repeat' : fill.repeat;
if(fill.scale) {
context.scale(fill.scale.x, fill.scale.y);
}
if(fill.offset) {
context.translate(fill.offset.x, fill.offset.y);
}
context.fillStyle = context.createPattern(fill.image, repeat);
context.fill(context);
break;
case 'LINEAR_GRADIENT':
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]);
}
context.fillStyle = grd;
context.fill(context);
break;
case 'RADIAL_GRADIENT':
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]);
}
context.fillStyle = grd;
context.fill(context);
break;
default:
context.fillStyle = 'black';
context.fill(context);
break;
}
context.restore();
if(appliedShadow) {
if(shadow.opacity) {
this._fillScene(context, fill);
}
return true;
}
}
return false;
},
_fillHit: function(context) {
context.save();
context.fillStyle = this.colorKey;
context.fill(context);
context.restore();
return false;
fill: function(context) {
context.renderer._fill(this);
},
/**
* helper method to draw an image
* stroke current path
* @name stroke
* @methodOf Kinetic.Shape.prototype
*/
stroke: function(context) {
context.renderer._stroke(this);
},
/**
* fill and stroke current path.&nbsp; Aside from being a convenience method
* which fills and strokes the current path with a single method, its main purpose is
* to ensure that the shadow object is not applied to both the fill and stroke.&nbsp; A shadow
* will only be applied to either the fill or stroke.&nbsp; Fill
* is given priority over stroke.
* @name fillStroke
* @methodOf Kinetic.Shape.prototype
*/
fillStroke: function(context) {
context.renderer._fill(this);
context.renderer._stroke(this, this.getShadow() && this.getFill());
},
/**
* draw an image
* @name drawImage
* @methodOf Kinetic.Shape.prototype
*/
@@ -287,34 +169,20 @@ Kinetic.Shape = (function() {
context.restore();
},
applyOpacity: function(context) {
_applyOpacity: function(context) {
var absOpacity = this.getAbsoluteOpacity();
if(absOpacity !== 1) {
context.globalAlpha = absOpacity;
}
},
/**
* helper method to set the line join of a shape
* based on the applyLineJoin property
* @name lineJoin
* @methodOf Kinetic.Shape.prototype
* @param {CanvasContext} context
* @param {String} lineJoin
*/
applyLineJoin: function(context, lineJoin) {
_applyLineJoin: function(context) {
var lineJoin = this.getLineJoin();
if(lineJoin) {
context.lineJoin = lineJoin;
}
},
/**
* helper method to set the line cap of a path
* based on the lineCap property
* @name applyLineCap
* @methodOf Kinetic.Shape.prototype
* @param {CanvasContext} context
* @param {String} lineCap
*/
applyLineCap: function(context, lineCap) {
_applyLineCap: function(context) {
var lineCap = this.getLineCap();
if(lineCap) {
context.lineCap = lineCap;
}
@@ -391,29 +259,6 @@ Kinetic.Shape = (function() {
_get: function(selector) {
return this.nodeType === selector || this.shapeType === selector ? [this] : [];
},
_applyShadow: function(context, shadow) {
if(shadow) {
var aa = this.getAbsoluteOpacity();
// defaults
var color = shadow.color || 'black';
var blur = shadow.blur || 5;
var offset = shadow.offset || {
x: 0,
y: 0
};
if(shadow.opacity) {
context.globalAlpha = shadow.opacity * aa;
}
context.shadowColor = color;
context.shadowBlur = blur;
context.shadowOffsetX = offset.x;
context.shadowOffsetY = offset.y;
return true;
}
return false;
},
/**
* determines if point is in the shape
* @param {Object|Array} point point can be an object containing
@@ -447,6 +292,9 @@ Kinetic.Shape = (function() {
}
context.save();
this._applyOpacity(context);
this._applyLineJoin(context);
this._applyLineCap(context);
var len = family.length;
for(var n = 0; n < len; n++) {
var node = family[n], t = node.getTransform(), m = t.getMatrix();
@@ -470,6 +318,9 @@ Kinetic.Shape = (function() {
}
context.save();
this._applyOpacity(context);
this._applyLineJoin(context);
this._applyLineCap(context);
var len = family.length;
for(var n = 0; n < len; n++) {
var node = family[n], t = node.getTransform(), m = t.getMatrix();
@@ -493,6 +344,8 @@ Kinetic.Shape = (function() {
}
context.save();
this._applyLineJoin(context);
this._applyLineCap(context);
var len = family.length;
for(var n = 0; n < len; n++) {
var node = family[n], t = node.getTransform(), m = t.getMatrix();

View File

@@ -27,7 +27,7 @@ Kinetic.Circle.prototype = {
context.beginPath();
context.arc(0, 0, this.getRadius(), 0, Math.PI * 2, true);
context.closePath();
this.render(context);
this.fillStroke(context);
},
getWidth: function() {
return this.getRadius() * 2;

View File

@@ -36,7 +36,7 @@ Kinetic.Ellipse.prototype = {
context.arc(0, 0, r.x, 0, Math.PI * 2, true);
context.restore();
context.closePath();
this.render(context);
this.fillStroke(context);
},
/**
* set radius

View File

@@ -36,7 +36,7 @@ Kinetic.Image.prototype = {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.render(context);
this.fillStroke(context);
if(this.attrs.image) {
// if cropping
@@ -59,7 +59,7 @@ Kinetic.Image.prototype = {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.render(context);
this.fillStroke(context);
if(imageBuffer) {
this.drawImage(context, this.imageBuffer, 0, 0, width, height);

View File

@@ -47,7 +47,7 @@ Kinetic.Line.prototype = {
}
}
this.stroke(context, this.getStroke(), this.getStrokeWidth(), this.getShadow());
this.stroke(context);
},
/**
* set points array

View File

@@ -68,7 +68,7 @@ Kinetic.Path.prototype = {
break;
}
}
this.render(context);
this.fillStroke(context);
}
};
Kinetic.Global.extend(Kinetic.Path, Kinetic.Shape);

View File

@@ -30,7 +30,7 @@ Kinetic.Polygon.prototype = {
context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y);
}
context.closePath();
this.render(context);
this.fillStroke(context);
},
/**
* set points array

View File

@@ -42,7 +42,7 @@ Kinetic.Rect.prototype = {
}
context.closePath();
this.render(context);
this.fillStroke(context);
}
};

View File

@@ -34,7 +34,7 @@ Kinetic.RegularPolygon.prototype = {
context.lineTo(x, y);
}
context.closePath();
this.render(context);
this.fillStroke(context);
}
};
Kinetic.Global.extend(Kinetic.RegularPolygon, Kinetic.Shape);

View File

@@ -35,11 +35,6 @@ Kinetic.Sprite.prototype = {
var index = this.attrs.index;
var f = this.attrs.animations[anim][index];
context.beginPath();
context.rect(0, 0, f.width, f.height);
context.closePath();
this.render(context);
if(this.attrs.image) {
context.beginPath();
@@ -57,8 +52,7 @@ Kinetic.Sprite.prototype = {
context.beginPath();
context.rect(0, 0, f.width, f.height);
context.closePath();
this.fill(context, this.getFill(), null);
this.stroke(context, this.getStroke(), this.getStrokeWidth(), null);
this.fillStroke(context);
},
/**
* start sprite animation

View File

@@ -37,7 +37,7 @@ Kinetic.Star.prototype = {
}
context.closePath();
this.render(context);
this.fillStroke(context);
}
};
Kinetic.Global.extend(Kinetic.Star, Kinetic.Shape);

View File

@@ -68,7 +68,7 @@ Kinetic.Text.prototype = {
}
context.closePath();
this.render(context);
this.fillStroke(context);
/*
* draw text
*/
@@ -96,10 +96,8 @@ Kinetic.Text.prototype = {
context.translate((this.getWidth() - this._getTextSize(text).width - p * 2) / 2, 0);
}
var appliedShadow = this.fillText(context, text, this.getTextFill(), this.getShadow());
this.strokeText(context, text, this.getTextStroke(), this.getTextStrokeWidth(), appliedShadow ? null : this.getShadow());
this.fillStrokeText(context, text);
context.restore();
context.translate(0, lineHeightPx);
}
context.restore();
@@ -128,7 +126,7 @@ Kinetic.Text.prototype = {
}
context.closePath();
this.render(context);
this.fillStroke(context);
},
/**
* set text
@@ -185,92 +183,60 @@ Kinetic.Text.prototype = {
height: parseInt(this.attrs.fontSize, 10)
};
},
/**
* helper method to fill text
* @param {String} text
* @name fillText
* @methodOf Kinetic.Text.prototype
*/
fillText: function(context, text, textFill, shadow) {
if(context.type === 'scene') {
return this._fillTextScene(context, text, textFill, shadow);
}
else if(context.type === 'hit') {
return this._fillTextHit(context, text);
}
return false;
},
_fillTextScene: function(context, text, textFill, shadow) {
fillText: function(context, text, skipShadow) {
var textFill = this.getTextFill(), shadow = this.getShadow();
if(textFill) {
context.save();
var appliedShadow = this._applyShadow(context, shadow);
if(!skipShadow && shadow) {
this._applyTextShadow(context);
}
context.fillStyle = textFill;
context.fillText(text, 0, 0);
context.restore();
if(appliedShadow) {
if(shadow.opacity) {
this._fillTextScene(context, text, textFill);
return true;
if(!skipShadow && shadow && shadow.opacity) {
this.fillText(context, text, true);
}
}
}
return false;
},
_fillTextHit: function(context, text) {
context.save();
context.fillStyle = this.colorKey;
context.fillText(text, 0, 0);
context.restore();
return false;
},
/**
* helper method to stroke text
* if needed
* @name strokeText
* @methodOf Kinetic.Shape.prototype
* @param {String} text
*/
strokeText: function(context, text, textStroke, textStrokeWidth, shadow) {
if(context.type === 'scene') {
this._strokeTextScene(context, text, textStroke, textStrokeWidth, shadow);
}
else if(context.type === 'hit') {
this._strokeTextHit(context, text, textStrokeWidth);
}
return false;
},
_strokeTextScene: function(context, text, textStroke, textStrokeWidth, shadow) {
strokeText: function(context, text, skipShadow) {
var textStroke = this.getTextStroke(), textStrokeWidth = this.getTextStrokeWidth(), shadow = this.getShadow();
if(textStroke || textStrokeWidth) {
context.save();
var appliedShadow = this._applyShadow(context, shadow);
// defaults
textStroke = textStroke || 'black';
textStrokeWidth = textStrokeWidth || 2;
context.lineWidth = textStrokeWidth;
context.strokeStyle = textStroke;
if(!skipShadow && shadow) {
this._applyTextShadow(context);
}
context.lineWidth = textStrokeWidth || 2;
context.strokeStyle = textStroke || 'black';
context.strokeText(text, 0, 0);
context.restore();
if(appliedShadow) {
if(shadow.opacity) {
this._strokeTextScene(context, text, textStroke, textStrokeWidth);
return true;
if(!skipShadow && shadow && shadow.opacity) {
this.strokeText(context, text, true);
}
}
}
return false;
},
_strokeTextHit: function(context, text, textStrokeWidth) {
context.save();
// defaults
var textStroke = this.colorKey ? this.colorKey : 'black';
var textStrokeWidth = textStrokeWidth || 2;
context.lineWidth = textStrokeWidth;
context.strokeStyle = textStroke;
context.strokeText(text, 0, 0);
context.restore();
return false;
fillStrokeText: function(context, text) {
this.fillText(context, text);
this.strokeText(context, text, this.getTextShadow() && this.getTextFill());
},
/**
* set text shadow object
* @name setTextShadow
* @methodOf Kinetic.Text.prototype
* @param {Object} config
* @param {String} config.color
* @param {Number} config.blur
* @param {Array|Object|Number} config.offset
* @param {Number} config.opacity
*/
setTextShadow: function(config) {
var type = Kinetic.Type;
if(config.offset !== undefined) {
config.offset = type._getXY(config.offset);
}
this.setAttr('textShadow', type._merge(config, this.getTextShadow()));
},
/**
* set text data. wrap logic and width and height setting occurs
@@ -337,13 +303,34 @@ Kinetic.Text.prototype = {
row++;
}
this.textArr = arr;
},
_applyTextShadow: function(context) {
var textShadow = this.getTextShadow();
if(textShadow) {
var aa = this.getAbsoluteOpacity();
// defaults
var color = textShadow.color || 'black';
var blur = textShadow.blur || 5;
var offset = textShadow.offset || {
x: 0,
y: 0
};
if(textShadow.opacity) {
context.globalAlpha = textShadow.opacity * aa;
}
context.shadowColor = color;
context.shadowBlur = blur;
context.shadowOffsetX = offset.x;
context.shadowOffsetY = offset.y;
}
}
};
Kinetic.Global.extend(Kinetic.Text, Kinetic.Shape);
// add getters setters
Kinetic.Node.addGettersSetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight']);
Kinetic.Node.addGetters(Kinetic.Text, ['text']);
Kinetic.Node.addGetters(Kinetic.Text, ['text', 'textShadow']);
/**
* set font family
* @name setFontFamily

View File

@@ -70,7 +70,7 @@ Kinetic.TextPath.prototype = {
context.translate(p0.x, p0.y);
context.rotate(glyphInfo[i].rotation);
this.render(context);
this.fillStrokeText(context);
context.restore();
@@ -307,11 +307,9 @@ Kinetic.Node.addGetters(Kinetic.TextPath, ['text']);
// reference Text methods
Kinetic.TextPath.prototype.fillText = Kinetic.Text.prototype.fillText;
Kinetic.TextPath.prototype._fillTextScene = Kinetic.Text.prototype._fillTextScene;
Kinetic.TextPath.prototype._fillTextHit = Kinetic.Text.prototype._fillTextHit;
Kinetic.TextPath.prototype.strokeText = Kinetic.Text.prototype.strokeText;
Kinetic.TextPath.prototype._strokeTextScene = Kinetic.Text.prototype._strokeTextScene;
Kinetic.TextPath.prototype._strokeTextHit = Kinetic.Text.prototype._strokeTextHit;
Kinetic.TextPath.prototype.fillStrokeText = Kinetic.Text.prototype.strokeText;
/**
* set font family
* @name setFontFamily

View File

@@ -12,8 +12,7 @@ Kinetic.Canvas = function(width, height, isHit) {
this.element.width = width || 0;
this.element.height = height || 0;
// set type
this.context.type = isHit ? 'hit' : 'scene';
this.context.renderer = isHit ? new Kinetic.HitRenderer(this.context) : new Kinetic.SceneRenderer(this.context);
};
Kinetic.Canvas.prototype = {
@@ -98,3 +97,137 @@ Kinetic.Canvas.prototype = {
}
}
};
Kinetic.SceneRenderer = function(context) {
this.context = context;
};
Kinetic.SceneRenderer.prototype = {
_fill: function(shape, skipShadow) {
var context = this.context, fill = shape.getFill(), fillType = shape._getFillType(fill), shadow = shape.getShadow();
if(fill) {
context.save();
if(!skipShadow && shadow) {
this._applyShadow(shape);
}
var s = fill.start;
var e = fill.end;
// color fill
switch(fillType) {
case 'COLOR':
context.fillStyle = fill;
context.fill(context);
break;
case 'PATTERN':
var repeat = !fill.repeat ? 'repeat' : fill.repeat;
if(fill.scale) {
context.scale(fill.scale.x, fill.scale.y);
}
if(fill.offset) {
context.translate(fill.offset.x, fill.offset.y);
} file:///C:/Users/Eric/Documents/Eric/workspaces/KineticJS/dist/kinetic-current.js
context.fillStyle = context.createPattern(fill.image, repeat);
context.fill(context);
break;
case 'LINEAR_GRADIENT':
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]);
}
context.fillStyle = grd;
context.fill(context);
break;
case 'RADIAL_GRADIENT':
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]);
}
context.fillStyle = grd;
context.fill(context);
break;
default:
context.fillStyle = 'black';
context.fill(context);
break;
}
context.restore();
if(!skipShadow && shadow && shadow.opacity) {
this._fill(shape, true);
}
}
},
_stroke: function(shape, skipShadow) {
var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(), shadow = shape.getShadow();
if(stroke || strokeWidth) {
context.save();
if(!skipShadow && shadow) {
this._applyShadow(shape);
}
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
if(!skipShadow && shadow && shadow.opacity) {
this._stroke(shape, true);
}
}
},
_applyShadow: function(shape) {
var context = this.context, shadow = shape.getShadow();
if(shadow) {
var aa = shape.getAbsoluteOpacity();
// defaults
var color = shadow.color || 'black';
var blur = shadow.blur || 5;
var offset = shadow.offset || {
x: 0,
y: 0
};
if(shadow.opacity) {
context.globalAlpha = shadow.opacity * aa;
}
context.shadowColor = color;
context.shadowBlur = blur;
context.shadowOffsetX = offset.x;
context.shadowOffsetY = offset.y;
}
}
};
Kinetic.HitRenderer = function(context) {
this.context = context;
};
Kinetic.HitRenderer.prototype = {
_fill: function(shape) {
var context = this.context;
context.save();
context.fillStyle = shape.colorKey;
context.fill(context);
context.restore();
},
_stroke: function(shape) {
var context = this.context, stroke = shape.colorKey, strokeWidth = shape.getStrokeWidth();
if(stroke || strokeWidth) {
context.save();
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
}
}
};

View File

@@ -141,7 +141,8 @@ Test.Modules.SHAPE = {
context.lineTo(100, 0);
context.lineTo(100, 100);
context.closePath();
this.render(context);
this.fill(context);
this.stroke(context);
},
x: 200,
y: 100,
@@ -156,7 +157,8 @@ Test.Modules.SHAPE = {
context.lineTo(200, 0);
context.lineTo(200, 100);
context.closePath();
this.render(context);
this.fill(context);
this.stroke(context);
});
var rect = new Kinetic.Rect({
x: 10,
@@ -175,7 +177,8 @@ Test.Modules.SHAPE = {
context.lineTo(200, 0);
context.lineTo(200, 100);
context.closePath();
this.render(context);
this.fill(context);
this.stroke(context);
});
layer.add(shape);

View File

@@ -1,4 +1,53 @@
Test.Modules.Text = {
'add text with shadows': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var text = new Kinetic.Text({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
text: 'Hello World!',
fontSize: 50,
fontFamily: 'Calibri',
fontStyle: 'normal',
textFill: '#888',
textStroke: '#333',
align: 'right',
lineHeight: 1.2,
width: 400,
height: 100,
padding: 10,
shadow: {
color: 'black',
blur: 1,
offset: [10, 10],
opacity: 0.2
},
textShadow: {
color: 'red',
blur: 1,
offset: [10, 10],
opacity: 0.2
},
cornerRadius: 10,
draggable: true
});
// center text box
text.setOffset(text.getWidth() / 2, text.getHeight() / 2);
layer.add(text);
stage.add(layer);
},
'text getters and setters': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,