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

Appearance settings

Commit 5b74696

Browse filesBrowse files
committed
Merge pull request #5718 from mdboom/image-interpolation
Rewrite of image infrastructure
2 parents 18169b2 + c9b2425 commit 5b74696
Copy full SHA for 5b74696

File tree

153 files changed

+13997
-12750
lines changed
Filter options

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner

153 files changed

+13997
-12750
lines changed
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Change in the ``draw_image`` backend API
2+
----------------------------------------
3+
4+
The ``draw_image`` method implemented by backends has changed its interface.
5+
6+
This change is only relevant if the backend declares that it is able
7+
to transform images by returning ``True`` from ``option_scale_image``.
8+
See the ``draw_image`` docstring for more information.
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Improved image support
2+
----------------------
3+
4+
Prior to version 2.0, matplotlib resampled images by first applying
5+
the color map and then resizing the result. Since the resampling was
6+
performed on the colored image, this introduced colors in the output
7+
image that didn't actually exist in the color map. Now, images are
8+
resampled first (and entirely in floating-point, if the input image is
9+
floating-point), and then the color map is applied.
10+
11+
In order to make this important change, the image handling code was
12+
almost entirely rewritten. As a side effect, image resampling uses
13+
less memory and fewer datatype conversions than before.
14+
15+
The experimental private feature where one could "skew" an image by
16+
setting the private member ``_image_skew_coordinate`` has been
17+
removed. Instead, images will obey the transform of the axes on which
18+
they are drawn.

‎doc/users/whats_new/rcparams.rst

Copy file name to clipboardExpand all lines: doc/users/whats_new/rcparams.rst
+12-2Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,20 @@ Configuration (rcParams)
1919
|`svg.hashsalt` | see note |
2020
+----------------------------+--------------------------------------------------+
2121

22-
``svg.hashsalt``
23-
````````````````
22+
Added ``svg.hashsalt`` key to rcParams
23+
```````````````````````````````````````
2424

2525
If ``svg.hashsalt`` is ``None`` (which it is by default), the svg
2626
backend uses ``uuid4`` to generate the hash salt. If it is not
2727
``None``, it must be a string that is used as the hash salt instead of
2828
``uuid4``. This allows for deterministic SVG output.
29+
30+
31+
Removed the ``svg.image_noscale`` rcParam
32+
`````````````````````````````````````````
33+
34+
As a result of the extensive changes to image handling, the
35+
``svg.image_noscale`` rcParam has been removed. The same
36+
functionality may be achieved by setting ``interpolation='none'`` on
37+
individual images or globally using the ``image.interpolation``
38+
rcParam.

‎examples/api/demo_affine_image.py

