Skip to content

Ability to select multiple rectangles #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions source/compound-shape.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
2 changes: 1 addition & 1 deletion source/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
48 changes: 24 additions & 24 deletions source/pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}

Expand Down
170 changes: 85 additions & 85 deletions source/selection.js
Original file line number Diff line number Diff line change
@@ -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());
}

Expand All @@ -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 };
}
}
8 changes: 0 additions & 8 deletions source/shape.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ export class Shape
this.blendMode = blendMode;
}

/**
* Abstract method, to be overridden
*/
drawInViewport ()
{

}

/**
* Abstract method, to be overridden
*/
Expand Down