text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  
  """
  Created on 2019年7月26日
  @author: Irony
  @site: https://pyqt5.com https://github.com/892768447
  @email: 892768447@qq.com
  @file: CustomWidgets.CAvatar
  @description: Avatar
  """
  import os
  
  from PyQt5.QtCore import QUrl, QRectF, Qt, QSize, QTimer, QPropertyAnimation,\
      QPointF, pyqtProperty
  from PyQt5.QtGui import QPixmap, QColor, QPainter, QPainterPath, QMovie
  from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkDiskCache,\
      QNetworkRequest
  from PyQt5.QtWidgets import QWidget, qApp
  
  
  __Author__ = 'Irony'
  __Copyright__ = 'Copyright (c) 2019 Irony'
  __Version__ = 1.0
  
  
  class CAvatar(QWidget):
  
      Circle = 0              # Circle
      Rectangle = 1           # Rounded Rectangle
      SizeLarge = QSize(128, 128)
      SizeMedium = QSize(64, 64)
      SizeSmall = QSize(32, 32)
      StartAngle = 0          # Start rotation angle
      EndAngle = 360          # End rotation angle
  
      def __init__(self, *args, shape=0, url='', cacheDir=False, size=QSize(64, 64), animation=False, **kwargs):
          super(CAvatar, self).__init__(*args, **kwargs)
          self.url = ''
          self._angle = 0             # angle
          self.pradius = 0            # Loading progress bar radius
          self.animation = animation  # Whether to use animation
          self._movie = None          # dynamic picture
          self._pixmap = QPixmap()    # Picture object
          self.pixmap = QPixmap()     # Drawed object
          self.isGif = url.endswith('.gif')
          # Progress animation timer
          self.loadingTimer = QTimer(self, timeout=self.onLoading)
          # Rotating animation
          self.rotateAnimation = QPropertyAnimation(
              self, b'angle', self, loopCount=1)
          self.setShape(shape)
          self.setCacheDir(cacheDir)
          self.setSize(size)
          self.setUrl(url)
  
      def paintEvent(self, event):
          super(CAvatar, self).paintEvent(event)
          # brush
          painter = QPainter(self)
          painter.setRenderHint(QPainter.Antialiasing, True)
          painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
          painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
          # draw
          path = QPainterPath()
          diameter = min(self.width(), self.height())
          if self.shape == self.Circle:
              radius = int(diameter / 2)
          elif self.shape == self.Rectangle:
              radius = 4
          halfW = self.width() / 2
          halfH = self.height() / 2
          painter.translate(halfW, halfH)
          path.addRoundedRect(
              QRectF(-halfW, -halfH, diameter, diameter), radius, radius)
          painter.setClipPath(path)
          # If it is an animation effect
          if self.rotateAnimation.state() == QPropertyAnimation.Running:
              painter.rotate(self._angle)  # Rotate
              painter.drawPixmap(
                  QPointF(-self.pixmap.width() / 2, -self.pixmap.height() / 2), self.pixmap)
          else:
              painter.drawPixmap(-int(halfW), -int(halfH), self.pixmap)
          # If loading
          if self.loadingTimer.isActive():
              diameter = 2 * self.pradius
              painter.setBrush(
                  QColor(45, 140, 240, (1 - self.pradius / 10) * 255))
              painter.setPen(Qt.NoPen)
              painter.drawRoundedRect(
                  QRectF(-self.pradius, -self.pradius, diameter, diameter), self.pradius, self.pradius)
  
      def enterEvent(self, event):
          """Mouse into the animation
          :param event:
          """
          if not (self.animation and not self.isGif):
              return
          self.rotateAnimation.stop()
          cv = self.rotateAnimation.currentValue() or self.StartAngle
          self.rotateAnimation.setDuration(
              540 if cv == 0 else int(cv / self.EndAngle * 540))
          self.rotateAnimation.setStartValue(cv)
          self.rotateAnimation.setEndValue(self.EndAngle)
          self.rotateAnimation.start()
  
      def leaveEvent(self, event):
          """Mouse leaves animation
          :param event:
          """
          if not (self.animation and not self.isGif):
              return
          self.rotateAnimation.stop()
          cv = self.rotateAnimation.currentValue() or self.EndAngle
          self.rotateAnimation.setDuration(int(cv / self.EndAngle * 540))
          self.rotateAnimation.setStartValue(cv)
          self.rotateAnimation.setEndValue(self.StartAngle)
          self.rotateAnimation.start()
  
      def onLoading(self):
          """Update progress animation
          """
          if self.loadingTimer.isActive():
              if self.pradius > 9:
                  self.pradius = 0
              self.pradius += 1
          else:
              self.pradius = 0
          self.update()
  
      def onFinished(self):
          """Picture download completion
          """
          self.loadingTimer.stop()
          self.pradius = 0
          reply = self.sender()
  
          if self.isGif:
              self._movie = QMovie(reply, b'gif', self)
              if self._movie.isValid():
                  self._movie.frameChanged.connect(self._resizeGifPixmap)
                  self._movie.start()
          else:
              data = reply.readAll().data()
              reply.deleteLater()
              del reply
              self._pixmap.loadFromData(data)
              if self._pixmap.isNull():
                  self._pixmap = QPixmap(self.size())
                  self._pixmap.fill(QColor(204, 204, 204))
              self._resizePixmap()
  
      def onError(self, code):
          """Download an error
          :param code:
          """
          self._pixmap = QPixmap(self.size())
          self._pixmap.fill(QColor(204, 204, 204))
          self._resizePixmap()
  
      def refresh(self):
          """Forced refresh
          """
          self._get(self.url)
  
      def isLoading(self):
          """Judging whether it is loading
          """
          return self.loadingTimer.isActive()
  
      def setShape(self, shape):
          """Shape
          :param shape:        0 = round, 1 = rounded rectangle
          """
          self.shape = shape
  
      def setUrl(self, url):
          """Set URL, can be a local path, or a network address
          :param url:
          """
          self.url = url
          self._get(url)
  
      def setCacheDir(self, cacheDir=''):
          """Set local cache path
          :param cacheDir:
          """
          self.cacheDir = cacheDir
          self._initNetWork()
  
      def setSize(self, size):
          """Set fixed size
          :param size:
          """
          if not isinstance(size, QSize):
              size = self.SizeMedium
          self.setMinimumSize(size)
          self.setMaximumSize(size)
          self._resizePixmap()
  
      @pyqtProperty(int)
      def angle(self):
          return self._angle
  
      @angle.setter
      def angle(self, value):
          self._angle = value
          self.update()
  
      def _resizePixmap(self):
          """Zoom picture
          """
          if not self._pixmap.isNull():
              self.pixmap = self._pixmap.scaled(
                  self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
          self.update()
  
      def _resizeGifPixmap(self, _):
          """Zoom animated picture
          """
          if self._movie:
              self.pixmap = self._movie.currentPixmap().scaled(
                  self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
          self.update()
  
      def _initNetWork(self):
          """Initialization asynchronous network library
          """
          if not hasattr(qApp, '_network'):
              network = QNetworkAccessManager(self.window())
              setattr(qApp, '_network', network)
          # Do you need a cache?
          if self.cacheDir and not qApp._network.cache():
              cache = QNetworkDiskCache(self.window())
              cache.setCacheDirectory(self.cacheDir)
              qApp._network.setCache(cache)
  
      def _get(self, url):
          """Set pictures or request web images
          :param url:
          """
          if not url:
              self.onError('')
              return
          if url.startswith('http') and not self.loadingTimer.isActive():
              url = QUrl(url)
              request = QNetworkRequest(url)
              request.setHeader(QNetworkRequest.UserAgentHeader, b'CAvatar')
              request.setRawHeader(b'Author', b'Irony')
              request.setAttribute(
                  QNetworkRequest.FollowRedirectsAttribute, True)
              if qApp._network.cache():
                  request.setAttribute(
                      QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferNetwork)
                  request.setAttribute(
                      QNetworkRequest.CacheSaveControlAttribute, True)
              reply = qApp._network.get(request)
              self.pradius = 0
              self.loadingTimer.start(50)  # 显示进度动画
              reply.finished.connect(self.onFinished)
              reply.error.connect(self.onError)
              return
          self.pradius = 0
          if os.path.exists(url) and os.path.isfile(url):
              if self.isGif:
                  self._movie = QMovie(url, parent=self)
                  if self._movie.isValid():
                      self._movie.frameChanged.connect(self._resizeGifPixmap)
                      self._movie.start()
              else:
                  self._pixmap = QPixmap(url)
                  self._resizePixmap()
          else:
              self.onError('')
  
  
  if __name__ == '__main__':
      import sys
      from PyQt5.QtWidgets import QApplication
      app = QApplication(sys.argv)
      w = CAvatar()
      w.show()
      sys.exit(app.exec_())
  