Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbd3ff10

Browse files
brunobeltranjklymaktacaswell
committed
BF: MixedModeRenderer scale rasters to "true" bbox
Co-authored-by: Jody Klymak <jklymak@gmail.com>Co-authored-by: Thomas A Caswell <tcaswell@gmail.com>
1 parente631edb commitbd3ff10

19 files changed

+2499
-1172
lines changed

‎lib/matplotlib/backends/backend_mixed.py

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
importnumpyasnp
22

33
frommatplotlibimportcbook
4-
from .backend_aggimportRendererAgg
54
frommatplotlib._tight_bboximportprocess_figure_for_rasterizing
5+
frommatplotlib.backends.backend_aggimportRendererAgg
6+
frommatplotlib.transformsimportBbox,Affine2D,IdentityTransform
67

78

89
classMixedModeRenderer:
@@ -68,6 +69,71 @@ def __getattr__(self, attr):
6869
# to the underlying C implementation).
6970
returngetattr(self._renderer,attr)
7071

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+
iftransformisNone:
82+
transform=IdentityTransform()
83+
bbox=bbox.transformed(transform+Affine2D().scale(
84+
self._figdpi/self.dpi))
85+
ifself._true_bboxisNone:
86+
self._true_bbox=bbox
87+
else:
88+
self._true_bbox=Bbox.union([self._true_bbox,bbox])
89+
90+
defdraw_path(self,gc,path,transform,rgbFace=None):
91+
ifself._rasterizing>0:
92+
bbox=Bbox.null()
93+
bbox.update_from_path(path,ignore=True)
94+
self._update_true_bbox(bbox,transform)
95+
returnself._renderer.draw_path(gc,path,transform,rgbFace)
96+
97+
defdraw_path_collection(self,gc,master_transform,paths,all_transforms,
98+
offsets,offsetTrans,facecolors,edgecolors,
99+
linewidths,linestyles,antialiaseds,urls,
100+
offset_position):
101+
ifself._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+
forpathinpaths:
107+
bbox.update_from_path(path,ignore=False)
108+
self._update_true_bbox(bbox,master_transform)
109+
returnself._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+
defdraw_quad_mesh(self,gc,master_transform,meshWidth,meshHeight,
115+
coordinates,offsets,offsetTrans,facecolors,
116+
antialiased,edgecolors):
117+
ifself._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+
returnself._renderer.draw_quad_mesh(
126+
gc,master_transform,meshWidth,meshHeight,coordinates,
127+
offsets,offsetTrans,facecolors,antialiased,edgecolors)
128+
129+
defdraw_gouraud_triangle(self,gc,points,colors,transform):
130+
ifself._rasterizing>0:
131+
bbox=Bbox.null()
132+
bbox.update_from_data_xy(points,ignore=True)
133+
self._update_true_bbox(bbox,transform)
134+
returnself._renderer.draw_gouraud_triangle(
135+
gc,points,colors,transform)
136+
71137
defstart_rasterizing(self):
72138
"""
73139
Enter "raster" mode. All subsequent drawing commands (until
@@ -83,6 +149,7 @@ def start_rasterizing(self):
83149
self._raster_renderer=self._raster_renderer_class(
84150
self._width*self.dpi,self._height*self.dpi,self.dpi)
85151
self._renderer=self._raster_renderer
152+
self._true_bbox=None
86153

87154
defstop_rasterizing(self):
88155
"""
@@ -92,21 +159,35 @@ def stop_rasterizing(self):
92159
"""
93160

94161
self._renderer=self._vector_renderer
95-
96162
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-
ifcropped_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+
ifself._true_bboxisnotNone:
169+
# raise NotImplementedError(
170+
# "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+
ifcropped_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+
110191
self._raster_renderer=None
111192

112193
# restore the figure dpi.

‎lib/matplotlib/backends/backend_pdf.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,21 +1980,28 @@ def check_gc(self, gc, fillcolor=None):
19801980
defget_image_magnification(self):
19811981
returnself.image_dpi/72.0
19821982

1983-
defdraw_image(self,gc,x,y,im,transform=None):
1983+
defoption_true_bbox_image(self):
1984+
returnTrue
1985+
1986+
defdraw_image(self,gc,x,y,im,transform=None,true_size=None):
19841987
# docstring inherited
19851988

19861989
h,w=im.shape[:2]
19871990
ifw==0orh==0:
19881991
return
19891992

1993+
iftrue_sizeisnotNone:
1994+
w,h=true_size
1995+
19901996
iftransformisNone:
19911997
# If there's no transform, alpha has already been applied
19921998
gc.set_alpha(1.0)
19931999

19942000
self.check_gc(gc)
19952001

1996-
w=72.0*w/self.image_dpi
1997-
h=72.0*h/self.image_dpi
2002+
iftrue_sizeisNone:
2003+
w=72.0*w/self.image_dpi
2004+
h=72.0*h/self.image_dpi
19982005

19992006
imob=self.file.imageObject(im)
20002007

‎lib/matplotlib/backends/backend_pgf.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,13 +605,19 @@ def option_image_nocomposite(self):
605605
# docstring inherited
606606
returnnotmpl.rcParams['image.composite_image']
607607

608-
defdraw_image(self,gc,x,y,im,transform=None):
608+
defoption_true_bbox_image(self):
609+
returnTrue
610+
611+
defdraw_image(self,gc,x,y,im,transform=None,true_size=None):
609612
# docstring inherited
610613

611614
h,w=im.shape[:2]
612615
ifw==0orh==0:
613616
return
614617

618+
iftrue_sizeisnotNone:
619+
w,h=true_size
620+
615621
ifnotos.path.exists(getattr(self.fh,"name","")):
616622
raiseValueError(
617623
"streamed pgf-code does not support raster graphics, consider "

‎lib/matplotlib/backends/backend_ps.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -424,30 +424,41 @@ def _get_clip_cmd(self, gc):
424424
clip.append(f"{custom_clip_cmd}\n")
425425
return"".join(clip)
426426

427+
defoption_true_bbox_image(self):
428+
returnTrue
429+
427430
@_log_if_debug_on
428-
defdraw_image(self,gc,x,y,im,transform=None):
431+
defdraw_image(self,gc,x,y,im,transform=None,true_size=None):
429432
# docstring inherited
430433

431434
h,w=im.shape[:2]
435+
436+
ifh==0orw==0:
437+
return
438+
432439
imagecmd="false 3 colorimage"
433440
data=im[::-1, :, :3]# Vertically flipped rgb values.
434441
hexdata=data.tobytes().hex("\n",-64)# Linewrap to 128 chars.
435442

436443
iftransformisNone:
437444
matrix="1 0 0 1 0 0"
438-
xscale=w/self.image_magnification
439-
yscale=h/self.image_magnification
445+
iftrue_sizeisNone:
446+
xscale=w/self.image_magnification
447+
yscale=h/self.image_magnification
448+
else:
449+
xscale=true_size[0]
450+
yscale=true_size[1]
440451
else:
441-
matrix=" ".join(map(str,transform.frozen().to_values()))
442452
xscale=1.0
443453
yscale=1.0
454+
matrix=" ".join(map(str,transform.frozen().to_values()))
444455

445456
self._pswriter.write(f"""\
446457
gsave
447458
{self._get_clip_cmd(gc)}
448-
{x:g}{y:g} translate
459+
{x:.2f}{y:.2f} translate
449460
[{matrix}] concat
450-
{xscale:g}{yscale:g} scale
461+
{xscale:.2f}{yscale:.2f} scale
451462
/DataString{w:d} string def
452463
{w:d}{h:d} 8 [{w:d} 0 0 -{h:d} 0{h:d} ]
453464
{{

‎lib/matplotlib/backends/backend_svg.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -915,10 +915,13 @@ def option_scale_image(self):
915915
# docstring inherited
916916
returnTrue
917917

918+
defoption_true_bbox_image(self):
919+
returnTrue
920+
918921
defget_image_magnification(self):
919922
returnself.image_dpi/72.0
920923

921-
defdraw_image(self,gc,x,y,im,transform=None):
924+
defdraw_image(self,gc,x,y,im,transform=None,true_size=None):
922925
# docstring inherited
923926

924927
h,w=im.shape[:2]
@@ -960,12 +963,28 @@ def draw_image(self, gc, x, y, im, transform=None):
960963
w=72.0*w/self.image_dpi
961964
h=72.0*h/self.image_dpi
962965

966+
iftrue_sizeisnotNone:
967+
width,height=true_size
968+
# because rasterization happens only for integer pixels, the
969+
# round-trip width w = # int(width/72*image_dpi)*72/image_dpi
970+
# need not match the "real" width
971+
scale_x=width/w
972+
scale_y=height/h
973+
real_h=height
974+
else:
975+
scale_x=1
976+
scale_y=1
977+
real_h=h
978+
963979
self.writer.element(
964980
'image',
965981
transform=_generate_transform([
966-
('scale', (1,-1)), ('translate', (0,-h))]),
982+
('translate',
983+
(x*(1-scale_x),y*(1-scale_y)+real_h)),
984+
('scale', (scale_x,-scale_y))
985+
]),
967986
x=_short_float_fmt(x),
968-
y=_short_float_fmt(-(self.height-y-h)),
987+
y=_short_float_fmt(-(self.height-y-real_h)),
969988
width=_short_float_fmt(w),height=_short_float_fmt(h),
970989
attrib=attrib)
971990
else:
Binary file not shown.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp