mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-03-16 22:10:32 +00:00
Smooth live free drawing on contextTop canvas
This commit is contained in:
parent
d0952172a3
commit
61db93d5ec
7 changed files with 604 additions and 188 deletions
4
build.js
4
build.js
|
|
@ -112,6 +112,8 @@ var filesToInclude = [
|
|||
'src/color.class.js',
|
||||
|
||||
'src/static_canvas.class.js',
|
||||
|
||||
ifSpecifiedInclude('interaction', 'src/freedrawing.class.js'),
|
||||
ifSpecifiedInclude('interaction', 'src/canvas.class.js'),
|
||||
|
||||
'src/canvas.animation.js',
|
||||
|
|
@ -162,4 +164,4 @@ appendFileContents(filesToInclude, function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
393
dist/all.js
vendored
393
dist/all.js
vendored
|
|
@ -4536,6 +4536,13 @@ fabric.util.string = {
|
|||
return Math.sqrt(dx * dx + dy * dy);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the point between A (x,y) and B (x,y)
|
||||
*/
|
||||
midPointFrom: function (that) {
|
||||
return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
|
||||
},
|
||||
|
||||
min: function (that) {
|
||||
return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
|
||||
},
|
||||
|
|
@ -4569,6 +4576,7 @@ fabric.util.string = {
|
|||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
||||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
|
@ -6191,6 +6199,282 @@ fabric.util.string = {
|
|||
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
|
||||
|
||||
})();
|
||||
|
||||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var fabric = global.fabric || (global.fabric = { });
|
||||
|
||||
var utilMin = fabric.util.array.min,
|
||||
utilMax = fabric.util.array.max;
|
||||
|
||||
if (fabric.FreeDrawing) {
|
||||
fabric.warn('fabric.FreeDrawin is already defined');
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.FreeDrawing = FreeDrawing;
|
||||
|
||||
function FreeDrawing(fabricCanvas) {
|
||||
this.init(fabricCanvas);
|
||||
}
|
||||
|
||||
FreeDrawing.prototype = {
|
||||
constructor: FreeDrawing,
|
||||
|
||||
/**
|
||||
* Free Drawer handles scribbling on a fabricCanvas.
|
||||
* It converts the hand writting to a SVG Path and adds this
|
||||
* path to the canvas.
|
||||
*
|
||||
* @metod init
|
||||
* @param fabricCavnas {FabricCanvas}
|
||||
*
|
||||
*/
|
||||
init: function(fabricCanvas) {
|
||||
this.canvas = fabricCanvas;
|
||||
this._points = [];
|
||||
this._color = this.canvas.freeDrawingColor;
|
||||
this._strokeWidth = this.canvas.freeDrawingLineWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set path color
|
||||
* @method setColor
|
||||
* @param color {String/rgb/rgba}
|
||||
*
|
||||
*/
|
||||
setColor: function(color) {
|
||||
this._color = color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set path thichness (strokeWidth)
|
||||
* @method setThickness
|
||||
* @param thickness {int}
|
||||
*
|
||||
*/
|
||||
setThickness: function(thickness) {
|
||||
this._strokeWidth = thickness;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _addPoint
|
||||
*
|
||||
*/
|
||||
_addPoint: function(point) {
|
||||
this._points.push(point);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear points array and set contextTop canvas
|
||||
* style.
|
||||
*
|
||||
* @private
|
||||
* @method _reset
|
||||
*
|
||||
*/
|
||||
_reset: function() {
|
||||
this._points.length = 0;
|
||||
var ctx = this.canvas.contextTop;
|
||||
|
||||
// set freehanddrawing line canvas parameters
|
||||
ctx.strokeStyle = this._color;
|
||||
ctx.lineWidth = this._strokeWidth;
|
||||
ctx.lineCap = ctx.lineJoin = 'round';
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _prepareForDrawing
|
||||
*/
|
||||
_prepareForDrawing: function(pointer) {
|
||||
|
||||
this.canvas._isCurrentlyDrawing = true;
|
||||
this.canvas.discardActiveObject().renderAll();
|
||||
|
||||
var p = new fabric.Point(pointer.x, pointer.y);
|
||||
this._reset();
|
||||
this._addPoint(p);
|
||||
this.canvas.contextTop.moveTo(p.x, p.y);
|
||||
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _captureDrawingPath
|
||||
*
|
||||
* @param point {pointer} (fabric.util.pointer) actual mouse position
|
||||
* related to the canvas.
|
||||
*/
|
||||
_captureDrawingPath: function(pointer) {
|
||||
var MIN_POINT_DISTANCE = 2;
|
||||
var pointerPoint = new fabric.Point(pointer.x, pointer.y);
|
||||
// Only push point if min_point_distance is reached
|
||||
var lastPoint = this._points[this._points.length -1];
|
||||
|
||||
if (pointerPoint.distanceFrom(lastPoint) > MIN_POINT_DISTANCE)
|
||||
this._addPoint(pointerPoint);
|
||||
|
||||
if (this._points.length < 2)
|
||||
this._addPoint(pointerPoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a smooth path on the topCanvas using
|
||||
* quadraticCurveTo.
|
||||
*
|
||||
* @private
|
||||
* @method _render
|
||||
*
|
||||
*/
|
||||
_render: function() {
|
||||
var ctx = this.canvas.contextTop;
|
||||
ctx.beginPath();
|
||||
|
||||
var p1 = this._points[0];
|
||||
var p2 = this._points[1];
|
||||
|
||||
for (var i = 1, len = this._points.length; i < len; i++) {
|
||||
// we pick the point between pi+1 & pi+2 as the
|
||||
// end point and p1 as our control point.
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
|
||||
|
||||
var p1 = this._points[i];
|
||||
var p2 = this._points[i+1];
|
||||
}
|
||||
// Draw last line as a straight line while
|
||||
// we wait for the next point to be able to calculate
|
||||
// the bezier control point
|
||||
ctx.lineTo(p1.x, p1.y);
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an SVG path based on our
|
||||
* captured points and their boundinb box.
|
||||
*
|
||||
* @private
|
||||
* @method _getSVGPathData
|
||||
*
|
||||
*/
|
||||
_getSVGPathData: function() {
|
||||
this.box = this.getPathBoundingBox(this._points);
|
||||
return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method getPathBoundingBox
|
||||
* @param points {Array of points}
|
||||
*/
|
||||
getPathBoundingBox: function(points) {
|
||||
var xBounds = [],
|
||||
yBounds = [],
|
||||
p1 = points[0],
|
||||
p2 = points[1],
|
||||
startPoint = p1;
|
||||
|
||||
for (var i = 1, len = points.length; i < len; i++) {
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
// with startPoint, p1 as control point, midpoint as end point
|
||||
xBounds.push(startPoint.x);
|
||||
xBounds.push(midPoint.x);
|
||||
yBounds.push(startPoint.y);
|
||||
yBounds.push(midPoint.y);
|
||||
|
||||
p1 = points[i];
|
||||
p2 = points[i+1];
|
||||
startPoint = midPoint;
|
||||
} // end for
|
||||
|
||||
xBounds.push(p1.x);
|
||||
yBounds.push(p1.y);
|
||||
|
||||
return {
|
||||
minx: utilMin(xBounds),
|
||||
miny: utilMin(yBounds),
|
||||
maxx: utilMax(xBounds),
|
||||
maxy: utilMax(yBounds)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @method convertPointsToSVGPath
|
||||
* @param points {Array of points}
|
||||
*/
|
||||
convertPointsToSVGPath: function(points, minX, maxX, minY, maxY) {
|
||||
var path = [];
|
||||
var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
|
||||
var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);
|
||||
|
||||
path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' ');
|
||||
for (var i = 1, len = points.length; i < len; i++) {
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
// p1 is our bezier control point
|
||||
// midpoint is our endpoint
|
||||
// start point is p(i-1) value.
|
||||
path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
|
||||
p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
|
||||
if ((i+1) < points.length)
|
||||
p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY);
|
||||
}
|
||||
path.push('L ', p1.x, ' ', p1.y, ' ');
|
||||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouseup after drawing the path on contextTop canvas
|
||||
* we use the points captured to create an new fabric path object
|
||||
* and add it to the fabric canvas.
|
||||
*
|
||||
* @method _finalizeAndAddPath
|
||||
*
|
||||
*/
|
||||
|
||||
_finalizeAndAddPath: function() {
|
||||
var ctx = this.canvas.contextTop;
|
||||
ctx.closePath();
|
||||
var path = this._getSVGPathData();
|
||||
path = path.join('');
|
||||
|
||||
if (path === "M 0 0 Q 0 0 0 0 L 0 0") {
|
||||
// do not create 0 width/height paths, as they are
|
||||
// rendered inconsistently across browsers
|
||||
// Firefox 4, for example, renders a dot,
|
||||
// whereas Chrome 10 renders nothing
|
||||
fabricCanvas.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
p.fill = null;
|
||||
p.stroke = this._color;
|
||||
p.strokeWidth = this._strokeWidth;
|
||||
this.canvas.add(p);
|
||||
|
||||
// set path origin coordinates based on our bouding box
|
||||
var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2;
|
||||
var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2;
|
||||
|
||||
this.canvas.contextTop.arc(originLeft, originTop, 3);
|
||||
|
||||
p.set("left", originLeft);
|
||||
p.set("top", originTop)
|
||||
// does not change position
|
||||
p.setCoords();
|
||||
|
||||
this.canvas.renderAll();
|
||||
|
||||
// fire event 'path' created
|
||||
this.canvas.fire('path:created', { path: p });
|
||||
}
|
||||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
||||
(function() {
|
||||
|
||||
var extend = fabric.util.object.extend,
|
||||
|
|
@ -6364,8 +6648,7 @@ fabric.util.string = {
|
|||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
this._freeDrawingXPoints = [ ];
|
||||
this._freeDrawingYPoints = [ ];
|
||||
this.freeDrawing = new fabric.FreeDrawing(this);
|
||||
this._initWrapperElement();
|
||||
this._createUpperCanvas();
|
||||
this._initEvents();
|
||||
|
|
@ -6442,7 +6725,8 @@ fabric.util.string = {
|
|||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._finalizeDrawingPath();
|
||||
this._isCurrentlyDrawing = false;
|
||||
this.freeDrawing._finalizeAndAddPath();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -6517,10 +6801,13 @@ fabric.util.string = {
|
|||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
this._prepareForDrawing(e);
|
||||
var pointer = this.getPointer(e);
|
||||
this.freeDrawing._prepareForDrawing(pointer);
|
||||
|
||||
// capture coordinates immediately;
|
||||
// this allows to draw dots (when movement never occurs)
|
||||
this.freeDrawing._captureDrawingPath(pointer);
|
||||
|
||||
// capture coordinates immediately; this allows to draw dots (when movement never occurs)
|
||||
this._captureDrawingPath(e);
|
||||
this.fire('mouse:down', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -6590,7 +6877,13 @@ fabric.util.string = {
|
|||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
this._captureDrawingPath(e);
|
||||
var pointer = this.getPointer(e);
|
||||
this.freeDrawing._captureDrawingPath(pointer);
|
||||
|
||||
// redraw curve
|
||||
// clear top canvas
|
||||
this.clearContext(this.contextTop);
|
||||
this.freeDrawing._render(this.contextTop);
|
||||
}
|
||||
this.fire('mouse:move', { e: e });
|
||||
return;
|
||||
|
|
@ -6902,91 +7195,6 @@ fabric.util.string = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _prepareForDrawing
|
||||
*/
|
||||
_prepareForDrawing: function(e) {
|
||||
|
||||
this._isCurrentlyDrawing = true;
|
||||
|
||||
this.discardActiveObject().renderAll();
|
||||
|
||||
var pointer = this.getPointer(e);
|
||||
|
||||
this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
|
||||
|
||||
this._freeDrawingXPoints.push(pointer.x);
|
||||
this._freeDrawingYPoints.push(pointer.y);
|
||||
|
||||
this.contextTop.beginPath();
|
||||
this.contextTop.moveTo(pointer.x, pointer.y);
|
||||
this.contextTop.strokeStyle = this.freeDrawingColor;
|
||||
this.contextTop.lineWidth = this.freeDrawingLineWidth;
|
||||
this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _captureDrawingPath
|
||||
*/
|
||||
_captureDrawingPath: function(e) {
|
||||
var pointer = this.getPointer(e);
|
||||
|
||||
this._freeDrawingXPoints.push(pointer.x);
|
||||
this._freeDrawingYPoints.push(pointer.y);
|
||||
|
||||
this.contextTop.lineTo(pointer.x, pointer.y);
|
||||
this.contextTop.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _finalizeDrawingPath
|
||||
*/
|
||||
_finalizeDrawingPath: function() {
|
||||
|
||||
this.contextTop.closePath();
|
||||
|
||||
this._isCurrentlyDrawing = false;
|
||||
|
||||
var minX = utilMin(this._freeDrawingXPoints),
|
||||
minY = utilMin(this._freeDrawingYPoints),
|
||||
maxX = utilMax(this._freeDrawingXPoints),
|
||||
maxY = utilMax(this._freeDrawingYPoints),
|
||||
path = [ ],
|
||||
xPoints = this._freeDrawingXPoints,
|
||||
yPoints = this._freeDrawingYPoints;
|
||||
|
||||
path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
|
||||
|
||||
for (var i = 1, len = xPoints.length; i < len; i++) {
|
||||
path.push('L ', xPoints[i] - minX, ' ', yPoints[i] - minY, ' ');
|
||||
}
|
||||
|
||||
// TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
|
||||
// and instead fire something like "drawing:completed" event with path string
|
||||
|
||||
path = path.join('');
|
||||
|
||||
if (path === "M 0 0 L 0 0 ") {
|
||||
// do not create 0 width/height paths, as they are rendered inconsistently across browsers
|
||||
// Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
|
||||
this.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
|
||||
p.fill = null;
|
||||
p.stroke = this.freeDrawingColor;
|
||||
p.strokeWidth = this.freeDrawingLineWidth;
|
||||
this.add(p);
|
||||
p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
|
||||
this.renderAll();
|
||||
this.fire('path:created', { path: p });
|
||||
},
|
||||
|
||||
/**
|
||||
* Translates object by "setting" its left/top
|
||||
* @method _translateObject
|
||||
|
|
@ -11550,6 +11758,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
||||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
|
|
|||
|
|
@ -171,8 +171,7 @@
|
|||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
this._freeDrawingXPoints = [ ];
|
||||
this._freeDrawingYPoints = [ ];
|
||||
this.freeDrawing = new fabric.FreeDrawing(this);
|
||||
this._initWrapperElement();
|
||||
this._createUpperCanvas();
|
||||
this._initEvents();
|
||||
|
|
@ -249,7 +248,8 @@
|
|||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._finalizeDrawingPath();
|
||||
this._isCurrentlyDrawing = false;
|
||||
this.freeDrawing._finalizeAndAddPath();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -324,10 +324,13 @@
|
|||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
this._prepareForDrawing(e);
|
||||
var pointer = this.getPointer(e);
|
||||
this.freeDrawing._prepareForDrawing(pointer);
|
||||
|
||||
// capture coordinates immediately;
|
||||
// this allows to draw dots (when movement never occurs)
|
||||
this.freeDrawing._captureDrawingPath(pointer);
|
||||
|
||||
// capture coordinates immediately; this allows to draw dots (when movement never occurs)
|
||||
this._captureDrawingPath(e);
|
||||
this.fire('mouse:down', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -397,7 +400,13 @@
|
|||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
this._captureDrawingPath(e);
|
||||
var pointer = this.getPointer(e);
|
||||
this.freeDrawing._captureDrawingPath(pointer);
|
||||
|
||||
// redraw curve
|
||||
// clear top canvas
|
||||
this.clearContext(this.contextTop);
|
||||
this.freeDrawing._render(this.contextTop);
|
||||
}
|
||||
this.fire('mouse:move', { e: e });
|
||||
return;
|
||||
|
|
@ -709,91 +718,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _prepareForDrawing
|
||||
*/
|
||||
_prepareForDrawing: function(e) {
|
||||
|
||||
this._isCurrentlyDrawing = true;
|
||||
|
||||
this.discardActiveObject().renderAll();
|
||||
|
||||
var pointer = this.getPointer(e);
|
||||
|
||||
this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
|
||||
|
||||
this._freeDrawingXPoints.push(pointer.x);
|
||||
this._freeDrawingYPoints.push(pointer.y);
|
||||
|
||||
this.contextTop.beginPath();
|
||||
this.contextTop.moveTo(pointer.x, pointer.y);
|
||||
this.contextTop.strokeStyle = this.freeDrawingColor;
|
||||
this.contextTop.lineWidth = this.freeDrawingLineWidth;
|
||||
this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _captureDrawingPath
|
||||
*/
|
||||
_captureDrawingPath: function(e) {
|
||||
var pointer = this.getPointer(e);
|
||||
|
||||
this._freeDrawingXPoints.push(pointer.x);
|
||||
this._freeDrawingYPoints.push(pointer.y);
|
||||
|
||||
this.contextTop.lineTo(pointer.x, pointer.y);
|
||||
this.contextTop.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _finalizeDrawingPath
|
||||
*/
|
||||
_finalizeDrawingPath: function() {
|
||||
|
||||
this.contextTop.closePath();
|
||||
|
||||
this._isCurrentlyDrawing = false;
|
||||
|
||||
var minX = utilMin(this._freeDrawingXPoints),
|
||||
minY = utilMin(this._freeDrawingYPoints),
|
||||
maxX = utilMax(this._freeDrawingXPoints),
|
||||
maxY = utilMax(this._freeDrawingYPoints),
|
||||
path = [ ],
|
||||
xPoints = this._freeDrawingXPoints,
|
||||
yPoints = this._freeDrawingYPoints;
|
||||
|
||||
path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
|
||||
|
||||
for (var i = 1, len = xPoints.length; i < len; i++) {
|
||||
path.push('L ', xPoints[i] - minX, ' ', yPoints[i] - minY, ' ');
|
||||
}
|
||||
|
||||
// TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
|
||||
// and instead fire something like "drawing:completed" event with path string
|
||||
|
||||
path = path.join('');
|
||||
|
||||
if (path === "M 0 0 L 0 0 ") {
|
||||
// do not create 0 width/height paths, as they are rendered inconsistently across browsers
|
||||
// Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
|
||||
this.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
|
||||
p.fill = null;
|
||||
p.stroke = this.freeDrawingColor;
|
||||
p.strokeWidth = this.freeDrawingLineWidth;
|
||||
this.add(p);
|
||||
p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
|
||||
this.renderAll();
|
||||
this.fire('path:created', { path: p });
|
||||
},
|
||||
|
||||
/**
|
||||
* Translates object by "setting" its left/top
|
||||
* @method _translateObject
|
||||
|
|
|
|||
274
src/freedrawing.class.js
Normal file
274
src/freedrawing.class.js
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var fabric = global.fabric || (global.fabric = { });
|
||||
|
||||
var utilMin = fabric.util.array.min,
|
||||
utilMax = fabric.util.array.max;
|
||||
|
||||
if (fabric.FreeDrawing) {
|
||||
fabric.warn('fabric.FreeDrawin is already defined');
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.FreeDrawing = FreeDrawing;
|
||||
|
||||
function FreeDrawing(fabricCanvas) {
|
||||
this.init(fabricCanvas);
|
||||
}
|
||||
|
||||
FreeDrawing.prototype = {
|
||||
constructor: FreeDrawing,
|
||||
|
||||
/**
|
||||
* Free Drawer handles scribbling on a fabricCanvas.
|
||||
* It converts the hand writting to a SVG Path and adds this
|
||||
* path to the canvas.
|
||||
*
|
||||
* @metod init
|
||||
* @param fabricCavnas {FabricCanvas}
|
||||
*
|
||||
*/
|
||||
init: function(fabricCanvas) {
|
||||
this.canvas = fabricCanvas;
|
||||
this._points = [];
|
||||
this._color = this.canvas.freeDrawingColor;
|
||||
this._strokeWidth = this.canvas.freeDrawingLineWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set path color
|
||||
* @method setColor
|
||||
* @param color {String/rgb/rgba}
|
||||
*
|
||||
*/
|
||||
setColor: function(color) {
|
||||
this._color = color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set path thichness (strokeWidth)
|
||||
* @method setThickness
|
||||
* @param thickness {int}
|
||||
*
|
||||
*/
|
||||
setThickness: function(thickness) {
|
||||
this._strokeWidth = thickness;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _addPoint
|
||||
*
|
||||
*/
|
||||
_addPoint: function(point) {
|
||||
this._points.push(point);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear points array and set contextTop canvas
|
||||
* style.
|
||||
*
|
||||
* @private
|
||||
* @method _reset
|
||||
*
|
||||
*/
|
||||
_reset: function() {
|
||||
this._points.length = 0;
|
||||
var ctx = this.canvas.contextTop;
|
||||
|
||||
// set freehanddrawing line canvas parameters
|
||||
ctx.strokeStyle = this._color;
|
||||
ctx.lineWidth = this._strokeWidth;
|
||||
ctx.lineCap = ctx.lineJoin = 'round';
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _prepareForDrawing
|
||||
*/
|
||||
_prepareForDrawing: function(pointer) {
|
||||
|
||||
this.canvas._isCurrentlyDrawing = true;
|
||||
this.canvas.discardActiveObject().renderAll();
|
||||
|
||||
var p = new fabric.Point(pointer.x, pointer.y);
|
||||
this._reset();
|
||||
this._addPoint(p);
|
||||
this.canvas.contextTop.moveTo(p.x, p.y);
|
||||
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _captureDrawingPath
|
||||
*
|
||||
* @param point {pointer} (fabric.util.pointer) actual mouse position
|
||||
* related to the canvas.
|
||||
*/
|
||||
_captureDrawingPath: function(pointer) {
|
||||
var MIN_POINT_DISTANCE = 2;
|
||||
var pointerPoint = new fabric.Point(pointer.x, pointer.y);
|
||||
// Only push point if min_point_distance is reached
|
||||
var lastPoint = this._points[this._points.length -1];
|
||||
|
||||
if (pointerPoint.distanceFrom(lastPoint) > MIN_POINT_DISTANCE)
|
||||
this._addPoint(pointerPoint);
|
||||
|
||||
if (this._points.length < 2)
|
||||
this._addPoint(pointerPoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a smooth path on the topCanvas using
|
||||
* quadraticCurveTo.
|
||||
*
|
||||
* @private
|
||||
* @method _render
|
||||
*
|
||||
*/
|
||||
_render: function() {
|
||||
var ctx = this.canvas.contextTop;
|
||||
ctx.beginPath();
|
||||
|
||||
var p1 = this._points[0];
|
||||
var p2 = this._points[1];
|
||||
|
||||
for (var i = 1, len = this._points.length; i < len; i++) {
|
||||
// we pick the point between pi+1 & pi+2 as the
|
||||
// end point and p1 as our control point.
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
|
||||
|
||||
var p1 = this._points[i];
|
||||
var p2 = this._points[i+1];
|
||||
}
|
||||
// Draw last line as a straight line while
|
||||
// we wait for the next point to be able to calculate
|
||||
// the bezier control point
|
||||
ctx.lineTo(p1.x, p1.y);
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an SVG path based on our
|
||||
* captured points and their boundinb box.
|
||||
*
|
||||
* @private
|
||||
* @method _getSVGPathData
|
||||
*
|
||||
*/
|
||||
_getSVGPathData: function() {
|
||||
this.box = this.getPathBoundingBox(this._points);
|
||||
return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method getPathBoundingBox
|
||||
* @param points {Array of points}
|
||||
*/
|
||||
getPathBoundingBox: function(points) {
|
||||
var xBounds = [],
|
||||
yBounds = [],
|
||||
p1 = points[0],
|
||||
p2 = points[1],
|
||||
startPoint = p1;
|
||||
|
||||
for (var i = 1, len = points.length; i < len; i++) {
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
// with startPoint, p1 as control point, midpoint as end point
|
||||
xBounds.push(startPoint.x);
|
||||
xBounds.push(midPoint.x);
|
||||
yBounds.push(startPoint.y);
|
||||
yBounds.push(midPoint.y);
|
||||
|
||||
p1 = points[i];
|
||||
p2 = points[i+1];
|
||||
startPoint = midPoint;
|
||||
} // end for
|
||||
|
||||
xBounds.push(p1.x);
|
||||
yBounds.push(p1.y);
|
||||
|
||||
return {
|
||||
minx: utilMin(xBounds),
|
||||
miny: utilMin(yBounds),
|
||||
maxx: utilMax(xBounds),
|
||||
maxy: utilMax(yBounds)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @method convertPointsToSVGPath
|
||||
* @param points {Array of points}
|
||||
*/
|
||||
convertPointsToSVGPath: function(points, minX, maxX, minY, maxY) {
|
||||
var path = [];
|
||||
var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
|
||||
var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);
|
||||
|
||||
path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' ');
|
||||
for (var i = 1, len = points.length; i < len; i++) {
|
||||
var midPoint = p1.midPointFrom(p2);
|
||||
// p1 is our bezier control point
|
||||
// midpoint is our endpoint
|
||||
// start point is p(i-1) value.
|
||||
path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
|
||||
p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
|
||||
if ((i+1) < points.length)
|
||||
p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY);
|
||||
}
|
||||
path.push('L ', p1.x, ' ', p1.y, ' ');
|
||||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouseup after drawing the path on contextTop canvas
|
||||
* we use the points captured to create an new fabric path object
|
||||
* and add it to the fabric canvas.
|
||||
*
|
||||
* @method _finalizeAndAddPath
|
||||
*
|
||||
*/
|
||||
|
||||
_finalizeAndAddPath: function() {
|
||||
var ctx = this.canvas.contextTop;
|
||||
ctx.closePath();
|
||||
var path = this._getSVGPathData();
|
||||
path = path.join('');
|
||||
|
||||
if (path === "M 0 0 Q 0 0 0 0 L 0 0") {
|
||||
// do not create 0 width/height paths, as they are
|
||||
// rendered inconsistently across browsers
|
||||
// Firefox 4, for example, renders a dot,
|
||||
// whereas Chrome 10 renders nothing
|
||||
fabricCanvas.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
p.fill = null;
|
||||
p.stroke = this._color;
|
||||
p.strokeWidth = this._strokeWidth;
|
||||
this.canvas.add(p);
|
||||
|
||||
// set path origin coordinates based on our bouding box
|
||||
var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2;
|
||||
var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2;
|
||||
|
||||
this.canvas.contextTop.arc(originLeft, originTop, 3);
|
||||
|
||||
p.set("left", originLeft);
|
||||
p.set("top", originTop)
|
||||
// does not change position
|
||||
p.setCoords();
|
||||
|
||||
this.canvas.renderAll();
|
||||
|
||||
// fire event 'path' created
|
||||
this.canvas.fire('path:created', { path: p });
|
||||
}
|
||||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
|
@ -760,4 +760,4 @@
|
|||
return new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options));
|
||||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
|
|
|||
|
|
@ -161,6 +161,13 @@
|
|||
return Math.sqrt(dx * dx + dy * dy);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the point between A (x,y) and B (x,y)
|
||||
*/
|
||||
midPointFrom: function (that) {
|
||||
return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
|
||||
},
|
||||
|
||||
min: function (that) {
|
||||
return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
|
||||
},
|
||||
|
|
@ -193,4 +200,4 @@
|
|||
}
|
||||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
|
|
|||
|
|
@ -1188,4 +1188,4 @@
|
|||
*/
|
||||
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue