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