diff --git a/src/document/Doc.js b/src/document/Doc.js index 8b5136da..83c37d33 100644 --- a/src/document/Doc.js +++ b/src/document/Doc.js @@ -33,7 +33,7 @@ Doc = Base.extend({ // this.canvas.width = this.canvas.width might be faster.. this.ctx.clearRect(0, 0, this.size.width + 1, this.size.height); for (var i = 0, l = this.layers.length; i < l; i++) { - this.layers[i].draw(this.ctx); + this.layers[i].draw(this.ctx, {}); } } } diff --git a/src/item/BlendMode.js b/src/item/BlendMode.js new file mode 100644 index 00000000..472003b0 --- /dev/null +++ b/src/item/BlendMode.js @@ -0,0 +1,494 @@ +/* + * BlendMode code ported from Pixastic Lib - Blend - v0.1.1 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + */ + +BlendMode = { + // TODO: Should we remove some of the blend modes? + // TODO: Add missing blendmodes like hue / saturation. + process: function(documentContext, item, param) { + var itemBounds = item.bounds; + var top = Math.floor(itemBounds.top); + var left = Math.floor(itemBounds.left); + var size = itemBounds.size.ceil(); + var width = size.width; + var height = size.height; + + var itemCanvas = CanvasProvider.getCanvas(size); + var itemContext = itemCanvas.getContext('2d'); + itemContext.translate(-itemBounds.left, -itemBounds.top); + param.ignoreBlendMode = true; + item.draw(itemContext, param); + + var data = documentContext.getImageData( + left, top, + width, height + ).data; + + var dataDesc2 = itemContext.getImageData( + 0, 0, + width, height + ); + var data2 = dataDesc2.data; + var p = size.width * size.height; + var pix = p * 4; + var pix1, pix2; + var r1, g1, b1; + var r2, g2, b2; + var r3, g3, b3; + var r4, g4, b4; + + switch (item.blendMode) { + case 'normal' : + break; + + case 'multiply' : + while (p--) { + data2[pix -= 4] = data[pix] * data2[pix] / 255; + data2[pix1 = pix + 1] = data[pix1] * data2[pix1] / 255; + data2[pix2 = pix + 2] = data[pix2] * data2[pix2] / 255; + } + break; + + case 'lighten' : + while (p--) { + if ((r1 = data[pix -= 4]) > data2[pix]) + data2[pix] = r1; + if ((g1 = data[pix1 = pix + 1]) > data2[pix1]) + data2[pix1] = g1; + if ((b1 = data[pix2 = pix + 2]) > data2[pix2]) + data2[pix2] = b1; + } + break; + + case 'darken' : + while (p--) { + if ((r1 = data[pix -= 4]) < data2[pix]) + data2[pix] = r1; + if ((g1 = data[pix1 = pix + 1]) < data2[pix1]) + data2[pix1] = g1; + if ((b1 = data[pix2 = pix + 2]) < data2[pix2]) + data2[pix2] = b1; + + } + break; + + case 'darker-color' : + while (p--) { + if (((r1 = data[pix -= 4]) * 0.3 + + (g1 = data[pix1 = pix + 1]) * 0.59 + + (b1 = data[pix2 = pix + 2]) * 0.11) + <= (data2[pix] * 0.3 + data2[pix1] * 0.59 + + data2[pix2] * 0.11)) { + data2[pix] = r1; + data2[pix1] = g1; + data2[pix2] = b1; + } + } + break; + + case 'lighter-color' : + while (p--) { + if (((r1 = data[pix -= 4]) * 0.3 + + (g1 = data[pix1 = pix + 1]) + * 0.59 + (b1 = data[pix2 = pix + 2]) * 0.11) + > (data2[pix] * 0.3 + data2[pix1] * 0.59 + + data2[pix2] * 0.11)) { + data2[pix] = r1; + data2[pix1] = g1; + data2[pix2] = b1; + } + } + break; + + case 'linear-dodge' : + /* + otherdocumentContext.globalCompositeOperation = 'source-over'; + otherdocumentContext.drawImage(params.canvas, 0, 0); + otherdocumentContext.globalCompositeOperation = 'lighter'; + otherdocumentContext.drawImage(image, 0, 0); + */ + + while (p--) { + if ((r3 = data[pix -= 4] + data2[pix]) > 255) + data2[pix] = 255; + else + data2[pix] = r3; + if ((g3 = data[pix1 = pix + 1] + data2[pix1]) > 255) + data2[pix1] = 255; + else + data2[pix1] = g3; + if ((b3 = data[pix2 = pix + 2] + data2[pix2]) > 255) + data2[pix2] = 255; + else + data2[pix2] = b3; + } + + break; + + case 'linear-burn' : + while (p--) { + if ((r3 = data[pix -= 4] + data2[pix]) < 255) + data2[pix] = 0; + else + data2[pix] = (r3 - 255); + if ((g3 = data[pix1 = pix + 1] + data2[pix1]) < 255) + data2[pix1] = 0; + else + data2[pix1] = (g3 - 255); + if ((b3 = data[pix2 = pix + 2] + data2[pix2]) < 255) + data2[pix2] = 0; + else + data2[pix2] = (b3 - 255); + } + break; + + case 'difference' : + while (p--) { + if ((r3 = data[pix -= 4] - data2[pix]) < 0) + data2[pix] = -r3; + else + data2[pix] = r3; + if ((g3 = data[pix1 = pix + 1] - data2[pix1]) < 0) + data2[pix1] = -g3; + else + data2[pix1] = g3; + if ((b3 = data[pix2 = pix + 2] - data2[pix2]) < 0) + data2[pix2] = -b3; + else + data2[pix2] = b3; + } + break; + + case 'screen' : + while (p--) { + data2[pix -= 4] = (255 - (((255 - data2[pix]) + * (255 - data[pix])) >> 8)); + data2[pix1 = pix + 1] = (255 - (((255 - data2[pix1]) + * (255 - data[pix1])) >> 8)); + data2[pix2 = pix + 2] = (255 - (((255 - data2[pix2]) + * (255 - data[pix2])) >> 8)); + } + break; + + case 'exclusion' : + var div_2_255 = 2 / 255; + while (p--) { + data2[pix -= 4] = (r1 = data[pix]) + - (r1 * div_2_255 - 1) * data2[pix]; + data2[pix1 = pix + 1] = (g1 = data[pix1]) + - (g1 * div_2_255 - 1) * data2[pix1]; + data2[pix2 = pix + 2] = (b1 = data[pix2]) + - (b1 * div_2_255 - 1) * data2[pix2]; + } + break; + + case 'overlay' : + var div_2_255 = 2 / 255; + while (p--) { + if ((r1 = data[pix -= 4]) < 128) + data2[pix] = data2[pix] * r1 * div_2_255; + else + data2[pix] = 255 - (255 - data2[pix]) * (255 - r1) + * div_2_255; + + if ((g1 = data[pix1 = pix + 1]) < 128) + data2[pix1] = data2[pix1] * g1 * div_2_255; + else + data2[pix1] = 255 - (255 - data2[pix1]) * (255 - g1) + * div_2_255; + + if ((b1 = data[pix2 = pix + 2]) < 128) + data2[pix2] = data2[pix2] * b1 * div_2_255; + else + data2[pix2] = 255 - (255 - data2[pix2]) * (255 - b1) + * div_2_255; + + } + break; + + case 'soft-light' : + var div_2_255 = 2 / 255; + while (p--) { + if ((r1 = data[pix -= 4]) < 128) + data2[pix] = ((data2[pix] >> 1) + 64) * r1 * div_2_255; + else + data2[pix] = 255 - (191 - (data2[pix] >> 1)) + * (255 - r1) * div_2_255; + + if ((g1 = data[pix1 = pix + 1]) < 128) + data2[pix1] = ((data2[pix1] >> 1) + 64) * g1 * div_2_255; + else + data2[pix1] = 255 - (191 - (data2[pix1] >> 1)) + * (255 - g1) * div_2_255; + + if ((b1 = data[pix2 = pix + 2]) < 128) + data2[pix2] = ((data2[pix2] >> 1) + 64) * b1 * div_2_255; + else + data2[pix2] = 255 - (191 - (data2[pix2] >> 1)) + * (255 - b1) * div_2_255; + + } + break; + + case 'hard-light' : + var div_2_255 = 2 / 255; + while (p--) { + if ((r2 = data2[pix -= 4]) < 128) + data2[pix] = data[pix] * r2 * div_2_255; + else + data2[pix] = 255 - (255 - data[pix]) * (255 - r2) + * div_2_255; + + if ((g2 = data2[pix1 = pix + 1]) < 128) + data2[pix1] = data[pix1] * g2 * div_2_255; + else + data2[pix1] = 255 - (255 - data[pix1]) * (255 - g2) + * div_2_255; + + if ((b2 = data2[pix2 = pix + 2]) < 128) + data2[pix2] = data[pix2] * b2 * div_2_255; + else + data2[pix2] = 255 - (255 - data[pix2]) * (255 - b2) + * div_2_255; + + } + break; + + case 'color-dodge' : + while (p--) { + if ((r3 = (data[pix -= 4] << 8) / (255 - (r2 = data2[pix]))) + > 255 || r2 == 255) + data2[pix] = 255; + else + data2[pix] = r3; + + if ((g3 = (data[pix1 = pix + 1] << 8) / (255 + - (g2 = data2[pix1]))) > 255 || g2 == 255) + data2[pix1] = 255; + else + data2[pix1] = g3; + + if ((b3 = (data[pix2 = pix + 2] << 8) / (255 + - (b2 = data2[pix2]))) > 255 || b2 == 255) + data2[pix2] = 255; + else + data2[pix2] = b3; + } + break; + + case 'color-burn' : + while (p--) { + if ((r3 = 255 - ((255 - data[pix -= 4]) << 8) / data2[pix]) + < 0 || data2[pix] == 0) + data2[pix] = 0; + else + data2[pix] = r3; + + if ((g3 = 255 - ((255 - data[pix1 = pix + 1]) << 8) / + data2[pix1]) < 0 || data2[pix1] == 0) + data2[pix1] = 0; + else + data2[pix1] = g3; + + if ((b3 = 255 - ((255 - data[pix2 = pix + 2]) << 8) / + data2[pix2]) < 0 || data2[pix2] == 0) + data2[pix2] = 0; + else + data2[pix2] = b3; + } + break; + + case 'linear-light' : + while (p--) { + if (((r3 = 2 * (r2 = data2[pix -= 4]) + data[pix] - 256) + < 0) || (r2 < 128 && r3 < 0)) { + data2[pix] = 0; + } else { + if (r3 > 255) + data2[pix] = 255; + else + data2[pix] = r3; + } + if (((g3 = 2 * (g2 = data2[pix1 = pix + 1]) + data[pix1] + - 256) < 0) || (g2 < 128 && g3 < 0)) { + data2[pix1] = 0; + } else { + if (g3 > 255) + data2[pix1] = 255; + else + data2[pix1] = g3; + } + if ( ((b3 = 2*(b2 = data2[pix2 = pix + 2])+ data[pix2]-256) + < 0) || (b2 < 128 && b3 < 0)) { + data2[pix2] = 0; + } else { + if (b3 > 255) + data2[pix2] = 255; + else + data2[pix2] = b3; + } + } + break; + + case 'vivid-light' : + while (p--) { + if ((r2 = data2[pix -= 4]) < 128) { + if (r2) { + if ((r3 = 255 - ((255 - data[pix]) << 8) / + (2 * r2)) < 0) + data2[pix] = 0; + else + data2[pix] = r3; + } else { + data2[pix] = 0; + } + } else if ((r3 = (r4 = 2 * r2 - 256)) < 255) { + if ((r3 = (data[pix] << 8) / (255 - r4)) > 255) + data2[pix] = 255; + else + data2[pix] = r3; + } else { + if (r3 < 0) + data2[pix] = 0; + else + data2[pix] = r3; + } + + if ((g2 = data2[pix1 = pix + 1]) < 128) { + if (g2) { + if ((g3 = 255 - ((255 - data[pix1]) << 8) / + (2 * g2)) < 0) + data2[pix1] = 0; + else + data2[pix1] = g3; + } else { + data2[pix1] = 0; + } + } else if ((g3 = (g4 = 2 * g2 - 256)) < 255) { + if ((g3 = (data[pix1] << 8) / (255 - g4)) > 255) + data2[pix1] = 255; + else + data2[pix1] = g3; + } else { + if (g3 < 0) + data2[pix1] = 0; + else + data2[pix1] = g3; + } + + if ((b2 = data2[pix2 = pix + 2]) < 128) { + if (b2) { + if ((b3 = 255 - ((255 - data[pix2]) << 8) / + (2 * b2)) < 0) + data2[pix2] = 0; + else + data2[pix2] = b3; + } else { + data2[pix2] = 0; + } + } else if ((b3 = (b4 = 2 * b2 - 256)) < 255) { + if ((b3 = (data[pix2] << 8) / (255 - b4)) > 255) + data2[pix2] = 255; + else + data2[pix2] = b3; + } else { + if (b3 < 0) + data2[pix2] = 0; + else + data2[pix2] = b3; + } + } + break; + + case 'pin-light' : + while (p--) { + if ((r2 = data2[pix -= 4]) < 128) + if ((r1 = data[pix]) < (r4 = 2 * r2)) + data2[pix] = r1; + else + data2[pix] = r4; + else + if ((r1 = data[pix]) > (r4 = 2 * r2 - 256)) + data2[pix] = r1; + else + data2[pix] = r4; + + if ((g2 = data2[pix1 = pix + 1]) < 128) + if ((g1 = data[pix1]) < (g4 = 2 * g2)) + data2[pix1] = g1; + else + data2[pix1] = g4; + else + if ((g1 = data[pix1]) > (g4 = 2 * g2 - 256)) + data2[pix1] = g1; + else + data2[pix1] = g4; + + if ((r2 = data2[pix2 = pix + 2]) < 128) + if ((r1 = data[pix2]) < (r4 = 2 * r2)) + data2[pix2] = r1; + else + data2[pix2] = r4; + else + if ((r1 = data[pix2]) > (r4 = 2 * r2 - 256)) + data2[pix2] = r1; + else + data2[pix2] = r4; + } + break; + + case 'hard-mix' : + while (p--) { + if ((r2 = data2[pix -= 4]) < 128) + if (255 - ((255 - data[pix]) << 8) / (2 * r2) < 128 + || r2 == 0) + data2[pix] = 0; + else + data2[pix] = 255; + else if ((r4 = 2 * r2 - 256) < 255 + && (data[pix] << 8) / (255 - r4) < 128) + data2[pix] = 0; + else + data2[pix] = 255; + + if ((g2 = data2[pix1 = pix + 1]) < 128) + if (255 - ((255 - data[pix1]) << 8) / (2 * g2) < 128 + || g2 == 0) + data2[pix1] = 0; + else + data2[pix1] = 255; + else if ((g4 = 2 * g2 - 256) < 255 + && (data[pix1] << 8) / (255 - g4) < 128) + data2[pix1] = 0; + else + data2[pix1] = 255; + + if ((b2 = data2[pix2 = pix + 2]) < 128) + if (255 - ((255 - data[pix2]) << 8) / (2 * b2) < 128 + || b2 == 0) + data2[pix2] = 0; + else + data2[pix2] = 255; + else if ((b4 = 2 * b2 - 256) < 255 + && (data[pix2] << 8) / (255 - b4) < 128) + data2[pix2] = 0; + else + data2[pix2] = 255; + } + break; + } + + itemContext.putImageData(dataDesc2, 0, 0); + + documentContext.drawImage( + itemCanvas, + 0, 0, + width, height, + left, top, + width, height + ); + CanvasProvider.returnCanvas(itemCanvas); + } +}; \ No newline at end of file diff --git a/src/item/Group.js b/src/item/Group.js index 552cd5c6..c5e92c5e 100644 --- a/src/item/Group.js +++ b/src/item/Group.js @@ -11,30 +11,35 @@ Group = Item.extend({ this.clipped = false; }, - draw: function(ctx) { + draw: function(ctx, param) { if (!this.visible) return; // If the group has an opacity of less then 1, draw its children on a // temporary canvas, and then draw that canvas onto ctx afterwards // with globalAlpha set. var tempCanvas, originalCtx; - if (this.opacity < 1) { - var originalCtx = ctx; - tempCanvas = CanvasProvider.getCanvas(this.document.size); - ctx = tempCanvas.getContext('2d'); - } - for (var i = 0, l = this.children.length; i < l; i++) { - this.children[i].draw(ctx); - if (this.clipped & i == 0) - ctx.clip(); - } - if (tempCanvas) { - originalCtx.save(); - originalCtx.globalAlpha = this.opacity; - originalCtx.drawImage(tempCanvas, 0, 0); - originalCtx.restore(); - // Return the canvas, so it can be reused - CanvasProvider.returnCanvas(tempCanvas); + if(this.blendMode != 'normal' && !param.ignoreBlendMode) { + BlendMode.process(ctx, this, param); + } else { + param.ignoreBlendMode = false; + if (this.opacity < 1) { + var originalCtx = ctx; + tempCanvas = CanvasProvider.getCanvas(this.document.size); + ctx = tempCanvas.getContext('2d'); + } + for (var i = 0, l = this.children.length; i < l; i++) { + this.children[i].draw(ctx, param); + if (this.clipped & i == 0) + ctx.clip(); + } + if (tempCanvas) { + originalCtx.save(); + originalCtx.globalAlpha = this.opacity; + originalCtx.drawImage(tempCanvas, 0, 0); + originalCtx.restore(); + // Return the canvas, so it can be reused + CanvasProvider.returnCanvas(tempCanvas); + } } }, diff --git a/src/item/Item.js b/src/item/Item.js index dad79723..5af9a6c8 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -87,6 +87,20 @@ Item = Base.extend({ opacity: 1, + /** + * The blend mode of the item. + * + * Sample code: + * + * var circle = new Path.Circle(new Point(50, 50), 10); + * print(circle.blendMode); // normal + * + * // Change the blend mode of the path item: + * circle.blendMode = 'multiply'; + * + */ + blendMode: 'normal', + /** * Specifies whether the item is hidden. * @@ -137,7 +151,6 @@ Item = Base.extend({ } }, - // TODO: getBlendMode / setBlendMode // TODO: getIsolated / setIsolated (print specific feature) // TODO: get/setKnockout (print specific feature) // TODO get/setAlphaIsShape diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js index 2c38bcb7..92fc022c 100644 --- a/src/item/PlacedSymbol.js +++ b/src/item/PlacedSymbol.js @@ -38,12 +38,17 @@ PlacedSymbol = Item.extend({ return this._bounds; }, - draw: function(ctx) { - // TODO: we need to preserve strokewidth, but still transform the fill - ctx.save(); - this.matrix.applyToContext(ctx); - this.symbol.definition.draw(ctx); - ctx.restore(); + draw: function(ctx, param) { + if(this.blendMode != 'normal' && !param.ignoreBlendMode) { + BlendMode.process(ctx, this, param); + } else { + param.ignoreBlendMode = false; + // TODO: we need to preserve strokewidth, but still transform the fill + ctx.save(); + this.matrix.applyToContext(ctx); + this.symbol.definition.draw(ctx); + ctx.restore(); + } } // TODO: // embed() diff --git a/src/item/Raster.js b/src/item/Raster.js index f66468ca..ce75016b 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -165,12 +165,17 @@ Raster = Item.extend({ return this._bounds; }, - draw: function(ctx) { - ctx.save(); - this.matrix.applyToContext(ctx); - ctx.drawImage(this._canvas || this._image, - -this.size.width / 2, -this.size.height / 2); - ctx.restore(); + draw: function(ctx, param) { + if(this.blendMode != 'normal' && !param.ignoreBlendMode) { + BlendMode.process(ctx, this, param); + } else { + param.ignoreBlendMode = false; + ctx.save(); + this.matrix.applyToContext(ctx); + ctx.drawImage(this._canvas || this._image, + -this.size.width / 2, -this.size.height / 2); + ctx.restore(); + } } }, new function() { function getAverageColor(pixels) { diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index c54a6057..e20a262e 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -19,24 +19,30 @@ CompoundPath = PathItem.extend(new function() { } }, - draw: function(ctx) { + draw: function(ctx, param) { if(!this.visible) return; if (this.children.length) { - var firstChild = this.children[0]; - ctx.beginPath(); - for (var i = 0, l = this.children.length; i < l; i++) { - var child = this.children[i]; - child.draw(ctx, true); - } - firstChild.setCtxStyles(ctx); - if (firstChild.fillColor) { - ctx.fillStyle = firstChild.fillColor.getCssString(); - ctx.fill(); - } - if (firstChild.strokeColor) { - ctx.strokeStyle = firstChild.strokeColor.getCssString(); - ctx.stroke(); + if(this.blendMode && !param.ignoreBlendMode) { + BlendMode.process(ctx, this, param); + } else { + var firstChild = this.children[0]; + ctx.beginPath(); + param.compound = true; + for (var i = 0, l = this.children.length; i < l; i++) { + var child = this.children[i]; + child.draw(ctx, param); + } + param.compound = false; + firstChild.setCtxStyles(ctx); + if (firstChild.fillColor) { + ctx.fillStyle = firstChild.fillColor.getCssString(); + ctx.fill(); + } + if (firstChild.strokeColor) { + ctx.strokeStyle = firstChild.strokeColor.getCssString(); + ctx.stroke(); + } } } }, diff --git a/src/path/Path.js b/src/path/Path.js index 9ebe308f..91110f62 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -352,57 +352,63 @@ Path = PathItem.extend({ this.closed = ture; }, - draw: function(ctx, compound) { + draw: function(ctx, param) { if (!this.visible) return; - if (!compound) - ctx.beginPath(); - - var segments = this._segments; - var length = segments.length; - for (var i = 0; i < length; i++) { - var segment = segments[i]; - var x = segment.point.x; - var y = segment.point.y; - var handleIn = segment.handleIn; - if (i == 0) { - ctx.moveTo(x, y); - } else { - if (handleOut.isZero() && handleIn.isZero()) { - ctx.lineTo(x, y); + if(this.blendMode != 'normal' && !param.ignoreBlendMode) { + BlendMode.process(ctx, this, param); + } else { + param.ignoreBlendMode = false; + if (!param.compound) + ctx.beginPath(); + + var segments = this._segments; + var length = segments.length; + for (var i = 0; i < length; i++) { + var segment = segments[i]; + var x = segment.point.x; + var y = segment.point.y; + var handleIn = segment.handleIn; + if (i == 0) { + ctx.moveTo(x, y); } else { - ctx.bezierCurveTo( - outX, outY, - handleIn.x + x, handleIn.y + y, - x, y - ); + if (handleOut.isZero() && handleIn.isZero()) { + ctx.lineTo(x, y); + } else { + ctx.bezierCurveTo( + outX, outY, + handleIn.x + x, handleIn.y + y, + x, y + ); + } } + var handleOut = segment.handleOut; + var outX = handleOut.x + x; + var outY = handleOut.y + y; } - var handleOut = segment.handleOut; - var outX = handleOut.x + x; - var outY = handleOut.y + y; - } - if (this.closed && length > 1) { - var segment = segments[0]; - var x = segment.point.x; - var y = segment.point.y; - var handleIn = segment.handleIn; - ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y); - ctx.closePath(); - } - if (!compound) { - this.setCtxStyles(ctx); - ctx.save(); - ctx.globalAlpha = this.opacity; - if (this.fillColor) { - ctx.fillStyle = this.fillColor.getCanvasStyle(ctx); - ctx.fill(); + if (this.closed && length > 1) { + var segment = segments[0]; + var x = segment.point.x; + var y = segment.point.y; + var handleIn = segment.handleIn; + ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y); + ctx.closePath(); } - if (this.strokeColor) { - ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx); - ctx.stroke(); + if (!param.compound) { + this.setCtxStyles(ctx); + ctx.save(); + ctx.globalAlpha = this.opacity; + if (this.fillColor) { + ctx.fillStyle = this.fillColor.getCanvasStyle(ctx); + ctx.fill(); + } + if (this.strokeColor) { + ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx); + ctx.stroke(); + } + ctx.restore(); } - ctx.restore(); } + } }, new function() { // inject methods that require scoped privates /**