1
1
import numpy as np
2
2
3
3
from matplotlib import cbook
4
- from .backend_agg import RendererAgg
5
4
from matplotlib ._tight_bbox import process_figure_for_rasterizing
5
+ from matplotlib .backends .backend_agg import RendererAgg
6
+ from matplotlib .transforms import Bbox , Affine2D , IdentityTransform
6
7
7
8
8
9
class MixedModeRenderer :
@@ -68,6 +69,71 @@ def __getattr__(self, attr):
68
69
# to the underlying C implementation).
69
70
return getattr (self ._renderer , attr )
70
71
72
+ # need to wrap each drawing function that might be called on the rasterized
73
+ # version of the renderer to save what the "true" bbox is for scaling the
74
+ # output correctly
75
+ # the functions we might want to overwrite are:
76
+ # `draw_path`, `draw_image`, `draw_gouraud_triangle`, `draw_text`,
77
+ # `draw_markers`, `draw_path_collection`, `draw_quad_mesh`
78
+
79
+ def _update_true_bbox (self , bbox , transform = None ):
80
+ """Convert to real units and update"""
81
+ if transform is None :
82
+ transform = IdentityTransform ()
83
+ bbox = bbox .transformed (transform + Affine2D ().scale (
84
+ self ._figdpi / self .dpi ))
85
+ if self ._true_bbox is None :
86
+ self ._true_bbox = bbox
87
+ else :
88
+ self ._true_bbox = Bbox .union ([self ._true_bbox , bbox ])
89
+
90
+ def draw_path (self , gc , path , transform , rgbFace = None ):
91
+ if self ._rasterizing > 0 :
92
+ bbox = Bbox .null ()
93
+ bbox .update_from_path (path , ignore = True )
94
+ self ._update_true_bbox (bbox , transform )
95
+ return self ._renderer .draw_path (gc , path , transform , rgbFace )
96
+
97
+ def draw_path_collection (self , gc , master_transform , paths , all_transforms ,
98
+ offsets , offsetTrans , facecolors , edgecolors ,
99
+ linewidths , linestyles , antialiaseds , urls ,
100
+ offset_position ):
101
+ if self ._rasterizing > 0 :
102
+ bbox = Bbox .null ()
103
+ # TODO probably faster to merge all coordinates from path using
104
+ # numpy for large lists of paths, such as the one produced by the
105
+ # test case tests/test_backed_pgf.py:test_mixed_mode
106
+ for path in paths :
107
+ bbox .update_from_path (path , ignore = False )
108
+ self ._update_true_bbox (bbox , master_transform )
109
+ return self ._renderer .draw_path_collection (
110
+ gc , master_transform , paths , all_transforms , offsets ,
111
+ offsetTrans , facecolors , edgecolors , linewidths , linestyles ,
112
+ antialiaseds , urls , offset_position )
113
+
114
+ def draw_quad_mesh (self , gc , master_transform , meshWidth , meshHeight ,
115
+ coordinates , offsets , offsetTrans , facecolors ,
116
+ antialiased , edgecolors ):
117
+ if self ._rasterizing > 0 :
118
+ # TODO should check if this is always Bbox.unit for efficiency
119
+ bbox = Bbox .null ()
120
+ cshape = coordinates .shape
121
+ flat_coords = coordinates .reshape ((cshape [0 ]* cshape [1 ], cshape [2 ]))
122
+ bbox .update_from_data_xy (flat_coords , ignore = True )
123
+ self ._update_true_bbox (bbox , master_transform )
124
+
125
+ return self ._renderer .draw_quad_mesh (
126
+ gc , master_transform , meshWidth , meshHeight , coordinates ,
127
+ offsets , offsetTrans , facecolors , antialiased , edgecolors )
128
+
129
+ def draw_gouraud_triangle (self , gc , points , colors , transform ):
130
+ if self ._rasterizing > 0 :
131
+ bbox = Bbox .null ()
132
+ bbox .update_from_data_xy (points , ignore = True )
133
+ self ._update_true_bbox (bbox , transform )
134
+ return self ._renderer .draw_gouraud_triangle (
135
+ gc , points , colors , transform )
136
+
71
137
def start_rasterizing (self ):
72
138
"""
73
139
Enter "raster" mode. All subsequent drawing commands (until
@@ -83,6 +149,7 @@ def start_rasterizing(self):
83
149
self ._raster_renderer = self ._raster_renderer_class (
84
150
self ._width * self .dpi , self ._height * self .dpi , self .dpi )
85
151
self ._renderer = self ._raster_renderer
152
+ self ._true_bbox = None
86
153
87
154
def stop_rasterizing (self ):
88
155
"""
@@ -92,21 +159,35 @@ def stop_rasterizing(self):
92
159
"""
93
160
94
161
self ._renderer = self ._vector_renderer
95
-
96
162
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 ])
163
+ # these bounds are in pixels, relative to the figure when pixelated
164
+ # at the requested DPI. However, the vectorized backends draw at a
165
+ # fixed DPI of 72, and typically aren't snapped to the
166
+ # requested-DPI pixel grid, so we have to grab the actual bounds to
167
+ # put the image into some other way
168
+ if self ._true_bbox is not None :
169
+ # raise NotImplementedError(
170
+ # "
F987
;Something was drawn using a method not wrapped by "
171
+ # "MixedModeRenderer.")
172
+ img = np .asarray (self ._raster_renderer .buffer_rgba ())
173
+ slice_y , slice_x = cbook ._get_nonzero_slices (img [..., 3 ])
174
+ cropped_img = img [slice_y , slice_x ]
175
+ if cropped_img .size :
176
+ gc = self ._renderer .new_gc ()
177
+ # TODO: If the mixedmode resolution differs from the figure's
178
+ # dpi, the image must be scaled (dpi->_figdpi). Not all
179
+ # 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
185
+
186
+ self ._renderer .draw_image (
187
+ gc , self ._true_bbox .x0 , self ._true_bbox .y0 , cropped_img [::- 1 ],
188
+ true_size = (self ._true_bbox .width , self ._true_bbox .height )
189
+ )
190
+
110
191
self ._raster_renderer = None
111
192
112
193
# restore the figure dpi.
0 commit comments