Coverage for /usr/lib/python3/dist-packages/matplotlib/backends/backend_mixed.py: 54%

41 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1import numpy as np 

2 

3from matplotlib import cbook 

4from .backend_agg import RendererAgg 

5from matplotlib._tight_bbox import process_figure_for_rasterizing 

6 

7 

8class MixedModeRenderer: 

9 """ 

10 A helper class to implement a renderer that switches between 

11 vector and raster drawing. An example may be a PDF writer, where 

12 most things are drawn with PDF vector commands, but some very 

13 complex objects, such as quad meshes, are rasterised and then 

14 output as images. 

15 """ 

16 def __init__(self, figure, width, height, dpi, vector_renderer, 

17 raster_renderer_class=None, 

18 bbox_inches_restore=None): 

19 """ 

20 Parameters 

21 ---------- 

22 figure : `matplotlib.figure.Figure` 

23 The figure instance. 

24 width : scalar 

25 The width of the canvas in logical units 

26 height : scalar 

27 The height of the canvas in logical units 

28 dpi : float 

29 The dpi of the canvas 

30 vector_renderer : `matplotlib.backend_bases.RendererBase` 

31 An instance of a subclass of 

32 `~matplotlib.backend_bases.RendererBase` that will be used for the 

33 vector drawing. 

34 raster_renderer_class : `matplotlib.backend_bases.RendererBase` 

35 The renderer class to use for the raster drawing. If not provided, 

36 this will use the Agg backend (which is currently the only viable 

37 option anyway.) 

38 

39 """ 

40 if raster_renderer_class is None: 

41 raster_renderer_class = RendererAgg 

42 

43 self._raster_renderer_class = raster_renderer_class 

44 self._width = width 

45 self._height = height 

46 self.dpi = dpi 

47 

48 self._vector_renderer = vector_renderer 

49 

50 self._raster_renderer = None 

51 

52 # A reference to the figure is needed as we need to change 

53 # the figure dpi before and after the rasterization. Although 

54 # this looks ugly, I couldn't find a better solution. -JJL 

55 self.figure = figure 

56 self._figdpi = figure.dpi 

57 

58 self._bbox_inches_restore = bbox_inches_restore 

59 

60 self._renderer = vector_renderer 

61 

62 def __getattr__(self, attr): 

63 # Proxy everything that hasn't been overridden to the base 

64 # renderer. Things that *are* overridden can call methods 

65 # on self._renderer directly, but must not cache/store 

66 # methods (because things like RendererAgg change their 

67 # methods on the fly in order to optimise proxying down 

68 # to the underlying C implementation). 

69 return getattr(self._renderer, attr) 

70 

71 def start_rasterizing(self): 

72 """ 

73 Enter "raster" mode. All subsequent drawing commands (until 

74 `stop_rasterizing` is called) will be drawn with the raster backend. 

75 """ 

76 # change the dpi of the figure temporarily. 

77 self.figure.dpi = self.dpi 

78 if self._bbox_inches_restore: # when tight bbox is used 

79 r = process_figure_for_rasterizing(self.figure, 

80 self._bbox_inches_restore) 

81 self._bbox_inches_restore = r 

82 

83 self._raster_renderer = self._raster_renderer_class( 

84 self._width*self.dpi, self._height*self.dpi, self.dpi) 

85 self._renderer = self._raster_renderer 

86 

87 def stop_rasterizing(self): 

88 """ 

89 Exit "raster" mode. All of the drawing that was done since 

90 the last `start_rasterizing` call will be copied to the 

91 vector backend by calling draw_image. 

92 """ 

93 

94 self._renderer = self._vector_renderer 

95 

96 height = self._height * self.dpi 

97 img = np.asarray(self._raster_renderer.buffer_rgba()) 

98 slice_y, slice_x = cbook._get_nonzero_slices(img[..., 3]) 

99 cropped_img = img[slice_y, slice_x] 

100 if cropped_img.size: 

101 gc = self._renderer.new_gc() 

102 # TODO: If the mixedmode resolution differs from the figure's 

103 # dpi, the image must be scaled (dpi->_figdpi). Not all 

104 # backends support this. 

105 self._renderer.draw_image( 

106 gc, 

107 slice_x.start * self._figdpi / self.dpi, 

108 (height - slice_y.stop) * self._figdpi / self.dpi, 

109 cropped_img[::-1]) 

110 self._raster_renderer = None 

111 

112 # restore the figure dpi. 

113 self.figure.dpi = self._figdpi 

114 

115 if self._bbox_inches_restore: # when tight bbox is used 

116 r = process_figure_for_rasterizing(self.figure, 

117 self._bbox_inches_restore, 

118 self._figdpi) 

119 self._bbox_inches_restore = r