pyqt中以鼠标所在位置为锚点缩放图片

在编写涉及到图片缩放的pyqt程序时,如果以鼠标为锚点缩放图片,图片上处于鼠标所在位置的点(通常也是用户关注的图片上的点)不会移动,更不会消失在图片显示区域之外,可以提高用户体验,是一个值得实现的效果。

实现以鼠标所在位置为锚点进行图片缩放的效果的最简单的方法是以QGraphicsView作为图片的容器,只需要在初始化时设置:

self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)

即可实现以鼠标为锚点缩放图片的效果。下面是一个简单示例:

python 复制代码
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QPushButton, QVBoxLayout, QFileDialog, QWidget
)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import sys


class ImageViewer(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.scene = QGraphicsScene(self)
        self.setScene(self.scene)

        self.scale_factor = 1.0  # 缩放因子
        self.setDragMode(QGraphicsView.ScrollHandDrag)  # 设置拖动模式
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)  # 设置缩放锚点为鼠标位置

    def add_image(self, image_path):
        """向 QGraphicsView 中添加一张图片"""
        pixmap = QPixmap(image_path)
        if pixmap.isNull():
            return

        # 创建 QGraphicsPixmapItem 并添加到场景中
        pixmap_item = self.scene.addPixmap(pixmap)
        pixmap_item.setTransformationMode(Qt.SmoothTransformation)

    
        # 其他图片:可移动、可缩放
        pixmap_item.setFlags(pixmap_item.flags() | pixmap_item.ItemIsMovable)  # 添加可移动标志


    def wheelEvent(self, event):
        """实现以鼠标位置为锚点的缩放"""

        # 获取鼠标在视图中的位置
        # mouse_pos = event.pos()
        # 将鼠标位置转换为场景坐标
        # scene_pos = self.mapToScene(mouse_pos)

        # 根据滚轮方向调整缩放因子
        if event.angleDelta().y() > 0:
            scale_change = 1.15  # 放大
        else:
            scale_change = 1 / 1.15  # 缩小

        # 执行缩放,水平方向与垂直方向等比例缩放
        self.scale(scale_change, scale_change)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Viewer with Multiple Images")
        self.setGeometry(100, 100, 800, 600)

        # 创建主窗口布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # 创建 QGraphicsView
        self.graphics_view = ImageViewer(self)
        layout.addWidget(self.graphics_view)

        # 创建按钮
        self.load_button = QPushButton("加载图片")
        self.load_button.clicked.connect(self.open_file_dialog)
        layout.addWidget(self.load_button)

    def open_file_dialog(self):
        """打开文件对话框选择图片"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        if file_path:
            self.graphics_view.add_image(file_path)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    # 禁用最大化按钮
    window.setWindowFlags(window.windowFlags() & ~Qt.WindowMaximizeButtonHint)
    window.show()
    sys.exit(app.exec_())

上面的示例包含三个步骤:

1、创建了一个类ImageViewer继承QGraphicsView,在__init__方法中设置以鼠标所在位置为锚点缩放:

设置缩放锚点为鼠标位置

self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)

2、通过QGraphicsPixmapItem加载一张图片,然后将QGraphicsPixmapItem加入场景:

创建并设置场景

self.scene = QGraphicsScene(self)

self.setScene(self.scene)
pixmap = QPixmap(image_path)

if pixmap.isNull():

return

创建 QGraphicsPixmapItem 加载图片,并将QGraphicsPixmapItem添加到场景中

pixmap_item = self.scene.addPixmap(pixmap)

3、重写wheelEvent事件实现图片缩放:

def wheelEvent(self, event):

"""实现以鼠标位置为锚点的缩放"""

根据滚轮方向调整缩放因子

if event.angleDelta().y() > 0:

scale_change = 1.15 # 放大

else:

scale_change = 1 / 1.15 # 缩小

执行缩放,水平方向与垂直方向等比例缩放

self.scale(scale_change, scale_change)

可以看到,通过以上三个步骤,基本上不用添加与图片位置相关的代码,QGraphicsView就实现了以鼠标所在位置为锚点缩放图片的效果。

但是,QGraphicsView有许多自身的特性,如果在一些特殊场景中需要避免使用QGraphicsView的某些特性,或者仅仅出于自己的喜好选择不使用QGraphicsView(不过处理大型和大量图片的最佳选择还是使用QGraphicsView),如何实现以鼠标所在位置为锚点缩放图片的效果呢?

先看以下图示:

首先有一个显示图片的容器,例如QLabel,给这个容器一个固定的尺寸,它有一个以左上角为(0,0)的坐标系。

其次有一张图片,为美观起见,将这张图片(实际上是经过缩放后的图片)水平垂直居中显示在容器中,图片自身拥有一个以图片的左上角为(0,0)的像素坐标系。像素坐标系与容器坐标系的原点存在偏差(img_offset),而图片上所有像素在图片像素坐标系中的坐标与在容器坐标系中的坐标的偏差与两个坐标系原点的偏差是相等的。经过缩放后,鼠标在图片上的位置会发生偏移delta_offset,只需将img_offset调整delta_offset,那么,缩放后的鼠标位置就调整回了缩放前的位置,形成了以鼠标为锚点的缩放效果。应当注意的是,容器响应鼠标事件时,鼠标位置的值是以容器坐标系的数据表示的,而前述调整img_offset的方法是基于图片像素坐标系的,因此要将容器坐标系表示的鼠标位置转换为图片像素坐标系,方法如下(以下代码中img_offset采用了scaled_pixmap_offset的变量名):

mouse_pos_on_scaled_pixmap = event.pos() - self.scaled_pixmap_offset

也就是说,需要在wheelEvent事件中添加一些坐标变换的代码,具体如下:

python 复制代码
     def wheelEvent(self, event: QWheelEvent):
        """重写滚轮事件,实现缩放"""
        mouse_pos_on_scaled_pixmap = event.pos() - self.scaled_pixmap_offset
        # 根据滚轮方向调整缩放因子
        if event.angleDelta().y() > 0:
            zoom_factor = 1.15  # 放大
        else:
            zoom_factor = 1 / 1.15  # 缩小
        # 计算累计缩放比例
        self.scale_factor *= zoom_factor
        # 缩放图片
        self.scaled_pixmap = self.original_pixmap.scaled(
            self.size() * self.scale_factor,
            Qt.KeepAspectRatio,
            Qt.SmoothTransformation
        )
        # 计算鼠标在图片缩放前后的偏移量
        delta_offset = mouse_pos_on_scaled_pixmap * (zoom_factor - 1)
        # 调整图像显示位置对容器左上角的偏移量
        self.scaled_pixmap_offset -= delta_offset
        # 更新图像显示
        self.update()

完整示例如下:

python 复制代码
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget, QFileDialog, QPushButton
from PyQt5.QtGui import QPixmap, QImage, QWheelEvent, QPainter
from PyQt5.QtCore import Qt, QPointF

class ImageLabel(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAlignment(Qt.AlignCenter)  # 图像居中显示
        self.scale_factor = 1.0  # 缩放因子
        self.original_pixmap = None  # 原始图像
        self.scaled_pixmap = None  # 缩放后的图像
        self.setFixedSize(600, 600)
        self.scaled_pixmap_offset = QPointF(0, 0)

    def set_image(self, image_path):
        """加载图像并显示"""
        self.original_pixmap = QPixmap(image_path)
        if self.original_pixmap.isNull():
            return
        if self.scaled_pixmap is None:
            self.scaled_pixmap = self.original_pixmap.scaled(
                self.size(),
                Qt.KeepAspectRatio,
                Qt.SmoothTransformation
            )
            self.scaled_pixmap_offset_x = (self.size().width() - self.scaled_pixmap.size().width()) / 2
            self.scaled_pixmap_offset_y = (self.size().height() - self.scaled_pixmap.size().height()) / 2
            self.scaled_pixmap_offset = QPointF(self.scaled_pixmap_offset_x, self.scaled_pixmap_offset_y)
        self.update()

    

    def paintEvent(self, event):
        painter = QPainter(self)
        if self.scaled_pixmap is not None:
            painter.drawPixmap(self.scaled_pixmap_offset, self.scaled_pixmap)
    
    def wheelEvent(self, event: QWheelEvent):
        """重写滚轮事件,实现缩放"""
        mouse_pos_on_scaled_pixmap = event.pos() - self.scaled_pixmap_offset
        # 根据滚轮方向调整缩放因子
        if event.angleDelta().y() > 0:
            zoom_factor = 1.15  # 放大
        else:
            zoom_factor = 1 / 1.15  # 缩小
        # 计算累计缩放比例
        self.scale_factor *= zoom_factor
        # 缩放图片
        self.scaled_pixmap = self.original_pixmap.scaled(
            self.size() * self.scale_factor,
            Qt.KeepAspectRatio,
            Qt.SmoothTransformation
        )
        # 计算鼠标在图片缩放前后的偏移量
        delta_offset = mouse_pos_on_scaled_pixmap * (zoom_factor - 1)
        # 调整图像显示位置对容器左上角的偏移量
        self.scaled_pixmap_offset -= delta_offset
        # 更新图像显示
        self.update()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Viewer with QLabel")
        self.setGeometry(100, 100, 800, 600)

        # 创建主窗口布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # 创建 QLabel 用于显示图像
        self.image_label = ImageLabel(self)
        layout.addWidget(self.image_label)

        # 创建按钮
        self.load_button = QPushButton("加载图片")
        self.load_button.clicked.connect(self.open_file_dialog)
        layout.addWidget(self.load_button)

    def open_file_dialog(self):
        """打开文件对话框选择图片"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        if file_path:
            self.image_label.set_image(file_path)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    # 禁用最大化按钮
    window.setWindowFlags(window.windowFlags() & ~Qt.WindowMaximizeButtonHint)
    window.show()
    app.exec_()

相信经过这两个示例,特别是后一个自己编程实现的示例,可以完全掌握以鼠标为锚点缩放图片的原理,并在pyqt以外的其他领域运用自如了。

相关推荐
恸流失2 分钟前
DJango项目
后端·python·django
Julyyyyyyyyyyy1 小时前
【软件测试】web自动化:Pycharm+Selenium+Firefox(一)
python·selenium·pycharm·自动化
蓝婷儿2 小时前
6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器
开发语言·python·学习
love530love2 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
水银嘻嘻3 小时前
05 APP 自动化- Appium 单点触控& 多点触控
python·appium·自动化
狐凄3 小时前
Python实例题:Python计算二元二次方程组
开发语言·python
烛阴4 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
Mikhail_G5 小时前
Python应用函数调用(二)
大数据·运维·开发语言·python·数据分析
weixin_472339465 小时前
使用Python提取PDF元数据的完整指南
java·python·pdf
QQ676580086 小时前
基于 PyTorch 的 VGG16 深度学习人脸识别检测系统的实现+ui界面
人工智能·pytorch·python·深度学习·ui·人脸识别