From 27f7102b06c4d2369ac6d1b9180496c972c4aec6 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 27 Jul 2017 17:36:17 -0400 Subject: [PATCH] add segment brush --- src/tools/blob.js | 41 +++++----- src/tools/broad-brush-helper.js | 12 ++- src/tools/segment-brush-helper.js | 132 ++++++++++++++++++------------ 3 files changed, 110 insertions(+), 75 deletions(-) diff --git a/src/tools/blob.js b/src/tools/blob.js index c6540633..509f28e4 100644 --- a/src/tools/blob.js +++ b/src/tools/blob.js @@ -1,7 +1,13 @@ import paper from 'paper'; import log from '../log/log'; 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 { static get BROAD () { @@ -15,7 +21,7 @@ class BlobTool { // Segment brush has performance issues at low threshold, but broad brush has weird corners // which get more obvious the bigger it is static get THRESHOLD () { - return 100000; + return 9; } setOptions (options) { @@ -62,10 +68,8 @@ class BlobTool { path.strokeWidth = 1; } } else { - // TODO keep a separate active toolbar style for brush vs pen? - //path = pg.stylebar.applyActiveToolbarStyle(path); - - //TODO FIX + // TODO: Add back brush styling. Keep a separate active toolbar style for brush vs pen. + // path = pg.stylebar.applyActiveToolbarStyle(path); path.fillColor = 'black'; if (path === this.cursorPreview) { @@ -80,8 +84,7 @@ class BlobTool { tool.fixedDistance = 1; broadBrushHelper(tool); - // TODO add - //pg.segmentbrushhelper(tool, options); + segmentBrushHelper(tool); tool.onMouseMove = function (event) { tool.resizeCursorIfNeeded(event.point); @@ -111,10 +114,10 @@ class BlobTool { if (event.event.button > 0) return; // only first mouse button if (this.brush === BlobTool.BROAD) { this.onBroadMouseDrag(event); - } else if (this.brush === Blob.SEGMENT) { + } else if (this.brush === BlobTool.SEGMENT) { this.onSegmentMouseDrag(event); } else { - log.warning(`Brush type does not exist: ${this.brush}`); + log.warn(`Brush type does not exist: ${this.brush}`); } this.cursorPreview.bringToFront(); @@ -132,7 +135,7 @@ class BlobTool { } else if (this.brush === BlobTool.SEGMENT) { lastPath = this.onSegmentMouseUp(event); } else { - log.warning(`Brush type does not exist: ${this.brush}`); + log.warn(`Brush type does not exist: ${this.brush}`); } if (isEraser) { @@ -195,8 +198,8 @@ class BlobTool { paths.splice(i, 1); } } - // TODO FIX - //pg.undo.snapshot('broadbrush'); + // TODO: Add back undo + // pg.undo.snapshot('broadbrush'); }; 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 // and deselect the selection if (items.length === 0) { - // TODO FIX - //pg.selection.clearSelection(); + // TODO: Add back selection handling + // pg.selection.clearSelection(); items = paper.project.getItems({ match: function (item) { return tool.isMergeable(lastPath, item) && tool.touches(lastPath, item); @@ -225,7 +228,7 @@ class BlobTool { // Gather path segments const subpaths = []; - // TODO handle compound path + // TODO: Handle compound path if (items[i] instanceof paper.Path && !items[i].closed) { const firstSeg = items[i].clone(); const intersections = firstSeg.getIntersections(lastPath); @@ -290,8 +293,8 @@ class BlobTool { items[i].remove(); } lastPath.remove(); - // TODO FIX - //pg.undo.snapshot('eraser'); + // TODO: Add back undo handling + // pg.undo.snapshot('eraser'); }; tool.colorMatch = function (existingPath, addedPath) { @@ -318,7 +321,7 @@ class BlobTool { path1.hitTest(path2.firstSegment.point)) { return true; } - // TODO clean up these no point paths + // TODO: clean up these no point paths return false; }; @@ -338,4 +341,4 @@ class BlobTool { } } -module.exports = BlobTool; +export default BlobTool; diff --git a/src/tools/broad-brush-helper.js b/src/tools/broad-brush-helper.js index 989e5749..718d5a4c 100644 --- a/src/tools/broad-brush-helper.js +++ b/src/tools/broad-brush-helper.js @@ -1,8 +1,14 @@ // 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 */ const broadBrushHelper = function (tool) { @@ -102,4 +108,4 @@ const broadBrushHelper = function (tool) { }; }; -module.exports = broadBrushHelper; +export default broadBrushHelper; diff --git a/src/tools/segment-brush-helper.js b/src/tools/segment-brush-helper.js index 449b5e52..d62772e1 100644 --- a/src/tools/segment-brush-helper.js +++ b/src/tools/segment-brush-helper.js @@ -1,59 +1,85 @@ -// Applies segment brush functions to the tool -pg.segmentbrushhelper = function(tool, options) { - var lastPoint, finalPath; +import paper from 'paper'; - tool.onSegmentMouseDown = function(event) { - tool.minDistance = options.brushSize/4; - tool.maxDistance = options.brushSize; - - finalPath = new Path.Circle({ - center: event.point, - radius: options.brushSize/2 - }); - tool.stylePath(finalPath); - lastPoint = event.point; - }; - - tool.onSegmentMouseDrag = function(event) { - var step = (event.delta).normalize(options.brushSize/2); - var handleVec = step.clone(); - handleVec.length = options.brushSize/2; - handleVec.angle += 90; +/** + * Applies segment brush functions to the tool. Call them when the corresponding mouse event happens + * to get the broad brush behavior. + * + * Segment brush draws by creating a rounded rectangle for each mouse move event and merging all of + * those shapes. Unlike the broad brush, the resulting shape will not self-intersect and when you make + * 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 + * with union on shapes with curves, so that chunks of the union tend to disappear. + * (https://github.com/paperjs/paper.js/issues/1321) + * + * @param {!Tool} tool paper.js mouse object + */ +const segmentBrushHelper = function (tool) { + let lastPoint; + let finalPath; - var path = new Path(); - path = pg.stylebar.applyActiveToolbarStyle(path); - path.strokeColor = null; - // Add handles to round the end caps - path.add(new Segment(lastPoint - step, -handleVec, handleVec)); - step.angle += 90; + tool.onSegmentMouseDown = function (event) { + if (event.event.button > 0) return; // only first mouse button - path.add(event.lastPoint + step); - path.insert(0, event.lastPoint - step); - path.add(event.point + step); - path.insert(0, event.point - step); + tool.minDistance = this.options.brushSize / 4; + tool.maxDistance = this.options.brushSize; + + 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 - step.angle -= 90; - path.add(new Segment(event.point + step, handleVec, -handleVec)); - 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(options.brushSize/5); - - lastPoint = event.point; - var newPath = finalPath.unite(path); - path.remove(); - finalPath.remove(); - finalPath = newPath; - }; + const step = (event.delta).normalize(this.options.brushSize / 2); + const handleVec = step.clone(); + handleVec.length = this.options.brushSize / 2; + handleVec.angle += 90; - tool.onSegmentMouseUp = function(event) { - // 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? + const path = new paper.Path(); + + // TODO: Add back brush styling + // path = pg.stylebar.applyActiveToolbarStyle(path); + path.fillColor = 'black'; - // Smooth the path. - finalPath.simplify(2); - // console.log(finalPath.segments); - return finalPath; - }; -} \ No newline at end of file + // Add handles to round the end caps + path.add(new paper.Segment(lastPoint.subtract(step), handleVec.multiply(-1), handleVec)); + step.angle += 90; + + 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;