为圆形标签图像添加边框

时间:2021-03-29 12:41:55

标签: python pyqt pyqt5

我正在尝试创建一个带边框的圆形标签,其中包含一个图像。到目前为止,我尝试设置一个样式表,将圆形图像 QLabel 重叠在带有彩色背景的更大圆形 QLabel 上并更改其大小策略,但无法获得非平庸的结果:

带有父背景标签(标记为 FIX 2):

enter image description here

设置样式表(标记为 FIX 1):

enter image description here

这是 MRE。在这种情况下,我在同一目录中使用蓝色背景:

"steel_blue.png"

from pathlib import Path
import sys
from PyQt5 import QtCore, QtWidgets as qtw
from PyQt5 import QtGui
from PyQt5.QtGui import QFont, QImage, QPainter, QPainterPath, QPixmap
import os
import urllib.request

RUNTIME_DIR = Path(os.path.split(sys.argv[0])[0])


class QCustomQWidget(qtw.QWidget):
    def __init__(self, parent=None, video=""):
        super(QCustomQWidget, self).__init__(parent)
        self.textQVBoxLayout = qtw.QVBoxLayout()
        self.textUpQLabel = qtw.QLabel()
        font = QFont()
        font.setPointSize(12)
        self.textUpQLabel.setFont(font)
        color_path = str(Path.joinpath(RUNTIME_DIR, 'steel_blue.png'))
        base_size = 40
        border_width = 2
        # FIX 2 : PARENT LABEL OFFSET BY border_width

        self.textDownQLabelBorder = RoundLabelImage(
            path=color_path, size=base_size + 2 * border_width
        )
        # Overlap filled border-label with image
        self.textDownQLabel = RoundLabelImage(
            parent=self.textDownQLabelBorder, urlpath=video.author_thumbnail, size=base_size
        )
        self.textDownQLabel.move(border_width, border_width)
        #test:
        # self.textDownQLabelBorder = RoundLabelImage(urlpath=video.author_thumbnail, size=base_size)

        self.textQVBoxLayout.addWidget(self.textUpQLabel)
        self.textQVBoxLayout.addWidget(self.textDownQLabelBorder)
        self.allQHBoxLayout = qtw.QHBoxLayout()
        self.iconQLabel = qtw.QLabel()
        self.allQHBoxLayout.addWidget(self.iconQLabel, 0)
        self.allQHBoxLayout.addLayout(self.textQVBoxLayout, 1)
        self.setLayout(self.allQHBoxLayout)
        self.textUpQLabel.setStyleSheet('''
            color: rgb(70,130,180);
        ''')

    def setTextUp(self, text):
        self.textUpQLabel.setText(text)

    def setIcon(self, imagePath):
        img = QPixmap(imagePath)
        img = img.scaledToWidth(200)
        self.iconQLabel.setPixmap(img)
        self.iconQLabel.setSizePolicy(
            qtw.QSizePolicy(qtw.QSizePolicy.Maximum, qtw.QSizePolicy.MinimumExpanding)
        )