Copy file name to clipboard
100755100644
+4-24Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
#!/usr/bin/env python
2-
3-
41
"""
52
For the backends that supports draw_image with optional affine
63
transform (e.g., agg, ps backend), the image of the output should
@@ -24,22 +21,15 @@ def get_image():
2421
return Z
2522

2623

27-
def imshow_affine(ax, z, *kl, **kwargs):
28-
im = ax.imshow(z, *kl, **kwargs)
29-
x1, x2, y1, y2 = im.get_extent()
30-
im._image_skew_coordinate = (x2, y1)
31-
return im
32-
33-
3424
if 1:
3525

3626
# image rotation
3727

38-
fig, (ax1, ax2) = plt.subplots(1, 2)
28+
fig, ax1 = plt.subplots(1, 1)
3929
Z = get_image()
40-
im1 = imshow_affine(ax1, Z, interpolation='none',
41-
origin='lower',
42-
extent=[-2, 4, -3, 2], clip_on=True)
30+
im1 = ax1.imshow(Z, interpolation='none',
31+
origin='lower',
32+
extent=[-2, 4, -3, 2], clip_on=True)
4333

4434
trans_data2 = mtransforms.Affine2D().rotate_deg(30) + ax1.transData
4535
im1.set_transform(trans_data2)
@@ -53,13 +43,3 @@ def imshow_affine(ax, z, *kl, **kwargs):
5343

5444
ax1.set_xlim(-3, 5)
5545
ax1.set_ylim(-4, 4)
56-
57-
# image skew
58-
59-
im2 = ax2.imshow(Z, interpolation='none',
60-
origin='lower',
61-
extent=[-2, 4, -3, 2], clip_on=True)
62-
im2._image_skew_coordinate = (3, -2)
63-
64-
plt.show()
65-
#plt.savefig("demo_affine_image")

‎examples/images_contours_and_fields/interpolation_methods.py

Copy file name to clipboardExpand all lines: examples/images_contours_and_fields/interpolation_methods.py
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
1919
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
2020

21+
np.random.seed(0)
2122
grid = np.random.rand(4, 4)
2223

2324
fig, axes = plt.subplots(3, 6, figsize=(12, 6),
@@ -26,7 +27,7 @@
2627
fig.subplots_adjust(hspace=0.3, wspace=0.05)
2728

2829
for ax, interp_method in zip(axes.flat, methods):
29-
ax.imshow(grid, interpolation=interp_method)
30+
ax.imshow(grid, interpolation=interp_method, cmap='viridis')
3031
ax.set_title(interp_method)
3132

3233
plt.show()

‎examples/pylab_examples/demo_annotation_box.py

Copy file name to clipboardExpand all lines: examples/pylab_examples/demo_annotation_box.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
arr = np.arange(100).reshape((10, 10))
5050
im = OffsetImage(arr, zoom=2)
51+
im.image.axes = ax
5152

5253
ab = AnnotationBbox(im, xy,
5354
xybox=(-50., 50.),
@@ -62,9 +63,10 @@
6263

6364
from matplotlib._png import read_png
6465
fn = get_sample_data("grace_hopper.png", asfileobj=False)
65-
arr_lena = read_png(fn)
66+
arr_img = read_png(fn)
6667

67-
imagebox = OffsetImage(arr_lena, zoom=0.2)
68+
imagebox = OffsetImage(arr_img, zoom=0.2)
69+
imagebox.image.axes = ax
6870

6971
ab = AnnotationBbox(imagebox, xy,
7072
xybox=(120., -80.),

‎extern/agg24-svn/include/agg_span_image_filter_gray.h

Copy file name to clipboardExpand all lines: extern/agg24-svn/include/agg_span_image_filter_gray.h
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ namespace agg
490490
fg_ptr = (const value_type*)base_type::source().next_y();
491491
}
492492

493-
fg >>= image_filter_shift;
493+
fg = color_type::downshift(fg, image_filter_shift);
494494
if(fg < 0) fg = 0;
495495
if(fg > color_type::full_value()) fg = color_type::full_value();
496496
span->v = (value_type)fg;

‎lib/matplotlib/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/__init__.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,7 @@ def matplotlib_fname():
847847
'savefig.extension': ('savefig.format', lambda x: x, None),
848848
'axes.color_cycle': ('axes.prop_cycle', lambda x: cycler('color', x),
849849
lambda x: [c.get('color', None) for c in x]),
850+
'svg.image_noscale': ('image.interpolation', None, None),
850851
}
851852

852853
_deprecated_ignore_map = {

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+1-44Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2323,15 +2323,6 @@ def draw(self, renderer=None, inframe=False):
23232323
artists.remove(self._left_title)
23242324
artists.remove(self._right_title)
23252325

2326-
# add images to dsu if the backend supports compositing.
2327-
# otherwise, does the manual compositing without adding images to dsu.
2328-
if len(self.images) <= 1 or renderer.option_image_nocomposite():
2329-
_do_composite = False
2330-
else:
2331-
_do_composite = True
2332-
for im in self.images:
2333-
artists.remove(im)
2334-
23352326
if self.figure.canvas.is_saving():
23362327
dsu = [(a.zorder, a) for a in artists]
23372328
else:
@@ -2356,46 +2347,12 @@ def draw(self, renderer=None, inframe=False):
23562347
if self.axison and self._frameon:
23572348
self.patch.draw(renderer)
23582349

2359-
if _do_composite:
2360-
# make a composite image, blending alpha
2361-
# list of (mimage.Image, ox, oy)
2362-
2363-
zorder_images = [(im.zorder, im) for im in self.images
2364-
if im.get_visible()]
2365-
zorder_images.sort(key=lambda x: x[0])
2366-
2367-
mag = renderer.get_image_magnification()
2368-
ims = [(im.make_image(mag), 0, 0, im.get_alpha())
2369-
for z, im in zorder_images]
2370-
2371-
l, b, r, t = self.bbox.extents
2372-
width = int(mag * ((np.round(r) + 0.5) - (np.round(l) - 0.5)))
2373-
height = int(mag * ((np.round(t) + 0.5) - (np.round(b) - 0.5)))
2374-
im = mimage.from_images(height,
2375-
width,
2376-
ims)
2377-
2378-
im.is_grayscale = False
2379-
l, b, w, h = self.bbox.bounds
2380-
# composite images need special args so they will not
2381-
# respect z-order for now
2382-
2383-
gc = renderer.new_gc()
2384-
gc.set_clip_rectangle(self.bbox)
2385-
gc.set_clip_path(mtransforms.TransformedPath(
2386-
self.patch.get_path(),
2387-
self.patch.get_transform()))
2388-
2389-
renderer.draw_image(gc, round(l), round(b), im)
2390-
gc.restore()
2391-
23922350
if dsu_rasterized:
23932351
for zorder, a in dsu_rasterized:
23942352
a.draw(renderer)
23952353
renderer.stop_rasterizing()
23962354

2397-
for zorder, a in dsu:
2398-
a.draw(renderer)
2355+
mimage._draw_list_compositing_images(renderer, self, dsu)
23992356

24002357
renderer.close_group('axes')
24012358
self._cachedRenderer = renderer

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def get_image_magnification(self):
515515
"""
516516
return 1.0
517517

