22
33from matplotlib .backends .backend_agg import RendererAgg
44from matplotlib .tight_bbox import process_figure_for_rasterizing
5+ from matplotlib .transforms import Bbox , Affine2D , IdentityTransform
56
67
78class MixedModeRenderer :
@@ -73,6 +74,56 @@ def __getattr__(self, attr):
7374 # to the underlying C implementation).
7475 return getattr (self ._renderer , attr )
7576
77+ # need to wrap each drawing function that might be called on the rasterized
78+ # version of the renderer to save what the "true" bbox is for scaling the
79+ # output correctly
80+ # the functions we might want to overwrite are:
81+ # `draw_path`, `draw_image`, `draw_gouraud_triangle`, `draw_text`,
82+ # `draw_markers`, `draw_path_collection`, `draw_quad_mesh`
83+
84+ def _update_true_bbox (self , bbox , transform = None ):
85+ """Convert to real units and update"""
86+ if transform is None :
87+ transform = IdentityTransform ()
88+ bbox = bbox .transformed (transform + Affine2D ().scale (
89+ self ._figdpi / self .dpi ))
90+ if self ._true_bbox is None :
91+ self ._true_bbox = bbox
92+ else :
93+ self ._true_bbox = Bbox .union ([self ._true_bbox , bbox ])
94+
95+ def draw_path_collection (self , gc , master_transform , paths , all_transforms ,
96+ offsets , offsetTrans , facecolors , edgecolors ,
97+ linewidths , linestyles , antialiaseds , urls ,
98+ offset_position ):
99+ if self ._rasterizing > 0 :
100+ bbox = Bbox .null ()
101+ #TODO probably faster to merge all coordinates from path using
102+ # numpy for large lists of paths, such as the one produced by the
103+ # test case tests/test_backed_pgf.py:test_mixed_mode
104+ for path in paths :
105+ bbox .update_from_path (path , ignore = False )
106+ self ._update_true_bbox (bbox , master_transform )
107+ return self ._renderer .draw_path_collection (
108+ gc , master_transform , paths , all_transforms , offsets ,
109+ offsetTrans , facecolors , edgecolors , linewidths , linestyles ,
110+ antialiaseds , urls , offset_position )
111+
112+ def draw_quad_mesh (self , gc , master_transform , meshWidth , meshHeight ,
113+ coordinates , offsets , offsetTrans , facecolors ,
114+ antialiased , edgecolors ):
115+ if self ._rasterizing > 0 :
116+ #TODO should check if this is always Bbox.unit for efficiency
117+ bbox = Bbox .null ()
118+ cshape = coordinates .shape
119+ flat_coords = coordinates .reshape ((cshape [0 ]* cshape [1 ], cshape [2 ]))
120+ bbox .update_from_data_xy (flat_coords , ignore = True )
121+ self ._update_true_bbox (bbox , master_transform )
122+
123+ return self ._renderer .draw_quad_mesh (
124+ gc , master_transform , meshWidth , meshHeight , coordinates ,
125+ offsets , offsetTrans , facecolors , antialiased , edgecolors )
126+
76127 def start_rasterizing (self ):
77128 """
78129 Enter "raster" mode. All subsequent drawing commands (until
@@ -88,6 +139,7 @@ def start_rasterizing(self):
88139 self ._raster_renderer = self ._raster_renderer_class (
89140 self ._width * self .dpi , self ._height * self .dpi , self .dpi )
90141 self ._renderer = self ._raster_renderer
142+ self ._true_bbox = None
91143 self ._rasterizing += 1
92144
93145 def stop_rasterizing (self ):
@@ -105,21 +157,36 @@ def stop_rasterizing(self):
105157 self ._renderer = self ._vector_renderer
106158
107159 height = self ._height * self .dpi
160+ # these bounds are in pixels, relative to the figure when pixelated
161+ # at the requested DPI. However, the vectorized backends draw at a
162+ # fixed DPI of 72, and typically aren't snapped to the
163+ # requested-DPI pixel grid, so we have to grab the actual bounds to
164+ # put the image into some other way
108165 buffer , bounds = self ._raster_renderer .tostring_rgba_minimized ()
109166 l , b , w , h = bounds
110167 if w > 0 and h > 0 :
168+ if self ._true_bbox is None :
169+ raise NotImplementedError (
170+ "Something was drawn using a method not wrapped by "
171+ "MixedModeRenderer." )
111172 image = np .frombuffer (buffer , dtype = np .uint8 )
112173 image = image .reshape ((h , w , 4 ))
113174 image = image [::- 1 ]
175+
114176 gc = self ._renderer .new_gc ()
115177 # TODO: If the mixedmode resolution differs from the figure's
116178 # dpi, the image must be scaled (dpi->_figdpi). Not all
117179 # backends support this.
180+ # because rasterizing will have rounded size to nearest
181+ # pixel, we need to rescale our drawing to fit the original
182+ # intended Bbox. This results in a slightly different DPI than
183+ # requested, but that's better than the drawing not fitting
184+ # into the space requested, see Issue #6827
118185 self ._renderer .draw_image (
119186 gc ,
120- l * self ._figdpi / self . dpi ,
121- ( height - b - h ) * self ._figdpi / self . dpi ,
122- image )
187+ self ._true_bbox . x0 ,
188+ self ._true_bbox . y0 ,
189+ image , bbox = self . _true_bbox<
D0E4
/span>)
123190 self ._raster_renderer = None
124191 self ._rasterizing = False
125192
0 commit comments