backend_mixed.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import numpy as np
  2. from matplotlib import cbook
  3. from .backend_agg import RendererAgg
  4. from matplotlib._tight_bbox import process_figure_for_rasterizing
  5. class MixedModeRenderer:
  6. """
  7. A helper class to implement a renderer that switches between
  8. vector and raster drawing. An example may be a PDF writer, where
  9. most things are drawn with PDF vector commands, but some very
  10. complex objects, such as quad meshes, are rasterised and then
  11. output as images.
  12. """
  13. def __init__(self, figure, width, height, dpi, vector_renderer,
  14. raster_renderer_class=None,
  15. bbox_inches_restore=None):
  16. """
  17. Parameters
  18. ----------
  19. figure : `~matplotlib.figure.Figure`
  20. The figure instance.
  21. width : scalar
  22. The width of the canvas in logical units
  23. height : scalar
  24. The height of the canvas in logical units
  25. dpi : float
  26. The dpi of the canvas
  27. vector_renderer : `~matplotlib.backend_bases.RendererBase`
  28. An instance of a subclass of
  29. `~matplotlib.backend_bases.RendererBase` that will be used for the
  30. vector drawing.
  31. raster_renderer_class : `~matplotlib.backend_bases.RendererBase`
  32. The renderer class to use for the raster drawing. If not provided,
  33. this will use the Agg backend (which is currently the only viable
  34. option anyway.)
  35. """
  36. if raster_renderer_class is None:
  37. raster_renderer_class = RendererAgg
  38. self._raster_renderer_class = raster_renderer_class
  39. self._width = width
  40. self._height = height
  41. self.dpi = dpi
  42. self._vector_renderer = vector_renderer
  43. self._raster_renderer = None
  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.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 backend.
  63. """
  64. # change the dpi of the figure temporarily.
  65. self.figure.dpi = self.dpi
  66. if self._bbox_inches_restore: # when tight bbox is used
  67. r = process_figure_for_rasterizing(self.figure,
  68. self._bbox_inches_restore)
  69. self._bbox_inches_restore = r
  70. self._raster_renderer = self._raster_renderer_class(
  71. self._width*self.dpi, self._height*self.dpi, self.dpi)
  72. self._renderer = self._raster_renderer
  73. def stop_rasterizing(self):
  74. """
  75. Exit "raster" mode. All of the drawing that was done since
  76. the last `start_rasterizing` call will be copied to the
  77. vector backend by calling draw_image.
  78. """
  79. self._renderer = self._vector_renderer
  80. height = self._height * self.dpi
  81. img = np.asarray(self._raster_renderer.buffer_rgba())
  82. slice_y, slice_x = cbook._get_nonzero_slices(img[..., 3])
  83. cropped_img = img[slice_y, slice_x]
  84. if cropped_img.size:
  85. gc = self._renderer.new_gc()
  86. # TODO: If the mixedmode resolution differs from the figure's
  87. # dpi, the image must be scaled (dpi->_figdpi). Not all
  88. # backends support this.
  89. self._renderer.draw_image(
  90. gc,
  91. slice_x.start * self._figdpi / self.dpi,
  92. (height - slice_y.stop) * self._figdpi / self.dpi,
  93. cropped_img[::-1])
  94. self._raster_renderer = None
  95. # restore the figure dpi.
  96. self.figure.dpi = self._figdpi
  97. if self._bbox_inches_restore: # when tight bbox is used
  98. r = process_figure_for_rasterizing(self.figure,
  99. self._bbox_inches_restore,
  100. self._figdpi)
  101. self._bbox_inches_restore = r