分享一个QLabel显示网络图片的方。
看网上基本都是使用requests来请求的,这会有个问题如果将请求放入主线程页面会直接卡死,那么肯定pass,如果将请求放入QThread中,网络图片只有10~20个还可以凑合,如果需要加载上百个网络图片,很有可能因为线程过多而堵塞,最大的问题就是慢,这个也可以pass了。
如果将requests异步,你会发现图片是一个一个的蹦出来的,也不太好看。
有缺点的一些方法
具体看代码细节吧
直接通过请求获取网络图片
def get_image_content(url: str) -> QPixmap:image = QPixmap()headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0'}try:response = requests.get(url, headers=headers)image.loadFromData(response.content)return imageexcept Exception as e:return image# label = QLabel()
# label.setPixmap(get_image_content(
# 'https://image.baidu.com/search/detail?ct=503316480&z=undefined&tn=baiduimagedetail&ipn=d&word=csdn&step_word=&lid=11776445228125483707&ie=utf-8&in=&cl=2&lm=-1&st=undefined&hd=undefined&latest=undefined©right=undefined&cs=181147288,2457056291&os=2182694500,281061618&simid=181147288,2457056291&pn=2&rn=1&di=46137345&ln=1914&fr=&fmq=1713437873349_R&fm=&ic=undefined&s=undefined&se=&sme=&tab=0&width=undefined&height=undefined&face=undefined&is=0,0&istype=0&ist=&jit=&bdtype=11&spn=0&pi=0&gsm=1e&objurl=https%3A%2F%2Fimg-blog.csdnimg.cn%2Ff2dc93498ac14647869750e19fa0fdae.png&rpstart=0&rpnum=0&adpicid=0&nojc=undefined&dyTabStr=MCwxLDMsMiw2LDQsNSw4LDcsOQ%3D%3D'))
QThread查看网络图片
class GetImageThread(QThread):imageChanged = pyqtSignal(QPixmap)def __init__(self, parent=None):super().__init__(parent)self._urls = []self._imageDataList = []self._headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0'}def run(self):for index, url in enumerate(self._urls):image = QPixmap()try:response = requests.get(url, headers=self._headers)image.loadFromData(response.content)self.imageChanged.emit(image)except Exception as e:self.imageChanged.emit(image)def setUrls(self, urls: list):self._urls = urlsdef urls(self):return self._urlsdef setHeaders(self, headers: dict = {}):self._headers.update(headers)
QThread+asyncio异步请求
请求后你会发现,QThread用不到1s就结束了,但是异步的返回值需要5s后才能接受到,很明显不符合需求
class GetImgAsyncThread(QThread):imgChanged = pyqtSignal(QPixmap)errorChange = pyqtSignal(str)def __init__(self, parent=None):super().__init__(parent)self._urlsData = []self._headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0'}def run(self):try:for index, response in enumerate(asyncio.run(self.async_response())): # type: int, requests.Responseimage = QPixmap()image.loadFromData(response.content)self.imgChanged.emit(image)response.close()except Exception as e:self.errorChange.emit(str(e))async def async_request(self, method, url, **kwargs):response = await asyncio.to_thread(requests.request, method, url, headers=self._headers,**kwargs) # type: requests.Responsereturn responseasync def async_response(self):task = []for urlData in self._urlsData:func_task: asyncio.Task[dict] = asyncio.create_task(self.async_request(**urlData))func_response = await func_tasktask.append(func_response)return taskdef setUrlsData(self, urlsData: list) -> None:"""[{'method': 'get', 'url': 'http://127.0.0.1:8888'}]:param urlsData: :return: """self._urlsData = urlsDatadef getUrlsData(self):return self._urlsDatadef setHeaders(self, headers: dict = {}):self._headers.update(headers)
这些方法都有些缺点,还有就是比较麻烦,每次都需要反复调用
推荐使用的方法
先分享一个ImageLabel的组件,它会让你显示图片更加丝滑
from functools import singledispatch, update_wrapper
from typing import Unionfrom PyQt5.Qt import *class singledispatchmethod:"""Single-dispatch generic method descriptor.Supports wrapping existing descriptors and handles non-descriptorcallables as instance methods."""def __init__(self, func):if not callable(func) and not hasattr(func, "__get__"):raise TypeError(f"{func!r} is not callable or a descriptor")self.dispatcher = singledispatch(func)self.func = funcdef register(self, cls, method=None):"""generic_method.register(cls, func) -> funcRegisters a new implementation for the given *cls* on a *generic_method*."""return self.dispatcher.register(cls, func=method)def __get__(self, obj, cls=None):def _method(*args, **kwargs):if args:method = self.dispatcher.dispatch(args[0].__class__)else:method = self.funcfor v in kwargs.values():if v.__class__ in self.dispatcher.registry:method = self.dispatcher.dispatch(v.__class__)if method is not self.func:breakreturn method.__get__(obj, cls)(*args, **kwargs)_method.__isabstractmethod__ = self.__isabstractmethod___method.register = self.registerupdate_wrapper(_method, self.func)return _method@propertydef __isabstractmethod__(self):return getattr(self.func, '__isabstractmethod__', False)class ImageLabel(QLabel):""" Image labelConstructors------------* ImageLabel(`parent`: QWidget = None)* ImageLabel(`image`: str | QImage | QPixmap, `parent`: QWidget = None)"""clicked = pyqtSignal()@singledispatchmethoddef __init__(self, parent: QWidget = None):super().__init__(parent)self.image = QImage()self.setBorderRadius(0, 0, 0, 0)self._postInit()@__init__.registerdef _(self, image: str, parent=None):self.__init__(parent)self.setImage(image)self._postInit()@__init__.registerdef _(self, image: QImage, parent=None):self.__init__(parent)self.setImage(image)self._postInit()@__init__.registerdef _(self, image: QPixmap, parent=None):self.__init__(parent)self.setImage(image)self._postInit()def _postInit(self):passdef _onFrameChanged(self, index: int):self.image = self.movie().currentImage()self.update()def setBorderRadius(self, topLeft: int, topRight: int, bottomLeft: int, bottomRight: int):""" set the border radius of image """self._topLeftRadius = topLeftself._topRightRadius = topRightself._bottomLeftRadius = bottomLeftself._bottomRightRadius = bottomRightself.update()def setImage(self, image: Union[str, QPixmap, QImage] = None):""" set the image of label """self.image = image or QImage()if isinstance(image, str):reader = QImageReader(image)if reader.supportsAnimation():self.setMovie(QMovie(image))else:self.image = reader.read()elif isinstance(image, QPixmap):self.image = image.toImage()self.setFixedSize(self.image.size())self.update()def scaledToWidth(self, width: int):if self.isNull():returnh = int(width / self.image.width() * self.image.height())self.setFixedSize(width, h)if self.movie():self.movie().setScaledSize(QSize(width, h))def scaledToHeight(self, height: int):if self.isNull():returnw = int(height / self.image.height() * self.image.width())self.setFixedSize(w, height)if self.movie():self.movie().setScaledSize(QSize(w, height))def isNull(self):return self.image.isNull()def mouseReleaseEvent(self, e):super().mouseReleaseEvent(e)self.clicked.emit()def setPixmap(self, pixmap: QPixmap):self.setImage(pixmap)def pixmap(self) -> QPixmap:return QPixmap.fromImage(self.image)def setMovie(self, movie: QMovie):super().setMovie(movie)self.movie().start()self.image = self.movie().currentImage()self.movie().frameChanged.connect(self._onFrameChanged)def paintEvent(self, e):if self.isNull():returnpainter = QPainter(self)painter.setRenderHints(QPainter.Antialiasing)path = QPainterPath()w, h = self.width(), self.height()# top linepath.moveTo(self.topLeftRadius, 0)path.lineTo(w - self.topRightRadius, 0)# top right arcd = self.topRightRadius * 2path.arcTo(w - d, 0, d, d, 90, -90)# right linepath.lineTo(w, h - self.bottomRightRadius)# bottom right arcd = self.bottomRightRadius * 2path.arcTo(w - d, h - d, d, d, 0, -90)# bottom linepath.lineTo(self.bottomLeftRadius, h)# bottom left arcd = self.bottomLeftRadius * 2path.arcTo(0, h - d, d, d, -90, -90)# left linepath.lineTo(0, self.topLeftRadius)# top left arcd = self.topLeftRadius * 2path.arcTo(0, 0, d, d, -180, -90)# draw imageimage = self.image.scaled(self.size() * self.devicePixelRatioF(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)painter.setPen(Qt.NoPen)painter.setClipPath(path)painter.drawImage(self.rect(), image)@pyqtProperty(int)def topLeftRadius(self):return self._topLeftRadius@topLeftRadius.setterdef topLeftRadius(self, radius: int):self.setBorderRadius(radius, self.topRightRadius, self.bottomLeftRadius, self.bottomRightRadius)@pyqtProperty(int)def topRightRadius(self):return self._topRightRadius@topRightRadius.setterdef topRightRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius, radius, self.bottomLeftRadius, self.bottomRightRadius)@pyqtProperty(int)def bottomLeftRadius(self):return self._bottomLeftRadius@bottomLeftRadius.setterdef bottomLeftRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius, self.topRightRadius, radius, self.bottomRightRadius)@pyqtProperty(int)def bottomRightRadius(self):return self._bottomRightRadius@bottomRightRadius.setterdef bottomRightRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius, self.topRightRadius, self.bottomLeftRadius, radius)
显示网络图片
在上面基础上添加了图片阴影,网络图片的显示
诸所周知C++运行非常快,那么很简单,将所有的请求都扔给C++就好,那么不得不使用Qt自带的一个请求管理器了,使用QNetworkAccessManager可以轻松的实现那些壁纸 软件的效果,需要注意的是QNetworkAccessManager也是多线程操作,你可以在finished中做任何事情,完全不会影响页面的运行
# coding: utf-8
from typing import Unionfrom PyQt5.QtCore import QUrl, Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QColor, QImage, QImageReader, QMovie
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QGraphicsDropShadowEffectclass WebImageLabel(ImageLabel):finished = pyqtSignal()def _postInit(self):self.shadowEffect = Noneself._shadowVisible = Falseself.networkAccessManager = QNetworkAccessManager(self)self.networkAccessManager.finished.connect(self.__on_finished_connect)self.setBorderRadius(5, 5, 5, 5)def setWebUrl(self, url: Union[str, QUrl], headers: dict = None):if isinstance(url, str):url = QUrl(url)request = QNetworkRequest(url)request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)self.__initHeaders(request, headers)self.networkAccessManager.get(request)self.update()def __initHeaders(self, request: QNetworkRequest, headers: dict = None):if headers is None:returnfor item in headers.items():request.setRawHeader(item[0].encode(), item[1].encode())def __on_finished_connect(self, reply: QNetworkReply):if reply.error() == QNetworkReply.NoError:pixmap = QPixmap()pixmap.loadFromData(reply.readAll().data())self.setPixmap(pixmap)else:self.setPixmap(QPixmap())self.finished.emit()def setImage(self, image: Union[str, QPixmap, QImage] = None):""" set the image of label """self.image = image or QImage()if isinstance(image, str):reader = QImageReader(image)if reader.supportsAnimation():self.setMovie(QMovie(image))else:self.image = reader.read()elif isinstance(image, QPixmap):self.image = image.toImage()# self.setFixedSize(self.image.size())self.update()def setShadowEffect(self, blurRadius=30, offset=(0, 0), color=QColor(0, 0, 0, 100)):"""为对话框添加阴影"""self.shadowEffect = QGraphicsDropShadowEffect(self)self.shadowEffect.setBlurRadius(blurRadius)self.shadowEffect.setOffset(*offset)self.shadowEffect.setColor(color)self.setGraphicsEffect(None)self.setGraphicsEffect(self.shadowEffect)def isShadowVisible(self):return self._shadowVisibledef setShadowVisible(self, b: bool):self._shadowVisible = bif self._shadowVisible:self.setShadowEffect()else:self.setGraphicsEffect(None)del self.shadowEffectself.shadowEffect = None