如何在pyqt中使用 QGraphicsView 实现图片查看器
前言
在 PyQt 中可以使用很多方式实现照片查看器,最朴素的做法就是重写 QWidget 的 paintEvent()、mouseMoveEvent 等事件,但是如果要在图像上多添加一些形状,那么在对图像进行缩放旋转等仿射变换时需要对这些形状也这些变换,虽然不难,但是从头实现这些变换还有形状还是挺讨厌的。好在 Qt 提供了图形视图框架,关于这个框架的基本使用可以参见 《快速掌握PyQt5》第三十四章 图形视图框架,下面进入正题。
实现方式
一个最基本的照片查看器应该具有以下功能:
- 载入图像
- 缩放图像
- 在窗口尺寸小于图像时允许拖拽图像
载入图像可以使用 QGraphicsPixmapItem 来解决,缩放图像使用 QGraphicsView 的 scale(sx, sy) 解决,移动图像只需将 QGraphicsView 的 dragMode 设置为 QGraphicsView.ScrollHandDrag 即可。因为常常使用鼠标滚轮来缩放图像,所以还需要重写重写以下 QGraphicsView 的 wheelEvent。
实际上由于窗口的缩放导致视口大小变化,还有一些细枝末节需要处理。具体代码如下:
# coding:utf-8
import sys
from PyQt5.QtCore import QRect, QRectF, QSize, Qt
from PyQt5.QtGui import QPainter, QPixmap, QWheelEvent
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsPixmapItem,
QGraphicsScene, QGraphicsView)
class ImageViewer(QGraphicsView):
""" 图片查看器 """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.zoomInTimes = 0
self.maxZoomInTimes = 22
# 创建场景
self.graphicsScene = QGraphicsScene()
# 图片
self.pixmap = QPixmap(r'D:\hzz\图片\硝子\硝子 (2).jpg')
self.pixmapItem = QGraphicsPixmapItem(self.pixmap)
self.displayedImageSize = QSize(0, 0)
# 初始化小部件
self.__initWidget()
def __initWidget(self):
""" 初始化小部件 """
self.resize(1200, 900)
# 隐藏滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 以鼠标所在位置为锚点进行缩放
self.setTransformationAnchor(self.AnchorUnderMouse)
# 平滑缩放
self.pixmapItem.setTransformationMode(Qt.SmoothTransformation)
self.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
# 设置场景
self.graphicsScene.addItem(self.pixmapItem)
self.setScene(self.graphicsScene)
def wheelEvent(self, e: QWheelEvent):
""" 滚动鼠标滚轮缩放图片 """
if e.angleDelta().y() > 0:
self.zoomIn()
else:
self.zoomOut()
def resizeEvent(self, e):
""" 缩放图片 """
super().resizeEvent(e)
if self.zoomInTimes > 0:
return
# 调整图片大小
ratio = self.__getScaleRatio()
self.displayedImageSize = self.pixmap.size()*ratio
if ratio < 1:
self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
else:
self.resetTransform()
def setImage(self, imagePath: str):
""" 设置显示的图片 """
self.resetTransform()
# 刷新图片
self.pixmap = QPixmap(imagePath)
self.pixmapItem.setPixmap(self.pixmap)
# 调整图片大小
self.setSceneRect(QRectF(self.pixmap.rect()))
ratio = self.__getScaleRatio()
self.displayedImageSize = self.pixmap.size()*ratio
if ratio < 1:
self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
def resetTransform(self):
""" 重置变换 """
super().resetTransform()
self.zoomInTimes = 0
self.__setDragEnabled(False)
def __isEnableDrag(self):
""" 根据图片的尺寸决定是否启动拖拽功能 """
v = self.verticalScrollBar().maximum() > 0
h = self.horizontalScrollBar().maximum() > 0
return v or h
def __setDragEnabled(self, isEnabled: bool):
""" 设置拖拽是否启动 """
self.setDragMode(
self.ScrollHandDrag if isEnabled else self.NoDrag)
def __getScaleRatio(self):
""" 获取显示的图像和原始图像的缩放比例 """
if self.pixmap.isNull():
return 1
pw = self.pixmap.width()
ph = self.pixmap.height()
rw = min(1, self.width()/pw)
rh = min(1, self.height()/ph)
return min(rw, rh)
def fitInView(self, item: QGraphicsItem, mode=Qt.KeepAspectRatio):
""" 缩放场景使其适应窗口大小 """
super().fitInView(item, mode)
self.displayedImageSize = self.__getScaleRatio()*self.pixmap.size()
self.zoomInTimes = 0
def zoomIn(self, viewAnchor=QGraphicsView.AnchorUnderMouse):
""" 放大图像 """
if self.zoomInTimes == self.maxZoomInTimes:
return
self.setTransformationAnchor(viewAnchor)
self.zoomInTimes += 1
self.scale(1.1, 1.1)
self.__setDragEnabled(self.__isEnableDrag())
# 还原 anchor
self.setTransformationAnchor(self.AnchorUnderMouse)
def zoomOut(self, viewAnchor=QGraphicsView.AnchorUnderMouse):
""" 缩小图像 """
if self.zoomInTimes == 0 and not self.__isEnableDrag():
return
self.setTransformationAnchor(viewAnchor)
self.zoomInTimes -= 1
# 原始图像的大小
pw = self.pixmap.width()
ph = self.pixmap.height()
# 实际显示的图像宽度
w = self.displayedImageSize.width()*1.1**self.zoomInTimes
h = self.displayedImageSize.height()*1.1**self.zoomInTimes
if pw > self.width() or ph > self.height():
# 在窗口尺寸小于原始图像时禁止继续缩小图像比窗口还小
if w <= self.width() and h <= self.height():
self.fitInView(self.pixmapItem)
else:
self.scale(1/1.1, 1/1.1)
else:
# 在窗口尺寸大于图像时不允许缩小的比原始图像小
if w <= pw:
self.resetTransform()
else:
self.scale(1/1.1, 1/1.1)
self.__setDragEnabled(self.__isEnableDrag())
# 还原 anchor
self.setTransformationAnchor(self.AnchorUnderMouse)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = ImageViewer()
w.show()
sys.exit(app.exec_())
测试
来看一下实际的使用效果:

