如何在 pyqt 中解決啟用 DPI 縮放後 QIcon 模糊的問題

之一Yo發表於2022-05-11

問題描述

如今顯示器的解析度越來越高,如果不啟用 DPI 縮放,軟體的字型和圖示在高分屏下就會顯得非常小,看得很累人。從 5.6 版本開始,Qt 便能支援 DPI 縮放功能,Qt6 開始這個功能是預設開啟的。

啟用 DPI 縮放後,文字不會有太明顯的鋸齒問題,但是使用 QIcon 設定的圖示卻會顯得很模糊。比如下述程式碼:

# coding:utf-8
import os
import sys

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget


class Demo(QWidget):

    def __init__(self):
        super().__init__(parent=None)
        self.resize(300, 300)
        self.button = QPushButton(' Shuffle all', self)
        self.button.setIcon(QIcon("Shuffle.png"))
        self.button.move(self.width()//2-self.button.width()//2,
                         self.height()//2-self.button.height()//2)


if __name__ == '__main__':
    os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "0"
    os.environ["QT_SCALE_FACTOR"] = '1.25'
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    app.exec_()

執行結果如下圖所示,可以看到圖示的鋸齒現象非常明顯:

圖示模糊

問題解決

QIcon 底層依靠 QIconEngine 來繪製圖示,生成圖示的 pixmap。猜測 QIconEngine 使用 QPainter 繪製圖示時沒有設定 QPainter.SmoothPixmapTransform 標誌位,所以導致只縮放了圖示,而沒有進行平滑插值。為了解決這個問題,我們可以繼承 QIconEngine,重寫兩個虛方法 paint()pixmap(),程式碼如下:

# coding:utf-8
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
from PyQt5.QtGui import QIcon, QIconEngine, QImage, QPixmap, QPainter


class PixmapIconEngine(QIconEngine):
    """ Pixmap icon engine """

    def __init__(self, iconPath: str):
        self.iconPath = iconPath
        super().__init__()

    def paint(self, painter: QPainter, rect: QRect, mode: QIcon.Mode, state: QIcon.State):
        painter.setRenderHints(QPainter.Antialiasing |
                               QPainter.SmoothPixmapTransform)
        painter.drawImage(rect, QImage(self.iconPath))

    def pixmap(self, size: QSize, mode: QIcon.Mode, state: QIcon.State) -> QPixmap:
        pixmap = QPixmap(size)
        pixmap.fill(Qt.transparent)
        self.paint(QPainter(pixmap), QRect(QPoint(0, 0), size), mode, state)
        return pixmap


class Icon(QIcon):

    def __init__(self, iconPath: str):
        self.iconPath = iconPath
        super().__init__(PixmapIconEngine(iconPath))

接著只要把 QIcon 換成 Icon,並開啟 app.setAttribute(Qt.AA_UseHighDpiPixmaps),就能解決圖示模糊的問題了,效果如下圖所示:

解決鋸齒問題

相關文章