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. 图像上的位置;

( x 0 , y 0 ) (x_0,y_0) (x0,y0)

  1. View中的位置;

( x , y ) (x,y) (x,y)

  1. Scene中的位置;

( X , Y ) (X,Y) (X,Y)

其对应的转换关系如下所示:

{ x 0 = X ∗ s c a l e X y 0 = Y ∗ s c a l e Y { X = x + h o r i z o n t a l = x + h Y = y + v e r t i c a l = 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} {x0=XscaleXy0=YscaleY{X=x+horizontal=x+hY=y+vertical=y+v

其中, h o r i z o n t a l horizontal horizontalView->horizontalScrollBar()->value()的值, v e r t i c a l vertical verticalView->verticalScrollBar()->value()的值, s c a l e X , s c a l e Y scaleX, scaleY scaleX,scaleY是当前图像 x , y x,y x,y方向上的缩放比例,其表达式为:

{ s c a l e X = W i d t h c u r r e n t W i d t h s c a l e Y = H e i g h t c u r r e n t H e i g h t \begin{cases} scaleX = \frac{Width}{currentWidth} \\ scaleY = \frac{Height}{currentHeight} \\ \end{cases} {scaleX=currentWidthWidthscaleY=currentHeightHeight

在引用HighGraphicsView的类中,存储两类鼠标位置 ( x 0 , y 0 ) , ( x , y ) (x_0,y_0), (x,y) (x0,y0),(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,y)、$ (x_0, y_0)$。

根据 ( x , y ) , ( x 0 , y 0 ) , ( X , Y ) (x,y), (x_0,y_0), (X,Y) (x,y),(x0,y0),(X,Y)之间的关系(见第1小节),可以得到

x 0 = X ∗ s c a l e X = ( x + h ) ∗ W i d t h c u r r e n t W i d t h y 0 = Y ∗ s c a l e Y = ( y + v ) ∗ H e i g h t c u r r e n t H e i g h t x_0 = X * scaleX = \frac{(x + h) * Width}{currentWidth} \\ y_0 = Y * scaleY = \frac{(y + v) * Height}{currentHeight} x0=XscaleX=currentWidth(x+h)Widthy0=YscaleY=currentHeight(y+v)Height

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

h = x 0 ∗ c u r r e n t W i d t h W i d t h − x v = y 0 ∗ c u r r e n t H e i g h t H e i g h t − y h = \frac{x_0 * currentWidth}{Width} - x \\ v = \frac{y_0 * currentHeight}{Height} - y h=Widthx0currentWidthxv=Heighty0currentHeighty

通过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)));
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页