前言

在 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. 【LeetCode】82. Remove Duplicates from Sorted List II 解题报告(Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/remove-du ...

  2. Arm64架构下静态编译Nginx

    这段时间,我一直忙于将 Rainbond 源码构建模块移植到 Arm64/aarch64 架构中.这一源码构建模块可以将指定代码仓库中包含的源码,拉取构建成为容器镜像,在各种容器平台中运行.目前支持的 ...

  3. Distillation as a Defense to Adversarial Perturbations against Deep Neural Networks

    目录 概 主要内容 算法 一些有趣的指标 鲁棒性定义 合格的抗干扰机制 Nicolas Papernot, Patrick McDaniel, Xi Wu, Somesh Jha, Ananthram ...

  4. Latex 入门教程

    Latex 入门教程 学习途径:LaTex入门_哔哩哔哩_bilibili 运行环境:texlive2021.texstudio-4.1.2-win-qt6 1. 基本结构 整个 Latex 文件分为 ...

  5. git报错 error: cannot stat 'file': Permission denied

    切换分支(git checkout xxx)时报错: error: cannot stat 'file': Permission denied 解决方法:退出编辑器.浏览器.资源管理器等,然后再切换就 ...

  6. 在 CentOS 7 上安装 GitLab

    1. 安装和配置必要的依赖库 sudo yum install -y curl policycoreutils-python openssh-server # the commands below w ...

  7. 曾经大量使用的Model1开发模式,虽不常用,但可以帮我们理解JSP

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6513394762370777604/ 1.<JSP页面实际上就是Servlet> 2.<JSP页 ...

  8. 一站式搭建 GitHub Pages 博客 (一)

    本文将详细讲解如何快速搭建 GitHub Pages 博客页面 关于博客主题,博客信息更改,上传文章等将会在 一站式搭建 GitHub Pages 博客 (二) 中进行详细讲解 准备阶段 注册 Git ...

  9. T-SQL创建数据库常用方法2020年10月29日20:12:04网课笔记

    2.接口的作用 第一.方便框架的设计.利于团队的开发. 第二.方便项目拓展.高内聚.低耦合. 3.反射 [1]反射的理解:通过读取程序集的信息,找到相关的类型和类型的成员,也可以得到相关的对象.而这种 ...

  10. 【Java常用类】LocalDate、LocalTime、LocalDateTime

    LocalDate.LocalTime.LocalDateTime 说明 JDK 1.0中包含了 一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被 ...