如何在pyqt中使用 QGraphicsView 实现图片查看器的更多相关文章
- 如何在latex 中插入EPS格式图片
如何在latex 中插入EPS格式图片 第一步:生成.eps格式的图片 1.利用visio画图,另存为pdf格式的图片 利用Adobe Acrobat裁边,使图片大小合适 另存为.eps格式,如下图所 ...
- 如何在mysql中存储音乐和图片文件
如何在mysql中存储音乐和图片文件? 果你想把二进制的数据,比如说图片文件和HTML文件,直接保存在你的MySQL数据库,那么这篇文章就是为你而写的! 我将告诉你怎样通过HTML表单来储存这些文件, ...
- angular 图片加载失败 情况处理? 如何在ionic中加载本地图片 ?
1.angular 图片加载失败 情况处理 在directive中定义组件,在ng-src错误时,调用err-src app.directive('errSrc',function(){ return ...
- 如何在pyqt中自定义无边框窗口
前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...
- 如何在pyqt中实现窗口磨砂效果
磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...
- 如何在pyqt中实现win10亚克力效果
亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...
- 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果
亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...
- 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)
无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...
- 如何在pyqt中给无边框窗口添加DWM环绕阴影
前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...
随机推荐
- 【剑指Offer】二叉搜索树与双向链表 解题报告(Python)
[剑指Offer]二叉搜索树与双向链表 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interview ...
- CS5213demoboard设计电路|DMI转VGA带II2S音频输出转接线|CS5213方案
CS5213是台湾CAPSTONE瑞奇达推出的一款HDMI(高清多媒体接口)到VGA转换芯片. CS5213设计HDMI转VGA带II2S转接线产品特性: ◇将完整的HDMI信号转换为VGA输出◇支持 ...
- MySQL数据操作与查询笔记 • 【第7章 连接查询】
全部章节 >>>> 本章目录 7.1 内连接查询 7.1.1 交叉连接(笛卡尔积) 7.1.2 内连接查询概要 7.1.3 内连接案例 7.1.4 自然连接 7.2 多表连 ...
- Lombok 安装配置及使用方法
pom.xml 引入依赖 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!--Feb 0 ...
- nginx+keepalived 简单实现主备和双主模式
准备nginx和keepalived 安装nginx(自行安装) yum install nginx 安装keepalived(安装包安装总报错,yum安装能好一点) yum install keep ...
- 整理spring + mysql + redis + 测试 的配置格式 和源码
经过多次整理,最终以这样的文件格式配置 目前配好的基本模板: 1 <?xml version="1.0" encoding="UTF-8"?> 2 ...
- Go语言系列之网络编程
现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其实是一个很庞大的领域,本 ...
- PPT2010制作翻牌动画
原文: https://www.toutiao.com/i6492653280676545037/ 新建一张空白幻灯片 选择"插入"选项卡,"插入"功能组,&q ...
- element ui 动态菜单解决方案集锦
1.<分享一个VUE Element-UI 的多级菜单动态渲染的组件> 2.<饿了么组件库,element-ui开发精美的后台管理系统系列之(一)开发伸缩菜单> 3.<V ...
- elasticsearch在linux上的安装,Centos7.X elasticsearch 7.6.2安装
本文环境:Elasticsearch7.6.2目前最先版本 centos7.X JDK1.8 elasticsearch介绍 官网:https://www.elastic.co/cn/pr ...