Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5b74696

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

File tree

153 files changed

+13997
-12750
lines changed

Some content is hidden

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

153 files changed

+13997
-12750
lines changed
Lines 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.
Lines 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

Lines 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

100755100644
Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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)