diff --git a/source/compound-shape.js b/source/compound-shape.js new file mode 100644 index 00000000..d463fd37 --- /dev/null +++ b/source/compound-shape.js @@ -0,0 +1,12 @@ +export class CompoundShape { + constructor(...shapes) { + this.shapes = shapes; + this.type = "shape"; + } + + drawOnPage(layer, pageIndex, zoomLevel, renderer, canvas) { + this.shapes.forEach((shape) => { + shape.drawOnPage(layer, pageIndex, zoomLevel, renderer, canvas); + }); + } +} \ No newline at end of file diff --git a/source/layer.js b/source/layer.js index 6206bee9..4c83a100 100644 --- a/source/layer.js +++ b/source/layer.js @@ -267,7 +267,7 @@ export class Layer this.actions.push(action); // Selection is temporary and only concerns this layer thus no need to add to global actions - if (!(action.object.type === "selection" && action.object.selectedShape.blendMode === "select")) + if (!(action.object.type === "selection" && action.object.selectedShapes[0].blendMode === "select")) this.pixelInstance.actions.push(action); } diff --git a/source/pixel.js b/source/pixel.js index 61496c06..bbf9180e 100644 --- a/source/pixel.js +++ b/source/pixel.js @@ -51,7 +51,7 @@ export default class PixelPlugin this.lastRelCoordY = null; this.uiManager = null; this.tools = null; - this.selection = null; + this.selection = new Selection(); this.horizontalMove = false; this.layerIdCounter = 2; this.editingLayerName = false; @@ -412,12 +412,9 @@ export default class PixelPlugin } break; case "escape": - if (this.selection !== null) + if (this.selection.imageDataList.length > 0) { - if (this.selection.imageData === null) - { - this.selection.clearSelection(this.core.getSettings().maxZoomLevel); - } + this.selection.clearSelection(this.core.getSettings().maxZoomLevel); } break; case "backspace": @@ -450,24 +447,24 @@ export default class PixelPlugin this.undoAction(); break; case "c": - if (e.ctrlKey || e.metaKey) // Cmd + c - this.selection.copyShape(this.core.getSettings().maxZoomLevel); + if (e.ctrlKey || e.metaKey) { // Cmd + c + this.selection.copySelection(this.core.getSettings().maxZoomLevel); + } break; case "x": - if (e.ctrlKey || e.metaKey) // Cmd + x - this.selection.cutShape(this.core.getSettings().maxZoomLevel); + if (e.ctrlKey || e.metaKey) { // Cmd + x + this.selection.cutSelection(this.core.getSettings().maxZoomLevel); + } break; case "v": if (e.ctrlKey || e.metaKey) // Cmd + v { - if (this.selection !== null) + if (this.selection.imageDataList.length > 0) { - if (this.selection.imageData !== null) - { - this.selection.pasteShapeToLayer(this.layers[this.selectedLayerIndex]); - this.redrawLayer(this.layers[this.selectedLayerIndex]); - } + this.selection.pasteShapeToLayer(this.layers[this.selectedLayerIndex]); + this.redrawLayer(this.layers[this.selectedLayerIndex]); } + this.selection = new Selection(); } break; case "shift": @@ -578,8 +575,10 @@ export default class PixelPlugin mousePos = this.getMousePos(mouseClickDiv, evt); // Clear Selection - if (this.selection !== null) + if (!evt.ctrlKey) { this.selection.clearSelection(this.core.getSettings().maxZoomLevel); + this.selection = new Selection(); + } if (evt.which === 1) this.rightMousePressed = false; @@ -608,7 +607,6 @@ export default class PixelPlugin this.initializeNewPathInCurrentLayer(mousePos); break; case this.tools.type.select: - this.selection = new Selection(); this.initializeRectanglePreview(mousePos); break; default: @@ -825,6 +823,10 @@ export default class PixelPlugin break; case "shape": action.layer.removeShapeFromLayer(action.object); + // If we are undoing a selection preview, we need to remove the shape from the selection + if (action.object.blendMode === "select" || action.object.blendMode === "subtract") { + this.selection.removeSelectedShape(action.object); + } break; case "selection": action.layer.removeSelectionFromLayer(action.object); @@ -1006,7 +1008,8 @@ export default class PixelPlugin { case this.tools.type.select: selectedLayer.addShapeToLayer(new Rectangle(new Point(relativeCoords.x,relativeCoords.y,pageIndex), 0, 0, "select", this.tools.getCurrentTool())); - this.selection.setSelectedShape(selectedLayer.getCurrentShape(), this.layers[this.selectedLayerIndex]); + this.selection.setLayer(this.layers[this.selectedLayerIndex]); + this.selection.addSelectedShape(selectedLayer.getCurrentShape()); break; case this.tools.type.rectangle: if (this.rightMousePressed) @@ -1137,12 +1140,9 @@ export default class PixelPlugin changeCurrentlySelectedLayerIndex (newIndex) { this.selectedLayerIndex = newIndex; - if (this.selection !== null) + if (this.selection.imageDataList.length > 0) { - if (this.selection.imageData === null) - { - this.selection.clearSelection(this.core.getSettings().maxZoomLevel); - } + this.selection.clearSelection(this.core.getSettings().maxZoomLevel); } } diff --git a/source/selection.js b/source/selection.js index ed5edbba..e7470b89 100644 --- a/source/selection.js +++ b/source/selection.js @@ -1,61 +1,31 @@ +import { CompoundShape } from "./compound-shape"; + /*jshint esversion: 6 */ -export class Selection -{ - constructor () - { +export class Selection { + constructor() { this.layer = null; - this.selectedShape = null; + this.selectedShapes = []; + this.imageDataList = []; this.type = "selection"; - this.imageData = null; } - copyShape (maxZoomLevel) - { - this.layer.removeShapeFromLayer(this.selectedShape); - this.layer.drawLayer(maxZoomLevel, this.layer.getCanvas()); - - let scaleRatio = Math.pow(2, maxZoomLevel); - - // Get coordinates of the selection shape (a rectangle here) - let absoluteRectOriginX = this.selectedShape.origin.relativeOriginX * scaleRatio, - absoluteRectOriginY = this.selectedShape.origin.relativeOriginY * scaleRatio, - absoluteRectWidth = this.selectedShape.relativeRectWidth * scaleRatio, - absoluteRectHeight = this.selectedShape.relativeRectHeight * scaleRatio; - - let xmin = Math.min(absoluteRectOriginX, absoluteRectOriginX + absoluteRectWidth), - ymin = Math.min(absoluteRectOriginY, absoluteRectOriginY + absoluteRectHeight); - - let selectedLayerCtx = this.layer.getCanvas().getContext("2d"); - let imageData = selectedLayerCtx.getImageData(xmin, ymin, Math.abs(absoluteRectWidth), Math.abs(absoluteRectHeight)); - - this.imageData = imageData; - - this.selectedShape.changeBlendModeTo("add"); + copySelection(maxZoomLevel) { + this.imageDataList = []; + this.selectedShapes.forEach((shape) => { + this._copyImageData(shape, maxZoomLevel); + this.layer.removeShapeFromLayer(shape); + shape.changeBlendModeTo("add"); + }); } - cutShape (maxZoomLevel) - { - this.layer.removeShapeFromLayer(this.selectedShape); - this.layer.drawLayer(maxZoomLevel, this.layer.getCanvas()); - - let scaleRatio = Math.pow(2, maxZoomLevel); - - // Get coordinates of the selection shape (a rectangle here) - let absoluteRectOriginX = this.selectedShape.origin.relativeOriginX * scaleRatio, - absoluteRectOriginY = this.selectedShape.origin.relativeOriginY * scaleRatio, - absoluteRectWidth = this.selectedShape.relativeRectWidth * scaleRatio, - absoluteRectHeight = this.selectedShape.relativeRectHeight * scaleRatio; - - let xmin = Math.min(absoluteRectOriginX, absoluteRectOriginX + absoluteRectWidth), - ymin = Math.min(absoluteRectOriginY, absoluteRectOriginY + absoluteRectHeight); + cutSelection(maxZoomLevel) { + this.imageDataList = []; + this.selectedShapes.forEach((shape) => { + this._copyImageData(shape, maxZoomLevel); + shape.changeBlendModeTo("subtract"); + }); - let selectedLayerCtx = this.layer.getCanvas().getContext("2d"); - let imageData = selectedLayerCtx.getImageData(xmin, ymin, Math.abs(absoluteRectWidth), Math.abs(absoluteRectHeight)); - - this.imageData = imageData; - - this.selectedShape.changeBlendModeTo("subtract"); - this.layer.addShapeToLayer(this.selectedShape); + this.layer.addShapeToLayer(new CompoundShape(...this.selectedShapes)); this.layer.drawLayer(maxZoomLevel, this.layer.getCanvas()); } @@ -64,55 +34,85 @@ export class Selection * @param layerToPasteTo * @param maxZoomLevel */ - pasteShapeToLayer (layerToPasteTo) - { - let data = this.imageData.data; - - // Change imageData colour to layer's colour - for (let i = 0; i < data.length; i += 4) - { - data[i] = layerToPasteTo.colour.red; // red - data[i + 1] = layerToPasteTo.colour.green; // green - data[i + 2] = layerToPasteTo.colour.blue; // blue - } + pasteShapeToLayer(layerToPasteTo) { + this.imageDataList.forEach(({ data }) => { + // Change imageData colour to layer's colour + for (let i = 0; i < data.length; i += 4) { + data[i] = layerToPasteTo.colour.red; // red + data[i + 1] = layerToPasteTo.colour.green; // green + data[i + 2] = layerToPasteTo.colour.blue; // blue + } + }); + layerToPasteTo.addToPastedRegions(this); } - setSelectedShape (selectedShape, selectedLayer) - { - this.selectedShape = selectedShape; - this.layer = selectedLayer; + setLayer(layer) { + this.layer = layer; + } + + addSelectedShape(shape) { + this.selectedShapes.push(shape); + } + + removeSelectedShape(shape) { + const index = this.selectedShapes.indexOf(shape); + if (index > -1) { + this.selectedShapes.splice(index, 1); + } } - clearSelection (maxZoomLevel) - { - if (this.layer !== null && this.selectedShape !== null) - { - if (this.selectedShape.blendMode === "select") - this.layer.removeShapeFromLayer(this.selectedShape); + clearSelection(maxZoomLevel) { + if (this.layer !== null) { + this.selectedShapes.forEach((shape) => { + if (shape.blendMode === "select") { + this.layer.removeShapeFromLayer(shape); + } + }); this.layer.drawLayer(maxZoomLevel, this.layer.getCanvas()); } } - drawOnPage (layer, pageIndex, zoomLevel, renderer, canvas) - { - let scaleRatio = Math.pow(2, zoomLevel); + drawOnPage(layer, pageIndex, zoomLevel, renderer, canvas) { + this.selectedShapes.forEach((shape, index) => { + // Get coordinates of the selection shape (a rectangle here) + let { x, y, absoluteRectWidth, absoluteRectHeight } = this._getBoundingDimensions(shape, zoomLevel); + + let pasteCanvas = document.createElement("canvas"); + pasteCanvas.width = Math.abs(absoluteRectWidth); + pasteCanvas.height = Math.abs(absoluteRectHeight); + + pasteCanvas.getContext("2d").putImageData(this.imageDataList[index], 0, 0); + + canvas.getContext("2d").drawImage(pasteCanvas, x, y); + }); + } + + _copyImageData(shape, maxZoomLevel) { + this.layer.removeShapeFromLayer(shape); + this.layer.drawLayer(maxZoomLevel, this.layer.getCanvas()); // Get coordinates of the selection shape (a rectangle here) - let absoluteRectOriginX = this.selectedShape.origin.relativeOriginX * scaleRatio, - absoluteRectOriginY = this.selectedShape.origin.relativeOriginY * scaleRatio, - absoluteRectWidth = this.selectedShape.relativeRectWidth * scaleRatio, - absoluteRectHeight = this.selectedShape.relativeRectHeight * scaleRatio; + let { x, y, absoluteRectWidth, absoluteRectHeight } = this._getBoundingDimensions(shape, maxZoomLevel); - let xmin = Math.min(absoluteRectOriginX, absoluteRectOriginX + absoluteRectWidth); - let ymin = Math.min(absoluteRectOriginY, absoluteRectOriginY + absoluteRectHeight); + let selectedLayerCtx = this.layer.getCanvas().getContext("2d"); + let imageData = selectedLayerCtx.getImageData(x, y, Math.abs(absoluteRectWidth), Math.abs(absoluteRectHeight)); - let pasteCanvas = document.createElement("canvas"); - pasteCanvas.width = Math.abs(absoluteRectWidth); - pasteCanvas.height = Math.abs(absoluteRectHeight); + this.imageDataList.push(imageData); + } + + _getBoundingDimensions(shape, zoomLevel) { + let scaleRatio = Math.pow(2, zoomLevel); + + // Get coordinates of the selection shape (a rectangle here) + let absoluteRectOriginX = shape.origin.relativeOriginX * scaleRatio, + absoluteRectOriginY = shape.origin.relativeOriginY * scaleRatio, + absoluteRectWidth = shape.relativeRectWidth * scaleRatio, + absoluteRectHeight = shape.relativeRectHeight * scaleRatio; - pasteCanvas.getContext("2d").putImageData(this.imageData, 0, 0); + let x = Math.min(absoluteRectOriginX, absoluteRectOriginX + absoluteRectWidth), + y = Math.min(absoluteRectOriginY, absoluteRectOriginY + absoluteRectHeight); - canvas.getContext("2d").drawImage(pasteCanvas, xmin, ymin); + return { x, y, absoluteRectWidth, absoluteRectHeight }; } } diff --git a/source/shape.js b/source/shape.js index f565fd63..272d83be 100644 --- a/source/shape.js +++ b/source/shape.js @@ -11,14 +11,6 @@ export class Shape this.blendMode = blendMode; } - /** - * Abstract method, to be overridden - */ - drawInViewport () - { - - } - /** * Abstract method, to be overridden */