# TODO cut off parent border
class RoundLabelImage(qtw.QLabel):
    """Based on:
    https://stackoverflow.com/questions/50819033/qlabel-with-image-in-round-shape/50821539"""
    def __init__(self, *args, path="", urlpath="", size=50, antialiasing=True, **kwargs):
        super().__init__(*args, **kwargs)
        self.Antialiasing = antialiasing
        self.setMaximumSize(size, size)
        self.setMinimumSize(size, size)
        self.radius = size / 2

        if path != "":
            p = QPixmap(path).scaled(
                size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
            )
        elif urlpath != "":
            data = urllib.request.urlopen(urlpath).read()
            pixmap_author = QPixmap()
            pixmap_author.loadFromData(data)
            p = pixmap_author.scaled(
                size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
            )
        ##### POSSIBLE FIX FOR CHILD
        if self.parent is not None:
            self.target = QPixmap(self.size())
            self.target.fill(QtCore.Qt.transparent)
        else:
            self.target = QPixmap(self.size())
            self.target.fill(QtCore.Qt.transparent)

        painter = QPainter(self.target)
        if self.Antialiasing:
            painter.setRenderHint(QPainter.Antialiasing, True)
            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)

        painter_path = QPainterPath()
        if self.parent is not None:
            painter_path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)

        else:
            painter_path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)

        # FIX 1 : STYLESHEET
        # self.setStyleSheet(
        #     f"""
        #         border: 2px solid blue;
        #         border-radius: {size/2}px;
        #         min-height: {size/2}px;
        #         min-width: {size/2}px;
        #         """
        # )
        painter.setClipPath(painter_path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(self.target)


class Video():
    """Store video information for ease of use
    """
    def __init__(self, id, title="", time="", author="", thumbnail="", author_thumbnail=""):
        self.id = str(id)
        self.url = "https://www.youtube.com/watch?v=" + self.id
        self.title = str(title)
        self.time = str(time)
        self.author = str(author)
        self.thumbnail = str(thumbnail)
        self.author_thumbnail = str(author_thumbnail)


class MainWindow(qtw.QWidget):
    def __init__(self):
        super().__init__()
        self.setLayout(qtw.QVBoxLayout())
        self.checkbox_dict = dict()
        self.button_dict = dict()
        listWidget = qtw.QListWidget()
        self.layout().addWidget(listWidget)
        data = {
            'id':
            'MpufpgwiW-0',
            'url':
            'https://www.youtube.com/watch?v=MpufpgwiW-0',
            'title':
            'Lycoriscoris - Seimei (生命)',
            'time':
            '2 hours ago',
            'author':
            'Anjunadeep',
            'thumbnail':
            'https://i.ytimg.com/vi/MpufpgwiW-0/hqdefault.jpg?sqp=-oaymwEbCNIBEHZIVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLDI8RjBj8u6GvY_bXeBqAOSCRNsqA',
            'author_thumbnail':
            'https://yt3.ggpht.com/ytc/AAUvwnie-87z6V-8oWCHVp4fn4CN17H5if1IqolvJaSL6g=s68-c-k-c0x00ffffff-no-rj',
        }
        my_videos = dict()
        for key, val in data.items():
            if key == "id":
                video_id = val
                my_videos[val] = Video(video_id)
            else:
                setattr(my_videos[video_id], key, val)

        for video_id, video in my_videos.items():
            myQCustomQWidget = QCustomQWidget(video=video)
            myQCustomQWidget.setTextUp(video.title)
            url = video.thumbnail
            data = urllib.request.urlopen(url).read()
            img_thumbnail = QImage()
            img_thumbnail.loadFromData(data)
            myQCustomQWidget.setIcon(img_thumbnail)
            myQListWidgetItem = qtw.QListWidgetItem(listWidget)
            myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
            listWidget.addItem(myQListWidgetItem)
            listWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)

        self.resize(800, 300)
        self.show()


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec())

1 个答案:

答案 0 :(得分:1)

如果您只想在圆形图像周围绘制边框,使用叠加的小部件当然不是最明智的选择。

您应该做的是直接创建一个包括边框的 QPixmap。

这是您的课程的修订和清理版本:

class RoundLabelImage(qtw.QLabel):
    def __init__(self, path="", urlpath="", size=50, border_width=0, border_color=None, antialiasing=True):
        super().__init__()
        self.setFixedSize(size, size)

        if path != "":
            source = QPixmap(path)
        elif urlpath != "":
            # you really shouldn't rely on functions that are possibly blocking...
            data = urllib.request.urlopen(urlpath).read()
            source = QPixmap()
            source.loadFromData(data)

        pixmap_size = size - border_width * 2
        p = source.scaled(
            pixmap_size, pixmap_size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)

        self.target = QPixmap(self.size())
        self.target.fill(QtCore.Qt.transparent)

        painter = QPainter(self.target)
        if antialiasing:
            painter.setRenderHint(QPainter.Antialiasing, True)
            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)

        rect = QtCore.QRectF(self.rect())
        if border_width:
            painter.setPen(QtCore.Qt.NoPen)
            painter.setBrush(QtGui.QColor(border_color))
            painter.drawEllipse(rect)
            rect.adjust(border_width, border_width, -border_width, -border_width)

        painter_path = QPainterPath()
        painter_path.addEllipse(rect)
        painter.setClipPath(painter_path)

        painter.drawPixmap(border_width, border_width, p)
        self.setPixmap(self.target)

您可以简单地使用正确的参数创建实例:

    self.textDownQLabel = RoundLabelImage(
        urlpath=video.author_thumbnail, size=base_size, border_width=border_width, 
        border_color=QtGui.QColor(0, 30, 66)
    )

请注意,我使用 steel_blue.png 图像的颜色作为边框:因为它只是一种颜色的图像,而且您总是使用相同的来源,因此使用 QPixmap 毫无意义。

最后,正如我在代码中添加的注释所指出的,您确实应该不要依赖 UI 元素中可能阻塞(甚至可能引发异常)的函数。下载 QWidget __init__ 中的任何内容肯定会阻塞,直到加载完成;这刚开始是错误的,但如果您还有很多项目(或连接速度较慢),情况会更糟。
对异步下载做一些研究(Qt 为这些情况提供了 QtNetwork 模块,有数百篇关于它的帖子),在下载其网络内容之前创建项目,然后在下载完成后更新这些内容, 通过适当的信号/插槽连接。