1 /*
2 * Copyright (c) 2012 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_zoom_and_pan_test.h"
20
21 #include <cmath>
22 #include <QTest>
23
24 #include <kis_filter_configuration.h>
25 #include "testutil.h"
26 #include "qimage_based_test.h"
27
28 #include <kactioncollection.h>
29
30 #include "kis_config.h"
31
32 #include "KisMainWindow.h"
33 #include "KoZoomController.h"
34 #include "KisDocument.h"
35 #include "KisPart.h"
36 #include "KisViewManager.h"
37 #include "KisView.h"
38 #include "kis_canvas2.h"
39 #include "kis_canvas_controller.h"
40 #include "kis_coordinates_converter.h"
41 #include "kis_filter_strategy.h"
42
43 #include "kistest.h"
44
45 class ZoomAndPanTester : public TestUtil::QImageBasedTest
46 {
47 public:
ZoomAndPanTester()48 ZoomAndPanTester()
49 // we are not going to use our own QImage sets,so
50 // just exploit the set of the selection manager test
51 : QImageBasedTest("selection_manager_test")
52 {
53 m_undoStore = new KisSurrogateUndoStore();
54 m_image = createImage(m_undoStore);
55 m_image->initialRefreshGraph();
56 QVERIFY(checkLayersInitial(m_image));
57
58 m_doc = KisPart::instance()->createDocument();
59
60 m_doc->setCurrentImage(m_image);
61
62 m_mainWindow = KisPart::instance()->createMainWindow();
63 m_view = new KisView(m_doc, m_mainWindow->viewManager(), m_mainWindow);
64
65 m_image->refreshGraph();
66
67 m_mainWindow->show();
68 }
69
~ZoomAndPanTester()70 ~ZoomAndPanTester() {
71 m_image->waitForDone();
72 QApplication::processEvents();
73
74 delete m_mainWindow;
75 delete m_doc;
76
77 /**
78 * The event queue may have up to 200k events
79 * by the time all the tests are finished. Removing
80 * all of them may last forever, so clear them after
81 * every single test is finished
82 */
83 QApplication::removePostedEvents(0);
84 }
85
view()86 QPointer<KisView> view() {
87 return m_view;
88 }
89
mainWindow()90 KisMainWindow* mainWindow() {
91 return m_mainWindow;
92 }
93
image()94 KisImageWSP image() {
95 return m_image;
96 }
97
canvas()98 KisCanvas2* canvas() {
99 return m_view->canvasBase();
100 }
101
canvasWidget()102 QWidget* canvasWidget() {
103 return m_view->canvasBase()->canvasWidget();
104 }
105
zoomController()106 KoZoomController* zoomController() {
107 return m_view->zoomController();
108 }
109
canvasController()110 KisCanvasController* canvasController() {
111 return dynamic_cast<KisCanvasController*>(m_view->canvasController());
112 }
113
coordinatesConverter()114 const KisCoordinatesConverter* coordinatesConverter() {
115 return m_view->canvasBase()->coordinatesConverter();
116 }
117
118 private:
119 KisSurrogateUndoStore *m_undoStore;
120 KisImageSP m_image;
121 KisDocument *m_doc;
122 QPointer<KisView>m_view;
123 KisMainWindow *m_mainWindow;
124 };
125
126 template<class P, class T>
compareWithRounding(const P & pt0,const P & pt1,T tolerance)127 inline bool compareWithRounding(const P &pt0, const P &pt1, T tolerance)
128 {
129 return qAbs(pt0.x() - pt1.x()) <= tolerance &&
130 qAbs(pt0.y() - pt1.y()) <= tolerance;
131 }
132
verifyOffset(ZoomAndPanTester & t,const QPoint & offset)133 bool verifyOffset(ZoomAndPanTester &t, const QPoint &offset) {
134
135 if (t.coordinatesConverter()->documentOffset() != offset) {
136 dbgKrita << "########################";
137 dbgKrita << "Expected Offset:" << offset;
138 dbgKrita << "Actual values:";
139 dbgKrita << "Offset:" << t.coordinatesConverter()->documentOffset();
140 dbgKrita << "wsize:" << t.canvasWidget()->size();
141 dbgKrita << "vport:" << t.canvasController()->viewportSize();
142 dbgKrita << "pref:" << t.canvasController()->preferredCenter();
143 dbgKrita << "########################";
144 }
145
146 return t.coordinatesConverter()->documentOffset() == offset;
147 }
148
checkPan(ZoomAndPanTester & t,QPoint shift)149 bool KisZoomAndPanTest::checkPan(ZoomAndPanTester &t, QPoint shift)
150 {
151 QPoint oldOffset = t.coordinatesConverter()->documentOffset();
152 QPointF oldPrefCenter = t.canvasController()->preferredCenter();
153
154 t.canvasController()->pan(shift);
155
156 QPoint newOffset = t.coordinatesConverter()->documentOffset();
157 QPointF newPrefCenter = t.canvasController()->preferredCenter();
158 QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
159
160 QPoint expectedOffset = oldOffset + shift;
161 QPointF expectedPrefCenter = oldPrefCenter + shift;
162
163
164 // no tolerance accepted for pan
165 bool offsetAsExpected = newOffset == expectedOffset;
166
167 // rounding can happen due to the scroll bars being the main
168 // source of the offset
169 bool preferredCenterAsExpected =
170 compareWithRounding(expectedPrefCenter, newPrefCenter, 1.0);
171
172 bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
173
174 if (!offsetAsExpected ||
175 !preferredCenterAsExpected ||
176 !topLeftAsExpected) {
177
178 dbgKrita << "***** PAN *****************";
179
180 if(!offsetAsExpected) {
181 dbgKrita << " ### Offset invariant broken";
182 }
183
184 if(!preferredCenterAsExpected) {
185 dbgKrita << " ### Preferred center invariant broken";
186 }
187
188 if(!topLeftAsExpected) {
189 dbgKrita << " ### TopLeft invariant broken";
190 }
191
192 dbgKrita << ppVar(expectedOffset);
193 dbgKrita << ppVar(expectedPrefCenter);
194 dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
195 dbgKrita << ppVar(oldPrefCenter) << ppVar(newPrefCenter);
196 dbgKrita << ppVar(newTopLeft);
197 dbgKrita << "***************************";
198 }
199
200 return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
201 }
202
checkInvariants(const QPointF & baseFlakePoint,const QPoint & oldOffset,const QPointF & oldPreferredCenter,qreal oldZoom,const QPoint & newOffset,const QPointF & newPreferredCenter,qreal newZoom,const QPointF & newTopLeft,const QSize & oldDocumentSize)203 bool KisZoomAndPanTest::checkInvariants(const QPointF &baseFlakePoint,
204 const QPoint &oldOffset,
205 const QPointF &oldPreferredCenter,
206 qreal oldZoom,
207 const QPoint &newOffset,
208 const QPointF &newPreferredCenter,
209 qreal newZoom,
210 const QPointF &newTopLeft,
211 const QSize &oldDocumentSize)
212 {
213 qreal k = newZoom / oldZoom;
214
215 QPointF expectedOffset = oldOffset + (k - 1) * baseFlakePoint;
216 QPointF expectedPreferredCenter = oldPreferredCenter + (k - 1) * baseFlakePoint;
217
218 qreal oldPreferredCenterFractionX = 1.0 * oldPreferredCenter.x() / oldDocumentSize.width();
219 qreal oldPreferredCenterFractionY = 1.0 * oldPreferredCenter.y() / oldDocumentSize.height();
220
221 qreal roundingTolerance =
222 qMax(qreal(1.0), qMax(oldPreferredCenterFractionX, oldPreferredCenterFractionY) / k);
223
224 /**
225 * In the computation of the offset two roundings happen:
226 * first for the computation of oldOffset and the second
227 * for the computation of newOffset. So the maximum tolerance
228 * should equal 2.
229 */
230 bool offsetAsExpected =
231 compareWithRounding(expectedOffset, QPointF(newOffset), 2 * roundingTolerance);
232
233 /**
234 * Rounding for the preferred center happens due to the rounding
235 * of the document size while zooming. The wider the step of the
236 * zooming, the bigger tolerance should be
237 */
238 bool preferredCenterAsExpected =
239 compareWithRounding(expectedPreferredCenter, newPreferredCenter,
240 roundingTolerance);
241
242 bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
243
244 if (!offsetAsExpected ||
245 !preferredCenterAsExpected ||
246 !topLeftAsExpected) {
247
248 dbgKrita << "***** ZOOM ****************";
249
250 if(!offsetAsExpected) {
251 dbgKrita << " ### Offset invariant broken";
252 }
253
254 if(!preferredCenterAsExpected) {
255 dbgKrita << " ### Preferred center invariant broken";
256 }
257
258 if(!topLeftAsExpected) {
259 dbgKrita << " ### TopLeft invariant broken";
260 }
261
262 dbgKrita << ppVar(expectedOffset);
263 dbgKrita << ppVar(expectedPreferredCenter);
264 dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
265 dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
266 dbgKrita << ppVar(oldPreferredCenterFractionX);
267 dbgKrita << ppVar(oldPreferredCenterFractionY);
268 dbgKrita << ppVar(oldZoom) << ppVar(newZoom);
269 dbgKrita << ppVar(baseFlakePoint);
270 dbgKrita << ppVar(newTopLeft);
271 dbgKrita << ppVar(roundingTolerance);
272 dbgKrita << "***************************";
273 }
274
275 return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
276 }
277
checkZoomWithAction(ZoomAndPanTester & t,qreal newZoom,bool limitedZoom)278 bool KisZoomAndPanTest::checkZoomWithAction(ZoomAndPanTester &t, qreal newZoom, bool limitedZoom)
279 {
280 QPoint oldOffset = t.coordinatesConverter()->documentOffset();
281 QPointF oldPrefCenter = t.canvasController()->preferredCenter();
282 qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
283 QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
284
285 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);
286
287 QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
288
289 return checkInvariants(oldPrefCenter,
290 oldOffset,
291 oldPrefCenter,
292 oldZoom,
293 t.coordinatesConverter()->documentOffset(),
294 t.canvasController()->preferredCenter(),
295 limitedZoom ? oldZoom : newZoom,
296 newTopLeft,
297 oldDocumentSize);
298 }
299
checkZoomWithWheel(ZoomAndPanTester & t,const QPoint & widgetPoint,qreal zoomCoeff,bool limitedZoom)300 bool KisZoomAndPanTest::checkZoomWithWheel(ZoomAndPanTester &t, const QPoint &widgetPoint, qreal zoomCoeff, bool limitedZoom)
301 {
302 QPoint oldOffset = t.coordinatesConverter()->documentOffset();
303 QPointF oldPrefCenter = t.canvasController()->preferredCenter();
304 qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
305 QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
306
307 t.canvasController()->zoomRelativeToPoint(widgetPoint, zoomCoeff);
308
309 QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
310
311 return checkInvariants(oldOffset + widgetPoint,
312 oldOffset,
313 oldPrefCenter,
314 oldZoom,
315 t.coordinatesConverter()->documentOffset(),
316 t.canvasController()->preferredCenter(),
317 limitedZoom ? oldZoom : zoomCoeff * oldZoom,
318 newTopLeft,
319 oldDocumentSize);
320 }
321
testZoom100ChangingWidgetSize()322 void KisZoomAndPanTest::testZoom100ChangingWidgetSize()
323 {
324 ZoomAndPanTester t;
325
326 QCOMPARE(t.image()->size(), QSize(640,441));
327 QCOMPARE(t.image()->xRes(), 1.0);
328 QCOMPARE(t.image()->yRes(), 1.0);
329
330 t.canvasController()->resize(QSize(1000,1000));
331 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
332 t.canvasController()->setPreferredCenter(QPoint(320,220));
333
334 QCOMPARE(t.canvasWidget()->size(), QSize(983,983));
335 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
336 QVERIFY(verifyOffset(t, QPoint(-171,-271)));
337
338 t.canvasController()->resize(QSize(700,700));
339
340 QCOMPARE(t.canvasWidget()->size(), QSize(683,683));
341 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
342 QVERIFY(verifyOffset(t, QPoint(-171,-271)));
343
344 t.canvasController()->setPreferredCenter(QPoint(320,220));
345
346 QVERIFY(verifyOffset(t, QPoint(-21,-121)));
347
348 t.canvasController()->resize(QSize(400,400));
349
350 QCOMPARE(t.canvasWidget()->size(), QSize(383,383));
351 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
352 QVERIFY(verifyOffset(t, QPoint(-21,-121)));
353
354 t.canvasController()->setPreferredCenter(QPoint(320,220));
355
356 QVERIFY(verifyOffset(t, QPoint(129,29)));
357
358 t.canvasController()->pan(QPoint(100,100));
359
360 QVERIFY(verifyOffset(t, QPoint(229,129)));
361 }
362
initializeViewport(ZoomAndPanTester & t,bool fullscreenMode,bool rotate,bool mirror)363 void KisZoomAndPanTest::initializeViewport(ZoomAndPanTester &t, bool fullscreenMode, bool rotate, bool mirror)
364 {
365 QCOMPARE(t.image()->size(), QSize(640,441));
366 QCOMPARE(t.image()->xRes(), 1.0);
367 QCOMPARE(t.image()->yRes(), 1.0);
368
369 t.canvasController()->resize(QSize(500,500));
370 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
371 t.canvasController()->setPreferredCenter(QPoint(320,220));
372
373 QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
374 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
375 QVERIFY(verifyOffset(t, QPoint(79,-21)));
376
377 if (fullscreenMode) {
378 QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320,220));
379
380 QAction *action = t.view()->viewManager()->actionCollection()->action("view_show_canvas_only");
381 action->setChecked(true);
382
383 QVERIFY(verifyOffset(t, QPoint(79,-21)));
384 QCOMPARE(t.canvasController()->preferredCenter(), QPointF(329,220));
385
386
387 t.canvasController()->resize(QSize(483,483));
388 QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
389 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
390 QVERIFY(verifyOffset(t, QPoint(79,-21)));
391
392
393 /**
394 * FIXME: here is a small flaw in KoCanvasControllerWidget
395 * We cannot set the center point explicitly, because it'll be rounded
396 * up by recenterPreferred function, so real center point will be
397 * different. Make the preferredCenter() return real center of the
398 * image instead of the set value
399 */
400 QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320.5,220));
401 }
402
403 if (rotate) {
404 t.canvasController()->rotateCanvas(90);
405 QVERIFY(verifyOffset(t, QPoint(-21,79)));
406 QVERIFY(compareWithRounding(QPointF(220,320), t.canvasController()->preferredCenter(), 2));
407 QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
408 }
409
410 if (mirror) {
411 t.canvasController()->mirrorCanvas(true);
412 QVERIFY(verifyOffset(t, QPoint(78, -21)));
413 QVERIFY(compareWithRounding(QPointF(320,220), t.canvasController()->preferredCenter(), 2));
414 QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
415 }
416 }
417
testSequentialActionZoomAndPan(bool fullscreenMode,bool rotate,bool mirror)418 void KisZoomAndPanTest::testSequentialActionZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
419 {
420 ZoomAndPanTester t;
421 initializeViewport(t, fullscreenMode, rotate, mirror);
422
423 QVERIFY(checkZoomWithAction(t, 0.5));
424 QVERIFY(checkPan(t, QPoint(100,100)));
425
426 QVERIFY(checkZoomWithAction(t, 0.25));
427 QVERIFY(checkPan(t, QPoint(-100,-100)));
428
429 QVERIFY(checkZoomWithAction(t, 0.35));
430 QVERIFY(checkPan(t, QPoint(100,100)));
431
432 QVERIFY(checkZoomWithAction(t, 0.45));
433 QVERIFY(checkPan(t, QPoint(100,100)));
434
435 QVERIFY(checkZoomWithAction(t, 0.85));
436 QVERIFY(checkPan(t, QPoint(-100,-100)));
437
438 QVERIFY(checkZoomWithAction(t, 2.35));
439 QVERIFY(checkPan(t, QPoint(100,100)));
440 }
441
testSequentialWheelZoomAndPan(bool fullscreenMode,bool rotate,bool mirror)442 void KisZoomAndPanTest::testSequentialWheelZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
443 {
444 ZoomAndPanTester t;
445 initializeViewport(t, fullscreenMode, rotate, mirror);
446
447 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
448 QVERIFY(checkPan(t, QPoint(100,100)));
449
450 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
451 QVERIFY(checkPan(t, QPoint(-100,-100)));
452
453 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.25));
454 QVERIFY(checkPan(t, QPoint(100,100)));
455
456 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.5));
457 QVERIFY(checkPan(t, QPoint(100,100)));
458
459 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.5));
460 QVERIFY(checkPan(t, QPoint(-100,-100)));
461
462 // check one point which is outside the widget
463 QVERIFY(checkZoomWithWheel(t, QPoint(-100,100), 2.5));
464 QVERIFY(checkPan(t, QPoint(-100,-100)));
465
466 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
467 QVERIFY(checkPan(t, QPoint(-100,-100)));
468 }
469
testSequentialActionZoomAndPan()470 void KisZoomAndPanTest::testSequentialActionZoomAndPan()
471 {
472 testSequentialActionZoomAndPan(false, false, false);
473 }
474
testSequentialActionZoomAndPanFullscreen()475 void KisZoomAndPanTest::testSequentialActionZoomAndPanFullscreen()
476 {
477 testSequentialActionZoomAndPan(true, false, false);
478 }
479
testSequentialActionZoomAndPanRotate()480 void KisZoomAndPanTest::testSequentialActionZoomAndPanRotate()
481 {
482 testSequentialActionZoomAndPan(false, true, false);
483 }
484
testSequentialActionZoomAndPanRotateFullscreen()485 void KisZoomAndPanTest::testSequentialActionZoomAndPanRotateFullscreen()
486 {
487 testSequentialActionZoomAndPan(true, true, false);
488 }
489
testSequentialActionZoomAndPanMirror()490 void KisZoomAndPanTest::testSequentialActionZoomAndPanMirror()
491 {
492 testSequentialActionZoomAndPan(false, false, true);
493 }
494
testSequentialWheelZoomAndPan()495 void KisZoomAndPanTest::testSequentialWheelZoomAndPan()
496 {
497 testSequentialWheelZoomAndPan(false, false, false);
498 }
499
testSequentialWheelZoomAndPanFullscreen()500 void KisZoomAndPanTest::testSequentialWheelZoomAndPanFullscreen()
501 {
502 testSequentialWheelZoomAndPan(true, false, false);
503 }
504
testSequentialWheelZoomAndPanRotate()505 void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotate()
506 {
507 testSequentialWheelZoomAndPan(false, true, false);
508 }
509
testSequentialWheelZoomAndPanRotateFullscreen()510 void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotateFullscreen()
511 {
512 testSequentialWheelZoomAndPan(true, true, false);
513 }
514
testSequentialWheelZoomAndPanMirror()515 void KisZoomAndPanTest::testSequentialWheelZoomAndPanMirror()
516 {
517 testSequentialWheelZoomAndPan(false, false, true);
518 }
519
testZoomOnBorderZoomLevels()520 void KisZoomAndPanTest::testZoomOnBorderZoomLevels()
521 {
522 ZoomAndPanTester t;
523 initializeViewport(t, false, false, false);
524
525 // QPoint widgetPoint(100,100);
526
527 warnKrita << "WARNING: testZoomOnBorderZoomLevels() is disabled due to some changes in KoZoomMode::minimum/maximumZoom()";
528 return;
529
530 // test min zoom level
531 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom());
532 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true));
533 QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true));
534
535 // test max zoom level
536 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom());
537 QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true));
538 QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true));
539 }
540
correctionMatrix(qreal angle)541 inline QTransform correctionMatrix(qreal angle)
542 {
543 return QTransform(0,0,0,sin(M_PI * angle / 180),0,0,0,0,1);
544 }
545
checkRotation(ZoomAndPanTester & t,qreal angle)546 bool KisZoomAndPanTest::checkRotation(ZoomAndPanTester &t, qreal angle)
547 {
548 // save old values
549 QPoint oldOffset = t.coordinatesConverter()->documentOffset();
550 QPointF oldCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
551 QPointF oldPreferredCenter = t.canvasController()->preferredCenter();
552 QPointF oldRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
553 QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
554
555 qreal baseAngle = t.coordinatesConverter()->rotationAngle();
556 t.canvasController()->rotateCanvas(angle);
557
558 // save result values
559 QPoint newOffset = t.coordinatesConverter()->documentOffset();
560 QPointF newCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
561 QPointF newPreferredCenter = t.canvasController()->preferredCenter();
562 QPointF newRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
563 QSize newDocumentSize = t.canvasController()->documentSize().toSize();
564
565
566 // calculate theoretical preferred center
567 QTransform rot;
568 rot.rotate(angle);
569
570 QSizeF dSize = t.coordinatesConverter()->imageSizeInFlakePixels();
571 QPointF dPoint(dSize.width(), dSize.height());
572
573 QPointF expectedPreferredCenter =
574 (oldPreferredCenter - dPoint * correctionMatrix(baseAngle)) * rot +
575 dPoint * correctionMatrix(baseAngle + angle);
576
577 // calculate theoretical offset based on the real preferred center
578 QPointF wPoint(t.canvasWidget()->size().width(), t.canvasWidget()->size().height());
579 QPointF expectedOldOffset = oldPreferredCenter - 0.5 * wPoint;
580 QPointF expectedNewOffset = newPreferredCenter - 0.5 * wPoint;
581
582 bool preferredCenterAsExpected =
583 compareWithRounding(expectedPreferredCenter, newPreferredCenter, 2);
584 bool oldOffsetAsExpected =
585 compareWithRounding(expectedOldOffset + oldCenteringCorrection, QPointF(oldOffset), 2);
586 bool newOffsetAsExpected =
587 compareWithRounding(expectedNewOffset + newCenteringCorrection, QPointF(newOffset), 3);
588
589 qreal zoom = t.zoomController()->zoomAction()->effectiveZoom();
590 bool realCenterPointAsExpected =
591 compareWithRounding(oldRealCenterPoint, newRealCenterPoint, 2/zoom);
592
593
594 if (!oldOffsetAsExpected ||
595 !newOffsetAsExpected ||
596 !preferredCenterAsExpected ||
597 !realCenterPointAsExpected) {
598
599 dbgKrita << "***** ROTATE **************";
600
601 if(!oldOffsetAsExpected) {
602 dbgKrita << " ### Old offset invariant broken";
603 }
604
605 if(!newOffsetAsExpected) {
606 dbgKrita << " ### New offset invariant broken";
607 }
608
609 if(!preferredCenterAsExpected) {
610 dbgKrita << " ### Preferred center invariant broken";
611 }
612
613 if(!realCenterPointAsExpected) {
614 dbgKrita << " ### *Real* center invariant broken";
615 }
616
617 dbgKrita << ppVar(expectedOldOffset);
618 dbgKrita << ppVar(expectedNewOffset);
619 dbgKrita << ppVar(expectedPreferredCenter);
620 dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
621 dbgKrita << ppVar(oldCenteringCorrection) << ppVar(newCenteringCorrection);
622 dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
623 dbgKrita << ppVar(oldRealCenterPoint) << ppVar(newRealCenterPoint);
624 dbgKrita << ppVar(oldDocumentSize) << ppVar(newDocumentSize);
625 dbgKrita << ppVar(baseAngle) << "deg";
626 dbgKrita << ppVar(angle) << "deg";
627 dbgKrita << "***************************";
628 }
629
630 return preferredCenterAsExpected && oldOffsetAsExpected && newOffsetAsExpected && realCenterPointAsExpected;
631 }
632
testRotation(qreal vastScrolling,qreal zoom)633 void KisZoomAndPanTest::testRotation(qreal vastScrolling, qreal zoom)
634 {
635 KisConfig cfg(false);
636 cfg.setVastScrolling(vastScrolling);
637
638 ZoomAndPanTester t;
639
640 QCOMPARE(t.image()->size(), QSize(640,441));
641 QCOMPARE(t.image()->xRes(), 1.0);
642 QCOMPARE(t.image()->yRes(), 1.0);
643
644 QPointF preferredCenter = zoom * t.image()->bounds().center();
645
646 t.canvasController()->resize(QSize(500,500));
647 t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom);
648 t.canvasController()->setPreferredCenter(preferredCenter.toPoint());
649
650 QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
651 QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
652
653 QPointF realCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
654 QPointF expectedCenterPoint = QPointF(t.image()->bounds().center());
655
656 if(!compareWithRounding(realCenterPoint, expectedCenterPoint, 2/zoom)) {
657 dbgKrita << "Failed to set initial center point";
658 dbgKrita << ppVar(expectedCenterPoint) << ppVar(realCenterPoint);
659 QFAIL("FAIL: Failed to set initial center point");
660 }
661
662 QVERIFY(checkRotation(t, 30));
663 QVERIFY(checkRotation(t, 20));
664 QVERIFY(checkRotation(t, 10));
665 QVERIFY(checkRotation(t, 5));
666 QVERIFY(checkRotation(t, 5));
667 QVERIFY(checkRotation(t, 5));
668
669 if(vastScrolling < 0.5 && zoom < 1) {
670 warnKrita << "Disabling a few tests for vast scrolling ="
671 << vastScrolling << ". See comment for more";
672 /**
673 * We have to disable a couple of tests here for the case when
674 * vastScrolling value is 0.2. The problem is that the centering
675 * correction applied to the offset in
676 * KisCanvasController::rotateCanvas pollutes the preferredCenter
677 * value, because KoCnvasControllerWidget has no access to this
678 * correction and cannot calculate the real value of the center of
679 * the image. To fix this bug the calculation of correction
680 * (aka "origin") should be moved to the KoCanvasControllerWidget
681 * itself which would cause quite huge changes (including the change
682 * of the external interface of it). Namely, we would have to
683 * *calculate* offset from the value of the scroll bars, but not
684 * use their values directly:
685 *
686 * offset = scrollBarValue - origin
687 *
688 * So now we just disable these unittests and allow a couple
689 * of "jumping" bugs appear in vastScrolling < 0.5 modes, which
690 * is, actually, not the default case.
691 */
692
693 } else {
694 QVERIFY(checkRotation(t, 5));
695 QVERIFY(checkRotation(t, 5));
696 QVERIFY(checkRotation(t, 5));
697 }
698 }
699
testRotation_VastScrolling_1_0()700 void KisZoomAndPanTest::testRotation_VastScrolling_1_0()
701 {
702 testRotation(0.9, 1.0);
703 }
704
testRotation_VastScrolling_0_5()705 void KisZoomAndPanTest::testRotation_VastScrolling_0_5()
706 {
707 testRotation(0.9, 0.5);
708 }
709
testRotation_NoVastScrolling_1_0()710 void KisZoomAndPanTest::testRotation_NoVastScrolling_1_0()
711 {
712 testRotation(0.2, 1.0);
713 }
714
testRotation_NoVastScrolling_0_5()715 void KisZoomAndPanTest::testRotation_NoVastScrolling_0_5()
716 {
717 testRotation(0.2, 0.5);
718 }
719
testImageRescaled_0_5()720 void KisZoomAndPanTest::testImageRescaled_0_5()
721 {
722 ZoomAndPanTester t;
723 QApplication::processEvents();
724 initializeViewport(t, false, false, false);
725 QApplication::processEvents();
726 QVERIFY(checkPan(t, QPoint(200,200)));
727 QApplication::processEvents();
728
729 QPointF oldStillPoint =
730 t.coordinatesConverter()->imageRectInWidgetPixels().center();
731
732 KisFilterStrategy *strategy = new KisBilinearFilterStrategy();
733 t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy);
734 t.image()->waitForDone();
735 QApplication::processEvents();
736 delete strategy;
737
738 QPointF newStillPoint =
739 t.coordinatesConverter()->imageRectInWidgetPixels().center();
740
741 QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
742 }
743
testImageCropped()744 void KisZoomAndPanTest::testImageCropped()
745 {
746 ZoomAndPanTester t;
747 QApplication::processEvents();
748 initializeViewport(t, false, false, false);
749 QApplication::processEvents();
750 QVERIFY(checkPan(t, QPoint(-150,-150)));
751 QApplication::processEvents();
752
753 QPointF oldStillPoint =
754 t.coordinatesConverter()->imageToWidget(QPointF(150,150));
755
756 t.image()->cropImage(QRect(100,100,100,100));
757 t.image()->waitForDone();
758 QApplication::processEvents();
759
760 QPointF newStillPoint =
761 t.coordinatesConverter()->imageToWidget(QPointF(50,50));
762
763 QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
764 }
765
766 KISTEST_MAIN(KisZoomAndPanTest)
767