Skip to content

Commit 79c296c

Browse files
authored
Merge pull request #13902 from anntzer/imsavemetadatapilkwargs
Add support for metadata= and pil_kwargs= in imsave().
2 parents d4ce99a + 5e91c5a commit 79c296c

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
`~pyplot.imsave` gained support for the ``metadata`` and ``pil_kwargs`` parameters
2+
``````````````````````````````````````````````````````````````````````````````````
3+
4+
These parameters behave similarly as for the `Figure.savefig()` method.

lib/matplotlib/image.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ def read_png(*args, **kwargs):
14341434

14351435

14361436
def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
1437-
origin=None, dpi=100):
1437+
origin=None, dpi=100, *, metadata=None, pil_kwargs=None):
14381438
"""
14391439
Save an array as an image file.
14401440
@@ -1467,6 +1467,17 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
14671467
dpi : int
14681468
The DPI to store in the metadata of the file. This does not affect the
14691469
resolution of the output image.
1470+
metadata : dict, optional
1471+
Metadata in the image file. The supported keys depend on the output
1472+
format, see the documentation of the respective backends for more
1473+
information.
1474+
pil_kwargs : dict, optional
1475+
If set to a non-None value, always use Pillow to save the figure
1476+
(regardless of the output format), and pass these keyword arguments to
1477+
`PIL.Image.save`.
1478+
1479+
If the 'pnginfo' key is present, it completely overrides
1480+
*metadata*, including the default 'Software' key.
14701481
"""
14711482
from matplotlib.figure import Figure
14721483
from matplotlib import _png
@@ -1477,10 +1488,14 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
14771488
else rcParams["savefig.format"]).lower()
14781489
if format in ["pdf", "ps", "eps", "svg"]:
14791490
# Vector formats that are not handled by PIL.
1491+
if pil_kwargs is not None:
1492+
raise ValueError(
1493+
f"Cannot use 'pil_kwargs' when saving to {format}")
14801494
fig = Figure(dpi=dpi, frameon=False)
14811495
fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin,
14821496
resize=True)
1483-
fig.savefig(fname, dpi=dpi, format=format, transparent=True)
1497+
fig.savefig(fname, dpi=dpi, format=format, transparent=True,
1498+
metadata=metadata)
14841499
else:
14851500
# Don't bother creating an image; this avoids rounding errors on the
14861501
# size when dividing and then multiplying by dpi.
@@ -1491,17 +1506,28 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
14911506
if origin == "lower":
14921507
arr = arr[::-1]
14931508
rgba = sm.to_rgba(arr, bytes=True)
1494-
if format == "png":
1495-
_png.write_png(rgba, fname, dpi=dpi)
1509+
if format == "png" and pil_kwargs is None:
1510+
_png.write_png(rgba, fname, dpi=dpi, metadata=metadata)
14961511
else:
14971512
try:
14981513
from PIL import Image
1514+
from PIL.PngImagePlugin import PngInfo
14991515
except ImportError as exc:
1500-
raise ImportError(
1501-
f"Saving to {format} requires Pillow") from exc
1516+
if pil_kwargs is not None:
1517+
raise ImportError("Setting 'pil_kwargs' requires Pillow")
1518+
else:
1519+
raise ImportError(f"Saving to {format} requires Pillow")
1520+
if pil_kwargs is None:
1521+
pil_kwargs = {}
15021522
pil_shape = (rgba.shape[1], rgba.shape[0])
15031523
image = Image.frombuffer(
15041524
"RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
1525+
if format == "png" and metadata is not None:
1526+
# cf. backend_agg's print_png.
1527+
pnginfo = PngInfo()
1528+
for k, v in metadata.items():
1529+
pnginfo.add_text(k, v)
1530+
pil_kwargs["pnginfo"] = pnginfo
15051531
if format in ["jpg", "jpeg"]:
15061532
format = "jpeg" # Pillow doesn't recognize "jpg".
15071533
color = tuple(
@@ -1510,7 +1536,9 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
15101536
background = Image.new("RGB", pil_shape, color)
15111537
background.paste(image, image)
15121538
image = background
1513-
image.save(fname, format=format, dpi=(dpi, dpi))
1539+
pil_kwargs.setdefault("format", format)
1540+
pil_kwargs.setdefault("dpi", (dpi, dpi))
1541+
image.save(fname, **pil_kwargs)
15141542

15151543

15161544
def pil_to_array(pilImage):

lib/matplotlib/tests/test_image.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,29 @@ def test_imsave_color_alpha():
208208
assert_array_equal(data, arr_buf)
209209

210210

211+
def test_imsave_pil_kwargs_png():
212+
Image = pytest.importorskip("PIL.Image")
213+
from PIL.PngImagePlugin import PngInfo
214+
buf = io.BytesIO()
215+
pnginfo = PngInfo()
216+
pnginfo.add_text("Software", "test")
217+
plt.imsave(buf, [[0, 1], [2, 3]],
218+
format="png", pil_kwargs={"pnginfo": pnginfo})
219+
im = Image.open(buf)
220+
assert im.info["Software"] == "test"
221+
222+
223+
def test_imsave_pil_kwargs_tiff():
224+
Image = pytest.importorskip("PIL.Image")
225+
from PIL.TiffTags import TAGS_V2 as TAGS
226+
buf = io.BytesIO()
227+
pil_kwargs = {"description": "test image"}
228+
plt.imsave(buf, [[0, 1], [2, 3]], format="tiff", pil_kwargs=pil_kwargs)
229+
im = Image.open(buf)
230+
tags = {TAGS[k].name: v for k, v in im.tag_v2.items()}
231+
assert tags["ImageDescription"] == "test image"
232+
233+
211234
@image_comparison(baseline_images=['image_alpha'], remove_text=True)
212235
def test_image_alpha():
213236
plt.figure()

0 commit comments

Comments
 (0)