Qt小组件 - 8 图片浏览器

发布于:2025-07-29 ⋅ 阅读:(31) ⋅ 点赞:(0)

一个自制的图片浏览器,如果不想安装qfluentwidgetsCommandBarView可以使用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

QImageQPixmap不太支持大文件浏览,或者未知图片类型,使用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())


在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到