File indexing completed on 2024-12-22 04:12:54

0001 /*
0002  *  SPDX-FileCopyrightText: 2012 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_zoom_and_pan_test.h"
0008 
0009 #include <cmath>
0010 #include <simpletest.h>
0011 
0012 #include <kis_filter_configuration.h>
0013 #include <testutil.h>
0014 #include "qimage_based_test.h"
0015 
0016 #include <kactioncollection.h>
0017 
0018 #include "kis_config.h"
0019 
0020 #include "KisMainWindow.h"
0021 #include "KoZoomController.h"
0022 #include "KisDocument.h"
0023 #include "KisPart.h"
0024 #include "KisViewManager.h"
0025 #include "KisView.h"
0026 #include "kis_canvas2.h"
0027 #include "kis_canvas_controller.h"
0028 #include "kis_coordinates_converter.h"
0029 #include "kis_filter_strategy.h"
0030 
0031 #include "testui.h"
0032 
0033 class ZoomAndPanTester : public TestUtil::QImageBasedTest
0034 {
0035 public:
0036     ZoomAndPanTester()
0037         // we are not going to use our own QImage sets,so
0038         // just exploit the set of the selection manager test
0039         : QImageBasedTest("selection_manager_test")
0040     {
0041         m_undoStore = new KisSurrogateUndoStore();
0042         m_image = createImage(m_undoStore);
0043         m_image->initialRefreshGraph();
0044         QVERIFY(checkLayersInitial(m_image));
0045 
0046         m_doc = KisPart::instance()->createDocument();
0047 
0048         m_doc->setCurrentImage(m_image);
0049 
0050         m_mainWindow = KisPart::instance()->createMainWindow();
0051         m_view = new KisView(m_doc, m_mainWindow->viewManager(), m_mainWindow);
0052 
0053         m_image->refreshGraph();
0054 
0055         m_mainWindow->show();
0056     }
0057 
0058     ~ZoomAndPanTester() {
0059         m_image->waitForDone();
0060         QApplication::processEvents();
0061 
0062         delete m_mainWindow;
0063         delete m_doc;
0064 
0065         /**
0066          * The event queue may have up to 200k events
0067          * by the time all the tests are finished. Removing
0068          * all of them may last forever, so clear them after
0069          * every single test is finished
0070          */
0071         QApplication::removePostedEvents(0);
0072     }
0073 
0074     QPointer<KisView> view() {
0075         return m_view;
0076     }
0077 
0078     KisMainWindow* mainWindow() {
0079         return m_mainWindow;
0080     }
0081 
0082     KisImageWSP image() {
0083         return m_image;
0084     }
0085 
0086     KisCanvas2* canvas() {
0087         return m_view->canvasBase();
0088     }
0089 
0090     QWidget* canvasWidget() {
0091         return m_view->canvasBase()->canvasWidget();
0092     }
0093 
0094     KoZoomController* zoomController() {
0095         return m_view->zoomController();
0096     }
0097 
0098     KisCanvasController* canvasController() {
0099         return dynamic_cast<KisCanvasController*>(m_view->canvasController());
0100     }
0101 
0102     const KisCoordinatesConverter* coordinatesConverter() {
0103         return m_view->canvasBase()->coordinatesConverter();
0104     }
0105 
0106 private:
0107     KisSurrogateUndoStore *m_undoStore;
0108     KisImageSP m_image;
0109     KisDocument *m_doc;
0110     QPointer<KisView>m_view;
0111     KisMainWindow *m_mainWindow;
0112 };
0113 
0114 template<class P, class T>
0115 inline bool compareWithRounding(const P &pt0, const P &pt1, T tolerance)
0116 {
0117     return qAbs(pt0.x() - pt1.x()) <= tolerance &&
0118         qAbs(pt0.y() - pt1.y()) <= tolerance;
0119 }
0120 
0121 bool verifyOffset(ZoomAndPanTester &t, const QPoint &offset) {
0122 
0123     if (t.coordinatesConverter()->documentOffset() != offset) {
0124             dbgKrita << "########################";
0125             dbgKrita << "Expected Offset:" << offset;
0126             dbgKrita << "Actual values:";
0127             dbgKrita << "Offset:" << t.coordinatesConverter()->documentOffset();
0128             dbgKrita << "wsize:"  << t.canvasWidget()->size();
0129             dbgKrita << "vport:"  << t.canvasController()->viewportSize();
0130             dbgKrita << "pref:"  << t.canvasController()->preferredCenter();
0131             dbgKrita << "########################";
0132     }
0133 
0134     return t.coordinatesConverter()->documentOffset() == offset;
0135 }
0136 
0137 bool KisZoomAndPanTest::checkPan(ZoomAndPanTester &t, QPoint shift)
0138 {
0139     QPoint oldOffset = t.coordinatesConverter()->documentOffset();
0140     QPointF oldPrefCenter = t.canvasController()->preferredCenter();
0141 
0142     t.canvasController()->pan(shift);
0143 
0144     QPoint newOffset  = t.coordinatesConverter()->documentOffset();
0145     QPointF newPrefCenter = t.canvasController()->preferredCenter();
0146     QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
0147 
0148     QPoint expectedOffset  = oldOffset + shift;
0149     QPointF expectedPrefCenter = oldPrefCenter + shift;
0150 
0151 
0152     // no tolerance accepted for pan
0153     bool offsetAsExpected = newOffset == expectedOffset;
0154 
0155     // rounding can happen due to the scroll bars being the main
0156     // source of the offset
0157     bool preferredCenterAsExpected =
0158         compareWithRounding(expectedPrefCenter, newPrefCenter, 1.0);
0159 
0160     bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
0161 
0162     if (!offsetAsExpected ||
0163         !preferredCenterAsExpected ||
0164         !topLeftAsExpected) {
0165 
0166         dbgKrita << "***** PAN *****************";
0167 
0168         if(!offsetAsExpected) {
0169             dbgKrita << " ### Offset invariant broken";
0170         }
0171 
0172         if(!preferredCenterAsExpected) {
0173             dbgKrita << " ### Preferred center invariant broken";
0174         }
0175 
0176         if(!topLeftAsExpected) {
0177             dbgKrita << " ### TopLeft invariant broken";
0178         }
0179 
0180         dbgKrita << ppVar(expectedOffset);
0181         dbgKrita << ppVar(expectedPrefCenter);
0182         dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
0183         dbgKrita << ppVar(oldPrefCenter) << ppVar(newPrefCenter);
0184         dbgKrita << ppVar(newTopLeft);
0185         dbgKrita << "***************************";
0186     }
0187 
0188     return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
0189 }
0190 
0191 bool KisZoomAndPanTest::checkInvariants(const QPointF &baseFlakePoint,
0192                                         const QPoint &oldOffset,
0193                                         const QPointF &oldPreferredCenter,
0194                                         qreal oldZoom,
0195                                         const QPoint &newOffset,
0196                                         const QPointF &newPreferredCenter,
0197                                         qreal newZoom,
0198                                         const QPointF &newTopLeft,
0199                                         const QSize &oldDocumentSize)
0200 {
0201     qreal k = newZoom / oldZoom;
0202 
0203     QPointF expectedOffset = oldOffset + (k - 1) * baseFlakePoint;
0204     QPointF expectedPreferredCenter = oldPreferredCenter + (k - 1) * baseFlakePoint;
0205 
0206     qreal oldPreferredCenterFractionX = 1.0 * oldPreferredCenter.x() / oldDocumentSize.width();
0207     qreal oldPreferredCenterFractionY = 1.0 * oldPreferredCenter.y() / oldDocumentSize.height();
0208 
0209     qreal roundingTolerance =
0210         qMax(qreal(1.0), qMax(oldPreferredCenterFractionX, oldPreferredCenterFractionY) / k);
0211 
0212     /**
0213      * In the computation of the offset two roundings happen:
0214      * first for the computation of oldOffset and the second
0215      * for the computation of newOffset. So the maximum tolerance
0216      * should equal 2.
0217      */
0218     bool offsetAsExpected =
0219         compareWithRounding(expectedOffset, QPointF(newOffset), 2 * roundingTolerance);
0220 
0221     /**
0222      * Rounding for the preferred center happens due to the rounding
0223      * of the document size while zooming. The wider the step of the
0224      * zooming, the bigger tolerance should be
0225      */
0226     bool preferredCenterAsExpected =
0227         compareWithRounding(expectedPreferredCenter, newPreferredCenter,
0228                             roundingTolerance);
0229 
0230     bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
0231 
0232     if (!offsetAsExpected ||
0233         !preferredCenterAsExpected ||
0234         !topLeftAsExpected) {
0235 
0236         dbgKrita << "***** ZOOM ****************";
0237 
0238         if(!offsetAsExpected) {
0239             dbgKrita << " ### Offset invariant broken";
0240         }
0241 
0242         if(!preferredCenterAsExpected) {
0243             dbgKrita << " ### Preferred center invariant broken";
0244         }
0245 
0246         if(!topLeftAsExpected) {
0247             dbgKrita << " ### TopLeft invariant broken";
0248         }
0249 
0250         dbgKrita << ppVar(expectedOffset);
0251         dbgKrita << ppVar(expectedPreferredCenter);
0252         dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
0253         dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
0254         dbgKrita << ppVar(oldPreferredCenterFractionX);
0255         dbgKrita << ppVar(oldPreferredCenterFractionY);
0256         dbgKrita << ppVar(oldZoom) << ppVar(newZoom);
0257         dbgKrita << ppVar(baseFlakePoint);
0258         dbgKrita << ppVar(newTopLeft);
0259         dbgKrita << ppVar(roundingTolerance);
0260         dbgKrita << "***************************";
0261     }
0262 
0263     return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
0264 }
0265 
0266 bool KisZoomAndPanTest::checkZoomWithAction(ZoomAndPanTester &t, qreal newZoom, bool limitedZoom)
0267 {
0268     QPoint oldOffset = t.coordinatesConverter()->documentOffset();
0269     QPointF oldPrefCenter = t.canvasController()->preferredCenter();
0270     qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
0271     QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
0272 
0273     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);
0274 
0275     QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
0276 
0277     return checkInvariants(oldPrefCenter,
0278                            oldOffset,
0279                            oldPrefCenter,
0280                            oldZoom,
0281                            t.coordinatesConverter()->documentOffset(),
0282                            t.canvasController()->preferredCenter(),
0283                            limitedZoom ? oldZoom : newZoom,
0284                            newTopLeft,
0285                            oldDocumentSize);
0286 }
0287 
0288 bool KisZoomAndPanTest::checkZoomWithWheel(ZoomAndPanTester &t, const QPoint &widgetPoint, qreal zoomCoeff, bool limitedZoom)
0289 {
0290     QPoint oldOffset = t.coordinatesConverter()->documentOffset();
0291     QPointF oldPrefCenter = t.canvasController()->preferredCenter();
0292     qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
0293     QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
0294 
0295     t.canvasController()->zoomRelativeToPoint(widgetPoint, zoomCoeff);
0296 
0297     QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
0298 
0299     return checkInvariants(oldOffset + widgetPoint,
0300                            oldOffset,
0301                            oldPrefCenter,
0302                            oldZoom,
0303                            t.coordinatesConverter()->documentOffset(),
0304                            t.canvasController()->preferredCenter(),
0305                            limitedZoom ? oldZoom : zoomCoeff * oldZoom,
0306                            newTopLeft,
0307                            oldDocumentSize);
0308 }
0309 
0310 void KisZoomAndPanTest::testZoom100ChangingWidgetSize()
0311 {
0312     ZoomAndPanTester t;
0313 
0314     QCOMPARE(t.image()->size(), QSize(640,441));
0315     QCOMPARE(t.image()->xRes(), 1.0);
0316     QCOMPARE(t.image()->yRes(), 1.0);
0317 
0318     t.canvasController()->resize(QSize(1000,1000));
0319     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
0320     t.canvasController()->setPreferredCenter(QPoint(320,220));
0321 
0322     QCOMPARE(t.canvasWidget()->size(), QSize(983,983));
0323     QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0324     QVERIFY(verifyOffset(t, QPoint(-171,-271)));
0325 
0326     t.canvasController()->resize(QSize(700,700));
0327 
0328     QCOMPARE(t.canvasWidget()->size(), QSize(683,683));
0329     QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0330     QVERIFY(verifyOffset(t, QPoint(-171,-271)));
0331 
0332     t.canvasController()->setPreferredCenter(QPoint(320,220));
0333 
0334     QVERIFY(verifyOffset(t, QPoint(-21,-121)));
0335 
0336     t.canvasController()->resize(QSize(400,400));
0337 
0338     QCOMPARE(t.canvasWidget()->size(), QSize(383,383));
0339     QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0340     QVERIFY(verifyOffset(t, QPoint(-21,-121)));
0341 
0342     t.canvasController()->setPreferredCenter(QPoint(320,220));
0343 
0344     QVERIFY(verifyOffset(t, QPoint(129,29)));
0345 
0346     t.canvasController()->pan(QPoint(100,100));
0347 
0348     QVERIFY(verifyOffset(t, QPoint(229,129)));
0349 }
0350 
0351 void KisZoomAndPanTest::initializeViewport(ZoomAndPanTester &t, bool fullscreenMode, bool rotate, bool mirror)
0352 {
0353     QCOMPARE(t.image()->size(), QSize(640,441));
0354     QCOMPARE(t.image()->xRes(), 1.0);
0355     QCOMPARE(t.image()->yRes(), 1.0);
0356 
0357     t.canvasController()->resize(QSize(500,500));
0358     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
0359     t.canvasController()->setPreferredCenter(QPoint(320,220));
0360 
0361     QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
0362     QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0363     QVERIFY(verifyOffset(t, QPoint(79,-21)));
0364 
0365     if (fullscreenMode) {
0366         QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320,220));
0367 
0368         QAction *action = t.view()->viewManager()->actionCollection()->action("view_show_canvas_only");
0369         action->setChecked(true);
0370 
0371         QVERIFY(verifyOffset(t, QPoint(79,-21)));
0372         QCOMPARE(t.canvasController()->preferredCenter(), QPointF(329,220));
0373 
0374 
0375         t.canvasController()->resize(QSize(483,483));
0376         QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
0377         QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0378         QVERIFY(verifyOffset(t, QPoint(79,-21)));
0379 
0380 
0381         /**
0382          * FIXME: here is a small flaw in KoCanvasControllerWidget
0383          * We cannot set the center point explicitly, because it'll be rounded
0384          * up by recenterPreferred function, so real center point will be
0385          * different. Make the preferredCenter() return real center of the
0386          * image instead of the set value
0387          */
0388         QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320.5,220));
0389     }
0390 
0391     if (rotate) {
0392         t.canvasController()->rotateCanvas(90);
0393         QVERIFY(verifyOffset(t, QPoint(-21,79)));
0394         QVERIFY(compareWithRounding(QPointF(220,320), t.canvasController()->preferredCenter(), 2));
0395         QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
0396     }
0397 
0398     if (mirror) {
0399         t.canvasController()->mirrorCanvas(true);
0400         QVERIFY(verifyOffset(t, QPoint(78, -21)));
0401         QVERIFY(compareWithRounding(QPointF(320,220), t.canvasController()->preferredCenter(), 2));
0402         QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
0403     }
0404 }
0405 
0406 void KisZoomAndPanTest::testSequentialActionZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
0407 {
0408     ZoomAndPanTester t;
0409     initializeViewport(t, fullscreenMode, rotate, mirror);
0410 
0411     QVERIFY(checkZoomWithAction(t, 0.5));
0412     QVERIFY(checkPan(t, QPoint(100,100)));
0413 
0414     QVERIFY(checkZoomWithAction(t, 0.25));
0415     QVERIFY(checkPan(t, QPoint(-100,-100)));
0416 
0417     QVERIFY(checkZoomWithAction(t, 0.35));
0418     QVERIFY(checkPan(t, QPoint(100,100)));
0419 
0420     QVERIFY(checkZoomWithAction(t, 0.45));
0421     QVERIFY(checkPan(t, QPoint(100,100)));
0422 
0423     QVERIFY(checkZoomWithAction(t, 0.85));
0424     QVERIFY(checkPan(t, QPoint(-100,-100)));
0425 
0426     QVERIFY(checkZoomWithAction(t, 2.35));
0427     QVERIFY(checkPan(t, QPoint(100,100)));
0428 }
0429 
0430 void KisZoomAndPanTest::testSequentialWheelZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
0431 {
0432     ZoomAndPanTester t;
0433     initializeViewport(t, fullscreenMode, rotate, mirror);
0434 
0435     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
0436     QVERIFY(checkPan(t, QPoint(100,100)));
0437 
0438     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
0439     QVERIFY(checkPan(t, QPoint(-100,-100)));
0440 
0441     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.25));
0442     QVERIFY(checkPan(t, QPoint(100,100)));
0443 
0444     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.5));
0445     QVERIFY(checkPan(t, QPoint(100,100)));
0446 
0447     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.5));
0448     QVERIFY(checkPan(t, QPoint(-100,-100)));
0449 
0450     // check one point which is outside the widget
0451     QVERIFY(checkZoomWithWheel(t, QPoint(-100,100), 2.5));
0452     QVERIFY(checkPan(t, QPoint(-100,-100)));
0453 
0454     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
0455     QVERIFY(checkPan(t, QPoint(-100,-100)));
0456 }
0457 
0458 void KisZoomAndPanTest::testSequentialActionZoomAndPan()
0459 {
0460     testSequentialActionZoomAndPan(false, false, false);
0461 }
0462 
0463 void KisZoomAndPanTest::testSequentialActionZoomAndPanFullscreen()
0464 {
0465     testSequentialActionZoomAndPan(true, false, false);
0466 }
0467 
0468 void KisZoomAndPanTest::testSequentialActionZoomAndPanRotate()
0469 {
0470     testSequentialActionZoomAndPan(false, true, false);
0471 }
0472 
0473 void KisZoomAndPanTest::testSequentialActionZoomAndPanRotateFullscreen()
0474 {
0475     testSequentialActionZoomAndPan(true, true, false);
0476 }
0477 
0478 void KisZoomAndPanTest::testSequentialActionZoomAndPanMirror()
0479 {
0480     testSequentialActionZoomAndPan(false, false, true);
0481 }
0482 
0483 void KisZoomAndPanTest::testSequentialWheelZoomAndPan()
0484 {
0485     testSequentialWheelZoomAndPan(false, false, false);
0486 }
0487 
0488 void KisZoomAndPanTest::testSequentialWheelZoomAndPanFullscreen()
0489 {
0490     testSequentialWheelZoomAndPan(true, false, false);
0491 }
0492 
0493 void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotate()
0494 {
0495     testSequentialWheelZoomAndPan(false, true, false);
0496 }
0497 
0498 void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotateFullscreen()
0499 {
0500     testSequentialWheelZoomAndPan(true, true, false);
0501 }
0502 
0503 void KisZoomAndPanTest::testSequentialWheelZoomAndPanMirror()
0504 {
0505     testSequentialWheelZoomAndPan(false, false, true);
0506 }
0507 
0508 void KisZoomAndPanTest::testZoomOnBorderZoomLevels()
0509 {
0510     ZoomAndPanTester t;
0511     initializeViewport(t, false, false, false);
0512 
0513 //    QPoint widgetPoint(100,100);
0514 
0515     warnKrita << "WARNING: testZoomOnBorderZoomLevels() is disabled due to some changes in KoZoomMode::minimum/maximumZoom()";
0516     return;
0517 
0518     // test min zoom level
0519     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom());
0520     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true));
0521     QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true));
0522 
0523     // test max zoom level
0524     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom());
0525     QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true));
0526     QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true));
0527 }
0528 
0529 inline QTransform correctionMatrix(qreal angle)
0530 {
0531     return QTransform(0,0,0,sin(M_PI * angle / 180),0,0,0,0,1);
0532 }
0533 
0534 bool KisZoomAndPanTest::checkRotation(ZoomAndPanTester &t, qreal angle)
0535 {
0536     // save old values
0537     QPoint oldOffset = t.coordinatesConverter()->documentOffset();
0538     QPointF oldCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
0539     QPointF oldPreferredCenter = t.canvasController()->preferredCenter();
0540     QPointF oldRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
0541     QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
0542 
0543     qreal baseAngle = t.coordinatesConverter()->rotationAngle();
0544     t.canvasController()->rotateCanvas(angle);
0545 
0546     // save result values
0547     QPoint newOffset = t.coordinatesConverter()->documentOffset();
0548     QPointF newCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
0549     QPointF newPreferredCenter = t.canvasController()->preferredCenter();
0550     QPointF newRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
0551     QSize newDocumentSize = t.canvasController()->documentSize().toSize();
0552 
0553 
0554     // calculate theoretical preferred center
0555     QTransform rot;
0556     rot.rotate(angle);
0557 
0558     QSizeF dSize = t.coordinatesConverter()->imageSizeInFlakePixels();
0559     QPointF dPoint(dSize.width(), dSize.height());
0560 
0561     QPointF expectedPreferredCenter =
0562         (oldPreferredCenter - dPoint * correctionMatrix(baseAngle)) * rot +
0563          dPoint * correctionMatrix(baseAngle + angle);
0564 
0565     // calculate theoretical offset based on the real preferred center
0566     QPointF wPoint(t.canvasWidget()->size().width(), t.canvasWidget()->size().height());
0567     QPointF expectedOldOffset = oldPreferredCenter - 0.5 * wPoint;
0568     QPointF expectedNewOffset = newPreferredCenter - 0.5 * wPoint;
0569 
0570     bool preferredCenterAsExpected =
0571         compareWithRounding(expectedPreferredCenter, newPreferredCenter, 2);
0572     bool oldOffsetAsExpected =
0573         compareWithRounding(expectedOldOffset + oldCenteringCorrection, QPointF(oldOffset), 2);
0574     bool newOffsetAsExpected =
0575         compareWithRounding(expectedNewOffset + newCenteringCorrection, QPointF(newOffset), 3);
0576 
0577     qreal zoom = t.zoomController()->zoomAction()->effectiveZoom();
0578     bool realCenterPointAsExpected =
0579         compareWithRounding(oldRealCenterPoint, newRealCenterPoint, 2/zoom);
0580 
0581 
0582     if (!oldOffsetAsExpected ||
0583         !newOffsetAsExpected ||
0584         !preferredCenterAsExpected ||
0585         !realCenterPointAsExpected) {
0586 
0587         dbgKrita << "***** ROTATE **************";
0588 
0589         if(!oldOffsetAsExpected) {
0590             dbgKrita << " ### Old offset invariant broken";
0591         }
0592 
0593         if(!newOffsetAsExpected) {
0594             dbgKrita << " ### New offset invariant broken";
0595         }
0596 
0597         if(!preferredCenterAsExpected) {
0598             dbgKrita << " ### Preferred center invariant broken";
0599         }
0600 
0601         if(!realCenterPointAsExpected) {
0602             dbgKrita << " ### *Real* center invariant broken";
0603         }
0604 
0605         dbgKrita << ppVar(expectedOldOffset);
0606         dbgKrita << ppVar(expectedNewOffset);
0607         dbgKrita << ppVar(expectedPreferredCenter);
0608         dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
0609         dbgKrita << ppVar(oldCenteringCorrection) << ppVar(newCenteringCorrection);
0610         dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
0611         dbgKrita << ppVar(oldRealCenterPoint) << ppVar(newRealCenterPoint);
0612         dbgKrita << ppVar(oldDocumentSize) << ppVar(newDocumentSize);
0613         dbgKrita << ppVar(baseAngle) << "deg";
0614         dbgKrita << ppVar(angle) << "deg";
0615         dbgKrita << "***************************";
0616     }
0617 
0618     return preferredCenterAsExpected && oldOffsetAsExpected && newOffsetAsExpected && realCenterPointAsExpected;
0619 }
0620 
0621 void KisZoomAndPanTest::testRotation(qreal vastScrolling, qreal zoom)
0622 {
0623     KisConfig cfg(false);
0624     cfg.setVastScrolling(vastScrolling);
0625 
0626     ZoomAndPanTester t;
0627 
0628     QCOMPARE(t.image()->size(), QSize(640,441));
0629     QCOMPARE(t.image()->xRes(), 1.0);
0630     QCOMPARE(t.image()->yRes(), 1.0);
0631 
0632     QPointF preferredCenter = zoom * t.image()->bounds().center();
0633 
0634     t.canvasController()->resize(QSize(500,500));
0635     t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom);
0636     t.canvasController()->setPreferredCenter(preferredCenter.toPoint());
0637 
0638     QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
0639     QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
0640 
0641     QPointF realCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
0642     QPointF expectedCenterPoint = QPointF(t.image()->bounds().center());
0643 
0644     if(!compareWithRounding(realCenterPoint, expectedCenterPoint, 2/zoom)) {
0645         dbgKrita << "Failed to set initial center point";
0646         dbgKrita << ppVar(expectedCenterPoint) << ppVar(realCenterPoint);
0647         QFAIL("FAIL: Failed to set initial center point");
0648     }
0649 
0650     QVERIFY(checkRotation(t, 30));
0651     QVERIFY(checkRotation(t, 20));
0652     QVERIFY(checkRotation(t, 10));
0653     QVERIFY(checkRotation(t, 5));
0654     QVERIFY(checkRotation(t, 5));
0655     QVERIFY(checkRotation(t, 5));
0656 
0657     if(vastScrolling < 0.5 && zoom < 1) {
0658         warnKrita << "Disabling a few tests for vast scrolling ="
0659                    << vastScrolling << ". See comment for more";
0660         /**
0661          * We have to disable a couple of tests here for the case when
0662          * vastScrolling value is 0.2. The problem is that the centering
0663          * correction applied  to the offset in
0664          * KisCanvasController::rotateCanvas pollutes the preferredCenter
0665          * value, because KoCanvasControllerWidget has no access to this
0666          * correction and cannot calculate the real value of the center of
0667          * the image. To fix this bug the calculation of correction
0668          * (aka "origin") should be moved to the KoCanvasControllerWidget
0669          * itself which would cause quite huge changes (including the change
0670          * of the external interface of it). Namely, we would have to
0671          * *calculate* offset from the value of the scroll bars, but not
0672          * use their values directly:
0673          *
0674          * offset = scrollBarValue - origin
0675          *
0676          * So now we just disable these unittests and allow a couple
0677          * of "jumping" bugs appear in vastScrolling < 0.5 modes, which
0678          * is, actually, not the default case.
0679          */
0680 
0681     } else {
0682         QVERIFY(checkRotation(t, 5));
0683         QVERIFY(checkRotation(t, 5));
0684         QVERIFY(checkRotation(t, 5));
0685     }
0686 }
0687 
0688 void KisZoomAndPanTest::testRotation_VastScrolling_1_0()
0689 {
0690     testRotation(0.9, 1.0);
0691 }
0692 
0693 void KisZoomAndPanTest::testRotation_VastScrolling_0_5()
0694 {
0695     testRotation(0.9, 0.5);
0696 }
0697 
0698 void KisZoomAndPanTest::testRotation_NoVastScrolling_1_0()
0699 {
0700     testRotation(0.2, 1.0);
0701 }
0702 
0703 void KisZoomAndPanTest::testRotation_NoVastScrolling_0_5()
0704 {
0705     testRotation(0.2, 0.5);
0706 }
0707 
0708 void KisZoomAndPanTest::testImageRescaled_0_5()
0709 {
0710     ZoomAndPanTester t;
0711     QApplication::processEvents();
0712     initializeViewport(t, false, false, false);
0713     QApplication::processEvents();
0714     QVERIFY(checkPan(t, QPoint(200,200)));
0715     QApplication::processEvents();
0716 
0717     QPointF oldStillPoint =
0718         t.coordinatesConverter()->imageRectInWidgetPixels().center();
0719 
0720     KisFilterStrategy *strategy = new KisBilinearFilterStrategy();
0721     t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy);
0722     t.image()->waitForDone();
0723     QApplication::processEvents();
0724     delete strategy;
0725 
0726     QPointF newStillPoint =
0727         t.coordinatesConverter()->imageRectInWidgetPixels().center();
0728 
0729     QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
0730 }
0731 
0732 void KisZoomAndPanTest::testImageCropped()
0733 {
0734     ZoomAndPanTester t;
0735     QApplication::processEvents();
0736     initializeViewport(t, false, false, false);
0737     QApplication::processEvents();
0738     QVERIFY(checkPan(t, QPoint(-150,-150)));
0739     QApplication::processEvents();
0740 
0741     QPointF oldStillPoint =
0742         t.coordinatesConverter()->imageToWidget(QPointF(150,150));
0743 
0744     t.image()->cropImage(QRect(100,100,100,100));
0745     t.image()->waitForDone();
0746     QApplication::processEvents();
0747 
0748     QPointF newStillPoint =
0749         t.coordinatesConverter()->imageToWidget(QPointF(50,50));
0750 
0751     QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
0752 }
0753 
0754 KISTEST_MAIN(KisZoomAndPanTest)