PyQt5 QPixmap.scaled滚轮缩放时如何自定义缩放原点
问题根源
缩放始终锁死固定点的原因很直接:
- 你只更新了QLabel里渲染的QPixmap尺寸,从来没调整过QLabel本身的位置,QLabel默认始终贴在父窗口左上角,缩放自然看起来是以左上角为固定中心
- 每次缩放都基于上一次缩放后的图做计算,多缩几次画质会糊,也不好做坐标换算
以鼠标位置为缩放中心的逻辑没有任何特殊API要求,核心就是一条:缩放前后,鼠标尖对着的那个图像像素,必须一直待在鼠标尖的位置,按这个规则算下缩放后图像控件该挪多少位置就行。
实现要点
- 单独存一份原始没缩过的QPixmap,所有缩放都从原图生成,不要在缩过的图上反复缩放
- 滚轮滚动时先记下来鼠标在当前图像上的位置比例,缩完按这个比例把图像挪到对应位置,保证鼠标指的点不跑
- 别给自定义方法起和Qt内置方法一样的名字,你原来写的
resize和QWidget自带的重名,很容易出莫名其妙的bug
修正后可运行代码
from PyQt5.QtCore import Qt, QPoint, QRect from PyQt5.QtWidgets import (QWidget, QLabel, QApplication, QRubberBand) from PyQt5.QtGui import QPixmap import sys class App(QWidget): def __init__(self): super().__init__() self.title = 'BoundingBoxQT' self.left = 1000 self.top = 100 self.width = 748 self.height = 1402 # 保存原始图像,所有缩放都基于该原图生成 self.origin_pixmap = QPixmap('BALMRCRTLOA_trisected.png') self.scale = 1 self.scrollcount = 0 self._rubber_band = None self.band_origin = QPoint() self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) # 图像显示标签 self.label = QLabel(self) self.label.setPixmap(self.origin_pixmap) self.label.resize(self.origin_pixmap.width(), self.origin_pixmap.height()) # 初始化选框 self._rubber_band = QRubberBand(QRubberBand.Rectangle, self) self._rubber_band.show() self.show() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.band_origin = event.pos() self._rubber_band.setGeometry(QRect(self.band_origin, event.pos()).normalized()) def mouseMoveEvent(self, event): if not self.band_origin.isNull(): self._rubber_band.setGeometry(QRect(self.band_origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: # 此处可添加选框四角像素值采集逻辑 print(f"选框起点窗口坐标: {self.band_origin.x()}, {self.band_origin.y()}") print(f"选框终点窗口坐标: {event.pos().x()}, {event.pos().y()}") self.band_origin = QPoint() def wheelEvent(self, event): delta = event.angleDelta().y() mouse_x = event.pos().x() mouse_y = event.pos().y() # 缩放步长固定1.5倍,限制最大最小缩放层级 if delta > 0 and self.scrollcount < 7: new_scale = self.scale * 1.5 self.scrollcount += 1 elif delta < 0 and self.scrollcount > 0: new_scale = self.scale / 1.5 self.scrollcount -= 1 else: return self.update_scaled_image(mouse_x, mouse_y, new_scale) def update_scaled_image(self, mouse_x, mouse_y, new_scale): # 记录缩放前鼠标相对于图像标签的位置比例 old_label_x = self.label.x() old_label_y = self.label.y() old_w = self.label.width() old_h = self.label.height() # 鼠标在图像内的相对占比(0~1) ratio_x = (mouse_x - old_label_x) / old_w if old_w else 0 ratio_y = (mouse_y - old_label_y) / old_h if old_h else 0 # 基于原图生成新的缩放后图像 new_w = int(self.origin_pixmap.width() * new_scale) new_h = int(self.origin_pixmap.height() * new_scale) scaled_pix = self.origin_pixmap.scaled(new_w, new_h, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.label.setPixmap(scaled_pix) self.label.resize(new_w, new_h) # 计算新的标签位置,保证鼠标指向的点位置不变 new_label_x = mouse_x - ratio_x * new_w new_label_y = mouse_y - ratio_y * new_h self.label.move(int(new_label_x), int(new_label_y)) # 更新当前缩放值 self.scale = new_scale if __name__ == '__main__': app = QApplication(sys.argv) ex = App() sys.exit(app.exec_())
补充说明
- 后续采集矩形选框四角像素值时,先把选框的窗口坐标减去图像标签的x、y偏移量,再除以当前缩放系数
self.scale,就能得到对应原始图像上的像素坐标,直接读原图的像素值即可 - 代码里已经删掉了原文件里没用到的冗余导入,修正了原代码方法缩进错误、事件绑定不规范的问题
- 需要加图像拖拽平移的话,只要在鼠标按住非选框区域拖动时,同步调用
self.label.move()更新图像位置就行
内容的提问来源于stack exchange,提问作者Modicin
相关产品推荐
相关产品推荐

