Reputation: 165
I'm trying to create a round label with border, containing an image inside. So far I tried setting a stylesheet, overlapping the round image QLabel on a bigger round QLabel with the colored background and changing it's size policy but can't get a non-mediocre result:
With parent background label (marked as FIX 2):
With stylesheet set (marked as FIX 1):
this is the MRE. in this case I use a blue background in the same dir:
"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())
Upvotes: 0
Views: 750
Reputation: 48335
If you just want to draw a border around a rounded image, using a superimposed widget is certainly not the smartest choice.
What you should do instead is to directly create a QPixmap that includes the border.
Here is a revised and cleaned up version of your class:
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)
You can simply create the instance with the correct arguments:
self.textDownQLabel = RoundLabelImage(
urlpath=video.author_thumbnail, size=base_size, border_width=border_width,
border_color=QtGui.QColor(0, 30, 66)
)
Note that I used the color of the steel_blue.png
image for the border: since it is just an image with one color and you're always using the same source, using a QPixmap for that is pointless.
Finally, as noted in a comment I put in the code, you really should not rely on functions that are possibly blocking (and could even raise exceptions) in a UI element. Downloading any content in a QWidget __init__
will certainly block until loading is complete; this is just wrong to begin with, but if also you have lots of items (or have a slow connection) it makes it even worse.
Do some research on asynchronous downloading (Qt provides the QtNetwork module for these situations, there are hundreds of posts about it), create the items before downloading their network content, and then update those contents when downloading is complete, through appropriate signal/slot connections.
Upvotes: 1