518-
def draw_image(self, gc, x, y, im):
518+
def draw_image(self, gc, x, y, im, trans=None):
519519
"""
520520
Draw the image instance into the current axes;
521521
@@ -531,7 +531,14 @@ def draw_image(self, gc, x, y, im):
531531
is the distance from bottom
532532
533533
*im*
534-
the :class:`matplotlib._image.Image` instance
534+
An NxMx4 array of RGBA pixels (of dtype uint8).
535+
536+
*trans*
537+
If the concrete backend is written such that
538+
`option_scale_image` returns `True`, an affine
539+
transformation may also be passed to `draw_image`. The
540+
backend should apply the transformation to the image
541+
before applying the translation of `x` and `y`.
535542
"""
536543
raise NotImplementedError
537544

‎lib/matplotlib/backends/backend_agg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_agg.py
+7-10Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,9 @@ def option_image_nocomposite(self):
319319

320320
def option_scale_image(self):
321321
"""
322-
agg backend support arbitrary scaling of image.
322+
agg backend doesn't support arbitrary scaling of image.
323323
"""
324-
return True
324+
return False
325325

326326
def restore_region(self, region, bbox=None, xy=None):
327327
"""
@@ -389,28 +389,25 @@ def post_processing(image, dpi):
389389
# For agg_filter to work, the rendere's method need
390390
# to overridden in the class. See draw_markers, and draw_path_collections
391391

392-
from matplotlib._image import fromarray
393-
394392
width, height = int(self.width), int(self.height)
395393

396394
buffer, bounds = self.tostring_rgba_minimized()
397395

398396
l, b, w, h = bounds
399397

400-
401398
self._renderer = self._filter_renderers.pop()
402399
self._update_methods()
403400

404401
if w > 0 and h > 0:
405402
img = np.fromstring(buffer, np.uint8)
406403
img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
407404
self.dpi)
408-
image = fromarray(img, 1)
409-
410405
gc = self.new_gc()
411-
self._renderer.draw_image(gc,
412-
l+ox, height - b - h +oy,
413-
image)
406+
if img.dtype.kind == 'f':
407+
img = np.asarray(img * 255., np.uint8)
408+
img = img[::-1]
409+
self._renderer.draw_image(
410+
gc, l + ox, height - b - h + oy, img)
414411

415412

416413
def new_figure_manager(num, *args, **kwargs):

‎lib/matplotlib/backends/backend_cairo.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_cairo.py
+9-5Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,18 @@ def draw_image(self, gc, x, y, im):
171171
# bbox - not currently used
172172
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
173173

174-
rows, cols, buf = im.color_conv (BYTE_FORMAT)
175-
surface = cairo.ImageSurface.create_for_data (
176-
buf, cairo.FORMAT_ARGB32, cols, rows, cols*4)
174+
if sys.byteorder == 'little':
175+
im = im[:, :, (2, 1, 0, 3)]
176+
else:
177+
im = im[:, :, (3, 0, 1, 2)]
178+
surface = cairo.ImageSurface.create_for_data(
179+
memoryview(im.flatten()), cairo.FORMAT_ARGB32, im.shape[1], im.shape[0],
180+
im.shape[1]*4)
177181
ctx = gc.ctx
178-
y = self.height - y - rows
182+
y = self.height - y - im.shape[0]
179183

180184
ctx.save()
181-
ctx.set_source_surface (surface, x, y)
185+
ctx.set_source_surface(surface, float(x), float(y))
182186
if gc.get_alpha() != 1.0:
183187
ctx.paint_with_alpha(gc.get_alpha())
184188
else:

‎lib/matplotlib/backends/backend_gdk.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_gdk.py
+2-5Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,14 @@ def draw_image(self, gc, x, y, im):
109109
# int(w), int(h))
110110
# set clip rect?
111111

112-
rows, cols, image_str = im.as_rgba_str()
113-
114-
image_array = np.fromstring(image_str, np.uint8)
115-
image_array.shape = rows, cols, 4
112+
rows, cols = im.shape[:2]
116113

117114
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,
118115
has_alpha=True, bits_per_sample=8,
119116
width=cols, height=rows)
120117

121118
array = pixbuf_get_pixels_array(pixbuf)
122-
array[:,:,:] = image_array[::-1]
119+
array[:, :, :] = im[::-1]
123120

124121
gc = self.new_gc()
125122

‎lib/matplotlib/backends/backend_mixed.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_mixed.py
+9-6Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from __future__ import (absolute_import, division, print_function,
22
unicode_literals)
33

4+
import numpy as np
5+
46
from matplotlib.externals import six
57

6-
from matplotlib._image import frombuffer
78
from matplotlib.backends.backend_agg import RendererAgg
89
from matplotlib.tight_bbox import process_figure_for_rasterizing
910

@@ -51,7 +52,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
5152
# A reference to the figure is needed as we need to change
5253
# the figure dpi before and after the rasterization. Although
5354
# this looks ugly, I couldn't find a better solution. -JJL
54-
self.figure=figure
55+
self.figure = figure
5556
self._figdpi = figure.get_dpi()
5657

5758
self._bbox_inches_restore = bbox_inches_restore
@@ -68,6 +69,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
6869
draw_gouraud_triangles option_scale_image
6970
_text2path _get_text_path_transform height width
7071
""".split()
72+
7173
def _set_current_renderer(self, renderer):
7274
self._renderer = renderer
7375

@@ -90,7 +92,7 @@ def start_rasterizing(self):
9092
# change the dpi of the figure temporarily.
9193
self.figure.set_dpi(self.dpi)
9294

93-
if self._bbox_inches_restore: # when tight bbox is used
95+
if self._bbox_inches_restore: # when tight bbox is used
9496
r = process_figure_for_rasterizing(self.figure,
9597
self._bbox_inches_restore)
9698
self._bbox_inches_restore = r
@@ -114,12 +116,13 @@ def stop_rasterizing(self):
114116
if self._rasterizing == 0:
115117
self._set_current_renderer(self._vector_renderer)
116118

117-
width, height = self._width * self.dpi, self._height * self.dpi
119+
height = self._height * self.dpi
118120
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
119121
l, b, w, h = bounds
120122
if w > 0 and h > 0:
121-
image = frombuffer(buffer, w, h, True)
122-
image.is_grayscale = False
123+
image = np.frombuffer(buffer, dtype=np.uint8)
124+
image = image.reshape((h, w, 4))
125+
image = image[::-1]
123126
gc = self._renderer.new_gc()
124127
# TODO: If the mixedmode resolution differs from the figure's
125128
# dpi, the image must be scaled (dpi->_figdpi). Not all

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.