1 /*
2 * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "kis_constrained_rect.h"
20
21 #include <cmath>
22 #include "kis_debug.h"
23 #include "kis_algebra_2d.h"
24
25
KisConstrainedRect()26 KisConstrainedRect::KisConstrainedRect()
27 : m_centered(false),
28 m_canGrow(true),
29 m_ratio(1.0),
30 m_widthLocked(false),
31 m_heightLocked(false),
32 m_ratioLocked(false)
33 {
34 }
35
~KisConstrainedRect()36 KisConstrainedRect::~KisConstrainedRect()
37 {
38 }
39
setRectInitial(const QRect & rect)40 void KisConstrainedRect::setRectInitial(const QRect &rect)
41 {
42 m_rect = rect;
43 if (!ratioLocked()) {
44 storeRatioSafe(m_rect.size());
45 }
46 emit sigValuesChanged();
47 }
48
setCropRect(const QRect & cropRect)49 void KisConstrainedRect::setCropRect(const QRect &cropRect)
50 {
51 m_cropRect = cropRect;
52 }
53
centered() const54 bool KisConstrainedRect::centered() const {
55 return m_centered;
56 }
setCentered(bool value)57 void KisConstrainedRect::setCentered(bool value) {
58 m_centered = value;
59 }
60
canGrow() const61 bool KisConstrainedRect::canGrow() const {
62 return m_canGrow;
63 }
setCanGrow(bool value)64 void KisConstrainedRect::setCanGrow(bool value) {
65 m_canGrow = value;
66 }
67
rect() const68 QRect KisConstrainedRect::rect() const {
69 return m_rect.normalized();
70 }
71
ratio() const72 qreal KisConstrainedRect::ratio() const {
73 return qAbs(m_ratio);
74 }
75
moveHandle(HandleType handle,const QPoint & offset,const QRect & oldRect)76 void KisConstrainedRect::moveHandle(HandleType handle, const QPoint &offset, const QRect &oldRect)
77 {
78 const QSize oldSize = oldRect.size();
79 QSize newSize = oldSize;
80 QPoint newOffset = oldRect.topLeft();
81
82 int xSizeCoeff = 1;
83 int ySizeCoeff = 1;
84
85 qreal xOffsetFromSizeChange = 1.0;
86 qreal yOffsetFromSizeChange = 1.0;
87
88 int baseSizeCoeff = 1;
89
90 bool useMoveOnly = false;
91
92 switch (handle) {
93 case UpperLeft:
94 xSizeCoeff = -1;
95 ySizeCoeff = -1;
96 xOffsetFromSizeChange = -1.0;
97 yOffsetFromSizeChange = -1.0;
98 break;
99 case UpperRight:
100 xSizeCoeff = 1;
101 ySizeCoeff = -1;
102 xOffsetFromSizeChange = 0.0;
103 yOffsetFromSizeChange = -1.0;
104 break;
105 case Creation:
106 baseSizeCoeff = 0;
107 Q_FALLTHROUGH();
108 case LowerRight:
109 xSizeCoeff = 1;
110 ySizeCoeff = 1;
111 xOffsetFromSizeChange = 0.0;
112 yOffsetFromSizeChange = 0.0;
113 break;
114 case LowerLeft:
115 xSizeCoeff = -1;
116 ySizeCoeff = 1;
117 xOffsetFromSizeChange = -1.0;
118 yOffsetFromSizeChange = 0.0;
119 break;
120 case Upper:
121 xSizeCoeff = 0;
122 ySizeCoeff = -1;
123 xOffsetFromSizeChange = -0.5;
124 yOffsetFromSizeChange = -1.0;
125 break;
126 case Right:
127 xSizeCoeff = 1;
128 ySizeCoeff = 0;
129 xOffsetFromSizeChange = 0.0;
130 yOffsetFromSizeChange = -0.5;
131 break;
132 case Lower:
133 xSizeCoeff = 0;
134 ySizeCoeff = 1;
135 xOffsetFromSizeChange = -0.5;
136 yOffsetFromSizeChange = 0.0;
137 break;
138 case Left:
139 xSizeCoeff = -1;
140 ySizeCoeff = 0;
141 xOffsetFromSizeChange = -1.0;
142 yOffsetFromSizeChange = -0.5;
143 break;
144 case Inside:
145 useMoveOnly = true;
146 break;
147 case None: // should never happen
148 break;
149 }
150
151 if (!useMoveOnly) {
152 const int centeringSizeCoeff = m_centered ? 2 : 1;
153 if (m_centered) {
154 xOffsetFromSizeChange = -0.5;
155 yOffsetFromSizeChange = -0.5;
156 }
157
158
159 QSize sizeDiff(offset.x() * xSizeCoeff * centeringSizeCoeff,
160 offset.y() * ySizeCoeff * centeringSizeCoeff);
161
162 QSize tempSize = baseSizeCoeff * oldSize + sizeDiff;
163 bool widthPreferrable = qAbs(tempSize.width()) > qAbs(tempSize.height() * m_ratio);
164
165 if (ratioLocked() && ((widthPreferrable && xSizeCoeff != 0) || ySizeCoeff == 0)) {
166 newSize.setWidth(tempSize.width());
167 newSize.setHeight(heightFromWidthUnsignedRatio(newSize.width(), m_ratio, tempSize.height()));
168 } else if (ratioLocked() && ((!widthPreferrable && ySizeCoeff != 0) || xSizeCoeff == 0)) {
169 newSize.setHeight(tempSize.height());
170 newSize.setWidth(widthFromHeightUnsignedRatio(newSize.height(), m_ratio, tempSize.width()));
171 } else if (widthLocked() && heightLocked()) {
172 newSize.setWidth(KisAlgebra2D::copysign(newSize.width(), tempSize.width()));
173 newSize.setHeight(KisAlgebra2D::copysign(newSize.height(), tempSize.height()));
174 } else if (widthLocked()) {
175 newSize.setWidth(KisAlgebra2D::copysign(newSize.width(), tempSize.width()));
176 newSize.setHeight(tempSize.height());
177 storeRatioSafe(newSize);
178 } else if (heightLocked()) {
179 newSize.setHeight(KisAlgebra2D::copysign(newSize.height(), tempSize.height()));
180 newSize.setWidth(tempSize.width());
181 storeRatioSafe(newSize);
182 } else {
183 newSize = baseSizeCoeff * oldSize + sizeDiff;
184 storeRatioSafe(newSize);
185 }
186
187 QSize realSizeDiff = newSize - baseSizeCoeff * oldSize;
188 QPoint offsetDiff(realSizeDiff.width() * xOffsetFromSizeChange,
189 realSizeDiff.height() * yOffsetFromSizeChange);
190
191 newOffset = oldRect.topLeft() + offsetDiff;
192 } else {
193 newOffset = oldRect.topLeft() + offset;
194 }
195
196 QPoint prevOffset = newOffset;
197
198 if (!m_canGrow) {
199 if (newOffset.x() + newSize.width() > m_cropRect.width()) {
200 newOffset.setX(m_cropRect.width() - newSize.width());
201 }
202
203 if (newOffset.y() + newSize.height() > m_cropRect.height()) {
204 newOffset.setY(m_cropRect.height() - newSize.height());
205 }
206 if (newOffset.x() < m_cropRect.x()) {
207 newOffset.setX(m_cropRect.x());
208 }
209 if (newOffset.y() < m_cropRect.y()) {
210 newOffset.setY(m_cropRect.y());
211 }
212 }
213
214 if (!m_ratioLocked && !useMoveOnly) {
215 newOffset = prevOffset;
216 }
217
218 m_rect = QRect(newOffset, newSize);
219
220 if (!m_canGrow) {
221 m_rect &= m_cropRect;
222 }
223
224 emit sigValuesChanged();
225 }
226
handleSnapPoint(HandleType handle,const QPointF & cursorPos)227 QPointF KisConstrainedRect::handleSnapPoint(HandleType handle, const QPointF &cursorPos)
228 {
229 QPointF snapPoint = cursorPos;
230
231 switch (handle) {
232 case UpperLeft:
233 snapPoint = m_rect.topLeft();
234 break;
235 case UpperRight:
236 snapPoint = m_rect.topRight() + QPointF(1, 0);
237 break;
238 case Creation:
239 break;
240 case LowerRight:
241 snapPoint = m_rect.bottomRight() + QPointF(1, 1);
242 break;
243 case LowerLeft:
244 snapPoint = m_rect.bottomLeft() + QPointF(0, 1);
245 break;
246 case Upper:
247 snapPoint.ry() = m_rect.y();
248 break;
249 case Right:
250 snapPoint.rx() = m_rect.right() + 1;
251 break;
252 case Lower:
253 snapPoint.ry() = m_rect.bottom() + 1;
254 break;
255 case Left:
256 snapPoint.rx() = m_rect.x();
257 break;
258 case Inside:
259 break;
260 case None: // should never happen
261 break;
262 }
263
264 return snapPoint;
265 }
266
normalize()267 void KisConstrainedRect::normalize()
268 {
269 setRectInitial(m_rect.normalized());
270 }
271
setOffset(const QPoint & offset)272 void KisConstrainedRect::setOffset(const QPoint &offset)
273 {
274 QRect newRect = m_rect;
275 newRect.moveTo(offset);
276
277 if (!m_canGrow) {
278 newRect &= m_cropRect;
279 }
280
281 if (!newRect.isEmpty()) {
282 m_rect = newRect;
283 }
284
285 emit sigValuesChanged();
286 }
287
setRatio(qreal value)288 void KisConstrainedRect::setRatio(qreal value) {
289 KIS_ASSERT_RECOVER_RETURN(value >= 0);
290
291 const qreal eps = 1e-7;
292 const qreal invEps = 1.0 / eps;
293
294 if (value < eps || value > invEps) {
295 emit sigValuesChanged();
296 return;
297 }
298
299 const QSize oldSize = m_rect.size();
300 QSize newSize = oldSize;
301
302 if (widthLocked() && heightLocked()) {
303 setHeightLocked(false);
304 }
305
306 m_ratio = value;
307
308 if (!widthLocked() && !heightLocked()) {
309 int area = oldSize.width() * oldSize.height();
310 newSize.setWidth(qRound(std::sqrt(area * m_ratio)));
311 newSize.setHeight(qRound(newSize.width() / m_ratio));
312 } else if (widthLocked()) {
313 newSize.setHeight(newSize.width() / m_ratio);
314 } else if (heightLocked()) {
315 newSize.setWidth(newSize.height() * m_ratio);
316 }
317
318 assignNewSize(newSize);
319 }
320
setWidth(int value)321 void KisConstrainedRect::setWidth(int value)
322 {
323 KIS_ASSERT_RECOVER_RETURN(value >= 0);
324
325 const QSize oldSize = m_rect.size();
326 QSize newSize = oldSize;
327
328 if (ratioLocked()) {
329 newSize.setWidth(value);
330 newSize.setHeight(newSize.width() / m_ratio);
331 } else {
332 newSize.setWidth(value);
333 storeRatioSafe(newSize);
334 }
335
336 assignNewSize(newSize);
337 }
338
setHeight(int value)339 void KisConstrainedRect::setHeight(int value)
340 {
341 KIS_ASSERT_RECOVER_RETURN(value >= 0);
342
343 const QSize oldSize = m_rect.size();
344 QSize newSize = oldSize;
345
346 if (ratioLocked()) {
347 newSize.setHeight(value);
348 newSize.setWidth(newSize.height() * m_ratio);
349 } else {
350 newSize.setHeight(value);
351 storeRatioSafe(newSize);
352 }
353
354 assignNewSize(newSize);
355 }
356
assignNewSize(const QSize & newSize)357 void KisConstrainedRect::assignNewSize(const QSize &newSize)
358 {
359 if (!m_centered) {
360 m_rect.setSize(newSize);
361 } else {
362 QSize sizeDiff = newSize - m_rect.size();
363 m_rect.translate(-qRound(sizeDiff.width() / 2.0), -qRound(sizeDiff.height() / 2.0));
364 m_rect.setSize(newSize);
365 }
366
367 if (!m_canGrow) {
368 m_rect &= m_cropRect;
369 }
370
371 emit sigValuesChanged();
372 }
373
storeRatioSafe(const QSize & newSize)374 void KisConstrainedRect::storeRatioSafe(const QSize &newSize)
375 {
376 m_ratio = qAbs(qreal(newSize.width()) / newSize.height());
377 }
378
widthFromHeightUnsignedRatio(int height,qreal ratio,int oldWidth) const379 int KisConstrainedRect::widthFromHeightUnsignedRatio(int height, qreal ratio, int oldWidth) const
380 {
381 int newWidth = qRound(height * ratio);
382 return KisAlgebra2D::copysign(newWidth, oldWidth);
383 }
384
heightFromWidthUnsignedRatio(int width,qreal ratio,int oldHeight) const385 int KisConstrainedRect::heightFromWidthUnsignedRatio(int width, qreal ratio, int oldHeight) const
386 {
387 int newHeight = qRound(width / ratio);
388 return KisAlgebra2D::copysign(newHeight, oldHeight);
389 }
390
widthLocked() const391 bool KisConstrainedRect::widthLocked() const {
392 return m_widthLocked;
393 }
heightLocked() const394 bool KisConstrainedRect::heightLocked() const {
395 return m_heightLocked;
396 }
ratioLocked() const397 bool KisConstrainedRect::ratioLocked() const {
398 return m_ratioLocked;
399 }
400
setWidthLocked(bool value)401 void KisConstrainedRect::setWidthLocked(bool value) {
402 m_widthLocked = value;
403 m_ratioLocked &= !(m_widthLocked || m_heightLocked);
404
405 emit sigLockValuesChanged();
406 }
407
setHeightLocked(bool value)408 void KisConstrainedRect::setHeightLocked(bool value) {
409 m_heightLocked = value;
410 m_ratioLocked &= !(m_widthLocked || m_heightLocked);
411
412 emit sigLockValuesChanged();
413 }
414
setRatioLocked(bool value)415 void KisConstrainedRect::setRatioLocked(bool value) {
416 m_ratioLocked = value;
417
418 m_widthLocked &= !m_ratioLocked;
419 m_heightLocked &= !m_ratioLocked;
420
421 emit sigLockValuesChanged();
422 }
423
424