mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-30 01:54:44 +00:00
Implement smooth free drawing. Free drawing is now a separate module. Thanks @coulix!
This commit is contained in:
commit
352dd44b3c
11 changed files with 551 additions and 208 deletions
|
|
@ -1,6 +1,6 @@
|
|||
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "0.9.27" };
|
||||
var fabric = fabric || { version: "0.9.28" };
|
||||
|
||||
if (typeof exports !== 'undefined') {
|
||||
exports.fabric = fabric;
|
||||
|
|
|
|||
5
build.js
5
build.js
|
|
@ -112,6 +112,9 @@ var filesToInclude = [
|
|||
'src/color.class.js',
|
||||
|
||||
'src/static_canvas.class.js',
|
||||
|
||||
ifSpecifiedInclude('freedrawing', 'src/freedrawing.class.js'),
|
||||
|
||||
ifSpecifiedInclude('interaction', 'src/canvas.class.js'),
|
||||
|
||||
'src/canvas.animation.js',
|
||||
|
|
@ -162,4 +165,4 @@ appendFileContents(filesToInclude, function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
370
dist/all.js
vendored
370
dist/all.js
vendored
|
|
@ -1,7 +1,7 @@
|
|||
/* build: `node build.js modules=ALL` */
|
||||
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "0.9.27" };
|
||||
var fabric = fabric || { version: "0.9.28" };
|
||||
|
||||
if (typeof exports !== 'undefined') {
|
||||
exports.fabric = fabric;
|
||||
|
|
@ -4561,6 +4561,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));
|
||||
},
|
||||
|
|
@ -4594,6 +4601,7 @@ fabric.util.string = {
|
|||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
||||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
|
@ -6249,6 +6257,246 @@ 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.FreeDrawing is already defined');
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.FreeDrawing = fabric.util.createClass({
|
||||
|
||||
/**
|
||||
* 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 fabricCanvas {FabricCanvas}
|
||||
*
|
||||
*/
|
||||
initialize: function(fabricCanvas) {
|
||||
this.canvas = fabricCanvas;
|
||||
this._points = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @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.canvas.freeDrawingColor;
|
||||
ctx.lineWidth = this.canvas.freeDrawingLineWidth;
|
||||
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 pointerPoint = new fabric.Point(pointer.x, pointer.y);
|
||||
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);
|
||||
|
||||
p1 = this._points[i];
|
||||
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() {
|
||||
this.canvas._isCurrentlyDrawing = false;
|
||||
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
|
||||
this.canvas.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
p.fill = null;
|
||||
p.stroke = this.canvas.freeDrawingColor;
|
||||
p.strokeWidth = this.canvas.freeDrawingLineWidth;
|
||||
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, 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,
|
||||
|
|
@ -6268,9 +6516,6 @@ fabric.util.string = {
|
|||
'mb': 's-resize'
|
||||
},
|
||||
|
||||
utilMin = fabric.util.array.min,
|
||||
utilMax = fabric.util.array.max,
|
||||
|
||||
sqrt = Math.sqrt,
|
||||
pow = Math.pow,
|
||||
atan2 = Math.atan2,
|
||||
|
|
@ -6416,8 +6661,7 @@ fabric.util.string = {
|
|||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
this._freeDrawingXPoints = [ ];
|
||||
this._freeDrawingYPoints = [ ];
|
||||
this.freeDrawing = fabric.FreeDrawing && new fabric.FreeDrawing(this);
|
||||
this._initWrapperElement();
|
||||
this._createUpperCanvas();
|
||||
this._initEvents();
|
||||
|
|
@ -6494,7 +6738,7 @@ fabric.util.string = {
|
|||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._finalizeDrawingPath();
|
||||
this.freeDrawing._finalizeAndAddPath();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -6564,15 +6808,20 @@ fabric.util.string = {
|
|||
*/
|
||||
__onMouseDown: function (e) {
|
||||
|
||||
var pointer;
|
||||
|
||||
// accept only left clicks
|
||||
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
|
||||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
this._prepareForDrawing(e);
|
||||
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;
|
||||
}
|
||||
|
|
@ -6581,10 +6830,11 @@ fabric.util.string = {
|
|||
if (this._currentTransform) return;
|
||||
|
||||
var target = this.findTarget(e),
|
||||
pointer = this.getPointer(e),
|
||||
activeGroup = this.getActiveGroup(),
|
||||
corner;
|
||||
|
||||
pointer = this.getPointer(e);
|
||||
|
||||
if (this._shouldClearSelection(e)) {
|
||||
|
||||
this._groupSelector = {
|
||||
|
|
@ -6638,17 +6888,23 @@ fabric.util.string = {
|
|||
*/
|
||||
__onMouseMove: function (e) {
|
||||
|
||||
var target;
|
||||
var target, pointer;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
this._captureDrawingPath(e);
|
||||
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;
|
||||
}
|
||||
|
||||
var groupSelector = this._groupSelector, pointer;
|
||||
var groupSelector = this._groupSelector;
|
||||
|
||||
// We initially clicked in an empty area, so we draw a box for multiple selection.
|
||||
if (groupSelector !== null) {
|
||||
|
|
@ -6954,91 +7210,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
|
||||
|
|
@ -11653,6 +11824,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
};
|
||||
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
||||
|
||||
(function(global) {
|
||||
|
||||
"use strict";
|
||||
|
|
|
|||
10
dist/all.min.js
vendored
10
dist/all.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/all.min.js.gz
vendored
BIN
dist/all.min.js.gz
vendored
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fabric",
|
||||
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
|
||||
"version": "0.9.27",
|
||||
"version": "0.9.28",
|
||||
"author": "Juriy Zaytsev <kangax@gmail.com>",
|
||||
"keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"],
|
||||
"repository": "git://github.com/kangax/fabric.js",
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@
|
|||
'mb': 's-resize'
|
||||
},
|
||||
|
||||
utilMin = fabric.util.array.min,
|
||||
utilMax = fabric.util.array.max,
|
||||
|
||||
sqrt = Math.sqrt,
|
||||
pow = Math.pow,
|
||||
atan2 = Math.atan2,
|
||||
|
|
@ -165,8 +162,7 @@
|
|||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
this._freeDrawingXPoints = [ ];
|
||||
this._freeDrawingYPoints = [ ];
|
||||
this.freeDrawing = fabric.FreeDrawing && new fabric.FreeDrawing(this);
|
||||
this._initWrapperElement();
|
||||
this._createUpperCanvas();
|
||||
this._initEvents();
|
||||
|
|
@ -243,7 +239,7 @@
|
|||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._finalizeDrawingPath();
|
||||
this.freeDrawing._finalizeAndAddPath();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
|
@ -313,15 +309,20 @@
|
|||
*/
|
||||
__onMouseDown: function (e) {
|
||||
|
||||
var pointer;
|
||||
|
||||
// accept only left clicks
|
||||
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
|
||||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
this._prepareForDrawing(e);
|
||||
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;
|
||||
}
|
||||
|
|
@ -330,10 +331,11 @@
|
|||
if (this._currentTransform) return;
|
||||
|
||||
var target = this.findTarget(e),
|
||||
pointer = this.getPointer(e),
|
||||
activeGroup = this.getActiveGroup(),
|
||||
corner;
|
||||
|
||||
pointer = this.getPointer(e);
|
||||
|
||||
if (this._shouldClearSelection(e)) {
|
||||
|
||||
this._groupSelector = {
|
||||
|
|
@ -387,17 +389,23 @@
|
|||
*/
|
||||
__onMouseMove: function (e) {
|
||||
|
||||
var target;
|
||||
var target, pointer;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
this._captureDrawingPath(e);
|
||||
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;
|
||||
}
|
||||
|
||||
var groupSelector = this._groupSelector, pointer;
|
||||
var groupSelector = this._groupSelector;
|
||||
|
||||
// We initially clicked in an empty area, so we draw a box for multiple selection.
|
||||
if (groupSelector !== null) {
|
||||
|
|
@ -703,91 +711,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
|
||||
|
|
|
|||
238
src/freedrawing.class.js
Normal file
238
src/freedrawing.class.js
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
(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.FreeDrawing is already defined');
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.FreeDrawing = fabric.util.createClass({
|
||||
|
||||
/**
|
||||
* 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 fabricCanvas {FabricCanvas}
|
||||
*
|
||||
*/
|
||||
initialize: function(fabricCanvas) {
|
||||
this.canvas = fabricCanvas;
|
||||
this._points = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @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.canvas.freeDrawingColor;
|
||||
ctx.lineWidth = this.canvas.freeDrawingLineWidth;
|
||||
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 pointerPoint = new fabric.Point(pointer.x, pointer.y);
|
||||
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);
|
||||
|
||||
p1 = this._points[i];
|
||||
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() {
|
||||
this.canvas._isCurrentlyDrawing = false;
|
||||
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
|
||||
this.canvas.renderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
p.fill = null;
|
||||
p.stroke = this.canvas.freeDrawingColor;
|
||||
p.strokeWidth = this.canvas.freeDrawingLineWidth;
|
||||
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, 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);
|
||||
|
|
@ -786,4 +786,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);
|
||||
|
|
|
|||
|
|
@ -1221,4 +1221,4 @@
|
|||
*/
|
||||
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue