一个自制的图片浏览器,如果不想安装
qfluentwidgets
,CommandBarView
可以使用QWidget+QPushButton替代
安装 qfluentwidgets
pip install PySide6-Fluent-Widgets[full]
代码示例
# coding: utf-8
from typing import Unionfrom 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, Actionfrom common import imageRequestclass PictureBrowserView(QGraphicsView):"""图片查看器"""closeSignal = Signal(bool)def __init__(self, parent=None):super().__init__(parent)self._rotationAngle = 0.0self.zoomInTimes = 0self.maxZoomInTimes = 22self.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 1pw = 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() > 0h = self.horizontalScrollBar().maximum() > 0return v or hdef 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:returnself.__animation.setStartValue(self._rotationAngle)self._rotationAngle += stepSizeself.__animation.setEndValue(self._rotationAngle)self.__animation.start()def resetTransform(self):"""重置变换:return:"""self.zoomInTimes = 0self.__setDragEnabled(False)super().resetTransform()def setAdaptation(self):"""缩放以适应:return:"""self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))self.fitInView(self.pixmapItem)self.__setDragEnabled(False)self.zoomInTimes = 0def 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:returnself.setTransformationAnchor(anchor)self.zoomInTimes += 1self.scale(1.1, 1.1)self.__setDragEnabled(self.__isEnableDrag())# 还原 anchorself.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)def shrinkPicture(self, anchor=QGraphicsView.ViewportAnchor.AnchorUnderMouse):"""缩小图片:return:"""if self.zoomInTimes == 0 and not self.__isEnableDrag():returnself.setTransformationAnchor(anchor)self.zoomInTimes -= 1# 原始图像的大小pm = self.pixmapItem.pixmap()pw = pm.width()ph = pm.height()# 实际显示的图像宽度w = self.displayedImageSize.width() * 1.1 ** self.zoomInTimesh = self.displayedImageSize.height() * 1.1 ** self.zoomInTimesif 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())# 还原 anchorself.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 ireturn self.maxZoomInTimesdef 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 = 0def 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() * ratioif 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:returnif 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() * ratioself.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 QApplicationfrom components import PictureBrowserViewapp = 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())