一个自制的图片浏览器,如果不想安装
qfluentwidgets
,CommandBarView
可以使用QWidget+QPushButton替代
安装 qfluentwidgets
pip install PySide6-Fluent-Widgets[full]
代码示例
# coding: utf-8
from typing import Union
from PySide6.QtCore import Qt, QRectF, QSize, Signal, QPropertyAnimation, QEasingCurve, Property
from PySide6.QtGui import QPainter, QPixmap, QWheelEvent, QResizeEvent, QImage
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsItem, QFileDialog, \
QVBoxLayout
from qfluentwidgets import FluentIcon as FIF, InfoBar, CommandBarView, MaskDialogBase, Action
from common import imageRequest
class PictureBrowserView(QGraphicsView):
"""
图片查看器
"""
closeSignal = Signal(bool)
def __init__(self, parent=None):
super().__init__(parent)
self._rotationAngle = 0.0
self.zoomInTimes = 0
self.maxZoomInTimes = 22
self.displayedImageSize = QSize(0, 0)
self.verticalLayout = QVBoxLayout(self)
self.toolsBar = CommandBarView(self)
self.graphicsScene = QGraphicsScene(self)
self.pixmapItem = QGraphicsPixmapItem()
self.__animation = QPropertyAnimation(self, b'rotation', self)
self.__animation.setDuration(100)
self.__animation.setEasingCurve(QEasingCurve.Type.InOutQuart)
self.__animation.finished.connect(self.setAdaptation)
self.__initWidgets()
self.__initSignals()
def __initWidgets(self):
self.toolsBar.move(0, 0)
self.toolsBar.resize(self.width(), 50)
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # 隐藏水平滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # 隐藏垂直滚动条
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) # 以鼠标所在位置为锚点进行缩放
self.pixmapItem.setTransformationMode(Qt.TransformationMode.SmoothTransformation) # 平滑转型
self.setRenderHints(
QPainter.RenderHint.Antialiasing | QPainter.RenderHint.LosslessImageRendering | QPainter.RenderHint.SmoothPixmapTransform) # 平滑像素图变换
self.setContentsMargins(0, 0, 0, 0)
self.setViewportMargins(0, 0, 0, 0)
# 设置布局
self.verticalLayout.setContentsMargins(10, 10, 10, 10)
self.verticalLayout.setSpacing(0)
self.verticalLayout.addWidget(self.toolsBar, 0, Qt.AlignTop | Qt.AlignRight)
# 添加图片标签
self.graphicsScene.addItem(self.pixmapItem)
self.setScene(self.graphicsScene)
# 设置控件样式
self.setStyleSheet(
'QGraphicsView{background-color: transparent; border: none;}'
)
def __initSignals(self):
self.toolsBar.addAction(Action(FIF.ROTATE, '旋转图片', triggered=lambda: self.setRotationByAnimation()))
self.toolsBar.addAction(Action(FIF.ZOOM_IN, '放大图片', triggered=lambda: self.enlargePicture()))
self.toolsBar.addAction(Action(FIF.ZOOM_OUT, '缩小图片', triggered=lambda: self.shrinkPicture()))
self.toolsBar.addAction(Action(FIF.SAVE, '保存图片', triggered=self.__saveImage))
self.toolsBar.addAction(Action(FIF.CLOSE, '关闭窗口', triggered=self.closeSignal.emit))
self.toolsBar.resizeToSuitableWidth()
def __saveImage(self):
fileName, t = QFileDialog.getSaveFileName(self, self.tr("保存图片"), "", self.tr("图片 (*.png)"))
if fileName:
self.pixmapItem.pixmap().save(fileName, 'PNG')
InfoBar.success('', self.tr("图片已保存到") + fileName, self.window())
def __getScaleRatio(self):
"""
获取显示的图像和原始图像的缩放比例
:return:
"""
pm = self.pixmapItem.pixmap()
if pm.isNull():
return 1
pw = pm.width()
ph = pm.height()
rw = min(1, self.width() / pw)
rh = min(1, self.height() / ph)
return min(rw, rh)
def __setDragEnabled(self, isEnabled: bool):
"""
设置拖拽是否启动
:param isEnabled: bool
:return:
"""
if isEnabled:
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
else:
self.setDragMode(QGraphicsView.DragMode.NoDrag)
def __isEnableDrag(self):
"""
根据图片的尺寸决定是否启动拖拽功能
:return:
"""
v = self.verticalScrollBar().maximum() > 0
h = self.horizontalScrollBar().maximum() > 0
return v or h
def getRotation(self) -> float:
return self.pixmapItem.rotation()
def setRotation(self, stepSize: Union[float, int] = 90.0):
"""
顺时针旋转
:param stepSize: 步长,旋转角度
:return:
"""
if self.pixmapItem.pixmap().isNull():
return
# self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center()) # 指定图片旋转中心点
self.pixmapItem.setRotation(stepSize)
def setRotationByAnimation(self, stepSize: int = 90):
"""
顺时针旋转动画
:param stepSize: 步长,旋转角度
:return:
"""
if self.__animation.state() == QPropertyAnimation.State.Running:
return
self.__animation.setStartValue(self._rotationAngle)
self._rotationAngle += stepSize
self.__animation.setEndValue(self._rotationAngle)
self.__animation.start()
def resetTransform(self):
"""
重置变换
:return:
"""
self.zoomInTimes = 0
self.__setDragEnabled(False)
super().resetTransform()
def setAdaptation(self):
"""
缩放以适应
:return:
"""
self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))
self.fitInView(self.pixmapItem)
self.__setDragEnabled(False)
self.zoomInTimes = 0
def setOriginalSize(self):
"""
设置 1:1 大小
:return:
"""
self.resetTransform()
self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))
self.__setDragEnabled(self.__isEnableDrag())
self.zoomInTimes = self.getZoomInTimes(self.pixmapItem.pixmap().width())
def enlargePicture(self, anchor=QGraphicsView.ViewportAnchor.AnchorUnderMouse):
"""
放大图片
:return:
"""
if self.zoomInTimes == self.maxZoomInTimes:
return
self.setTransformationAnchor(anchor)
self.zoomInTimes += 1
self.scale(1.1, 1.1)
self.__setDragEnabled(self.__isEnableDrag())
# 还原 anchor
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
def shrinkPicture(self, anchor=QGraphicsView.ViewportAnchor.AnchorUnderMouse):
"""
缩小图片
:return:
"""
if self.zoomInTimes == 0 and not self.__isEnableDrag():
return
self.setTransformationAnchor(anchor)
self.zoomInTimes -= 1
# 原始图像的大小
pm = self.pixmapItem.pixmap()
pw = pm.width()
ph = pm.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(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
def getZoomInTimes(self, width: int, step: int = 100):
for i in range(0, self.maxZoomInTimes):
if width - self.displayedImageSize.width() * 1.1 ** i <= step:
return i
return self.maxZoomInTimes
def fitInView(self, item: QGraphicsItem, mode=Qt.AspectRatioMode.KeepAspectRatio):
"""
缩放场景使其适应窗口大小
:param item:
:param mode:
:return:
"""
super().fitInView(item, mode)
self.displayedImageSize = self.__getScaleRatio() * self.pixmapItem.pixmap().size()
self.zoomInTimes = 0
def resizeEvent(self, event: QResizeEvent):
"""
重写 resizeEvent 事件,调整图片大小
:param event:
:return:
"""
# super().resizeEvent(event)
if self.zoomInTimes > 0:
return
# 调整图片大小
ratio = self.__getScaleRatio()
self.displayedImageSize = self.pixmapItem.pixmap().size() * ratio
if ratio < 1:
self.fitInView(self.pixmapItem)
else:
self.resetTransform()
def wheelEvent(self, e: QWheelEvent):
"""
滚动鼠标滚轮缩放图片
:param e:
:return:
"""
if e.angleDelta().y() > 0:
self.enlargePicture()
else:
self.shrinkPicture()
def setPixmap(self, pixmap: Union[str, QPixmap, QImage]):
"""
设置图片
:param pixmap:
:return:
"""
if not pixmap:
return
if isinstance(pixmap, str):
pixmap = QPixmap(pixmap)
elif isinstance(pixmap, QImage):
pixmap = QPixmap.fromImage(pixmap)
# 设置图片, 并设置场景大小
self.pixmapItem.setPixmap(pixmap)
# 缩放图片
ratio = self.__getScaleRatio()
self.displayedImageSize = pixmap.size() * ratio
self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center())
self.setSceneRect(QRectF(pixmap.rect()))
self.setAdaptation()
def setUrl(self, url: str):
imageRequest(self, url)
rotation = Property(float, getRotation, setRotation)
class PictureBrowserDialog(MaskDialogBase):
"""
图片浏览器
"""
def __init__(self, parent=None):
super().__init__(parent)
self.vBoxLayout = QVBoxLayout(self.widget)
self.view = PictureBrowserView(self.widget)
self.view.closeSignal.connect(self.close)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self._hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addWidget(self.view)
self.setClosableOnMaskClicked(True)
def setUrl(self, url: str):
self.view.setUrl(url)
main.py
QImage
与QPixmap
不太支持大文件浏览,或者未知图片类型,使用pillow
嵌套使用
# coding: utf-8
import sys
from PIL import Image
from PIL.ImageQt import toqpixmap
from PySide6.QtWidgets import QApplication
from components import PictureBrowserView
app = QApplication(sys.argv)
view = PictureBrowserView()
# view.setPixmap(r"G:\手机\壁纸\20250616153644.png")
view.setPixmap(toqpixmap(Image.open(r"G:\手机\壁纸\0250616153644.png")))
view.show()
sys.exit(app.exec())