add segment brush

This commit is contained in:
DD Liu 2017-07-27 17:36:17 -04:00
parent 4a5c9f0b54
commit 27f7102b06
3 changed files with 110 additions and 75 deletions

View file

@ -1,7 +1,13 @@
import paper from 'paper'; import paper from 'paper';
import log from '../log/log'; import log from '../log/log';
import broadBrushHelper from './broad-brush-helper'; import broadBrushHelper from './broad-brush-helper';
import segmentBrushHelper from './segment-brush-helper';
/**
* Shared code for the brush and eraser tool. Adds functions on the paper tool object
* to handle mouse events, which are delegated to broad-brush-helper and segment-brush-helper
* based on the brushSize in the state.
*/
class BlobTool { class BlobTool {
static get BROAD () { static get BROAD () {
@ -15,7 +21,7 @@ class BlobTool {
// Segment brush has performance issues at low threshold, but broad brush has weird corners // Segment brush has performance issues at low threshold, but broad brush has weird corners
// which get more obvious the bigger it is // which get more obvious the bigger it is
static get THRESHOLD () { static get THRESHOLD () {
return 100000; return 9;
} }
setOptions (options) { setOptions (options) {
@ -62,10 +68,8 @@ class BlobTool {
path.strokeWidth = 1; path.strokeWidth = 1;
} }
} else { } else {
// TODO keep a separate active toolbar style for brush vs pen? // TODO: Add back brush styling. Keep a separate active toolbar style for brush vs pen.
//path = pg.stylebar.applyActiveToolbarStyle(path); // path = pg.stylebar.applyActiveToolbarStyle(path);
//TODO FIX
path.fillColor = 'black'; path.fillColor = 'black';
if (path === this.cursorPreview) { if (path === this.cursorPreview) {
@ -80,8 +84,7 @@ class BlobTool {
tool.fixedDistance = 1; tool.fixedDistance = 1;
broadBrushHelper(tool); broadBrushHelper(tool);
// TODO add segmentBrushHelper(tool);
//pg.segmentbrushhelper(tool, options);
tool.onMouseMove = function (event) { tool.onMouseMove = function (event) {
tool.resizeCursorIfNeeded(event.point); tool.resizeCursorIfNeeded(event.point);
@ -111,10 +114,10 @@ class BlobTool {
if (event.event.button > 0) return; // only first mouse button if (event.event.button > 0) return; // only first mouse button
if (this.brush === BlobTool.BROAD) { if (this.brush === BlobTool.BROAD) {
this.onBroadMouseDrag(event); this.onBroadMouseDrag(event);
} else if (this.brush === Blob.SEGMENT) { } else if (this.brush === BlobTool.SEGMENT) {
this.onSegmentMouseDrag(event); this.onSegmentMouseDrag(event);
} else { } else {
log.warning(`Brush type does not exist: ${this.brush}`); log.warn(`Brush type does not exist: ${this.brush}`);
} }
this.cursorPreview.bringToFront(); this.cursorPreview.bringToFront();
@ -132,7 +135,7 @@ class BlobTool {
} else if (this.brush === BlobTool.SEGMENT) { } else if (this.brush === BlobTool.SEGMENT) {
lastPath = this.onSegmentMouseUp(event); lastPath = this.onSegmentMouseUp(event);
} else { } else {
log.warning(`Brush type does not exist: ${this.brush}`); log.warn(`Brush type does not exist: ${this.brush}`);
} }
if (isEraser) { if (isEraser) {
@ -195,8 +198,8 @@ class BlobTool {
paths.splice(i, 1); paths.splice(i, 1);
} }
} }
// TODO FIX // TODO: Add back undo
//pg.undo.snapshot('broadbrush'); // pg.undo.snapshot('broadbrush');
}; };
tool.mergeEraser = function (lastPath) { tool.mergeEraser = function (lastPath) {
@ -210,8 +213,8 @@ class BlobTool {
// Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset // Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset
// and deselect the selection // and deselect the selection
if (items.length === 0) { if (items.length === 0) {
// TODO FIX // TODO: Add back selection handling
//pg.selection.clearSelection(); // pg.selection.clearSelection();
items = paper.project.getItems({ items = paper.project.getItems({
match: function (item) { match: function (item) {
return tool.isMergeable(lastPath, item) && tool.touches(lastPath, item); return tool.isMergeable(lastPath, item) && tool.touches(lastPath, item);
@ -225,7 +228,7 @@ class BlobTool {
// Gather path segments // Gather path segments
const subpaths = []; const subpaths = [];
// TODO handle compound path // TODO: Handle compound path
if (items[i] instanceof paper.Path && !items[i].closed) { if (items[i] instanceof paper.Path && !items[i].closed) {
const firstSeg = items[i].clone(); const firstSeg = items[i].clone();
const intersections = firstSeg.getIntersections(lastPath); const intersections = firstSeg.getIntersections(lastPath);
@ -290,8 +293,8 @@ class BlobTool {
items[i].remove(); items[i].remove();
} }
lastPath.remove(); lastPath.remove();
// TODO FIX // TODO: Add back undo handling
//pg.undo.snapshot('eraser'); // pg.undo.snapshot('eraser');
}; };
tool.colorMatch = function (existingPath, addedPath) { tool.colorMatch = function (existingPath, addedPath) {
@ -318,7 +321,7 @@ class BlobTool {
path1.hitTest(path2.firstSegment.point)) { path1.hitTest(path2.firstSegment.point)) {
return true; return true;
} }
// TODO clean up these no point paths // TODO: clean up these no point paths
return false; return false;
}; };
@ -338,4 +341,4 @@ class BlobTool {
} }
} }
module.exports = BlobTool; export default BlobTool;

View file

@ -1,8 +1,14 @@
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/ // Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
const paper = require('paper'); import paper from 'paper';
/** /**
* Applies segment brush functions to the tool. * Applies broad brush functions to the tool. Call them when the corresponding mouse event happens
* to get the broad brush behavior.
*
* Broad brush draws strokes by drawing points equidistant from the mouse event, perpendicular to the
* direction of motion. Shortcomings are that this path can cross itself, and 180 degree turns result
* in a flat edge.
*
* @param {!Tool} tool paper.js mouse object * @param {!Tool} tool paper.js mouse object
*/ */
const broadBrushHelper = function (tool) { const broadBrushHelper = function (tool) {
@ -102,4 +108,4 @@ const broadBrushHelper = function (tool) {
}; };
}; };
module.exports = broadBrushHelper; export default broadBrushHelper;

View file

@ -1,59 +1,85 @@
// Applies segment brush functions to the tool import paper from 'paper';
pg.segmentbrushhelper = function(tool, options) {
var lastPoint, finalPath;
tool.onSegmentMouseDown = function(event) { /**
tool.minDistance = options.brushSize/4; * Applies segment brush functions to the tool. Call them when the corresponding mouse event happens
tool.maxDistance = options.brushSize; * to get the broad brush behavior.
*
finalPath = new Path.Circle({ * Segment brush draws by creating a rounded rectangle for each mouse move event and merging all of
center: event.point, * those shapes. Unlike the broad brush, the resulting shape will not self-intersect and when you make
radius: options.brushSize/2 * 180 degree turns, you will get a rounded point as expected. Shortcomings include that performance is
}); * worse, especially as the number of segments to join increase, and that there are problems in paper.js
tool.stylePath(finalPath); * with union on shapes with curves, so that chunks of the union tend to disappear.
lastPoint = event.point; * (https://github.com/paperjs/paper.js/issues/1321)
}; *
* @param {!Tool} tool paper.js mouse object
tool.onSegmentMouseDrag = function(event) { */
var step = (event.delta).normalize(options.brushSize/2); const segmentBrushHelper = function (tool) {
var handleVec = step.clone(); let lastPoint;
handleVec.length = options.brushSize/2; let finalPath;
handleVec.angle += 90;
var path = new Path(); tool.onSegmentMouseDown = function (event) {
path = pg.stylebar.applyActiveToolbarStyle(path); if (event.event.button > 0) return; // only first mouse button
path.strokeColor = null;
// Add handles to round the end caps
path.add(new Segment(lastPoint - step, -handleVec, handleVec));
step.angle += 90;
path.add(event.lastPoint + step); tool.minDistance = this.options.brushSize / 4;
path.insert(0, event.lastPoint - step); tool.maxDistance = this.options.brushSize;
path.add(event.point + step);
path.insert(0, event.point - step); finalPath = new paper.Path.Circle({
center: event.point,
radius: this.options.brushSize / 2
});
tool.stylePath(finalPath);
lastPoint = event.point;
};
tool.onSegmentMouseDrag = function (event) {
if (event.event.button > 0) return; // only first mouse button
// Add end cap const step = (event.delta).normalize(this.options.brushSize / 2);
step.angle -= 90; const handleVec = step.clone();
path.add(new Segment(event.point + step, handleVec, -handleVec)); handleVec.length = this.options.brushSize / 2;
path.closed = true; handleVec.angle += 90;
// The unite function on curved paths does not always work (sometimes deletes half the path)
// so we have to flatten.
path.flatten(options.brushSize/5);
lastPoint = event.point;
var newPath = finalPath.unite(path);
path.remove();
finalPath.remove();
finalPath = newPath;
};
tool.onSegmentMouseUp = function(event) { const path = new paper.Path();
// TODO: This smoothing tends to cut off large portions of the path! Would like to eventually
// add back smoothing, maybe a custom implementation that only applies to a subset of the line? // TODO: Add back brush styling
// path = pg.stylebar.applyActiveToolbarStyle(path);
path.fillColor = 'black';
// Smooth the path. // Add handles to round the end caps
finalPath.simplify(2); path.add(new paper.Segment(lastPoint.subtract(step), handleVec.multiply(-1), handleVec));
// console.log(finalPath.segments); step.angle += 90;
return finalPath;
}; path.add(event.lastPoint.add(step));
} path.insert(0, event.lastPoint.subtract(step));
path.add(event.point.add(step));
path.insert(0, event.point.subtract(step));
// Add end cap
step.angle -= 90;
path.add(new paper.Segment(event.point.add(step), handleVec, handleVec.multiply(-1)));
path.closed = true;
// The unite function on curved paths does not always work (sometimes deletes half the path)
// so we have to flatten.
path.flatten(this.options.brushSize / 5);
lastPoint = event.point;
const newPath = finalPath.unite(path);
path.remove();
finalPath.remove();
finalPath = newPath;
};
tool.onSegmentMouseUp = function (event) {
if (event.event.button > 0) return; // only first mouse button
// TODO: This smoothing tends to cut off large portions of the path! Would like to eventually
// add back smoothing, maybe a custom implementation that only applies to a subset of the line?
// Smooth the path.
finalPath.simplify(2);
// console.log(finalPath.segments);
return finalPath;
};
};
export default segmentBrushHelper;