Skip to content

Commit 2d37c64

Browse files
committed
Fix pixel alignment for PcolorImage and NonUniformImage
Calculations now take into account that rendering will be to the nearest display pixel.
1 parent d9d7d15 commit 2d37c64

1 file changed

Lines changed: 28 additions & 23 deletions

File tree

lib/matplotlib/image.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,21 +1055,24 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
10551055
B[:, :, 0:3] = A
10561056
B[:, :, 3] = 255
10571057
A = B
1058-
l, b, r, t = self.axes.bbox.extents
1059-
width = int(((round(r) + 0.5) - (round(l) - 0.5)) * magnification)
1060-
height = int(((round(t) + 0.5) - (round(b) - 0.5)) * magnification)
1058+
magnified_extents = (self.axes.bbox.extents * magnification + 0.5).astype(int)
1059+
l, b, r, t = magnified_extents / magnification
1060+
width = int((r - l) * magnification)
1061+
height = int((t - b) * magnification)
10611062

10621063
invertedTransform = self.axes.transData.inverted()
1063-
x_pix = invertedTransform.transform(
1064-
[(x, b) for x in np.linspace(l, r, width)])[:, 0]
1065-
y_pix = invertedTransform.transform(
1066-
[(l, y) for y in np.linspace(b, t, height)])[:, 1]
1064+
x_pix_edges = invertedTransform.transform(
1065+
[(x, b) for x in np.linspace(l, r, width + 1)])[:, 0]
1066+
y_pix_edges = invertedTransform.transform(
1067+
[(l, y) for y in np.linspace(b, t, height + 1)])[:, 1]
1068+
x_pix_centers = (x_pix_edges[:-1] + x_pix_edges[1:]) / 2
1069+
y_pix_centers = (y_pix_edges[:-1] + y_pix_edges[1:]) / 2
10671070

10681071
if self._interpolation == "nearest":
10691072
x_mid = (self._Ax[:-1] + self._Ax[1:]) / 2
10701073
y_mid = (self._Ay[:-1] + self._Ay[1:]) / 2
1071-
x_int = x_mid.searchsorted(x_pix)
1072-
y_int = y_mid.searchsorted(y_pix)
1074+
x_int = x_mid.searchsorted(x_pix_centers)
1075+
y_int = y_mid.searchsorted(y_pix_centers)
10731076
# The following is equal to `A[y_int[:, None], x_int[None, :]]`,
10741077
# but many times faster. Both casting to uint32 (to have an
10751078
# effectively 1D array) and manual index flattening matter.
@@ -1080,16 +1083,16 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
10801083
else: # self._interpolation == "bilinear"
10811084
# Use np.interp to compute x_int/x_float has similar speed.
10821085
x_int = np.clip(
1083-
self._Ax.searchsorted(x_pix) - 1, 0, len(self._Ax) - 2)
1086+
self._Ax.searchsorted(x_pix_centers) - 1, 0, len(self._Ax) - 2)
10841087
y_int = np.clip(
1085-
self._Ay.searchsorted(y_pix) - 1, 0, len(self._Ay) - 2)
1088+
self._Ay.searchsorted(y_pix_centers) - 1, 0, len(self._Ay) - 2)
10861089
idx_int = np.add.outer(y_int * A.shape[1], x_int)
10871090
x_frac = np.clip(
1088-
np.divide(x_pix - self._Ax[x_int], np.diff(self._Ax)[x_int],
1091+
np.divide(x_pix_centers - self._Ax[x_int], np.diff(self._Ax)[x_int],
10891092
dtype=np.float32), # Downcasting helps with speed.
10901093
0, 1)
10911094
y_frac = np.clip(
1092-
np.divide(y_pix - self._Ay[y_int], np.diff(self._Ay)[y_int],
1095+
np.divide(y_pix_centers - self._Ay[y_int], np.diff(self._Ay)[y_int],
10931096
dtype=np.float32),
10941097
0, 1)
10951098
f00 = np.outer(1 - y_frac, 1 - x_frac)
@@ -1242,22 +1245,24 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
12421245
if (padded_A[0, 0] != bg).all():
12431246
padded_A[[0, -1], :] = padded_A[:, [0, -1]] = bg
12441247

1245-
l, b, r, t = self.axes.bbox.extents
1246-
width = (round(r) + 0.5) - (round(l) - 0.5)
1247-
height = (round(t) + 0.5) - (round(b) - 0.5)
1248-
width = round(width * magnification)
1249-
height = round(height * magnification)
1248+
# Round to the nearest output pixels after magnification
1249+
l, b, r, t = (self.axes.bbox.extents * magnification + 0.5).astype(int)
1250+
width = r - l
1251+
height = t - b
1252+
12501253
vl = self.axes.viewLim
12511254

1252-
x_pix = np.linspace(vl.x0, vl.x1, width)
1253-
y_pix = np.linspace(vl.y0, vl.y1, height)
1254-
x_int = self._Ax.searchsorted(x_pix)
1255-
y_int = self._Ay.searchsorted(y_pix)
1255+
x_pix_edges = np.linspace(vl.x0, vl.x1, width + 1)
1256+
y_pix_edges = np.linspace(vl.y0, vl.y1, height + 1)
1257+
x_pix_centers = (x_pix_edges[:-1] + x_pix_edges[1:]) / 2
1258+
y_pix_centers = (y_pix_edges[:-1] + y_pix_edges[1:]) / 2
1259+
x_int = self._Ax.searchsorted(x_pix_centers)
1260+
y_int = self._Ay.searchsorted(y_pix_centers)
12561261
im = ( # See comment in NonUniformImage.make_image re: performance.
12571262
padded_A.view(np.uint32).ravel()[
12581263
np.add.outer(y_int * padded_A.shape[1], x_int)]
12591264
.view(np.uint8).reshape((height, width, 4)))
1260-
return im, l, b, IdentityTransform()
1265+
return im, l / magnification, b / magnification, IdentityTransform()
12611266

12621267
def _check_unsampled_image(self):
12631268
return False

0 commit comments

Comments
 (0)