Qt 在鼠標位置縮放圖像

問題描述

使用Qt的QGraphicsView顯示圖像時,鼠標滾輪的動作由其自帶的wheelEvent事件控制。滾動滾輪可以控制圖像上下移動。

現希望通過滾動滾輪實現圖像縮放,且縮放的中心爲鼠標所在位置。

解決方案

1. 自定義GraphicsView

HighGraphicsView類繼承自QGraphicsView類,重寫了wheelEventmouseMoveEvent兩個事件。其中,對mouseMoveEvent,在原有的基礎上加入了觸發鼠標位置變化的事件,這個鼠標位置是相對於View(x,y)座標。對wheelEvent,放棄原有事件,記錄滾動間隔,發送信號。

HighGraphicsView.h

#pragma once
#include <qgraphicsview.h>
#include <QMouseEvent>
#include <QWheelEvent>

#include <QScrollBar>

class HighGraphicsView :
	public QGraphicsView
{
	Q_OBJECT

public:
	HighGraphicsView(QWidget *parent = nullptr);
	~HighGraphicsView();

signals:
	void mousePositionChanged(int x, int y);
	void wheelScrollChanged(int step);

protected:
	void wheelEvent(QWheelEvent *event);
	void mouseMoveEvent(QMouseEvent *event);

private:
	// Mouse Position
	int x, y;
};

HighGraphicsView.cpp

#include "HighGraphicsView.h"


HighGraphicsView::HighGraphicsView(QWidget *parent)
	: QGraphicsView(parent)
{
}


HighGraphicsView::~HighGraphicsView()
{
}

void HighGraphicsView::wheelEvent(QWheelEvent *event)
{
	QPoint numPixels, numDegrees;
	numPixels = event->pixelDelta();
	numDegrees = event->angleDelta() / 8;

	int step = 0;
	if (!numPixels.isNull())
	{
		step = numPixels.y();
	}
	else if (!numDegrees.isNull())
	{
		QPoint numSteps = numDegrees / 15;
		step = numSteps.y();
	}

	// Enlarge: +; Shrink: -
	emit wheelScrollChanged(step);
}

void HighGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
	QGraphicsView::mouseMoveEvent(event);

	x = event->x();
	y = event->y();

	emit mousePositionChanged(x, y);
}

2. 記錄鼠標位置

鼠標的位置包括三類:

  1. 圖像上的位置;

(x0,y0) (x_0,y_0)

  1. View中的位置;

(x,y) (x,y)

  1. Scene中的位置;

(X,Y) (X,Y)

其對應的轉換關係如下所示:

{x0=XscaleXy0=YscaleY{X=x+horizontal=x+hY=y+vertical=y+v \begin{cases} x_0 = X * scaleX \\ y_0 = Y * scaleY \end{cases}\\ \begin{cases} X = x + horizontal = x + h\\ Y = y + vertical = y + v \end{cases}

其中,horizontalhorizontalView->horizontalScrollBar()->value()的值,verticalverticalView->verticalScrollBar()->value()的值,scaleX,scaleYscaleX, scaleY是當前圖像x,yx,y方向上的縮放比例,其表達式爲:

{scaleX=WidthcurrentWidthscaleY=HeightcurrentHeight \begin{cases} scaleX = \frac{Width}{currentWidth} \\ scaleY = \frac{Height}{currentHeight} \\ \end{cases}

在引用HighGraphicsView的類中,存儲兩類鼠標位置(x0,y0),(x,y)(x_0,y_0), (x,y)

// Mouse Position
int x, y;         // x0,y0
int viewX, viewY; // x,y

創建槽onMousePositionChanged,用來接收View發出的mousePositionChanged信號

void onMousePositionChanged(int x, int y)
{
	viewX = x;
	viewY = y;

	QRectF rect = View->mapToScene(View->viewport()->geometry()).boundingRect();
	x += int(rect.x() - 1); // Rectangle begin with (1,1)
	y += int(rect.y() - 1);

	x = int(double(x) * (double(Width) / double(currentWidth)));
	y = int(double(y) * (double(Height) / double(currentHeight)));

	this->x = x;
	this->y = y;

	if (x >= Width || x < 0 || y >= Height || y < 0)
	{
		ui.statusBar->showMessage(u8"Outside");
	}
	else
	{
		ui.statusBar->showMessage(QString().sprintf(u8"(x=%d, y=%d)", x, y));
	}
}

綁定信號與槽

// Receive (x,y) coordinate
connect(View, SIGNAL(mousePositionChanged(int, int)), this, SLOT(onMousePositionChanged(int, int)));

3. 圖像縮放

要以鼠標爲中心進行圖像縮放,可以分爲兩個步驟進行:1. 縮放;2. 平移。縮放過程較爲簡單,只需要根據比例對原圖進行縮放即可。平移的目的是爲了使圖像在縮放前後鼠標所在的位置相對於圖像不變、相對於視窗(View)不變,不變量爲(x,y)(x,y)、$ (x_0, y_0)$。

根據(x,y),(x0,y0),(X,Y)(x,y), (x_0,y_0), (X,Y)之間的關係(見第1小節),可以得到

x0=XscaleX=(x+h)WidthcurrentWidthy0=YscaleY=(y+v)HeightcurrentHeight x_0 = X * scaleX = \frac{(x + h) * Width}{currentWidth} \\ y_0 = Y * scaleY = \frac{(y + v) * Height}{currentHeight}

由於(x,y)(x,y)、$ (x_0, y_0)是不變量,因此可以得到變化量h,v$的值,即View相對於Scene的偏移量:

h=x0currentWidthWidthxv=y0currentHeightHeighty h = \frac{x_0 * currentWidth}{Width} - x \\ v = \frac{y_0 * currentHeight}{Height} - y

通過View->horizontalScrollBar()->setValue()View->verticalScrollBar()->setValue()設置偏移量,完成平移。

創建響應滾輪變化信號的槽:

void onWheelScrollChanged(int step)
{
	currentHeight += currentHeight / 20 * step;
	currentWidth += currentWidth / 20 * step;
	ImageItem->setPixmap(QPixmap::fromImage(Image.scaled(currentWidth, currentHeight)));

	Scene = new QGraphicsScene(this);
	Scene->addItem(ImageItem);
	View->setScene(Scene);

	int horizontal, vertical;
	horizontal = int(double(x * currentWidth) / double(Width) - viewX);
	vertical = int(double(y * currentHeight) / double(Height) - viewY);

	View->horizontalScrollBar()->setValue(horizontal);
	View->verticalScrollBar()->setValue(vertical);
}

綁定信號與槽

// Receive wheel scroll signal
connect(View, SIGNAL(wheelScrollChanged(int)), this, SLOT(onWheelScrollChanged(int)));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章