Commit 25572c41 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fixed a recentering after crop/resize bug

This introduces quite a non-trivial still-point concept... please, read
about it in the docs for KisImage::sigImageSizeChanged.

The general idea of user iteractions:
1) When rescaling/rotating the whole image (the actual content
   doesn't change):

   -> the scaling/rotating of the image happens around its center
      (not around the center of the screen!)

2) When cropping/changing the size of the canvas (the amount of content
   changes):

   -> the content of the image that is left after cropping does not
      change its position on screen

3) When working in FIT_WIDTH/FIT_PAGE modes:

   -> changing the size of the image switches zoom mode to CONSTANT

BUG:313489
parent 3ccf7d9f
......@@ -96,7 +96,6 @@ class KisImage::KisImagePrivate
public:
KisBackgroundSP backgroundPattern;
quint32 lockCount;
bool sizeChangedWhileLocked;
KisPerspectiveGrid* perspectiveGrid;
qint32 width;
......@@ -291,7 +290,6 @@ void KisImage::init(KisUndoStore *undoStore, qint32 width, qint32 height, const
}
m_d->lockCount = 0;
m_d->sizeChangedWhileLocked = false;
m_d->perspectiveGrid = 0;
m_d->signalRouter = new KisImageSignalRouter(this);
......@@ -345,7 +343,6 @@ void KisImage::barrierLock()
if (m_d->scheduler) {
m_d->scheduler->barrierLock();
}
m_d->sizeChangedWhileLocked = false;
}
m_d->lockCount++;
}
......@@ -358,10 +355,6 @@ bool KisImage::tryBarrierLock()
if (m_d->scheduler) {
result = m_d->scheduler->tryBarrierLock();
}
if (result) {
m_d->sizeChangedWhileLocked = false;
}
}
if (result) {
......@@ -379,7 +372,6 @@ void KisImage::lock()
if (m_d->scheduler) {
m_d->scheduler->lock();
}
m_d->sizeChangedWhileLocked = false;
}
m_d->lockCount++;
}
......@@ -392,10 +384,6 @@ void KisImage::unlock()
m_d->lockCount--;
if (m_d->lockCount == 0) {
if (m_d->sizeChangedWhileLocked) {
m_d->signalRouter->emitNotification(SizeChangedSignal);
}
if (m_d->scheduler) {
m_d->scheduler->unlock();
}
......@@ -429,7 +417,6 @@ void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
emitSizeChanged();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
......@@ -439,7 +426,8 @@ void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
QString actionName = cropLayers ? i18n("Crop Image") : i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << SizeChangedSignal << ModifiedSignal;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
......@@ -483,15 +471,6 @@ void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
applicator.end();
}
void KisImage::emitSizeChanged()
{
if (!locked()) {
m_d->signalRouter->emitNotification(SizeChangedSignal);
} else {
m_d->sizeChangedWhileLocked = true;
}
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
......@@ -501,11 +480,10 @@ void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterSt
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << SizeChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
// XXX: Translate after 2.4 is released
QString actionName = sizeChanged ? "Scale Image" : "Change Image Resolution";
QString actionName = sizeChanged ? i18n("Scale Image") : i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
......@@ -584,7 +562,7 @@ void KisImage::rotateImpl(const QString &actionName,
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << SizeChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
......@@ -658,7 +636,7 @@ void KisImage::shearImpl(const QString &actionName,
if (newSize == size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << SizeChangedSignal;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
......
......@@ -529,7 +529,24 @@ signals:
*/
void sigImageModified();
void sigSizeChanged(qint32 w, qint32 h);
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the reciever the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
......
......@@ -41,7 +41,7 @@ KisImageSignalRouter::KisImageSignalRouter(KisImageWSP image)
SLOT(slotNotification(KisImageSignalType)));
CONNECT_TO_IMAGE(sigImageModified());
CONNECT_TO_IMAGE(sigSizeChanged(qint32, qint32));
CONNECT_TO_IMAGE(sigSizeChanged(const QPointF&, const QPointF&));
CONNECT_TO_IMAGE(sigProfileChanged(const KoColorProfile*));
CONNECT_TO_IMAGE(sigColorSpaceChanged(const KoColorSpace*));
CONNECT_TO_IMAGE(sigResolutionChanged(double, double));
......@@ -86,7 +86,7 @@ void KisImageSignalRouter::emitAboutToRemoveANode(KisNode *parent, int index)
void KisImageSignalRouter::slotNotification(KisImageSignalType type)
{
switch(type) {
switch(type.id) {
case LayersChangedSignal:
emit sigLayersChangedAsync();
break;
......@@ -94,7 +94,8 @@ void KisImageSignalRouter::slotNotification(KisImageSignalType type)
emit sigImageModified();
break;
case SizeChangedSignal:
emit sigSizeChanged(m_image->width(), m_image->height());
emit sigSizeChanged(type.sizeChangedSignal.oldStillPoint,
type.sizeChangedSignal.newStillPoint);
break;
case ProfileChangedSignal:
emit sigProfileChanged(m_image->profile());
......
......@@ -29,7 +29,7 @@
class KoColorSpace;
class KoColorProfile;
enum KisImageSignalType {
enum KisImageSignalTypeEnum {
LayersChangedSignal,
ModifiedSignal,
SizeChangedSignal,
......@@ -38,6 +38,65 @@ enum KisImageSignalType {
ResolutionChangedSignal
};
/**
* A special signal which handles stillPoint capabilities of the image
*
* \see KisImage::sigSizeChanged()
*/
struct ComplexSizeChangedSignal {
ComplexSizeChangedSignal() {}
ComplexSizeChangedSignal(QPointF _oldStillPoint, QPointF _newStillPoint)
: oldStillPoint(_oldStillPoint),
newStillPoint(_newStillPoint)
{
}
/**
* A helper method calculating the still points from image areas
* we process. It works as if the source image was "cropped" by \p
* portionOfOldImage, and this portion formed the new image of size
* \p transformedIntoImageOfSize.
*
* Note, that \p portionOfTheImage may be equal to the image bounds().
*/
ComplexSizeChangedSignal(const QRect &portionOfOldImage, const QSize &transformedIntoImageOfSize)
{
oldStillPoint = QRectF(portionOfOldImage).center();
newStillPoint = QRectF(QPointF(), QSizeF(transformedIntoImageOfSize)).center();
}
ComplexSizeChangedSignal inverted() const {
return ComplexSizeChangedSignal(newStillPoint, oldStillPoint);
}
QPointF oldStillPoint;
QPointF newStillPoint;
};
struct KisImageSignalType {
KisImageSignalType() {}
KisImageSignalType(KisImageSignalTypeEnum _id)
: id(_id)
{
}
KisImageSignalType(ComplexSizeChangedSignal signal)
: id(SizeChangedSignal),
sizeChangedSignal(signal)
{
}
KisImageSignalType inverted() const {
KisImageSignalType t;
t.id = id;
t.sizeChangedSignal = sizeChangedSignal.inverted();
return t;
}
KisImageSignalTypeEnum id;
ComplexSizeChangedSignal sizeChangedSignal;
};
typedef QVector<KisImageSignalType> KisImageSignalVector;
class KRITAIMAGE_EXPORT KisImageSignalRouter : public QObject
......@@ -65,7 +124,7 @@ signals:
// Notifications
void sigImageModified();
void sigSizeChanged(qint32 w, qint32 h);
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
......
......@@ -155,7 +155,7 @@ public:
KisImageSignalVector::iterator i = m_emitSignals.end();
while (i != m_emitSignals.begin()) {
--i;
reverseSignals.append(*i);
reverseSignals.append(i->inverted());
}
doUpdate(reverseSignals);
......
......@@ -55,7 +55,8 @@ void KisImageSignalRouterTest::testSignalForwarding()
{
checkNotification(LayersChangedSignal, SIGNAL(sigLayersChangedAsync()));
checkNotification(ModifiedSignal, SIGNAL(sigImageModified()));
checkNotification(SizeChangedSignal, SIGNAL(sigSizeChanged(qint32, qint32)));
checkNotification(SizeChangedSignal, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)));
checkNotification(ComplexSizeChangedSignal(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)));
checkNotification(ProfileChangedSignal, SIGNAL(sigProfileChanged(const KoColorProfile*)));
checkNotification(ColorSpaceChangedSignal, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
checkNotification(ResolutionChangedSignal, SIGNAL(sigResolutionChanged(double, double)));
......
......@@ -464,7 +464,6 @@ void KisToolCrop::crop()
m_rectCrop = QRect(0, 0, 0, 0);
updateWidgetValues();
dynamic_cast<KisCanvas2*>(canvas())->view()->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 0);
}
void KisToolCrop::setCropX(int x)
......
......@@ -372,13 +372,13 @@ void KisCanvas2::connectCurrentImage()
connect(this, SIGNAL(sigCanvasCacheUpdated(KisUpdateInfoSP)),
this, SLOT(updateCanvasProjection(KisUpdateInfoSP)));
connect(image, SIGNAL(sigSizeChanged(qint32,qint32)),
SLOT(startResizingImage(qint32,qint32)),
connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)),
SLOT(startResizingImage()),
Qt::DirectConnection);
connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)),
this, SLOT(finishResizingImage(qint32,qint32)));
startResizingImage(image->width(), image->height());
startResizingImage();
emit imageChanged(image);
}
......@@ -500,8 +500,12 @@ void KisCanvas2::setDisplayFilter(KisDisplayFilter *displayFilter)
}
void KisCanvas2::startResizingImage(qint32 w, qint32 h)
void KisCanvas2::startResizingImage()
{
KisImageWSP image = this->image();
qint32 w = image->width();
qint32 h = image->height();
emit sigContinueResizeImage(w, h);
QRect imageBounds(0, 0, w, h);
......
......@@ -164,7 +164,7 @@ public slots:
void setDisplayFilter(KisDisplayFilter *displayFilter);
void startResizingImage(qint32 w, qint32 h);
void startResizingImage();
void finishResizingImage(qint32 w, qint32 h);
/// canvas rotation in degrees
......
......@@ -37,6 +37,8 @@ public:
virtual bool eventFilter(QObject *watched, QEvent *event);
virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter);
public:
using KoCanvasController::documentSize;
public slots:
void mirrorCanvas(bool enable);
......
......@@ -116,8 +116,12 @@ void KisStatusBar::documentMousePositionChanged(const QPointF &pos)
m_pointerPositionLabel->setText(QString("%1, %2").arg(pixelPos.x()).arg(pixelPos.y()));
}
void KisStatusBar::imageSizeChanged(qint32 w, qint32 h)
void KisStatusBar::imageSizeChanged()
{
KisImageWSP image = m_view->image();
qint32 w = image->width();
qint32 h = image->height();
m_imageSizeLabel->setText(QString("%1 x %2").arg(w).arg(h));
}
......
......@@ -45,7 +45,7 @@ public slots:
void setZoom(int percentage);
void documentMousePositionChanged(const QPointF &p);
void imageSizeChanged(qint32 w, qint32 h);
void imageSizeChanged();
void setSelection(KisImageWSP image);
void setProfile(KisImageWSP image);
void setHelp(const QString &t);
......
......@@ -716,11 +716,11 @@ KisUndoAdapter * KisView2::undoAdapter()
void KisView2::slotLoadingFinished()
{
/**
* Cold-start of image-size signals
* Cold-start of image size/resolution signals
*/
slotImageSizeChanged();
slotImageResolutionChanged();
if (m_d->statusBar) {
m_d->statusBar->imageSizeChanged(image()->width(), image()->height());
m_d->statusBar->imageSizeChanged();
}
if (m_d->resourceProvider) {
m_d->resourceProvider->slotImageSizeChanged();
......@@ -846,18 +846,18 @@ void KisView2::connectCurrentImage()
if (m_d->statusBar) {
connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), m_d->statusBar, SLOT(updateStatusBarProfileLabel()));
connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), m_d->statusBar, SLOT(updateStatusBarProfileLabel()));
connect(image(), SIGNAL(sigSizeChanged(qint32,qint32)), m_d->statusBar, SLOT(imageSizeChanged(qint32,qint32)));
connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), m_d->statusBar, SLOT(imageSizeChanged()));
}
connect(image(), SIGNAL(sigSizeChanged(qint32,qint32)), m_d->resourceProvider, SLOT(slotImageSizeChanged()));
connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), m_d->resourceProvider, SLOT(slotImageSizeChanged()));
connect(image(), SIGNAL(sigResolutionChanged(double,double)),
m_d->resourceProvider, SLOT(slotOnScreenResolutionChanged()));
connect(zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
m_d->resourceProvider, SLOT(slotOnScreenResolutionChanged()));
connect(image(), SIGNAL(sigSizeChanged(qint32,qint32)), this, SLOT(slotImageSizeChanged()));
connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageSizeChanged()));
connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&)));
connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
connect(image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotNodeChanged()));
connect(image()->undoAdapter(), SIGNAL(selectionChanged()), selectionManager(), SLOT(selectionChanged()));
......@@ -916,21 +916,71 @@ void KisView2::slotBlacklistCleanup()
dialog.exec();
}
void KisView2::resetImageSizeAndScroll(bool changeCentering,
const QPointF oldImageStillPoint,
const QPointF newImageStillPoint) {
const KisCoordinatesConverter *converter =
canvasBase()->coordinatesConverter();
QPointF oldPreferredCenter =
m_d->canvasController->preferredCenter();
/**
* Calculating the still point in old coordinates depending on the
* parameters given
*/
QPointF oldStillPoint;
if (changeCentering) {
oldStillPoint =
converter->imageToWidget(oldImageStillPoint) +
converter->documentOffset();
} else {
QSize oldDocumentSize = m_d->canvasController->documentSize();
oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
}
/**
* Updating the document size
*/
void KisView2::slotImageSizeChanged()
{
QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
m_d->zoomManager->zoomController()->setPageSize(size);
m_d->zoomManager->zoomController()->setDocumentSize(size);
KoZoomController *zc = m_d->zoomManager->zoomController();
zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom());
zc->setPageSize(size);
zc->setDocumentSize(size, true);
canvasBase()->notifyZoomChanged();
QSize widgetSize = canvasBase()->coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect().size();
m_d->canvasController->updateDocumentSize(widgetSize, true);
/**
* Calculating the still point in new coordinates depending on the
* parameters given
*/
QPointF newStillPoint;
if (changeCentering) {
newStillPoint =
converter->imageToWidget(newImageStillPoint) +
converter->documentOffset();
} else {
QSize newDocumentSize = m_d->canvasController->documentSize();
newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
}
m_d->canvasController->setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
}
void KisView2::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
{
resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
m_d->zoomManager->updateGUI();
}
//update view
canvas()->update();
void KisView2::slotImageResolutionChanged()
{
resetImageSizeAndScroll(false);
m_d->zoomManager->updateGUI();
}
void KisView2::slotNodeChanged()
......
......@@ -199,7 +199,8 @@ private slots:
void slotPreferences();
void slotBlacklistCleanup();
void slotImageSizeChanged();
void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void slotImageResolutionChanged();
void slotNodeChanged();
void slotTotalRefresh();
void slotCreateTemplate();
......@@ -210,14 +211,14 @@ private slots:
void showJustTheCanvas(bool toggled);
private:
void createGUI();
void createActions();
void createManagers();
void loadPlugins();
void resetImageSizeAndScroll(bool changeCentering,
const QPointF oldImageStillPoint = QPointF(),
const QPointF newImageStillPoint = QPointF());
private:
class KisView2Private;
KisView2Private * const m_d;
......
......@@ -36,6 +36,7 @@
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_coordinates_converter.h"
#include "kis_filter_strategy.h"
class ZoomAndPanTester : public TestUtil::QImageBasedTest
......@@ -715,4 +716,50 @@ void KisZoomAndPanTest::testRotation_NoVastScrolling_0_5()
testRotation(0.2, 0.5);
}
void KisZoomAndPanTest::testImageRescaled_0_5()
{
ZoomAndPanTester t;
QApplication::processEvents();
initializeViewport(t, false, false, false);
QApplication::processEvents();
QVERIFY(checkPan(t, QPoint(200,200)));
QApplication::processEvents();
QPointF oldStillPoint =
t.coordinatesConverter()->imageRectInWidgetPixels().center();
KisFilterStrategy *strategy = new KisBilinearFilterStrategy();
t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy);
t.image()->waitForDone();
QApplication::processEvents();
delete strategy;
QPointF newStillPoint =
t.coordinatesConverter()->imageRectInWidgetPixels().center();
QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
void KisZoomAndPanTest::testImageCropped()
{
ZoomAndPanTester t;
QApplication::processEvents();
initializeViewport(t, false, false, false);
QApplication::processEvents();
QVERIFY(checkPan(t, QPoint(-150,-150)));
QApplication::processEvents();
QPointF oldStillPoint =
t.coordinatesConverter()->imageToWidget(QPointF(150,150));
t.image()->cropImage(QRect(100,100,100,100));
t.image()->waitForDone();
QApplication::processEvents();
QPointF newStillPoint =
t.coordinatesConverter()->imageToWidget(QPointF(50,50));
QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
QTEST_KDEMAIN(KisZoomAndPanTest, GUI)
......@@ -43,6 +43,9 @@ private slots:
void testSequentialWheelZoomAndPanRotateFullscreen();
void testSequentialWheelZoomAndPanMirror();
void testImageRescaled_0_5();
void testImageCropped();
void testRotation_VastScrolling_1_0();
void testRotation_VastScrolling_0_5();
......
......@@ -103,7 +103,7 @@ void KoZoomController::setTextMinMax(qreal min, qreal max)
void KoZoomController::setDocumentSize(const QSizeF &documentSize, bool recalculateCenter)
{
d->documentSize = documentSize;
d->canvasController->updateDocumentSize( d->zoomHandler->documentToView(d->documentSize).toSize(), recalculateCenter);
d->canvasController->updateDocumentSize(documentToViewport(d->documentSize), recalculateCenter);
// Finally ask the canvasController to recenter
d->canvasController->recenterPreferred();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment