mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Merge pull request #317 from juliandescottes/add-lasso-tool
Add lasso tool
This commit is contained in:
commit
2448e65ffa
72
misc/icons/SVG/lasso.svg
Normal file
72
misc/icons/SVG/lasso.svg
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="224.38731"
|
||||||
|
height="177.96065"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.4 r9939"
|
||||||
|
sodipodi:docname="New document 1">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.175"
|
||||||
|
inkscape:cx="-1542.2107"
|
||||||
|
inkscape:cy="-1434.6156"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1148"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-212.79148,-238.81242)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:30, 30;stroke-dashoffset:0"
|
||||||
|
d="m 429.0234,294.50503 c 0,26.68038 -49.13123,48.3091 -109.73768,48.3091 -60.60644,0 -110.61221,-39.48861 -96.16624,-73.3091 19.3392,-45.27639 60.80353,-4.92189 116.16624,-16.88052 101.16819,-21.85285 89.73768,15.20015 89.73768,41.88052 z"
|
||||||
|
id="path3761"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="sssss" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 309.32357,338.9218 c 0,0 8.97064,20.56224 -30.34229,45.31736 -39.31294,24.75512 -40.99309,30.13484 -40.99309,30.13484"
|
||||||
|
id="path3764"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="czc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -75,6 +75,10 @@
|
|||||||
background-size: 24px 20px;
|
background-size: 24px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-icon.tool-lasso-select {
|
||||||
|
background-image: url(../img/tools/lasso.png);
|
||||||
|
}
|
||||||
|
|
||||||
.tool-icon.tool-shape-select {
|
.tool-icon.tool-shape-select {
|
||||||
background-image: url(../img/tools/magicwand.png);
|
background-image: url(../img/tools/magicwand.png);
|
||||||
}
|
}
|
||||||
@ -159,7 +163,8 @@
|
|||||||
cursor: url(../img/icons/hand.png) 7 7, pointer;
|
cursor: url(../img/icons/hand.png) 7 7, pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-rectangle-select .drawing-canvas-container:hover {
|
.tool-rectangle-select .drawing-canvas-container:hover,
|
||||||
|
.tool-lasso-select .drawing-canvas-container:hover {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
src/img/tools/lasso-dark.png
Normal file
BIN
src/img/tools/lasso-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
src/img/tools/lasso.png
Normal file
BIN
src/img/tools/lasso.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
@ -58,7 +58,7 @@
|
|||||||
this.redraw();
|
this.redraw();
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.CursorCoordinatesController.prototype.onDragEnd_ = function (event, x, y) {
|
ns.CursorCoordinatesController.prototype.onDragEnd_ = function (event) {
|
||||||
this.origin = null;
|
this.origin = null;
|
||||||
this.redraw();
|
this.redraw();
|
||||||
};
|
};
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
toDescriptor('rectangle', 'R', new pskl.tools.drawing.Rectangle()),
|
toDescriptor('rectangle', 'R', new pskl.tools.drawing.Rectangle()),
|
||||||
toDescriptor('circle', 'C', new pskl.tools.drawing.Circle()),
|
toDescriptor('circle', 'C', new pskl.tools.drawing.Circle()),
|
||||||
toDescriptor('move', 'M', new pskl.tools.drawing.Move()),
|
toDescriptor('move', 'M', new pskl.tools.drawing.Move()),
|
||||||
toDescriptor('rectangleSelect', 'S', new pskl.tools.drawing.RectangleSelect()),
|
toDescriptor('shapeSelect', 'Z', new pskl.tools.drawing.selection.ShapeSelect()),
|
||||||
toDescriptor('shapeSelect', 'Z', new pskl.tools.drawing.ShapeSelect()),
|
toDescriptor('rectangleSelect', 'S', new pskl.tools.drawing.selection.RectangleSelect()),
|
||||||
|
toDescriptor('lassoSelect', 'H', new pskl.tools.drawing.selection.LassoSelect()),
|
||||||
toDescriptor('lighten', 'U', new pskl.tools.drawing.Lighten()),
|
toDescriptor('lighten', 'U', new pskl.tools.drawing.Lighten()),
|
||||||
toDescriptor('dithering', 'T', new pskl.tools.drawing.DitheringTool()),
|
toDescriptor('dithering', 'T', new pskl.tools.drawing.DitheringTool()),
|
||||||
toDescriptor('colorPicker', 'O', new pskl.tools.drawing.ColorPicker())
|
toDescriptor('colorPicker', 'O', new pskl.tools.drawing.ColorPicker())
|
||||||
|
73
src/js/selection/LassoSelection.js
Normal file
73
src/js/selection/LassoSelection.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
(function () {
|
||||||
|
var ns = $.namespace('pskl.selection');
|
||||||
|
|
||||||
|
var OUTSIDE = -1;
|
||||||
|
var INSIDE = 1;
|
||||||
|
var VISITED = 2;
|
||||||
|
|
||||||
|
ns.LassoSelection = function (pixels, frame) {
|
||||||
|
// transform the selected pixels array to a Map to get a faster lookup
|
||||||
|
this.pixelsMap = {};
|
||||||
|
pixels.forEach(function (pixel) {
|
||||||
|
this.setPixelInMap_(pixel, INSIDE);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.pixels = this.getLassoPixels_(frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
pskl.utils.inherit(ns.LassoSelection, ns.BaseSelection);
|
||||||
|
|
||||||
|
ns.LassoSelection.prototype.getLassoPixels_ = function (frame) {
|
||||||
|
var lassoPixels = [];
|
||||||
|
|
||||||
|
frame.forEachPixel(function (color, pixelCol, pixelRow) {
|
||||||
|
var pixel = {col : pixelCol, row : pixelRow};
|
||||||
|
if (this.isInSelection_(pixel, frame)) {
|
||||||
|
lassoPixels.push(pixel);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return lassoPixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.LassoSelection.prototype.isInSelection_ = function (pixel, frame) {
|
||||||
|
var alreadyVisited = this.getPixelInMap_(pixel);
|
||||||
|
if (!alreadyVisited) {
|
||||||
|
this.visitPixel_(pixel, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getPixelInMap_(pixel) == INSIDE;
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.LassoSelection.prototype.visitPixel_ = function (pixel, frame) {
|
||||||
|
var frameBorderReached = false;
|
||||||
|
var visitedPixels = pskl.PixelUtils.visitConnectedPixels(pixel, frame, function (connectedPixel) {
|
||||||
|
var alreadyVisited = this.getPixelInMap_(connectedPixel);
|
||||||
|
if (alreadyVisited) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame.containsPixel(connectedPixel.col, connectedPixel.row)) {
|
||||||
|
frameBorderReached = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setPixelInMap_(connectedPixel, VISITED);
|
||||||
|
return true;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
visitedPixels.forEach(function (visitedPixel) {
|
||||||
|
this.setPixelInMap_(visitedPixel, frameBorderReached ? OUTSIDE : INSIDE);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.LassoSelection.prototype.setPixelInMap_ = function (pixel, value) {
|
||||||
|
this.pixelsMap[pixel.col] = this.pixelsMap[pixel.col] || {};
|
||||||
|
this.pixelsMap[pixel.col][pixel.row] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.LassoSelection.prototype.getPixelInMap_ = function (pixel) {
|
||||||
|
return this.pixelsMap[pixel.col] && this.pixelsMap[pixel.col][pixel.row];
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
@ -41,7 +41,7 @@
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ns.SelectionManager.prototype.onToolSelected_ = function(evt, tool) {
|
ns.SelectionManager.prototype.onToolSelected_ = function(evt, tool) {
|
||||||
var isSelectionTool = tool instanceof pskl.tools.drawing.BaseSelect;
|
var isSelectionTool = tool instanceof pskl.tools.drawing.selection.BaseSelect;
|
||||||
if (!isSelectionTool) {
|
if (!isSelectionTool) {
|
||||||
this.cleanSelection_();
|
this.cleanSelection_();
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
var color = this.getToolColor();
|
var color = this.getToolColor();
|
||||||
this.draw(coords.col, coords.row, color, frame);
|
this.draw(coords.col, coords.row, color, frame);
|
||||||
|
|
||||||
$.publish(Events.DRAG_END, [coords.col, coords.row]);
|
$.publish(Events.DRAG_END);
|
||||||
this.raiseSaveStateEvent({
|
this.raiseSaveStateEvent({
|
||||||
col : coords.col,
|
col : coords.col,
|
||||||
row : coords.row,
|
row : coords.row,
|
||||||
|
61
src/js/tools/drawing/selection/AbstractDragSelect.js
Normal file
61
src/js/tools/drawing/selection/AbstractDragSelect.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Base class for all select tools that use a dragging mechanism to define the selection.
|
||||||
|
*
|
||||||
|
* @provide pskl.tools.drawing.selection.AbstractDragSelect
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
var ns = $.namespace('pskl.tools.drawing.selection');
|
||||||
|
|
||||||
|
ns.AbstractDragSelect = function () {
|
||||||
|
ns.BaseSelect.call(this);
|
||||||
|
this.hasSelection = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
pskl.utils.inherit(ns.AbstractDragSelect, ns.BaseSelect);
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.AbstractDragSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
|
||||||
|
if (this.hasSelection) {
|
||||||
|
this.hasSelection = false;
|
||||||
|
overlay.clear();
|
||||||
|
$.publish(Events.SELECTION_DISMISSED);
|
||||||
|
} else {
|
||||||
|
this.hasSelection = true;
|
||||||
|
this.onDragSelectStart_(col, row);
|
||||||
|
overlay.setPixel(col, row, this.getTransparentVariant_(Constants.SELECTION_TRANSPARENT_COLOR));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.AbstractDragSelect.prototype.onSelect_ = function (col, row, frame, overlay) {
|
||||||
|
if (!this.hasSelection && (this.startCol !== col || this.startRow !== row)) {
|
||||||
|
this.hasSelection = true;
|
||||||
|
this.onDragSelectStart_(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasSelection) {
|
||||||
|
this.onDragSelect_(col, row, frame, overlay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.AbstractDragSelect.prototype.onSelectEnd_ = function (col, row, frame, overlay) {
|
||||||
|
if (this.hasSelection) {
|
||||||
|
this.onDragSelectEnd_(col, row, frame, overlay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
ns.AbstractDragSelect.prototype.startDragSelection_ = function (col, row, overlay) {
|
||||||
|
this.hasSelection = true;
|
||||||
|
this.onDragSelectStart_(col, row);
|
||||||
|
overlay.setPixel(col, row, this.getTransparentVariant_(Constants.SELECTION_TRANSPARENT_COLOR));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @protected */
|
||||||
|
ns.AbstractDragSelect.prototype.onDragSelectStart_ = function (col, row, frame, overlay) {};
|
||||||
|
/** @protected */
|
||||||
|
ns.AbstractDragSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) {};
|
||||||
|
/** @protected */
|
||||||
|
ns.AbstractDragSelect.prototype.onDragSelectEnd_ = function (col, row, frame, overlay) {};
|
||||||
|
})();
|
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @provide pskl.tools.drawing.BaseSelect
|
* @provide pskl.tools.drawing.selection.BaseSelect
|
||||||
*
|
*
|
||||||
* @require pskl.utils
|
* @require pskl.utils
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
var ns = $.namespace('pskl.tools.drawing');
|
var ns = $.namespace('pskl.tools.drawing.selection');
|
||||||
|
|
||||||
ns.BaseSelect = function() {
|
ns.BaseSelect = function() {
|
||||||
this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID;
|
this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID;
|
||||||
@ -14,6 +14,9 @@
|
|||||||
this.startCol = null;
|
this.startCol = null;
|
||||||
this.startRow = null;
|
this.startRow = null;
|
||||||
|
|
||||||
|
this.lastMoveCol = null;
|
||||||
|
this.lastMoveRow = null;
|
||||||
|
|
||||||
this.selection = null;
|
this.selection = null;
|
||||||
|
|
||||||
this.tooltipDescriptors = [
|
this.tooltipDescriptors = [
|
||||||
@ -23,7 +26,7 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
pskl.utils.inherit(ns.BaseSelect, ns.BaseTool);
|
pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@ -32,20 +35,20 @@
|
|||||||
this.startCol = col;
|
this.startCol = col;
|
||||||
this.startRow = row;
|
this.startRow = row;
|
||||||
|
|
||||||
this.lastCol = col;
|
this.lastMoveCol = col;
|
||||||
this.lastRow = row;
|
this.lastMoveRow = row;
|
||||||
|
|
||||||
// The select tool can be in two different state.
|
// The select tool can be in two different state.
|
||||||
// If the inital click of the tool is not on a selection, we go in 'select'
|
// If the inital click of the tool is not on a selection, we go in 'select'
|
||||||
// mode to create a selection.
|
// mode to create a selection.
|
||||||
// If the initial click is on a previous selection, we go in 'moveSelection'
|
// If the initial click is on a previous selection, we go in 'moveSelection'
|
||||||
// mode to allow to move the selection by drag'n dropping it.
|
// mode to allow to move the selection by drag'n dropping it.
|
||||||
if (this.isInSelection(col, row)) {
|
if (!this.isInSelection(col, row)) {
|
||||||
this.mode = 'moveSelection';
|
|
||||||
this.onSelectionDragStart_(col, row, frame, overlay);
|
|
||||||
} else {
|
|
||||||
this.mode = 'select';
|
this.mode = 'select';
|
||||||
this.onSelectStart_(col, row, frame, overlay);
|
this.onSelectStart_(col, row, frame, overlay);
|
||||||
|
} else {
|
||||||
|
this.mode = 'moveSelection';
|
||||||
|
this.onSelectionMoveStart_(col, row, frame, overlay);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,7 +59,7 @@
|
|||||||
if (this.mode == 'select') {
|
if (this.mode == 'select') {
|
||||||
this.onSelect_(col, row, frame, overlay);
|
this.onSelect_(col, row, frame, overlay);
|
||||||
} else if (this.mode == 'moveSelection') {
|
} else if (this.mode == 'moveSelection') {
|
||||||
this.onSelectionDrag_(col, row, frame, overlay);
|
this.onSelectionMove_(col, row, frame, overlay);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,7 +70,7 @@
|
|||||||
if (this.mode == 'select') {
|
if (this.mode == 'select') {
|
||||||
this.onSelectEnd_(col, row, frame, overlay);
|
this.onSelectEnd_(col, row, frame, overlay);
|
||||||
} else if (this.mode == 'moveSelection') {
|
} else if (this.mode == 'moveSelection') {
|
||||||
this.onSelectionDragEnd_(col, row, frame, overlay);
|
this.onSelectionMoveEnd_(col, row, frame, overlay);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,12 +135,13 @@
|
|||||||
|
|
||||||
// The list of callbacks that define the drag'n drop behavior of the selection.
|
// The list of callbacks that define the drag'n drop behavior of the selection.
|
||||||
/** @private */
|
/** @private */
|
||||||
ns.BaseSelect.prototype.onSelectionDragStart_ = function (col, row, frame, overlay) {};
|
|
||||||
|
ns.BaseSelect.prototype.onSelectionMoveStart_ = function (col, row, frame, overlay) {};
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
ns.BaseSelect.prototype.onSelectionDrag_ = function (col, row, frame, overlay) {
|
ns.BaseSelect.prototype.onSelectionMove_ = function (col, row, frame, overlay) {
|
||||||
var deltaCol = col - this.lastCol;
|
var deltaCol = col - this.lastMoveCol;
|
||||||
var deltaRow = row - this.lastRow;
|
var deltaRow = row - this.lastMoveRow;
|
||||||
|
|
||||||
var colDiff = col - this.startCol;
|
var colDiff = col - this.startCol;
|
||||||
var rowDiff = row - this.startRow;
|
var rowDiff = row - this.startRow;
|
||||||
@ -147,12 +151,12 @@
|
|||||||
overlay.clear();
|
overlay.clear();
|
||||||
this.drawSelectionOnOverlay_(overlay);
|
this.drawSelectionOnOverlay_(overlay);
|
||||||
|
|
||||||
this.lastCol = col;
|
this.lastMoveCol = col;
|
||||||
this.lastRow = row;
|
this.lastMoveRow = row;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
ns.BaseSelect.prototype.onSelectionDragEnd_ = function (col, row, frame, overlay) {
|
ns.BaseSelect.prototype.onSelectionMoveEnd_ = function (col, row, frame, overlay) {
|
||||||
this.onSelectionDrag_(col, row, frame, overlay);
|
this.onSelectionMove_(col, row, frame, overlay);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
87
src/js/tools/drawing/selection/LassoSelect.js
Normal file
87
src/js/tools/drawing/selection/LassoSelect.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @provide pskl.tools.drawing.selection.LassoSelect
|
||||||
|
*
|
||||||
|
* @require pskl.utils
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
var ns = $.namespace('pskl.tools.drawing.selection');
|
||||||
|
|
||||||
|
ns.LassoSelect = function() {
|
||||||
|
this.toolId = 'tool-lasso-select';
|
||||||
|
this.helpText = 'Lasso selection';
|
||||||
|
|
||||||
|
ns.AbstractDragSelect.call(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
pskl.utils.inherit(ns.LassoSelect, ns.AbstractDragSelect);
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.LassoSelect.prototype.onDragSelectStart_ = function (col, row) {
|
||||||
|
this.pixels = [{col : col, row : row}];
|
||||||
|
|
||||||
|
this.startCol = col;
|
||||||
|
this.startRow = row;
|
||||||
|
|
||||||
|
this.previousCol = col;
|
||||||
|
this.previousRow = row;
|
||||||
|
|
||||||
|
$.publish(Events.DRAG_START, [col, row]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.LassoSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) {
|
||||||
|
this.addPixel_(col, row, frame);
|
||||||
|
// use ShapeSelection during selection, contains only the pixels hovered by the user
|
||||||
|
var selection = new pskl.selection.ShapeSelection(this.getLassoPixels_());
|
||||||
|
this.setSelection_(selection, overlay);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ns.LassoSelect.prototype.onDragSelectEnd_ = function (col, row, frame, overlay) {
|
||||||
|
this.addPixel_(col, row, frame);
|
||||||
|
// use LassoSelection to finalize selection, includes pixels inside the lasso shape
|
||||||
|
var selection = new pskl.selection.LassoSelection(this.getLassoPixels_(), frame);
|
||||||
|
this.setSelection_(selection, overlay);
|
||||||
|
|
||||||
|
$.publish(Events.DRAG_END);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the lasso shape as an array of pixels. A line is added between the origin of the selection
|
||||||
|
* and the last known coordinate to make sure the shape is closed.
|
||||||
|
*
|
||||||
|
* @return {Array} array of pixels corresponding to the whole lasso shape
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ns.LassoSelect.prototype.getLassoPixels_ = function () {
|
||||||
|
var line = this.getLinePixels_(this.previousCol, this.startCol, this.previousRow, this.startRow);
|
||||||
|
return this.pixels.concat(line);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the provided pixel to the lasso pixels Array.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ns.LassoSelect.prototype.addPixel_ = function (col, row, frame) {
|
||||||
|
// normalize coordinates to always remain inside the frame
|
||||||
|
col = pskl.utils.Math.minmax(col, 0, frame.getWidth() - 1);
|
||||||
|
row = pskl.utils.Math.minmax(row, 0, frame.getHeight() - 1);
|
||||||
|
|
||||||
|
// line interpolation needed in case mousemove was too fast
|
||||||
|
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
|
||||||
|
this.pixels = this.pixels.concat(interpolatedPixels);
|
||||||
|
|
||||||
|
// update state
|
||||||
|
this.previousCol = col;
|
||||||
|
this.previousRow = row;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
ns.LassoSelect.prototype.setSelection_ = function (selection, overlay) {
|
||||||
|
this.selection = selection;
|
||||||
|
|
||||||
|
$.publish(Events.SELECTION_CREATED, [this.selection]);
|
||||||
|
overlay.clear();
|
||||||
|
this.drawSelectionOnOverlay_(overlay);
|
||||||
|
};
|
||||||
|
})();
|
@ -1,46 +1,23 @@
|
|||||||
/**
|
/**
|
||||||
* @provide pskl.tools.drawing.RectangleSelect
|
* @provide pskl.tools.drawing.selection.RectangleSelect
|
||||||
*
|
*
|
||||||
* @require pskl.utils
|
* @require pskl.utils
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
var ns = $.namespace('pskl.tools.drawing');
|
var ns = $.namespace('pskl.tools.drawing.selection');
|
||||||
|
|
||||||
ns.RectangleSelect = function() {
|
ns.RectangleSelect = function() {
|
||||||
this.toolId = 'tool-rectangle-select';
|
this.toolId = 'tool-rectangle-select';
|
||||||
|
|
||||||
this.helpText = 'Rectangle selection';
|
this.helpText = 'Rectangle selection';
|
||||||
|
|
||||||
ns.BaseSelect.call(this);
|
ns.AbstractDragSelect.call(this);
|
||||||
this.hasSelection = false;
|
|
||||||
|
|
||||||
this.selectionOrigin_ = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pskl.utils.inherit(ns.RectangleSelect, ns.BaseSelect);
|
pskl.utils.inherit(ns.RectangleSelect, ns.AbstractDragSelect);
|
||||||
|
|
||||||
/**
|
/** @override */
|
||||||
* @override
|
ns.RectangleSelect.prototype.onDragSelectStart_ = function (col, row) {
|
||||||
*/
|
|
||||||
ns.RectangleSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
|
|
||||||
this.selectionOrigin_ = {
|
|
||||||
col : col,
|
|
||||||
row : row
|
|
||||||
};
|
|
||||||
if (this.hasSelection) {
|
|
||||||
this.hasSelection = false;
|
|
||||||
overlay.clear();
|
|
||||||
$.publish(Events.SELECTION_DISMISSED);
|
|
||||||
} else {
|
|
||||||
this.startSelection_(col, row);
|
|
||||||
overlay.setPixel(col, row, Constants.SELECTION_TRANSPARENT_COLOR);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ns.RectangleSelect.prototype.startSelection_ = function (col, row) {
|
|
||||||
this.hasSelection = true;
|
|
||||||
$.publish(Events.DRAG_START, [col, row]);
|
$.publish(Events.DRAG_START, [col, row]);
|
||||||
// Drawing the first point of the rectangle in the fake overlay canvas:
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,25 +26,17 @@
|
|||||||
* the current mouse coordiinate in sprite.
|
* the current mouse coordiinate in sprite.
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
ns.RectangleSelect.prototype.onSelect_ = function (col, row, frame, overlay) {
|
ns.RectangleSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) {
|
||||||
if (!this.hasSelection && (this.selectionOrigin_.col !== col || this.selectionOrigin_.row !== row)) {
|
|
||||||
this.startSelection_(col, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasSelection) {
|
|
||||||
overlay.clear();
|
overlay.clear();
|
||||||
this.selection = new pskl.selection.RectangularSelection(
|
this.selection = new pskl.selection.RectangularSelection(this.startCol, this.startRow, col, row);
|
||||||
this.startCol, this.startRow, col, row);
|
|
||||||
$.publish(Events.SELECTION_CREATED, [this.selection]);
|
$.publish(Events.SELECTION_CREATED, [this.selection]);
|
||||||
this.drawSelectionOnOverlay_(overlay);
|
this.drawSelectionOnOverlay_(overlay);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.RectangleSelect.prototype.onSelectEnd_ = function (col, row, frame, overlay) {
|
/** @override */
|
||||||
if (this.hasSelection) {
|
ns.RectangleSelect.prototype.onDragSelectEnd_ = function (col, row, frame, overlay) {
|
||||||
this.onSelect_(col, row, frame, overlay);
|
this.onSelect_(col, row, frame, overlay);
|
||||||
$.publish(Events.DRAG_END, [col, row]);
|
$.publish(Events.DRAG_END);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @provide pskl.tools.drawing.ShapeSelect
|
* @provide pskl.tools.drawing.selection.ShapeSelect
|
||||||
*
|
*
|
||||||
* @require pskl.utils
|
* @require pskl.utils
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
var ns = $.namespace('pskl.tools.drawing');
|
var ns = $.namespace('pskl.tools.drawing.selection');
|
||||||
|
|
||||||
ns.ShapeSelect = function() {
|
ns.ShapeSelect = function() {
|
||||||
this.toolId = 'tool-shape-select';
|
this.toolId = 'tool-shape-select';
|
||||||
|
@ -99,10 +99,6 @@
|
|||||||
* 13. Continue looping until Q is exhausted.
|
* 13. Continue looping until Q is exhausted.
|
||||||
* 14. Return.
|
* 14. Return.
|
||||||
*/
|
*/
|
||||||
var paintedPixels = [];
|
|
||||||
var queue = [];
|
|
||||||
var dy = [-1, 0, 1, 0];
|
|
||||||
var dx = [0, 1, 0, -1];
|
|
||||||
var targetColor;
|
var targetColor;
|
||||||
try {
|
try {
|
||||||
targetColor = frame.getPixel(col, row);
|
targetColor = frame.getPixel(col, row);
|
||||||
@ -114,22 +110,57 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.push({'col': col, 'row': row});
|
var paintedPixels = pskl.PixelUtils.visitConnectedPixels({col:col, row:row}, frame, function (pixel) {
|
||||||
|
if (frame.containsPixel(pixel.col, pixel.row) && frame.getPixel(pixel.col, pixel.row) == targetColor) {
|
||||||
|
frame.setPixel(pixel.col, pixel.row, replacementColor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return paintedPixels;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting from a provided origin, visit connected pixels using a visitor function.
|
||||||
|
* After visiting a pixel, select the 4 connected pixels (up, right, down, left).
|
||||||
|
* Call the provided visitor on each pixel. The visitor should return true if the
|
||||||
|
* pixel should be considered as connected. If the pixel is connected repeat the
|
||||||
|
* process with its own connected pixels
|
||||||
|
*
|
||||||
|
* TODO : Julian : The visitor is also responsible for making sure a pixel is never
|
||||||
|
* visited twice. This could be handled by default by this method.
|
||||||
|
*
|
||||||
|
* @return {Array} the array of visited pixels {col, row}
|
||||||
|
*/
|
||||||
|
visitConnectedPixels : function (pixel, frame, pixelVisitor) {
|
||||||
|
var col = pixel.col;
|
||||||
|
var row = pixel.row;
|
||||||
|
|
||||||
|
var queue = [];
|
||||||
|
var visitedPixels = [];
|
||||||
|
var dy = [-1, 0, 1, 0];
|
||||||
|
var dx = [0, 1, 0, -1];
|
||||||
|
|
||||||
|
queue.push(pixel);
|
||||||
|
visitedPixels.push(pixel);
|
||||||
|
pixelVisitor(pixel);
|
||||||
|
|
||||||
var loopCount = 0;
|
var loopCount = 0;
|
||||||
var cellCount = frame.getWidth() * frame.getHeight();
|
var cellCount = frame.getWidth() * frame.getHeight();
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
loopCount ++;
|
loopCount ++;
|
||||||
|
|
||||||
var currentItem = queue.pop();
|
var currentItem = queue.pop();
|
||||||
frame.setPixel(currentItem.col, currentItem.row, replacementColor);
|
|
||||||
paintedPixels.push({'col': currentItem.col, 'row': currentItem.row});
|
|
||||||
|
|
||||||
for (var i = 0; i < 4; i++) {
|
for (var i = 0; i < 4; i++) {
|
||||||
var nextCol = currentItem.col + dx[i];
|
var nextCol = currentItem.col + dx[i];
|
||||||
var nextRow = currentItem.row + dy[i];
|
var nextRow = currentItem.row + dy[i];
|
||||||
try {
|
try {
|
||||||
if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
|
var connectedPixel = {'col': nextCol, 'row': nextRow};
|
||||||
queue.push({'col': nextCol, 'row': nextRow});
|
var isValid = pixelVisitor(connectedPixel);
|
||||||
|
if (isValid) {
|
||||||
|
queue.push(connectedPixel);
|
||||||
|
visitedPixels.push(connectedPixel);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Frame out of bound exception.
|
// Frame out of bound exception.
|
||||||
@ -142,7 +173,8 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paintedPixels;
|
|
||||||
|
return visitedPixels;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
// Selection
|
// Selection
|
||||||
"js/selection/SelectionManager.js",
|
"js/selection/SelectionManager.js",
|
||||||
"js/selection/BaseSelection.js",
|
"js/selection/BaseSelection.js",
|
||||||
|
"js/selection/LassoSelection.js",
|
||||||
"js/selection/RectangularSelection.js",
|
"js/selection/RectangularSelection.js",
|
||||||
"js/selection/ShapeSelection.js",
|
"js/selection/ShapeSelection.js",
|
||||||
|
|
||||||
@ -184,6 +185,8 @@
|
|||||||
"js/tools/drawing/Circle.js",
|
"js/tools/drawing/Circle.js",
|
||||||
"js/tools/drawing/Move.js",
|
"js/tools/drawing/Move.js",
|
||||||
"js/tools/drawing/selection/BaseSelect.js",
|
"js/tools/drawing/selection/BaseSelect.js",
|
||||||
|
"js/tools/drawing/selection/AbstractDragSelect.js",
|
||||||
|
"js/tools/drawing/selection/LassoSelect.js",
|
||||||
"js/tools/drawing/selection/RectangleSelect.js",
|
"js/tools/drawing/selection/RectangleSelect.js",
|
||||||
"js/tools/drawing/selection/ShapeSelect.js",
|
"js/tools/drawing/selection/ShapeSelect.js",
|
||||||
"js/tools/drawing/ColorPicker.js",
|
"js/tools/drawing/ColorPicker.js",
|
||||||
|
@ -22,5 +22,6 @@
|
|||||||
"transform.rotate.alt.twice.undo.once.json",
|
"transform.rotate.alt.twice.undo.once.json",
|
||||||
"transform.flip.once.alt.json",
|
"transform.flip.once.alt.json",
|
||||||
"transform.flip.twice.undo.once.json",
|
"transform.flip.twice.undo.once.json",
|
||||||
"transform.flip.thrice.undo.all.redo.all.json"
|
"transform.flip.thrice.undo.all.redo.all.json",
|
||||||
|
"selection.lasso.json"
|
||||||
]}
|
]}
|
@ -21,5 +21,6 @@
|
|||||||
"transform.rotate.alt.twice.undo.once.json",
|
"transform.rotate.alt.twice.undo.once.json",
|
||||||
"transform.flip.once.alt.json",
|
"transform.flip.once.alt.json",
|
||||||
"transform.flip.twice.undo.once.json",
|
"transform.flip.twice.undo.once.json",
|
||||||
"transform.flip.thrice.undo.all.redo.all.json"
|
"transform.flip.thrice.undo.all.redo.all.json",
|
||||||
|
"selection.lasso.json"
|
||||||
];
|
];
|
1
test/drawing/tests/selection.lasso.json
Normal file
1
test/drawing/tests/selection.lasso.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user