backend_mixed.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import numpy as np
  2. from matplotlib.backends.backend_agg import RendererAgg
  3. from matplotlib.tight_bbox import process_figure_for_rasterizing
  4. class MixedModeRenderer:
  5. """
  6. A helper class to implement a renderer that switches between
  7. vector and raster drawing. An example may be a PDF writer, where
  8. most things are drawn with PDF vector commands, but some very
  9. complex objects, such as quad meshes, are rasterised and then
  10. output as images.
  11. """
  12. def __init__(self, figure, width, height, dpi, vector_renderer,
  13. raster_renderer_class=None,
  14. bbox_inches_restore=None):
  15. """
  16. Parameters
  17. ----------
  18. figure : `matplotlib.figure.Figure`
  19. The figure instance.
  20. width : scalar
  21. The width of the canvas in logical units
  22. height : scalar
  23. The height of the canvas in logical units
  24. dpi : scalar
  25. The dpi of the canvas
  26. vector_renderer : `matplotlib.backend_bases.RendererBase`
  27. An instance of a subclass of
  28. `~matplotlib.backend_bases.RendererBase` that will be used for the
  29. vector drawing.
  30. raster_renderer_class : `matplotlib.backend_bases.RendererBase`
  31. The renderer class to use for the raster drawing. If not provided,
  32. this will use the Agg backend (which is currently the only viable
  33. option anyway.)
  34. """
  35. if raster_renderer_class is None:
  36. raster_renderer_class = RendererAgg
  37. self._raster_renderer_class = raster_renderer_class
  38. self._width = width
  39. self._height = height
  40. self.dpi = dpi
  41. self._vector_renderer = vector_renderer
  42. self._raster_renderer = None
  43. self._rasterizing = 0
  44. # A reference to the figure is needed as we need to change
  45. # the figure dpi before and after the rasterization. Although
  46. # this looks ugly, I couldn't find a better solution. -JJL
  47. self.figure = figure
  48. self._figdpi = figure.get_dpi()
  49. self._bbox_inches_restore = bbox_inches_restore
  50. self._renderer = vector_renderer
  51. def __getattr__(self, attr):
  52. # Proxy everything that hasn't been overridden to the base
  53. # renderer. Things that *are* overridden can call methods
  54. # on self._renderer directly, but must not cache/store
  55. # methods (because things like RendererAgg change their
  56. # methods on the fly in order to optimise proxying down
  57. # to the underlying C implementation).
  58. return getattr(self._renderer, attr)
  59. def start_rasterizing(self):
  60. """
  61. Enter "raster" mode. All subsequent drawing commands (until
  62. stop_rasterizing is called) will be drawn with the raster
  63. backend.
  64. If start_rasterizing is called multiple times before
  65. stop_rasterizing is called, this method has no effect.
  66. """
  67. # change the dpi of the figure temporarily.
  68. self.figure.set_dpi(self.dpi)
  69. if self._bbox_inches_restore: # when tight bbox is used
  70. r = process_figure_for_rasterizing(self.figure,
  71. self._bbox_inches_restore)
  72. self._bbox_inches_restore = r
  73. if self._rasterizing == 0:
  74. self._raster_renderer = self._raster_renderer_class(
  75. self._width*self.dpi, self._height*self.dpi, self.dpi)
  76. self._renderer = self._raster_renderer
  77. self._rasterizing += 1
  78. def stop_rasterizing(self):
  79. """
  80. Exit "raster" mode. All of the drawing that was done since
  81. the last start_rasterizing command will be copied to the
  82. vector backend by calling draw_image.
  83. If stop_rasterizing is called multiple times before
  84. start_rasterizing is called, this method has no effect.
  85. """
  86. self._rasterizing -= 1
  87. if self._rasterizing == 0:
  88. self._renderer = self._vector_renderer
  89. height = self._height * self.dpi
  90. buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
  91. l, b, w, h = bounds
  92. if w > 0 and h > 0:
  93. image = np.frombuffer(buffer, dtype=np.uint8)
  94. image = image.reshape((h, w, 4))
  95. image = image[::-1]
  96. gc = self._renderer.new_gc()
  97. # TODO: If the mixedmode resolution differs from the figure's
  98. # dpi, the image must be scaled (dpi->_figdpi). Not all
  99. # backends support this.
  100. self._renderer.draw_image(
  101. gc,
  102. l * self._figdpi / self.dpi,
  103. (height-b-h) * self._figdpi / self.dpi,
  104. image)
  105. self._raster_renderer = None
  106. self._rasterizing = False
  107. # restore the figure dpi.
  108. self.figure.set_dpi(self._figdpi)
  109. if self._bbox_inches_restore: # when tight bbox is used
  110. r = process_figure_for_rasterizing(self.figure,
  111. self._bbox_inches_restore,
  112. self._figdpi)
  113. self._bbox_inches_restore = r