前言

在 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 实现图片查看器的更多相关文章

  1. 如何在latex 中插入EPS格式图片

    如何在latex 中插入EPS格式图片 第一步:生成.eps格式的图片 1.利用visio画图,另存为pdf格式的图片 利用Adobe Acrobat裁边,使图片大小合适 另存为.eps格式,如下图所 ...

  2. 如何在mysql中存储音乐和图片文件

    如何在mysql中存储音乐和图片文件? 果你想把二进制的数据,比如说图片文件和HTML文件,直接保存在你的MySQL数据库,那么这篇文章就是为你而写的! 我将告诉你怎样通过HTML表单来储存这些文件, ...

  3. angular 图片加载失败 情况处理? 如何在ionic中加载本地图片 ?

    1.angular 图片加载失败 情况处理 在directive中定义组件,在ng-src错误时,调用err-src app.directive('errSrc',function(){ return ...

  4. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  5. 如何在pyqt中实现窗口磨砂效果

    磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...

  6. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  7. 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果

    亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...

  8. 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)

    无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...

  9. 如何在pyqt中给无边框窗口添加DWM环绕阴影

    前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...

随机推荐

  1. 1030 - Discovering Gold

    1030 - Discovering Gold    PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 M ...

  2. css--深入理解z-index引发的层叠上下文、层叠等级和层叠顺序

    前言 在编写css样式代码的时候,我们经常会遇到z-index属性的使用,我们可能只了解z-index能够提高元素的层级,并不知道具体是怎么实现的.本文就来总结一个由z-index 引发的层叠上下文和 ...

  3. matplotlib 进阶之origin and extent in imshow

    目录 显示的extent Explicit extent and axes limits matplotlib教程学习笔记 import numpy as np import matplotlib.p ...

  4. Dimension reduction in principal component analysis for trees

    目录 问题 重要的定义 距离 支撑树 交树 序 tree-line path 重要的性质 其它 Alfaro C A, Aydin B, Valencia C E, et al. Dimension ...

  5. [算法笔记-题解]问题 D: 习题4-4 三个整数求最大值

    问题 D: 习题4-4 三个整数求最大值 [命题人 : 外部导入] 时间限制 : 1.000 sec 内存限制 : 12 MB 题目描述 有3个整数a, b, c,由键盘输入,输出其中最大的数. 输入 ...

  6. MySQL数据操作与查询笔记 • 【第7章 连接查询】

    全部章节   >>>> 本章目录 7.1 内连接查询 7.1.1 交叉连接(笛卡尔积) 7.1.2 内连接查询概要 7.1.3 内连接案例 7.1.4 自然连接 7.2 多表连 ...

  7. vue grammer one

    本文所有内容均来自 书籍<vue.js实战> 完整代码请查看github v-model <!DOCTYPE html> <html> <head> & ...

  8. 大厂必问的Spring面试题

    准备了一个月的八股文,经历了二十几场秋招面试之后,发现Spring很受面试官青睐.最近有空将Spring常见的面试题总结了一下,希望对大家有所帮助. 文章目录: Spring的优点 Spring 用到 ...

  9. ElasticSearch、Kibana 介绍&安装

    目录 ElasticSearch 介绍 基于数据库查询的问题 倒排(反向)索引 ES 存储和查询的原理 ES 核心概念 ES 安装 Kibana ElasticSearch 介绍 基于数据库查询的问题 ...

  10. react中label标签的作用

    当我们点击输入内容触发input焦点的时候,就需要使用到label标签里的htmlFor属性来扩大点击的区域 代码如下: