diff --git a/build.js b/build.js index 7c9327cb..304742b2 100644 --- a/build.js +++ b/build.js @@ -115,8 +115,12 @@ var filesToInclude = [ 'src/static_canvas.class.js', + ifSpecifiedInclude('freedrawing', 'src/base_brush.class.js'), + ifSpecifiedInclude('freedrawing', 'src/pencil_brush.class.js'), ifSpecifiedInclude('freedrawing', 'src/circle_brush.class.js'), + ifSpecifiedInclude('freedrawing', 'src/spray_brush.class.js'), + ifSpecifiedInclude('freedrawing', 'src/pattern_brush.class.js'), ifSpecifiedInclude('interaction', 'src/canvas.class.js'), diff --git a/src/base_brush.class.js b/src/base_brush.class.js new file mode 100644 index 00000000..84dc23bf --- /dev/null +++ b/src/base_brush.class.js @@ -0,0 +1,75 @@ +/** + * BaseBrush class + * @class fabric.BaseBrush + */ +fabric.BaseBrush = fabric.util.createClass({ + + /** + * Color of a brush + * @property + * @type String + */ + color: 'rgb(0, 0, 0)', + + /** + * Width of a brush + * @property + * @type Number + */ + width: 1, + + /** + * Shadow blur of a brush + * @property + * @type Number + */ + shadowBlur: 0, + + /** + * Shadow color of a brush + * @property + * @type String + */ + shadowColor: '', + + /** + * Shadow offset x of a brush + * @property + * @type Number + */ + shadowOffsetX: 0, + + /** + * Shadow offset y of a brush + * @property + * @type Number + */ + shadowOffsetY: 0, + + /** + * Sets brush styles + * @method setBrushStyles + */ + setBrushStyles: function() { + var ctx = this.canvas.contextTop; + + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.lineCap = ctx.lineJoin = 'round'; + }, + + /** + * Sets brush shadow styles + * @method setShadowStyles + */ + setShadowStyles: function() { + var ctx = this.canvas.contextTop; + + if (this.shadowBlur) { + ctx.shadowBlur = this.shadowBlur; + ctx.shadowColor = this.shadowColor || this.color; + ctx.shadowOffsetX = this.shadowOffsetX; + ctx.shadowOffsetY = this.shadowOffsetY; + } + } +}); \ No newline at end of file diff --git a/src/circle_brush.class.js b/src/circle_brush.class.js index e0150b54..44f6d098 100644 --- a/src/circle_brush.class.js +++ b/src/circle_brush.class.js @@ -2,14 +2,7 @@ * CircleBrush class * @class fabric.CircleBrush */ -fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prototype */ { - - /** - * Color of the brush - * @property - * @type String - */ - color: 'rgb(0, 0, 0)', +fabric.CircleBrush = fabric.util.createClass( fabric.BaseBrush, /** @scope fabric.CircleBrush.prototype */ { /** * Width of a brush @@ -18,34 +11,6 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot */ width: 10, - /** - * Shadow blur of a pencil - * @property - * @type Number - */ - shadowBlur: 0, - - /** - * Shadow color of a pencil - * @property - * @type String - */ - shadowColor: '', - - /** - * Shadow offset x of a pencil - * @property - * @type Number - */ - shadowOffsetX: 0, - - /** - * Shadow offset y of a pencil - * @property - * @type Number - */ - shadowOffsetY: 0, - /** * Constructor * @method initialize diff --git a/src/pattern_brush.class.js b/src/pattern_brush.class.js new file mode 100644 index 00000000..d0eee40a --- /dev/null +++ b/src/pattern_brush.class.js @@ -0,0 +1,41 @@ +/** + * PatternBrush class + * @class fabric.PatternBrush + * @extends fabric.BaseBrush + */ +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @scope fabric.PatternBrush.prototype */ { + + createPattern: function(patternCanvas) { + + var dotWidth = 20, + dotDistance = 5, + patternCtx = patternCanvas.getContext('2d'); + + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + }, + + /** + * Creates "pattern" instance property + * @method createPattern + */ + getPattern: function() { + var patternCanvas = fabric.document.createElement('canvas'); + this.createPattern(patternCanvas); + return this.canvas.contextTop.createPattern(patternCanvas, 'repeat'); + }, + + /** + * Sets brush styles + * @method setBrushStyles + */ + setBrushStyles: function() { + this.callSuper('setBrushStyles'); + this.canvas.contextTop.strokeStyle = this.getPattern(); + } +}); \ No newline at end of file diff --git a/src/pencil_brush.class.js b/src/pencil_brush.class.js index dba4bb8e..61d50e40 100644 --- a/src/pencil_brush.class.js +++ b/src/pencil_brush.class.js @@ -6,50 +6,9 @@ /** * PencilBrush class * @class fabric.PencilBrush + * @extends fabric.BaseBrush */ - fabric.PencilBrush = fabric.util.createClass( /** @scope fabric.PencilBrush.prototype */ { - - /** - * Color of the pencil - * @property - * @type String - */ - color: 'rgb(0, 0, 0)', - - /** - * Width of a pencil - * @property - * @type Number - */ - width: 1, - - /** - * Shadow blur of a pencil - * @property - * @type Number - */ - shadowBlur: 0, - - /** - * Shadow color of a pencil - * @property - * @type String - */ - shadowColor: '', - - /** - * Shadow offset x of a pencil - * @property - * @type Number - */ - shadowOffsetX: 0, - - /** - * Shadow offset y of a pencil - * @property - * @type Number - */ - shadowOffsetY: 0, + fabric.PencilBrush = fabric.util.createClass( fabric.BaseBrush, /** @scope fabric.PencilBrush.prototype */ { /** * Constructor @@ -126,19 +85,8 @@ _reset: function() { this._points.length = 0; - var ctx = this.canvas.contextTop; - - ctx.strokeStyle = this.color; - ctx.lineWidth = this.width; - - if (this.shadowBlur) { - ctx.shadowBlur = this.shadowBlur; - ctx.shadowColor = this.shadowColor || this.color; - ctx.shadowOffsetX = this.shadowOffsetX; - ctx.shadowOffsetY = this.shadowOffsetY; - } - - ctx.lineCap = ctx.lineJoin = 'round'; + this.setBrushStyles(); + this.setShadowStyles(); }, /** diff --git a/src/spray_brush.class.js b/src/spray_brush.class.js new file mode 100644 index 00000000..94c1da1b --- /dev/null +++ b/src/spray_brush.class.js @@ -0,0 +1,157 @@ +/** + * SprayBrush class + * @class fabric.SprayBrush + */ +fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @scope fabric.SprayBrush.prototype */ { + + /** + * Width of a spray + * @property + * @type Number + */ + width: 10, + + /** + * Density of a spray (number of dots per chunk) + * @property + * @type Number + */ + density: 20, + + /** + * Width of spray dots + * @property + * @type Number + */ + dotWidth: 1, + + /** + * Width variance of spray dots + * @property + * @type Number + */ + dotWidthVariance: 1, + + /** + * Whether opacity of a dot should be random + * @property + * @type Boolean + */ + randomOpacity: false, + + /** + * Constructor + * @method initialize + * @param {fabric.Canvas} canvas + * @return {fabric.SprayBrush} Instance of a spray brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = [ ]; + }, + + /** + * @method onMouseDown + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this.setShadowStyles(); + + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * @method onMouseMove + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * @method onMouseUp + */ + onMouseUp: function() { + var originalRenderOnAddition = this.canvas.renderOnAddition; + this.canvas.renderOnAddition = false; + + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + fill: this.color + }); + + this.canvas.add(rect); + } + } + + this.canvas.renderOnAddition = originalRenderOnAddition; + this.canvas.clearContext(this.canvas.contextTop); + this.canvas.renderAll(); + }, + + /** + * @method render + */ + render: function() { + var ctx = this.canvas.contextTop; + ctx.fillStyle = this.color; + ctx.save(); + + for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { + var point = this.sprayChunkPoints[i]; + if (typeof point.opacity !== 'undefined') { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + + /** + * @method addSprayChunk + * @param {Object} pointer + */ + addSprayChunk: function(pointer) { + this.sprayChunkPoints = [ ]; + + var x, y, width, radius = this.width / 2; + + for (var i = 0; i < this.density; i++) { + + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt( + // bottom clamp width to 1 + Math.max(1, this.dotWidth - this.dotWidthVariance), + this.dotWidth + this.dotWidthVariance); + } + else { + width = this.dotWidth; + } + + var point = { x: x, y: y, width: width }; + + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + + this.sprayChunkPoints.push(point); + } + + this.sprayChunks.push(this.sprayChunkPoints); + } +}); \ No newline at end of file