backend_qt5agg.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. """
  2. Render to qt from agg.
  3. """
  4. import ctypes
  5. from matplotlib.transforms import Bbox
  6. from .. import cbook
  7. from .backend_agg import FigureCanvasAgg
  8. from .backend_qt5 import (
  9. QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT,
  10. NavigationToolbar2QT, backend_version)
  11. from .qt_compat import QT_API
  12. class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
  13. def __init__(self, figure):
  14. # Must pass 'figure' as kwarg to Qt base class.
  15. super().__init__(figure=figure)
  16. def paintEvent(self, event):
  17. """
  18. Copy the image from the Agg canvas to the qt.drawable.
  19. In Qt, all drawing should be done inside of here when a widget is
  20. shown onscreen.
  21. """
  22. if self._update_dpi():
  23. # The dpi update triggered its own paintEvent.
  24. return
  25. self._draw_idle() # Only does something if a draw is pending.
  26. # If the canvas does not have a renderer, then give up and wait for
  27. # FigureCanvasAgg.draw(self) to be called.
  28. if not hasattr(self, 'renderer'):
  29. return
  30. painter = QtGui.QPainter(self)
  31. # See documentation of QRect: bottom() and right() are off by 1, so use
  32. # left() + width() and top() + height().
  33. rect = event.rect()
  34. # scale rect dimensions using the screen dpi ratio to get
  35. # correct values for the Figure coordinates (rather than QT5's coords)
  36. width = rect.width() * self._dpi_ratio
  37. height = rect.height() * self._dpi_ratio
  38. left, top = self.mouseEventCoords(rect.topLeft())
  39. # shift the "top" by the height of the image to get the
  40. # correct corner for our coordinate system
  41. bottom = top - height
  42. # same with the right side of the image
  43. right = left + width
  44. # create a buffer using the image bounding box
  45. bbox = Bbox([[left, bottom], [right, top]])
  46. reg = self.copy_from_bbox(bbox)
  47. buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(
  48. memoryview(reg))
  49. # clear the widget canvas
  50. painter.eraseRect(rect)
  51. qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
  52. QtGui.QImage.Format_ARGB32_Premultiplied)
  53. if hasattr(qimage, 'setDevicePixelRatio'):
  54. # Not available on Qt4 or some older Qt5.
  55. qimage.setDevicePixelRatio(self._dpi_ratio)
  56. # set origin using original QT coordinates
  57. origin = QtCore.QPoint(rect.left(), rect.top())
  58. painter.drawImage(origin, qimage)
  59. # Adjust the buf reference count to work around a memory
  60. # leak bug in QImage under PySide on Python 3.
  61. if QT_API in ('PySide', 'PySide2'):
  62. ctypes.c_long.from_address(id(buf)).value = 1
  63. self._draw_rect_callback(painter)
  64. painter.end()
  65. def blit(self, bbox=None):
  66. # docstring inherited
  67. # If bbox is None, blit the entire canvas. Otherwise
  68. # blit only the area defined by the bbox.
  69. if bbox is None and self.figure:
  70. bbox = self.figure.bbox
  71. # repaint uses logical pixels, not physical pixels like the renderer.
  72. l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds]
  73. t = b + h
  74. self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h)
  75. def print_figure(self, *args, **kwargs):
  76. super().print_figure(*args, **kwargs)
  77. self.draw()
  78. @_BackendQT5.export
  79. class _BackendQT5Agg(_BackendQT5):
  80. FigureCanvas = FigureCanvasQTAgg