1 /* 2 Drawpile - a collaborative drawing program. 3 4 Copyright (C) 2015-2019 Calle Laakkonen 5 6 Drawpile is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 Drawpile is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #ifndef SELECTION_H 21 #define SELECTION_H 22 23 #include "core/blendmodes.h" 24 #include "../libshared/net/message.h" 25 26 #include <QObject> 27 #include <QImage> 28 #include <QPolygonF> 29 30 namespace canvas { 31 32 /** 33 * @brief This class represents a canvas area selection 34 * 35 * A selection can contain in image to be pasted. 36 */ 37 class Selection : public QObject 38 { 39 Q_PROPERTY(QPolygonF shape READ shape WRITE setShape NOTIFY shapeChanged) 40 Q_PROPERTY(QImage pasteImage READ pasteImage WRITE setPasteImage NOTIFY pasteImageChanged) 41 Q_PROPERTY(int handleSize READ handleSize CONSTANT) 42 Q_OBJECT 43 public: 44 enum class Handle {Outside, Center, TopLeft, TopRight, BottomRight, BottomLeft, Top, Right, Bottom, Left}; 45 enum class AdjustmentMode { Scale, Rotate, Distort }; 46 47 explicit Selection(QObject *parent=nullptr); 48 49 //! Get the selection shape polygon shape()50 QPolygonF shape() const { return m_shape; } 51 52 //! Set the selection shape polygon. 53 void setShape(const QPolygonF &shape); 54 55 //! Set the selection shape from a rectangle. 56 void setShapeRect(const QRect &rect); 57 58 //! Add a point to the selection polygon. The shape must be open 59 void addPointToShape(const QPointF &point); 60 61 //! Close the selection 62 void closeShape(); 63 64 /** 65 * Close the selection and clip it to the given rectangle 66 * @param clipRect rectangle that represents the working area 67 * @return false if selection was entirely outside the clip rectangle 68 */ 69 bool closeShape(const QRectF &clipRect); 70 71 /** 72 * @brief Is the polygon closed? 73 * 74 * An open shape is a polygon that is still being drawn. 75 */ isClosed()76 bool isClosed() const { return m_closedPolygon; } 77 78 /** 79 * @brief Get the adjustment handle (if any) at the given coordinates 80 * @param point point in canvas space 81 * @param zoom zoom factor (affects visible handle size) 82 */ 83 Handle handleAt(const QPointF &point, qreal zoom) const; 84 85 /** 86 * Select the mode for the adjustment handles 87 */ 88 void setAdjustmentMode(AdjustmentMode mode); 89 adjustmentMode()90 AdjustmentMode adjustmentMode() const { return m_adjustmentMode; } 91 92 /** 93 * @brief Save the current shape as the pre-adjustment shape 94 * 95 * The adjust* functions called afterwards replace the current shape 96 * with an adjusted version of the pre-adjustment shape. 97 * 98 * @param handle the handle to be adjusted 99 */ 100 void beginAdjustment(Handle handle); 101 102 /** 103 * @brief Adjust shape geometry by dragging from one of the handles 104 * 105 * The selection can be translated and scaled by handle dragging. 106 * This replaces the current shape with a modified version of the shape saved 107 * when beginAdjustment was called. 108 * 109 * @param start the starting coordinate 110 * @param point current pointer position 111 * @param constrain apply constraint (aspect ratio or discrete angle) 112 */ 113 void adjustGeometry(const QPointF &start, const QPointF &point, bool constrain); 114 115 /** 116 * @brief Check if the selection is an axis-aligned rectangle 117 * 118 * Several optimizations can be used when this is true 119 */ 120 bool isAxisAlignedRectangle() const; 121 122 //! Forget that the selection buffer came from the canvas (will be treated as a pasted image) detachMove()123 void detachMove() { m_moveRegion = QPolygon(); } 124 125 //! Did the selection buffer come from the canvas? (as opposed to an external pasted image) isMovedFromCanvas()126 bool isMovedFromCanvas() const { return !m_moveRegion.isEmpty(); } 127 128 //! Get the shape at which the move started moveSourceRegion()129 QPolygonF moveSourceRegion() const { return m_moveRegion; } 130 boundingRect()131 QRect boundingRect() const { return m_shape.boundingRect().toRect(); } 132 133 QImage shapeMask(const QColor &color, QRect *maskBounds) const; 134 135 //! Set the image from pasting 136 void setPasteImage(const QImage &image); 137 138 /** 139 * @brief Set the preview image for moving a layer region 140 * 141 * The current shape will be remembered. 142 * @param image 143 * @param imageRect the rectangle from which the image was captured 144 * @param canvasSize the size of the canvas (selection rectangle is clipped to canvas bounds 145 * @param sourceLayerId the ID of the layer where the image came from 146 */ 147 void setMoveImage(const QImage &image, const QRect &imageRect, const QSize &canvasSize, int sourceLayerId); 148 149 //! Get the image to be pasted (or the move preview) pasteImage()150 QImage pasteImage() const { return m_pasteImage; } 151 152 //! Get the image to be pasted with the current transformation applied 153 QImage transformedPasteImage() const; 154 155 /** 156 * @brief Generate the commands to paste or move an image 157 * 158 * If this selection contains a moved piece (setMoveImage called) calling this method 159 * will return the commands for a RegionMove. 160 * If the image came from outside (i.e the clipboard,) a PutImage command set will be returned 161 * instead. 162 * 163 * If a MoveRegion command is generated, the source layer ID set previously in setMoveImage is used 164 * instead of the given target layer 165 * 166 * @param contextId user ID for the commands 167 * @param layer target layer 168 * @return set of commands 169 */ 170 protocol::MessageList pasteOrMoveToCanvas(uint8_t contextId, int layer) const; 171 172 /** 173 * @brief Generate the commands to fill the selection with solid color 174 * 175 * If this is an axis aligned rectangle, a FillRect command will be returned. 176 * Otherwise, a PutImage set will be generated. 177 * 178 * @param contextId user ID for the commands 179 * @param color fill color 180 * @param mode blending mode 181 * @param layer target layer 182 * @return set of commands 183 */ 184 protocol::MessageList fillCanvas(uint8_t contextId, const QColor &color, paintcore::BlendMode::Mode mode, int layer) const; 185 186 /** 187 * @brief Get the size of the adjustment handles in pixels at 1:1 zoom level 188 */ handleSize()189 int handleSize() const { return 20; } 190 191 //! Has the selection been moved or transformed? 192 bool isTransformed() const; 193 194 public slots: 195 //! Restore the original shape (but not the position) 196 void resetShape(); 197 198 //! Restore the original shape and position 199 void reset(); 200 201 //! Translate the selection directly (does not use pre-adjustment shape) 202 void translate(const QPoint &offset); 203 204 //! Scale the selection directly (does not use pre-adjustment shape) 205 void scale(qreal x, qreal y); 206 207 signals: 208 void shapeChanged(const QPolygonF &shape); 209 void pasteImageChanged(const QImage &image); 210 void closed(); 211 void adjustmentModeChanged(AdjustmentMode mode); 212 213 private: 214 void adjustGeometryScale(const QPoint &delta, bool keepAspect); 215 void adjustGeometryRotate(const QPointF &start, const QPointF &point, bool constrain); 216 void adjustGeometryDistort(const QPointF &delta); 217 void adjustTranslation(const QPointF &start, const QPointF &point); 218 void adjustTranslation(const QPointF &delta); 219 void adjustScale(qreal dx1, qreal dy1, qreal dx2, qreal dy2); 220 void adjustRotation(qreal angle); 221 void adjustShear(qreal sh, qreal sv); 222 void adjustDistort(const QPointF &delta, int corner1, int corner2 = -1); 223 224 void saveShape(); 225 226 QPolygonF m_shape; 227 QPolygonF m_originalShape; 228 QPolygonF m_preAdjustmentShape; 229 QPolygonF m_moveRegion; 230 231 AdjustmentMode m_adjustmentMode = AdjustmentMode::Scale; 232 Handle m_adjustmentHandle = Handle::Outside; 233 QPointF m_originalCenter; 234 235 QImage m_pasteImage; 236 237 int m_sourceLayerId = 0; 238 239 bool m_closedPolygon = false; 240 }; 241 242 } 243 244 #endif // SELECTION_H 245