diff --git a/src/QuickEditor/CropSurface.qml b/src/QuickEditor/CropSurface.qml index 212b322..624cfb0 100644 --- a/src/QuickEditor/CropSurface.qml +++ b/src/QuickEditor/CropSurface.qml @@ -1,181 +1,181 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import "Shapes/ShapeContainer.Rectangle.js" as RectContainer import "Shapes/Shape.CropRectangle.js" as CropRectangle Item { id: cropSurface; objectName: "cropSurface"; // interaction active property bool interactionActive: false; // the selection property var mSelection: null; property var mCurrentHandle: null; property bool mDragging: false; property bool mDrawing: false; property color maskColour: Qt.rgba(0, 0, 0, 0.75); property color strokeColour: Qt.rgba(0.114, 0.6, 0.953, 1); // selection getters function getSelectionX() { if (mSelection) { return mSelection.x; } else { return -1; } } function getSelectionY() { if (mSelection) { return mSelection.y; } else { return -1; } } function getSelectionW() { if (mSelection) { return mSelection.w; } else { return -1; } } function getSelectionH() { if (mSelection) { return mSelection.h; } else { return -1; } } // signal to accept selection signal acceptImage(); // convenience functions function setInitialSelection(xx, yy, hh, ww) { mSelection = new RectContainer.ShapeContainer(xx, yy, hh, ww, strokeColour, maskColour, 1, CropRectangle.drawCropRectangle, CropRectangle.drawCropHandles, strokeColour); cropSurfaceCanvas.requestPaint(); } // the draw canvas Canvas { id: cropSurfaceCanvas; objectName: "cropSurfaceCanvas"; anchors.fill: parent; renderTarget: Canvas.FramebufferObject; renderStrategy: Canvas.Cooperative; onPaint: { // get a context on the canvas and clear it var ctx = cropSurfaceCanvas.getContext("2d"); ctx.clearRect(0, 0, width, height); // draw a sheet all over the canvas ctx.strokeStyle = strokeColour; ctx.fillStyle = maskColour; ctx.fillRect(0, 0, width, height); // if there's a selection, draw the cutout and handles if (mSelection) { mSelection.drawShape(ctx); mSelection.drawHandles(ctx); } } } // the mouse area to interact with the cropper MouseArea { id: cropSurfaceInteractionArea; objectName: "cropSurfaceInteractionArea"; enabled: interactionActive; anchors.fill: parent; cursorShape: Qt.CrossCursor; hoverEnabled: true; property int dx: 0; property int dy: 0; onPressed: { if (mSelection) { var curHandle = mSelection.withinHandleBounds(mouse.x, mouse.y); if (curHandle >= 0) { mCurrentHandle = curHandle; cursorShape = Qt.ClosedHandCursor; } else if (mSelection.withinShapeBounds(mouse.x, mouse.y)) { dx = mouse.x - mSelection.x; dy = mouse.y - mSelection.y; cursorShape = Qt.ClosedHandCursor; mDragging = true; } else { dx = mouse.x; dy = mouse.y; mSelection = new RectContainer.ShapeContainer(dx, dy, 1, 1, strokeColour, maskColour, 1, CropRectangle.drawCropRectangle, CropRectangle.drawCropHandles, strokeColour); mDrawing = true; cursorShape = Qt.CrossCursor; } } else { dx = mouse.x; dy = mouse.y; mSelection = new RectContainer.ShapeContainer(dx, dy, 1, 1, strokeColour, maskColour, 1, CropRectangle.drawCropRectangle, CropRectangle.drawCropHandles, strokeColour); mDrawing = true; cursorShape = Qt.CrossCursor; } } onPositionChanged: { if (pressed) { if (mSelection) { if (mDragging) { var xmax = width - mSelection.w; var ymax = height - mSelection.h; var xset = mouse.x - dx; var yset = mouse.y - dy; if ((xset >= 0) && (xset <= xmax) && (yset >= 0) && (yset <= ymax)) { mSelection.x = xset; mSelection.y = yset; } } else if (mCurrentHandle != null) { mSelection.moveHandle(mCurrentHandle, mouse.x, mouse.y); } else if (mDrawing) { var tx = Math.min(mouse.x, dx); var ty = Math.min(mouse.y, dy); var tw = Math.max(mouse.x, dx) - tx; var th = Math.max(mouse.y, dy) - ty; mSelection = new RectContainer.ShapeContainer(tx, ty, tw, th, strokeColour, maskColour, 1, CropRectangle.drawCropRectangle, CropRectangle.drawCropHandles, strokeColour); } } cropSurfaceCanvas.requestPaint(); } else { - if (mSelection.withinShapeBounds(mouse.x, mouse.y) || (mSelection.withinHandleBounds(mouse.x, mouse.y) >= 0)) { + if (mSelection && (mSelection.withinShapeBounds(mouse.x, mouse.y) + || (mSelection.withinHandleBounds(mouse.x, mouse.y) >= 0))) { cursorShape = Qt.OpenHandCursor; } else { cursorShape = Qt.CrossCursor; } } - } onReleased: { cursorShape = Qt.OpenHandCursor; mCurrentHandle = null; mDragging = false; mDrawing = false; dx = 0; dy = 0; cropSurfaceCanvas.requestPaint(); } onDoubleClicked: { acceptImage(); } } } diff --git a/src/QuickEditor/DrawSurface.qml b/src/QuickEditor/DrawSurface.qml index d2dd0ae..938ef36 100644 --- a/src/QuickEditor/DrawSurface.qml +++ b/src/QuickEditor/DrawSurface.qml @@ -1,162 +1,160 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import "Shapes/Shapes.Rectangle.js" as SRectangle Item { id: drawSurface; objectName: "drawSurface"; // scene manager draw queue property var mDrawQueue: []; // selections and handle property var mCurrentSelection: null; property var mCurrentHandle: null; property var mCurrentDrawing: null; // tool settings property var mCurrentTool: null; property color mStrokeColor: Qt.rgba(0, 0, 0, 1); property color mFillColor: Qt.rgba(1, 0, 0, 1); property int mLineWidth: 1; // control properties property bool interactionActive: false; // convenience functions function deleteSelectedShape() { if (mCurrentSelection != null) { mDrawQueue.splice(mCurrentSelection, 1); mCurrentSelection = null; drawSurfaceCanvas.requestPaint(); } } Component.onCompleted: { mCurrentTool = SRectangle; - //mDrawQueue.push(new SRectangle.Shape(20, 20, 150, 150, "black", "blue")); - //mDrawQueue.push(new SRectangle.Shape(600, 500, 150, 150, "black", "blue")); } // the draw canvas Canvas { id: drawSurfaceCanvas; objectName: "drawSurfaceCanvas"; anchors.fill: parent; renderTarget: Canvas.FramebufferObject; renderStrategy: Canvas.Cooperative; onPaint: { var ctx = drawSurfaceCanvas.getContext("2d"); ctx.clearRect(0, 0, width, height); for (var i = 0; i < mDrawQueue.length; i++) { mDrawQueue[i].drawShape(ctx); } if (mCurrentDrawing != null) { mCurrentDrawing.drawShape(ctx); } if (mCurrentSelection != null) { mDrawQueue[mCurrentSelection].drawHandles(ctx); } } } // the mouse area to interact with the shapes MouseArea { id: drawSurfaceInteractionArea; objectName: "drawSurfaceInteractionArea"; enabled: interactionActive; anchors.fill: parent; cursorShape: Qt.CrossCursor; property int dx: 0; property int dy: 0; onClicked: { if (!mouse.wasHeld) { for (var i = mDrawQueue.length - 1; i >= 0; i--) { if (mDrawQueue[i].withinShapeBounds(mouse.x, mouse.y)) { mCurrentSelection = i; drawSurfaceCanvas.requestPaint(); return; } } mCurrentSelection = null; drawSurfaceCanvas.requestPaint(); } } onPressed: { if (mCurrentSelection != null) { cursorShape = Qt.ClosedHandCursor; var curHandle = mDrawQueue[mCurrentSelection].withinHandleBounds(mouse.x, mouse.y); if (curHandle >= 0) { mCurrentHandle = curHandle } else { dx = mouse.x - mDrawQueue[mCurrentSelection].x; dy = mouse.y - mDrawQueue[mCurrentSelection].y; } } else { dx = mouse.x; dy = mouse.y; mCurrentDrawing = new mCurrentTool.Shape(dx, dy, 1, 1, mStrokeColor, mFillColor); } } onPositionChanged: { if (mCurrentSelection != null) { if (mCurrentHandle != null) { mDrawQueue[mCurrentSelection].moveHandle(mCurrentHandle, mouse.x, mouse.y); } else { mDrawQueue[mCurrentSelection].x = mouse.x - dx; mDrawQueue[mCurrentSelection].y = mouse.y - dy; } } else if (mCurrentDrawing != null) { var tx = Math.min(mouse.x, dx); var ty = Math.min(mouse.y, dy); var tw = Math.max(mouse.x, dx) - tx; var th = Math.max(mouse.y, dy) - ty; mCurrentDrawing = new mCurrentTool.Shape(tx, ty, tw, th, mStrokeColor, mFillColor); } drawSurfaceCanvas.requestPaint(); } onReleased: { cursorShape = Qt.OpenHandCursor; mCurrentHandle = null; dx = 0; dy = 0; if (mCurrentDrawing != null) { mDrawQueue.push(mCurrentDrawing); mCurrentDrawing = null; } drawSurfaceCanvas.requestPaint(); } } } diff --git a/src/QuickEditor/EditorRoot.qml b/src/QuickEditor/EditorRoot.qml index e4c71e2..afd8e49 100644 --- a/src/QuickEditor/EditorRoot.qml +++ b/src/QuickEditor/EditorRoot.qml @@ -1,128 +1,136 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Window 2.2 Item { id: editorRoot; objectName: "editorRoot"; // properties and setters property color maskColour: Qt.rgba(0, 0, 0, 0.75); property color strokeColour: Qt.rgba(0.114, 0.6, 0.953, 1); function setInitialSelection(xx, yy, ww, hh) { cropSurface.setInitialSelection(xx, yy, ww, hh); } function grabImage() { var xx = cropSurface.getSelectionX() * Screen.devicePixelRatio; var yy = cropSurface.getSelectionY() * Screen.devicePixelRatio; var ww = cropSurface.getSelectionW() * Screen.devicePixelRatio; var hh = cropSurface.getSelectionH() * Screen.devicePixelRatio; acceptImage(xx, yy, ww, hh); } // key handlers focus: true; Keys.onReturnPressed: { grabImage(); } + Keys.onEnterPressed: { + grabImage(); + } + Keys.onEscapePressed: { cancelImage(); } Keys.onDeletePressed: { editorSurface.deleteSelectedShape(); } // signals signal acceptImage(int x, int y, int width, int height); signal cancelImage(); // states states: [ State { name: "CropState"; PropertyChanges { target: cropSurface; interactionActive: true; } PropertyChanges { target: editorSurface; interactionActive: false; } }, State { name: "RectToolState"; PropertyChanges { target: cropSurface; interactionActive: false; } PropertyChanges { target: editorSurface; interactionActive: true; } } ] state: "CropState"; + // surfaces + Image { id: imageBackground; objectName: "imageBackground"; source: "image://snapshot/rawimage"; cache: false; height: Window.height / Screen.devicePixelRatio; width: Window.width / Screen.devicePixelRatio; fillMode: Image.PreserveAspectFit; } DrawSurface { id: editorSurface; objectName: "editorSurface"; anchors.fill: imageBackground; interactionActive: false; } CropSurface { id: cropSurface; objectName: "cropSurface"; anchors.fill: imageBackground; interactionActive: true; maskColour: editorRoot.maskColour; strokeColour: editorRoot.strokeColour; onAcceptImage: { grabImage(); } } + // manipulation and controls + ControlBar { id: toolSelector; objectName: "toolSelector"; onCropToolSelected: { editorRoot.state = "CropState"; } onRectToolSelected: { editorRoot.state = "RectToolState"; } //anchors.bottom: imageBackground.bottom; //anchors.left: imageBackground.left; //anchors.leftMargin: 32; //anchors.bottomMargin: -32; } } diff --git a/src/QuickEditor/Shapes/Shape.CropRectangle.js b/src/QuickEditor/Shapes/Shape.CropRectangle.js index 8f2d59d..478549b 100644 --- a/src/QuickEditor/Shapes/Shape.CropRectangle.js +++ b/src/QuickEditor/Shapes/Shape.CropRectangle.js @@ -1,59 +1,78 @@ +/* + * Copyright (C) 2016 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + function drawCropRectangle(ctx, x, y, w, h, sStyle, fStyle, lWidth) { // set up the colours ctx.fillStyle = sStyle; // draw the border and cut out the selection ctx.fillRect(x, y, w, h); ctx.clearRect(x + lWidth, y + lWidth, w - (lWidth * 2), h - (lWidth * 2)); }; function drawCropHandles(ctx, x, y, w, h, sStyle, fStyle, lWidth) { ctx.strokeStyle = sStyle; ctx.fillStyle = fStyle; if ((w > 20) && (h > 20)) { // top-left handle ctx.beginPath(); ctx.arc(x, y, 8, 0, 0.5 * Math.PI); ctx.lineTo(x, y); ctx.fill(); // top-right handle ctx.beginPath(); ctx.arc(x + w, y, 8, 0.5 * Math.PI, Math.PI); ctx.lineTo(x + w, y); ctx.fill(); // bottom-left handle ctx.beginPath(); ctx.arc(x + w, y + h, 8, Math.PI, 1.5 * Math.PI); ctx.lineTo(x + w, y + h); ctx.fill(); // bottom-right handle ctx.beginPath(); ctx.arc(x, y + h, 8, 1.5 * Math.PI, 2 * Math.PI); ctx.lineTo(x, y + h); ctx.fill(); // top-center handle ctx.beginPath(); ctx.arc(x + w / 2, y, 5, 0, Math.PI); ctx.fill(); // right-center handle ctx.beginPath(); ctx.arc(x + w, y + h / 2, 5, 0.5 * Math.PI, 1.5 * Math.PI); ctx.fill(); // bottom-center handle ctx.beginPath(); ctx.arc(x + w / 2, y + h, 5, Math.PI, 2 * Math.PI); ctx.fill(); // left-center handle ctx.beginPath(); ctx.arc(x, y + h / 2, 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.fill(); } };