mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-03-23 03:15:55 -04:00
add segment brush
This commit is contained in:
parent
4a5c9f0b54
commit
27f7102b06
3 changed files with 110 additions and 75 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue