From 370e9a2d5d9e637abc90b3270d368642c69f66c6 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 3 Sep 2019 10:39:12 +0200 Subject: [PATCH] Switch to using pillow for png as well. The version check in _has_pil (pillow>=3.4) was dropped as the oldest Pillow that supports Py3.6 (the oldest Python supported by Matplotlib) is 4.0 anyways. Some complications arise from an unfortunate design decision that imread() returns pngs in 0-1 float dtype but others formats in integer dtype (via Pillow), and from Pillow having not-so-great support for 16-bit images (e.g. Pillow#3041). This makes reading of 16-bit RGB(A) PNGs (which are exposed as 0-1 floats by imread()) slightly less accurate, because Pillow coarsens them to 8-bit first (by truncation); hence the slight change in the pngsuite.png baseline image. (Note that the *renderers* only have 8-bit precision anyways (unless using mplcairo with cairo master...), but the discrepancy comes from differences in rounding between Pillow and Matplotlib). --- .appveyor.yml | 6 +- INSTALL.rst | 28 +- ci/azure-pipelines-steps.yml | 4 - doc/api/next_api_changes/development.rst | 11 + doc/devel/min_dep_policy.rst | 2 +- examples/misc/image_thumbnail_sgskip.py | 6 +- lib/matplotlib/animation.py | 4 - lib/matplotlib/backend_bases.py | 38 +- lib/matplotlib/backends/backend_agg.py | 184 +++-- lib/matplotlib/backends/backend_pdf.py | 6 +- lib/matplotlib/backends/backend_pgf.py | 12 +- lib/matplotlib/backends/backend_svg.py | 20 +- .../backends/backend_webagg_core.py | 16 +- lib/matplotlib/backends/backend_wx.py | 19 +- lib/matplotlib/image.py | 149 ++-- lib/matplotlib/mathtext.py | 5 +- lib/matplotlib/testing/compare.py | 23 +- .../baseline_images/test_png/pngsuite.png | Bin 46873 -> 47308 bytes lib/matplotlib/tests/test_basic.py | 1 - lib/matplotlib/tests/test_png.py | 11 - lib/matplotlib/texmanager.py | 4 +- setup.py | 2 +- setupext.py | 34 +- src/_png.cpp | 683 ------------------ src/checkdep_libpng.c | 17 - 25 files changed, 246 insertions(+), 1039 deletions(-) delete mode 100644 src/_png.cpp delete mode 100644 src/checkdep_libpng.c diff --git a/.appveyor.yml b/.appveyor.yml index 442f0f445166..b5efd3af0256 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -60,7 +60,7 @@ install: # if conda-forge gets a new pyqt, it might be nice to install it as well to have more backends # https://github.com/conda-forge/conda-forge.github.io/issues/157#issuecomment-223536381 - conda create -q -n test-environment python=%PYTHON_VERSION% - freetype=2.6 "libpng>=1.6.21,<1.7" zlib=1.2 tk=8.5 + freetype=2.6 zlib=1.2 tk=8.5 pip setuptools numpy sphinx tornado - activate test-environment - echo %PYTHON_VERSION% %TARGET_ARCH% @@ -81,11 +81,9 @@ test_script: # Now build the thing.. - set LINK=/LIBPATH:%cd%\lib - pip install -ve . - # these should show no z, png, or freetype dll... + # these should show no z or freetype dll... - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" - 'if x%MPLSTATICBUILD% == xTrue "%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' - - 'if x%MPLSTATICBUILD% == xTrue "%DUMPBIN%" /DEPENDENTS lib\matplotlib\_png*.pyd | findstr z.*.dll && exit /b 1 || exit /b 0' - - 'if x%MPLSTATICBUILD% == xTrue "%DUMPBIN%" /DEPENDENTS lib\matplotlib\_png*.pyd | findstr png.*.dll && exit /b 1 || exit /b 0' # this are optional dependencies so that we don't skip so many tests... - if x%TEST_ALL% == xyes conda install -q ffmpeg inkscape miktex pillow diff --git a/INSTALL.rst b/INSTALL.rst index 3d64becf80f5..2f68290c0a09 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -99,7 +99,7 @@ toolchain is prefixed. This may be used for cross compiling. :: export PKG_CONFIG=x86_64-pc-linux-gnu-pkg-config Once you have satisfied the requirements detailed below (mainly -Python, NumPy, libpng and FreeType), you can build Matplotlib. +Python, NumPy, and FreeType), you can build Matplotlib. :: cd matplotlib @@ -121,7 +121,6 @@ Matplotlib requires the following dependencies: * `Python `_ (>= 3.6) * `FreeType `_ (>= 2.3) -* `libpng `_ (>= 1.2) * `NumPy `_ (>= 1.11) * `setuptools `_ * `cycler `_ (>= 0.10.0) @@ -177,8 +176,8 @@ etc., you can install the following: .. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ If not using pkg-config (in particular on Windows), you may need to set the - include path (to the FreeType, libpng, and zlib headers) and link path (to - the FreeType, libpng, and zlib libraries) explicitly, if they are not in + include path (to the FreeType and zlib headers) and link path (to + the FreeType and zlib libraries) explicitly, if they are not in standard locations. This can be done using standard environment variables -- on Linux and OSX: @@ -195,8 +194,8 @@ etc., you can install the following: set LINK=/LIBPATH:C:\directory\containing\freetype.lib ... where ``...`` means "also give, in the same format, the directories - containing ``png.h`` and ``zlib.h`` for the include path, and for - ``libpng.so``/``png.lib`` and ``libz.so``/``z.lib`` for the link path." + containing ``zlib.h`` for the include path, and for + ``libz.so``/``z.lib`` for the link path." .. note:: @@ -237,20 +236,20 @@ Building on macOS ----------------- The build situation on macOS is complicated by the various places one -can get the libpng and FreeType requirements (MacPorts, Fink, +can get FreeType (MacPorts, Fink, /usr/X11R6), the different architectures (e.g., x86, ppc, universal), and the different macOS versions (e.g., 10.4 and 10.5). We recommend that you build the way we do for the macOS release: get the source from the tarball or the git repository and install the required dependencies through a third-party package manager. Two widely used package managers are Homebrew, and MacPorts. -The following example illustrates how to install libpng and FreeType using +The following example illustrates how to install FreeType using ``brew``:: - brew install libpng freetype pkg-config + brew install freetype pkg-config If you are using MacPorts, execute the following instead:: - port install libpng freetype pkgconfig + port install freetype pkgconfig After installing the above requirements, install Matplotlib from source by executing:: @@ -274,7 +273,7 @@ https://packaging.python.org/guides/packaging-binary-extensions/#setting-up-a-bu for how to set up a build environment. Since there is no canonical Windows package manager, the methods for building -FreeType, zlib, and libpng from source code are documented as a build script +FreeType and zlib from source code are documented as a build script at `matplotlib-winbuild `_. There are a few possibilities to build Matplotlib on Windows: @@ -290,17 +289,16 @@ Wheel builds using conda packages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is a wheel build, but we use conda packages to get all the requirements. -The binary requirements (png, FreeType,...) are statically linked and therefore -not needed during the wheel install. +FreeType is statically linked and therefore not needed during the wheel install. Set up the conda environment. Note, if you want a qt backend, add ``pyqt`` to the list of conda packages. :: - conda create -n "matplotlib_build" python=3.7 numpy python-dateutil pyparsing tornado cycler tk libpng zlib freetype + conda create -n "matplotlib_build" python=3.7 numpy python-dateutil pyparsing tornado cycler tk zlib freetype conda activate matplotlib_build - # force the build against static libpng and zlib libraries + # force the build against static zlib libraries set MPLSTATICBUILD=True python setup.py bdist_wheel diff --git a/ci/azure-pipelines-steps.yml b/ci/azure-pipelines-steps.yml index f8b2a58fe3eb..5331806440d7 100644 --- a/ci/azure-pipelines-steps.yml +++ b/ci/azure-pipelines-steps.yml @@ -21,12 +21,8 @@ steps: displayName: 'Use latest available Nuget' - script: | - nuget install libpng-msvc14-x64 -ExcludeVersion -OutputDirectory "$(build.BinariesDirectory)" nuget install zlib-msvc14-x64 -ExcludeVersion -OutputDirectory "$(build.BinariesDirectory)" - echo ##vso[task.prependpath]$(build.BinariesDirectory)\libpng-msvc14-x64\build\native\bin_release echo ##vso[task.prependpath]$(build.BinariesDirectory)\zlib-msvc14-x64\build\native\bin_release - echo ##vso[task.setvariable variable=CL]/I$(build.BinariesDirectory)\libpng-msvc14-x64\build\native\include /I$(build.BinariesDirectory)\zlib-msvc14-x64\build\native\include - echo ##vso[task.setvariable variable=LINK]/LIBPATH:$(build.BinariesDirectory)\libpng-msvc14-x64\build\native\lib_release /LIBPATH:$(build.BinariesDirectory)\zlib-msvc14-x64\build\native\lib_release displayName: 'Install dependencies with nuget' diff --git a/doc/api/next_api_changes/development.rst b/doc/api/next_api_changes/development.rst index 1cb1679c48c1..389cff4f1067 100644 --- a/doc/api/next_api_changes/development.rst +++ b/doc/api/next_api_changes/development.rst @@ -1,2 +1,13 @@ Development changes ------------------- + +Matplotlib now uses Pillow to save and read pngs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The builtin png encoder and decoder has been removed, and Pillow is now a +dependency. Note that when reading 16-bit RGB(A) images, Pillow truncates them +to 8-bit precision, whereas the old builtin decoder kept the full precision. + +The deprecated wx backend (not wxagg!) now always uses wx's builtin jpeg and +tiff support rather than relying on Pillow for writing these formats; this +behavior is consistent with wx's png output. diff --git a/doc/devel/min_dep_policy.rst b/doc/devel/min_dep_policy.rst index c27761deff0d..19ddd13672aa 100644 --- a/doc/devel/min_dep_policy.rst +++ b/doc/devel/min_dep_policy.rst @@ -60,7 +60,7 @@ minimum Python and numpy. System and C-dependencies ========================= -For system or c-dependencies (libpng, freetype, GUI frameworks, latex, +For system or C-dependencies (FreeType, GUI frameworks, LaTeX, gs, ffmpeg) support as old as practical. These can be difficult to install for end-users and we want to be usable on as many systems as possible. We will bump these on a case-by-case basis. diff --git a/examples/misc/image_thumbnail_sgskip.py b/examples/misc/image_thumbnail_sgskip.py index ae82e616743b..85ebbdc08b61 100644 --- a/examples/misc/image_thumbnail_sgskip.py +++ b/examples/misc/image_thumbnail_sgskip.py @@ -4,10 +4,10 @@ =============== You can use matplotlib to generate thumbnails from existing images. -matplotlib natively supports PNG files on the input side, and other -image types transparently if your have PIL installed - +Matplotlib relies on Pillow_ for reading images, and thus supports all formats +supported by Pillow. +.. _Pillow: http://python-pillow.org/ """ # build thumbnails of all images in a directory diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 762572b211f1..ff818ab9eb06 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -543,10 +543,6 @@ def cleanup(self): class PillowWriter(MovieWriter): @classmethod def isAvailable(cls): - try: - import PIL - except ImportError: - return False return True def __init__(self, *args, **kwargs): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index f52e43be4aa3..47b10a327e86 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -53,42 +53,37 @@ from matplotlib.transforms import Affine2D from matplotlib.path import Path -try: - from PIL import PILLOW_VERSION - from distutils.version import LooseVersion - if LooseVersion(PILLOW_VERSION) >= "3.4": - _has_pil = True - else: - _has_pil = False - del PILLOW_VERSION -except ImportError: - _has_pil = False _log = logging.getLogger(__name__) - _default_filetypes = { - 'ps': 'Postscript', 'eps': 'Encapsulated Postscript', + 'jpg': 'Joint Photographic Experts Group', + 'jpeg': 'Joint Photographic Experts Group', 'pdf': 'Portable Document Format', 'pgf': 'PGF code for LaTeX', 'png': 'Portable Network Graphics', + 'ps': 'Postscript', 'raw': 'Raw RGBA bitmap', 'rgba': 'Raw RGBA bitmap', 'svg': 'Scalable Vector Graphics', - 'svgz': 'Scalable Vector Graphics' + 'svgz': 'Scalable Vector Graphics', + 'tif': 'Tagged Image File Format', + 'tiff': 'Tagged Image File Format', } - - _default_backends = { - 'ps': 'matplotlib.backends.backend_ps', 'eps': 'matplotlib.backends.backend_ps', + 'jpg': 'matplotlib.backends.backend_agg', + 'jpeg': 'matplotlib.backends.backend_agg', 'pdf': 'matplotlib.backends.backend_pdf', 'pgf': 'matplotlib.backends.backend_pgf', 'png': 'matplotlib.backends.backend_agg', + 'ps': 'matplotlib.backends.backend_ps', 'raw': 'matplotlib.backends.backend_agg', 'rgba': 'matplotlib.backends.backend_agg', 'svg': 'matplotlib.backends.backend_svg', 'svgz': 'matplotlib.backends.backend_svg', + 'tif': 'matplotlib.backends.backend_agg', + 'tiff': 'matplotlib.backends.backend_agg', } @@ -1604,17 +1599,6 @@ class FigureCanvasBase: fixed_dpi = None filetypes = _default_filetypes - if _has_pil: - # JPEG support - register_backend('jpg', 'matplotlib.backends.backend_agg', - 'Joint Photographic Experts Group') - register_backend('jpeg', 'matplotlib.backends.backend_agg', - 'Joint Photographic Experts Group') - # TIFF support - register_backend('tif', 'matplotlib.backends.backend_agg', - 'Tagged Image File Format') - register_backend('tiff', 'matplotlib.backends.backend_agg', - 'Tagged Image File Format') @cbook._classproperty def supports_blit(cls): diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 73dea192b392..2d3ec989f1c3 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -8,7 +8,7 @@ * linewidth * lines, rectangles, ellipses * clipping to a rectangle - * output to RGBA and PNG, optionally JPEG and TIFF + * output to RGBA and Pillow-supported image formats * alpha blending * DPI scaling properly - everything scales properly (dashes, linewidths, etc) * draw polygon @@ -30,6 +30,8 @@ from math import radians, cos, sin import numpy as np +from PIL import Image +from PIL.PngImagePlugin import PngInfo from matplotlib import cbook, rcParams, __version__ from matplotlib.backend_bases import ( @@ -41,13 +43,8 @@ from matplotlib.path import Path from matplotlib.transforms import Bbox, BboxBase from matplotlib import colors as mcolors - from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg -from matplotlib.backend_bases import _has_pil - -if _has_pil: - from PIL import Image backend_version = 'v2.2' @@ -498,43 +495,32 @@ def print_png(self, filename_or_obj, *args, https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords pil_kwargs : dict, optional - If set to a non-None value, use Pillow to save the figure instead - of Matplotlib's builtin PNG support, and pass these keyword - arguments to `PIL.Image.save`. + Keyword arguments passed to `PIL.Image.save`. If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ - from matplotlib import _png if metadata is None: metadata = {} + if pil_kwargs is None: + pil_kwargs = {} metadata = { "Software": f"matplotlib version{__version__}, http://matplotlib.org/", **metadata, } - FigureCanvasAgg.draw(self) - if pil_kwargs is not None: - from PIL import Image - from PIL.PngImagePlugin import PngInfo - # Only use the metadata kwarg if pnginfo is not set, because the - # semantics of duplicate keys in pnginfo is unclear. - if "pnginfo" not in pil_kwargs: - pnginfo = PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format="png", **pil_kwargs)) - - else: - renderer = self.get_renderer() - with cbook.open_file_cm(filename_or_obj, "wb") as fh: - _png.write_png(renderer._renderer, fh, - self.figure.dpi, metadata=metadata) + # Only use the metadata kwarg if pnginfo is not set, because the + # semantics of duplicate keys in pnginfo is unclear. + if "pnginfo" not in pil_kwargs: + pnginfo = PngInfo() + for k, v in metadata.items(): + pnginfo.add_text(k, v) + pil_kwargs["pnginfo"] = pnginfo + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + (Image.fromarray(np.asarray(self.buffer_rgba())) + .save(filename_or_obj, format="png", **pil_kwargs)) def print_to_buffer(self): FigureCanvasAgg.draw(self) @@ -542,76 +528,74 @@ def print_to_buffer(self): return (bytes(renderer.buffer_rgba()), (int(renderer.width), int(renderer.height))) - if _has_pil: - - # Note that these methods should typically be called via savefig() and - # print_figure(), and the latter ensures that `self.figure.dpi` already - # matches the dpi kwarg (if any). - - @cbook._delete_parameter("3.2", "dryrun") - def print_jpg(self, filename_or_obj, *args, dryrun=False, - pil_kwargs=None, **kwargs): - """ - Write the figure to a JPEG file. - - Parameters - ---------- - filename_or_obj : str or PathLike or file-like object - The file to write to. - - Other Parameters - ---------------- - quality : int, default: :rc:`savefig.jpeg_quality` - The image quality, on a scale from 1 (worst) to 95 (best). - Values above 95 should be avoided; 100 disables portions of - the JPEG compression algorithm, and results in large files - with hardly any gain in image quality. - - optimize : bool, default: False - Whether the encoder should make an extra pass over the image - in order to select optimal encoder settings. - - progressive : bool, default: False - Whether the image should be stored as a progressive JPEG file. - - pil_kwargs : dict, optional - Additional keyword arguments that are passed to - `PIL.Image.save` when saving the figure. These take precedence - over *quality*, *optimize* and *progressive*. - """ - FigureCanvasAgg.draw(self) - if dryrun: - return - # The image is pasted onto a white background image to handle - # transparency. - image = Image.fromarray(np.asarray(self.buffer_rgba())) - background = Image.new('RGB', image.size, "white") - background.paste(image, image) - if pil_kwargs is None: - pil_kwargs = {} - for k in ["quality", "optimize", "progressive"]: - if k in kwargs: - pil_kwargs.setdefault(k, kwargs[k]) - pil_kwargs.setdefault("quality", rcParams["savefig.jpeg_quality"]) - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - return background.save( - filename_or_obj, format='jpeg', **pil_kwargs) - - print_jpeg = print_jpg - - @cbook._delete_parameter("3.2", "dryrun") - def print_tif(self, filename_or_obj, *args, dryrun=False, - pil_kwargs=None, **kwargs): - FigureCanvasAgg.draw(self) - if dryrun: - return - if pil_kwargs is None: - pil_kwargs = {} - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - return (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format='tiff', **pil_kwargs)) - - print_tiff = print_tif + # Note that these methods should typically be called via savefig() and + # print_figure(), and the latter ensures that `self.figure.dpi` already + # matches the dpi kwarg (if any). + + @cbook._delete_parameter("3.2", "dryrun") + def print_jpg(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None, + **kwargs): + """ + Write the figure to a JPEG file. + + Parameters + ---------- + filename_or_obj : str or PathLike or file-like object + The file to write to. + + Other Parameters + ---------------- + quality : int, default: :rc:`savefig.jpeg_quality` + The image quality, on a scale from 1 (worst) to 95 (best). + Values above 95 should be avoided; 100 disables portions of + the JPEG compression algorithm, and results in large files + with hardly any gain in image quality. + + optimize : bool, default: False + Whether the encoder should make an extra pass over the image + in order to select optimal encoder settings. + + progressive : bool, default: False + Whether the image should be stored as a progressive JPEG file. + + pil_kwargs : dict, optional + Additional keyword arguments that are passed to + `PIL.Image.save` when saving the figure. These take precedence + over *quality*, *optimize* and *progressive*. + """ + FigureCanvasAgg.draw(self) + if dryrun: + return + # The image is "pasted" onto a white background image to safely + # handle any transparency + image = Image.fromarray(np.asarray(self.buffer_rgba())) + background = Image.new("RGB", image.size, "white") + background.paste(image, image) + if pil_kwargs is None: + pil_kwargs = {} + for k in ["quality", "optimize", "progressive"]: + if k in kwargs: + pil_kwargs.setdefault(k, kwargs[k]) + pil_kwargs.setdefault("quality", rcParams["savefig.jpeg_quality"]) + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + return background.save( + filename_or_obj, format='jpeg', **pil_kwargs) + + print_jpeg = print_jpg + + @cbook._delete_parameter("3.2", "dryrun") + def print_tif(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None, + **kwargs): + FigureCanvasAgg.draw(self) + if dryrun: + return + if pil_kwargs is None: + pil_kwargs = {} + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + return (Image.fromarray(np.asarray(self.buffer_rgba())) + .save(filename_or_obj, format='tiff', **pil_kwargs)) + + print_tiff = print_tif @_Backend.export diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 50b1c0913d36..fb5e90a9f64a 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -20,6 +20,7 @@ import zlib import numpy as np +from PIL import Image from matplotlib import _text_layout, cbook, __version__, rcParams from matplotlib._pylab_helpers import Gcf @@ -39,7 +40,6 @@ from matplotlib.path import Path from matplotlib.dates import UTC from matplotlib import _path -from matplotlib import _png from matplotlib import ttconv from . import _backend_pdf_ps @@ -1440,7 +1440,9 @@ def _writePng(self, data): predictors with Flate compression. """ buffer = BytesIO() - _png.write_png(data, buffer) + if data.shape[-1] == 1: + data = data.squeeze(axis=-1) + Image.fromarray(data).save(buffer, format="png") buffer.seek(8) while True: length, type = struct.unpack(b'!L4s', buffer.read(8)) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 12bdc68ebb19..8cc344c7f8d7 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -12,8 +12,10 @@ import tempfile import weakref +from PIL import Image + import matplotlib as mpl -from matplotlib import _png, cbook, font_manager as fm, __version__, rcParams +from matplotlib import cbook, font_manager as fm, __version__, rcParams from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) @@ -658,12 +660,10 @@ def draw_image(self, gc, x, y, im, transform=None): return # save the images to png files - path = os.path.dirname(self.fh.name) - fname = os.path.splitext(os.path.basename(self.fh.name))[0] - fname_img = "%s-img%d.png" % (fname, self.image_counter) + path = pathlib.Path(self.fh.name) + fname_img = "%s-img%d.png" % (path.stem, self.image_counter) + Image.fromarray(im[::-1]).save(path.parent / fname_img) self.image_counter += 1 - with pathlib.Path(path, fname_img).open("wb") as file: - _png.write_png(im[::-1], file) # reference the image in the pgf picture writeln(self.fh, r"\begin{pgfscope}") diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index df1b572f0a01..493bb362d12d 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -2,13 +2,14 @@ import base64 import gzip import hashlib -import io +from io import BytesIO, StringIO, TextIOWrapper import itertools import logging import re import uuid import numpy as np +from PIL import Image from matplotlib import cbook, __version__, rcParams from matplotlib.backend_bases import ( @@ -21,7 +22,6 @@ from matplotlib.path import Path from matplotlib import _path from matplotlib.transforms import Affine2D, Affine2DBase -from matplotlib import _png _log = logging.getLogger(__name__) @@ -242,7 +242,7 @@ def flush(self): def generate_transform(transform_list=[]): if len(transform_list): - output = io.StringIO() + output = StringIO() for type, value in transform_list: if (type == 'scale' and (value == (1,) or value == (1, 1)) or type == 'translate' and value == (0, 0) @@ -258,7 +258,7 @@ def generate_transform(transform_list=[]): def generate_css(attrib={}): if attrib: - output = io.StringIO() + output = StringIO() attrib = sorted(attrib.items()) for k, v in attrib: k = escape_attrib(k) @@ -821,11 +821,12 @@ def draw_image(self, gc, x, y, im, transform=None): if url is not None: self.writer.start('a', attrib={'xlink:href': url}) if rcParams['svg.image_inline']: - buf = _png.write_png(im, None) - oid = oid or self._make_id('image', buf) + buf = BytesIO() + Image.fromarray(im).save(buf, format="png") + oid = oid or self._make_id('image', buf.getvalue()) attrib['xlink:href'] = ( "data:image/png;base64,\n" + - base64.b64encode(buf).decode('ascii')) + base64.b64encode(buf.getvalue()).decode('ascii')) else: if self.basename is None: raise ValueError("Cannot save image data to filesystem when " @@ -833,8 +834,7 @@ def draw_image(self, gc, x, y, im, transform=None): filename = '{}.image{}.png'.format( self.basename, next(self._image_counter)) _log.info('Writing image file for inclusion: %s', filename) - with open(filename, 'wb') as file: - _png.write_png(im, file) + Image.fromarray(im).save(filename) oid = oid or 'Im_' + self._make_id('image', filename) attrib['xlink:href'] = filename @@ -1190,7 +1190,7 @@ def print_svg(self, filename, *args, **kwargs): if cbook.file_requires_unicode(fh): detach = False else: - fh = io.TextIOWrapper(fh, 'utf-8') + fh = TextIOWrapper(fh, 'utf-8') detach = True result = self._print_svg(filename, fh, **kwargs) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index c000e537e136..139892f27704 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -11,16 +11,17 @@ # application, implemented with tornado. import datetime -from io import StringIO +from io import BytesIO, StringIO import json import logging import os from pathlib import Path import numpy as np +from PIL import Image import tornado -from matplotlib import backend_bases, cbook, _png +from matplotlib import backend_bases, cbook from matplotlib.backends import backend_agg from matplotlib.backend_bases import _Backend @@ -194,18 +195,15 @@ def get_diff_image(self): diff = buff != last_buffer output = np.where(diff, buff, 0) - # TODO: We should write a new version of write_png that - # handles the differencing inline - buff = _png.write_png( - output.view(dtype=np.uint8).reshape(output.shape + (4,)), - None, compression=6, filter=_png.PNG_FILTER_NONE) - + buf = BytesIO() + data = output.view(dtype=np.uint8).reshape((*output.shape, 4)) + Image.fromarray(data).save(buf, format="png") # Swap the renderer frames self._renderer, self._last_renderer = ( self._last_renderer, renderer) self._force_full = False self._png_is_old = False - return buff + return buf.getvalue() def get_renderer(self, cleared=None): # Mirrors super.get_renderer, but caches the old one diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 4c9cdc44c2cb..73b60708c5fb 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -17,7 +17,7 @@ from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, MouseButton, NavigationToolbar2, RendererBase, StatusbarBase, TimerBase, - ToolContainerBase, _has_pil, cursors) + ToolContainerBase, cursors) from matplotlib import cbook, rcParams, backend_tools from matplotlib._pylab_helpers import Gcf @@ -926,11 +926,10 @@ def draw(self, drawDC=None): def print_bmp(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) - if not _has_pil: - def print_jpeg(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_JPEG, - *args, **kwargs) - print_jpg = print_jpeg + def print_jpeg(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_JPEG, + *args, **kwargs) + print_jpg = print_jpeg def print_pcx(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_PCX, *args, **kwargs) @@ -938,11 +937,9 @@ def print_pcx(self, filename, *args, **kwargs): def print_png(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_PNG, *args, **kwargs) - if not _has_pil: - def print_tiff(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_TIF, - *args, **kwargs) - print_tif = print_tiff + def print_tiff(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_TIF, *args, **kwargs) + print_tif = print_tiff def print_xpm(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_XPM, *args, **kwargs) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index eeb2215b341e..2890b5793344 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -12,6 +12,7 @@ import urllib.parse import numpy as np +import PIL.PngImagePlugin from matplotlib import rcParams import matplotlib.artist as martist @@ -19,14 +20,11 @@ import matplotlib.colors as mcolors import matplotlib.cm as cm import matplotlib.cbook as cbook - # For clarity, names from _image are given explicitly in this module: import matplotlib._image as _image - # For user convenience, the names from _image are also imported into # the image namespace: from matplotlib._image import * - from matplotlib.transforms import (Affine2D, BboxBase, Bbox, BboxTransform, IdentityTransform, TransformedBbox) @@ -664,11 +662,9 @@ def contains(self, mouseevent): def write_png(self, fname): """Write the image to png file with fname""" - from matplotlib import _png im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A, bytes=True, norm=True) - with open(fname, "wb") as file: - _png.write_png(im, file) + PIL.Image.fromarray(im).save(fname, format="png") def set_data(self, A): """ @@ -680,13 +676,8 @@ def set_data(self, A): ---------- A : array-like or `PIL.Image.Image` """ - try: - from PIL import Image - except ImportError: - pass - else: - if isinstance(A, Image.Image): - A = pil_to_array(A) # Needed e.g. to apply png palette. + if isinstance(A, PIL.Image.Image): + A = pil_to_array(A) # Needed e.g. to apply png palette. self._A = cbook.safe_masked_invalid(A, copy=True) if (self._A.dtype != np.uint8 and @@ -1395,15 +1386,6 @@ def imread(fname, format=None): - (M, N) for grayscale images. - (M, N, 3) for RGB images. - (M, N, 4) for RGBA images. - - Notes - ----- - Matplotlib can only read PNGs natively. Further image formats are - supported via the optional dependency on Pillow. Note, URL strings - are not compatible with Pillow. Check the `Pillow documentation`_ - for more information. - - .. _Pillow documentation: http://pillow.readthedocs.io/en/latest/ """ if format is None: if isinstance(fname, str): @@ -1429,24 +1411,18 @@ def imread(fname, format=None): ext = 'png' else: ext = format - if ext != 'png': - try: # Try to load the image with PIL. - from PIL import Image - except ImportError: - raise ValueError('Only know how to handle PNG; with Pillow ' - 'installed, Matplotlib can handle more images') - with Image.open(fname) as image: - return pil_to_array(image) - from matplotlib import _png + img_open = ( + PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open) if isinstance(fname, str): parsed = urllib.parse.urlparse(fname) - # If fname is a URL, download the data - if len(parsed.scheme) > 1: + if len(parsed.scheme) > 1: # Pillow doesn't handle URLs directly. from urllib import request - fd = BytesIO(request.urlopen(fname).read()) - return _png.read_png(fd) - with cbook.open_file_cm(fname, "rb") as file: - return _png.read_png(file) + with urllib.request.urlopen(fname) as response: + return imread(response, format=ext) + with img_open(fname) as image: + return (_pil_png_to_float_array(image) + if isinstance(image, PIL.PngImagePlugin.PngImageFile) else + pil_to_array(image)) def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, @@ -1488,15 +1464,11 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, format, see the documentation of the respective backends for more information. pil_kwargs : dict, optional - If set to a non-None value, always use Pillow to save the figure - (regardless of the output format), and pass these keyword arguments to - `PIL.Image.save`. - - If the 'pnginfo' key is present, it completely overrides - *metadata*, including the default 'Software' key. + Keyword arguments passed to `PIL.Image.save`. If the 'pnginfo' key is + present, it completely overrides *metadata*, including the default + 'Software' key. """ from matplotlib.figure import Figure - from matplotlib import _png if isinstance(fname, os.PathLike): fname = os.fspath(fname) if format is None: @@ -1522,44 +1494,32 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, if origin == "lower": arr = arr[::-1] rgba = sm.to_rgba(arr, bytes=True) - if format == "png" and pil_kwargs is None: - with cbook.open_file_cm(fname, "wb") as file: - _png.write_png(rgba, file, dpi=dpi, metadata=metadata) - else: - try: - from PIL import Image - from PIL.PngImagePlugin import PngInfo - except ImportError as exc: - if pil_kwargs is not None: - raise ImportError("Setting 'pil_kwargs' requires Pillow") - else: - raise ImportError(f"Saving to {format} requires Pillow") - if pil_kwargs is None: - pil_kwargs = {} - pil_shape = (rgba.shape[1], rgba.shape[0]) - image = Image.frombuffer( - "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) - if format == "png" and metadata is not None: - # cf. backend_agg's print_png. - pnginfo = PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo - if format in ["jpg", "jpeg"]: - format = "jpeg" # Pillow doesn't recognize "jpg". - color = tuple( - int(x * 255) - for x in mcolors.to_rgb(rcParams["savefig.facecolor"])) - background = Image.new("RGB", pil_shape, color) - background.paste(image, image) - image = background - pil_kwargs.setdefault("format", format) - pil_kwargs.setdefault("dpi", (dpi, dpi)) - image.save(fname, **pil_kwargs) + if pil_kwargs is None: + pil_kwargs = {} + pil_shape = (rgba.shape[1], rgba.shape[0]) + image = PIL.Image.frombuffer( + "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) + if format == "png" and metadata is not None: + # cf. backend_agg's print_png. + pnginfo = PIL.PngImagePlugin.PngInfo() + for k, v in metadata.items(): + pnginfo.add_text(k, v) + pil_kwargs["pnginfo"] = pnginfo + if format in ["jpg", "jpeg"]: + format = "jpeg" # Pillow doesn't recognize "jpg". + color = tuple( + int(x * 255) + for x in mcolors.to_rgb(rcParams["savefig.facecolor"])) + background = PIL.Image.new("RGB", pil_shape, color) + background.paste(image, image) + image = background + pil_kwargs.setdefault("format", format) + pil_kwargs.setdefault("dpi", (dpi, dpi)) + image.save(fname, **pil_kwargs) def pil_to_array(pilImage): - """Load a `PIL image`_ and return it as a numpy array. + """Load a `PIL image`_ and return it as a numpy int array. .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html @@ -1572,7 +1532,6 @@ def pil_to_array(pilImage): - (M, N) for grayscale images. - (M, N, 3) for RGB images. - (M, N, 4) for RGBA images. - """ if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']: # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array @@ -1593,6 +1552,36 @@ def pil_to_array(pilImage): return np.asarray(pilImage) # return MxNx4 RGBA array +def _pil_png_to_float_array(pil_png): + """Convert a PIL `PNGImageFile` to a 0-1 float array.""" + # Unlike pil_to_array this converts to 0-1 float32s for backcompat with the + # old libpng-based loader. + # The supported rawmodes are from PIL.PngImagePlugin._MODES. When + # mode == "RGB(A)", the 16-bit raw data has already been coarsened to 8-bit + # by Pillow. + mode = pil_png.mode + rawmode = pil_png.png.im_rawmode + if rawmode == "1": # Grayscale. + return np.asarray(pil_png, np.float32) + if rawmode == "L;2": # Grayscale. + return np.divide(pil_png, 2**2 - 1, dtype=np.float32) + if rawmode == "L;4": # Grayscale. + return np.divide(pil_png, 2**4 - 1, dtype=np.float32) + if rawmode == "L": # Grayscale. + return np.divide(pil_png, 2**8 - 1, dtype=np.float32) + if rawmode == "I;16B": # Grayscale. + return np.divide(pil_png, 2**16 - 1, dtype=np.float32) + if mode == "RGB": # RGB. + return np.divide(pil_png, 2**8 - 1, dtype=np.float32) + if mode == "P": # Palette. + return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32) + if mode == "LA": # Grayscale + alpha. + return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32) + if mode == "RGBA": # RGBA. + return np.divide(pil_png, 2**8 - 1, dtype=np.float32) + raise ValueError(f"Unknown PIL rawmode: {rawmode}") + + def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear', preview=False): """ diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index 3c1ba5750cf4..bec7e03843a8 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -24,6 +24,7 @@ import unicodedata import numpy as np +from PIL import Image from pyparsing import ( Combine, Empty, FollowedBy, Forward, Group, Literal, oneOf, OneOrMore, Optional, ParseBaseException, ParseFatalException, ParserElement, @@ -3430,11 +3431,9 @@ def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): depth : int Offset of the baseline from the bottom of the image, in pixels. """ - from matplotlib import _png rgba, depth = self.to_rgba( texstr, color=color, dpi=dpi, fontsize=fontsize) - with open(filename, "wb") as file: - _png.write_png(rgba, file) + Image.fromarray(rgba).save(filename, format="png") return depth def get_depth(self, texstr, dpi=120, fontsize=14): diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index baae5114faf6..81577e4803b6 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -14,6 +14,7 @@ from tempfile import TemporaryFile import numpy as np +import PIL import matplotlib as mpl from matplotlib.testing.exceptions import ImageComparisonFailure @@ -316,6 +317,10 @@ def calculate_rms(expected_image, actual_image): return np.sqrt(((expected_image - actual_image).astype(float) ** 2).mean()) +# NOTE: compare_image and save_diff_image assume that the image does not have +# 16-bit depth, as Pillow converts these to RGB incorrectly. + + def compare_images(expected, actual, tol, in_decorator=False): """ Compare two "image" files checking differences within a tolerance. @@ -365,8 +370,6 @@ def compare_images(expected, actual, tol, in_decorator=False): compare_images(img1, img2, 0.001) """ - from matplotlib import _png - actual = os.fspath(actual) if not os.path.exists(actual): raise Exception("Output image %s does not exist." % actual) @@ -383,10 +386,8 @@ def compare_images(expected, actual, tol, in_decorator=False): expected = convert(expected, cache=True) # open the image files and remove the alpha channel (if it exists) - with open(expected, "rb") as expected_file: - expected_image = _png.read_png_int(expected_file)[:, :, :3] - with open(actual, "rb") as actual_file: - actual_image = _png.read_png_int(actual_file)[:, :, :3] + expected_image = np.asarray(PIL.Image.open(expected).convert("RGB")) + actual_image = np.asarray(PIL.Image.open(actual).convert("RGB")) actual_image, expected_image = crop_to_same( actual, actual_image, expected, expected_image) @@ -436,11 +437,8 @@ def save_diff_image(expected, actual, output): File path to save difference image to. ''' # Drop alpha channels, similarly to compare_images. - from matplotlib import _png - with open(expected, "rb") as expected_file: - expected_image = _png.read_png(expected_file)[..., :3] - with open(actual, "rb") as actual_file: - actual_image = _png.read_png(actual_file)[..., :3] + expected_image = np.asarray(PIL.Image.open(expected).convert("RGB")) + actual_image = np.asarray(PIL.Image.open(actual).convert("RGB")) actual_image, expected_image = crop_to_same( actual, actual_image, expected, expected_image) expected_image = np.array(expected_image).astype(float) @@ -466,5 +464,4 @@ def save_diff_image(expected, actual, output): # Hard-code the alpha channel to fully solid save_image_np[:, :, 3] = 255 - with open(output, "wb") as output_file: - _png.write_png(save_image_np, output_file) + PIL.Image.fromarray(save_image_np).save(output, format="png") diff --git a/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png b/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png index d97265215a4c05bf93c40e19f8a0e8b055a3d4fc..e8ba8c51be427de08d89d7a0deb5320b163a2738 100644 GIT binary patch literal 47308 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 literal 46873 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 >mH>B8{h&$nG>1E$kla}d*J4P} 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 diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index e8e8da6c5f4c..111998a5cbd3 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -42,7 +42,6 @@ def test_lazy_imports(): import matplotlib.backend_bases import matplotlib.pyplot - assert 'matplotlib._png' not in sys.modules assert 'matplotlib._tri' not in sys.modules assert 'matplotlib._qhull' not in sys.modules assert 'matplotlib._contour' not in sys.modules diff --git a/lib/matplotlib/tests/test_png.py b/lib/matplotlib/tests/test_png.py index 8f355d2cc463..3369452cfb89 100644 --- a/lib/matplotlib/tests/test_png.py +++ b/lib/matplotlib/tests/test_png.py @@ -1,9 +1,7 @@ from io import BytesIO import glob import os -from pathlib import Path -import numpy as np import pytest from matplotlib.testing.decorators import image_comparison @@ -33,15 +31,6 @@ def test_pngsuite(): plt.gca().set_xlim(0, len(files)) -def test_imread_png_uint16(): - from matplotlib import _png - with (Path(__file__).parent - / 'baseline_images/test_png/uint16.png').open('rb') as file: - img = _png.read_png_int(file) - assert (img.dtype == np.uint16) - assert np.sum(img.flatten()) == 134184960 - - def test_truncated_file(tmpdir): d = tmpdir.mkdir('test') fname = str(d.join('test.png')) diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 1e788a64c4cd..3847a1a09358 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -398,13 +398,11 @@ def make_png(self, tex, fontsize, dpi): def get_grey(self, tex, fontsize=None, dpi=None): """Return the alpha channel.""" - from matplotlib import _png key = tex, self.get_font_config(), fontsize, dpi alpha = self.grey_arrayd.get(key) if alpha is None: pngfile = self.make_png(tex, fontsize, dpi) - with open(os.path.join(self.texcache, pngfile), "rb") as file: - X = _png.read_png(file) + X = mpl.image.imread(os.path.join(self.texcache, pngfile)) self.grey_arrayd[key] = alpha = X[:, :, -1] return alpha diff --git a/setup.py b/setup.py index 760e84af1261..4de989563478 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,6 @@ setupext.LibAgg(), setupext.FreeType(), setupext.FT2Font(), - setupext.Png(), setupext.Qhull(), setupext.Image(), setupext.TTConv(), @@ -262,6 +261,7 @@ def run(self): "cycler>=0.10", "kiwisolver>=1.0.1", "numpy>=1.11", + "pillow", "pyparsing>=2.0.1,!=2.0.4,!=2.1.2,!=2.1.6", "python-dateutil>=2.1", ], diff --git a/setupext.py b/setupext.py index 74e0ffb96461..10ae966364de 100644 --- a/setupext.py +++ b/setupext.py @@ -207,8 +207,6 @@ def deplib(libname): return libname known_libs = { - # TODO: support versioned libpng on build system rewrite - 'libpng16': ('libpng16', '_static'), 'z': ('zlib', 'static'), } @@ -492,11 +490,9 @@ def add_flags(self, ext, add_sources=True): for x in agg_sources) -# For FreeType2 and libpng, we add a separate checkdep_foo.c source to at the -# top of the extension sources. This file is compiled first and immediately -# aborts the compilation either with "foo.h: No such file or directory" if the -# header is not found, or an appropriate error message if the header indicates -# a too-old version. +# First compile checkdep_freetype2.c, which aborts the compilation either +# with "foo.h: No such file or directory" if the header is not found, or an +# appropriate error message if the header indicates a too-old version. class FreeType(SetupPackage): @@ -642,30 +638,6 @@ def get_extension(self): return ext -class Png(SetupPackage): - name = "png" - - def get_extension(self): - sources = [ - 'src/checkdep_libpng.c', - 'src/_png.cpp', - 'src/mplutils.cpp', - ] - ext = Extension('matplotlib._png', sources) - pkg_config_setup_extension( - ext, 'libpng', - atleast_version='1.2', - alt_exec=['libpng-config', '--ldflags'], - default_libraries=( - ['png', 'z'] if os.name == 'posix' else - # libpng upstream names their lib libpng16.lib, not png.lib. - [deplib('libpng16'), deplib('z')] if os.name == 'nt' else - [] - )) - add_numpy_flags(ext) - return ext - - class Qhull(SetupPackage): name = "qhull" diff --git a/src/_png.cpp b/src/_png.cpp deleted file mode 100644 index 0cc883df658e..000000000000 --- a/src/_png.cpp +++ /dev/null @@ -1,683 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -/* For linux, png.h must be imported before Python.h because - png.h needs to be the one to define setjmp. - Undefining _POSIX_C_SOURCE and _XOPEN_SOURCE stops a couple - of harmless warnings. -*/ -#define PY_SSIZE_T_CLEAN - -extern "C" { -# include -# ifdef _POSIX_C_SOURCE -# undef _POSIX_C_SOURCE -# endif -# ifndef _AIX -# ifdef _XOPEN_SOURCE -# undef _XOPEN_SOURCE -# endif -# endif -} - -#include "numpy_cpp.h" - -# include -# include "Python.h" - - -// As reported in [3082058] build _png.so on aix -#ifdef _AIX -#undef jmpbuf -#endif - -struct buffer_t { - PyObject *str; - size_t cursor; - size_t size; -}; - - -static void write_png_data_buffer(png_structp png_ptr, png_bytep data, png_size_t length) -{ - buffer_t *buff = (buffer_t *)png_get_io_ptr(png_ptr); - if (buff->cursor + length < buff->size) { - memcpy(PyBytes_AS_STRING(buff->str) + buff->cursor, data, length); - buff->cursor += length; - } -} - -static void flush_png_data_buffer(png_structp png_ptr) -{ - -} - -static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length) -{ - PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); - PyObject *write_method = PyObject_GetAttrString(py_file_obj, "write"); - PyObject *result = NULL; - if (write_method) { - result = PyObject_CallFunction(write_method, (char *)"y#", data, length); - } - Py_XDECREF(write_method); - Py_XDECREF(result); -} - -static void flush_png_data(png_structp png_ptr) -{ - PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); - PyObject *flush_method = PyObject_GetAttrString(py_file_obj, "flush"); - PyObject *result = NULL; - if (flush_method) { - result = PyObject_CallFunction(flush_method, (char *)""); - } - Py_XDECREF(flush_method); - Py_XDECREF(result); -} - -const char *Py_write_png__doc__ = - "write_png(buffer, file, dpi=0, compression=6, filter=auto, metadata=None)\n" - "\n" - "Parameters\n" - "----------\n" - "buffer : numpy array of image data\n" - " Must be an MxNxD array of dtype uint8.\n" - " - if D is 1, the image is greyscale\n" - " - if D is 3, the image is RGB\n" - " - if D is 4, the image is RGBA\n" - "\n" - "file : binary-mode file-like object or None\n" - " If None, a byte string containing the PNG data will be returned\n" - "\n" - "dpi : float\n" - " The dpi to store in the file metadata.\n" - "\n" - "compression : int\n" - " The level of lossless zlib compression to apply. 0 indicates no\n" - " compression. Values 1-9 indicate low/fast through high/slow\n" - " compression. Default is 6.\n" - "\n" - "filter : int\n" - " Filter to apply. Must be one of the constants: PNG_FILTER_NONE,\n" - " PNG_FILTER_SUB, PNG_FILTER_UP, PNG_FILTER_AVG, PNG_FILTER_PAETH.\n" - " See the PNG standard for more information.\n" - " If not provided, libpng will try to automatically determine the\n" - " best filter on a line-by-line basis.\n" - "\n" - "metadata : dictionary\n" - " The keyword-text pairs that are stored as comments in the image.\n" - " Keys must be shorter than 79 chars. The only supported encoding\n" - " for both keywords and values is Latin-1 (ISO 8859-1).\n" - " Examples given in the PNG Specification are:\n" - " - Title: Short (one line) title or caption for image\n" - " - Author: Name of image's creator\n" - " - Description: Description of image (possibly long)\n" - " - Copyright: Copyright notice\n" - " - Creation Time: Time of original image creation\n" - " (usually RFC 1123 format, see below)\n" - " - Software: Software used to create the image\n" - " - Disclaimer: Legal disclaimer\n" - " - Warning: Warning of nature of content\n" - " - Source: Device used to create the image\n" - " - Comment: Miscellaneous comment; conversion\n" - " from other image format\n" - "\n" - " For more details see the PNG specification:\n" - " https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords\n" - "\n" - "Returns\n" - "-------\n" - "buffer : bytes or None\n" - " Byte string containing the PNG content if None was passed in for\n" - " file, otherwise None is returned.\n"; - -// this code is heavily adapted from -// https://www.object-craft.com.au/projects/paint/ which licensed under the -// (BSD compatible) LICENSE_PAINT which is included in this distribution. -static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) -{ - numpy::array_view buffer; - PyObject *filein; - PyObject *metadata = NULL; - PyObject *meta_key, *meta_val; - png_text *text; - Py_ssize_t pos = 0; - int meta_pos = 0; - Py_ssize_t meta_size; - double dpi = 0; - int compression = 6; - int filter = -1; - const char *names[] = { "buffer", "file", "dpi", "compression", "filter", "metadata", NULL }; - - // We don't need strict contiguity, just for each row to be - // contiguous, and libpng has special handling for getting RGB out - // of RGBA, ARGB or BGR. But the simplest thing to do is to - // enforce contiguity using array_view::converter_contiguous. - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&O|diiO:write_png", - (char **)names, - &buffer.converter_contiguous, - &buffer, - &filein, - &dpi, - &compression, - &filter, - &metadata)) { - return NULL; - } - - png_uint_32 width = (png_uint_32)buffer.dim(1); - png_uint_32 height = (png_uint_32)buffer.dim(0); - npy_intp channels = buffer.dim(2); - std::vector row_pointers(height); - for (png_uint_32 row = 0; row < (png_uint_32)height; ++row) { - row_pointers[row] = (png_bytep)&buffer(row, 0, 0); - } - - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - struct png_color_8_struct sig_bit; - int png_color_type; - buffer_t buff; - buff.str = NULL; - - switch (channels) { - case 1: - png_color_type = PNG_COLOR_TYPE_GRAY; - break; - case 3: - png_color_type = PNG_COLOR_TYPE_RGB; - break; - case 4: - png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; - break; - default: - PyErr_SetString(PyExc_ValueError, - "Buffer must be an NxMxD array with D in 1, 3, 4 " - "(grayscale, RGB, RGBA)"); - goto exit; - } - - if (compression < 0 || compression > 9) { - PyErr_Format(PyExc_ValueError, - "compression must be in range 0-9, got %d", compression); - goto exit; - } - - if (filein == Py_None) { - buff.size = width * height * 4 + 1024; - buff.str = PyBytes_FromStringAndSize(NULL, buff.size); - if (buff.str == NULL) { - goto exit; - } - buff.cursor = 0; - } else { - PyErr_Clear(); - PyObject *write_method = PyObject_GetAttrString(filein, "write"); - if (!(write_method && PyCallable_Check(write_method))) { - Py_XDECREF(write_method); - PyErr_SetString(PyExc_TypeError, - "Object does not appear to be a file-like object"); - goto exit; - } - Py_XDECREF(write_method); - } - - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Could not create write struct"); - goto exit; - } - - png_set_compression_level(png_ptr, compression); - if (filter >= 0) { - png_set_filter(png_ptr, 0, filter); - } - - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Could not create info struct"); - goto exit; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - PyErr_SetString(PyExc_RuntimeError, "libpng signaled error"); - goto exit; - } - - if (buff.str) { - png_set_write_fn(png_ptr, (void *)&buff, &write_png_data_buffer, &flush_png_data_buffer); - } else { - png_set_write_fn(png_ptr, (void *)filein, &write_png_data, &flush_png_data); - } - png_set_IHDR(png_ptr, - info_ptr, - width, - height, - 8, - png_color_type, - PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, - PNG_FILTER_TYPE_BASE); - - // Save the dpi of the image in the file - if (dpi > 0.0) { - png_uint_32 dots_per_meter = (png_uint_32)(dpi / (2.54 / 100.0)); - png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); - } - -#ifdef PNG_TEXT_SUPPORTED - // Save the metadata - if (metadata != NULL && metadata != Py_None) { - if (!PyDict_Check(metadata)) { - PyErr_SetString(PyExc_TypeError, "metadata must be a dict or None"); - goto exit; - } - meta_size = PyDict_Size(metadata); - text = new png_text[meta_size]; - - while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { - text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; - if (PyUnicode_Check(meta_key)) { - PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "latin_1", "strict"); - if (temp_key != NULL) { - text[meta_pos].key = PyBytes_AsString(temp_key); - } - } else if (PyBytes_Check(meta_key)) { - text[meta_pos].key = PyBytes_AsString(meta_key); - } else { - char invalid_key[79]; - sprintf(invalid_key,"INVALID KEY %d", meta_pos); - text[meta_pos].key = invalid_key; - } - if (PyUnicode_Check(meta_val)) { - PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "latin_1", "strict"); - if (temp_val != NULL) { - text[meta_pos].text = PyBytes_AsString(temp_val); - } - } else if (PyBytes_Check(meta_val)) { - text[meta_pos].text = PyBytes_AsString(meta_val); - } else { - text[meta_pos].text = (char *)"Invalid value in metadata"; - } -#ifdef PNG_iTXt_SUPPORTED - text[meta_pos].lang = NULL; -#endif - meta_pos++; - } - png_set_text(png_ptr, info_ptr, text, meta_size); - delete[] text; - } -#endif - - sig_bit.alpha = 0; - switch (png_color_type) { - case PNG_COLOR_TYPE_GRAY: - sig_bit.gray = 8; - sig_bit.red = 0; - sig_bit.green = 0; - sig_bit.blue = 0; - break; - case PNG_COLOR_TYPE_RGB_ALPHA: - sig_bit.alpha = 8; - // fall through - case PNG_COLOR_TYPE_RGB: - sig_bit.gray = 0; - sig_bit.red = 8; - sig_bit.green = 8; - sig_bit.blue = 8; - break; - default: - PyErr_SetString(PyExc_RuntimeError, "internal error, bad png_color_type"); - goto exit; - } - png_set_sBIT(png_ptr, info_ptr, &sig_bit); - - png_write_info(png_ptr, info_ptr); - png_write_image(png_ptr, &row_pointers[0]); - png_write_end(png_ptr, info_ptr); - -exit: - if (png_ptr && info_ptr) { - png_destroy_write_struct(&png_ptr, &info_ptr); - } - if (PyErr_Occurred()) { - Py_XDECREF(buff.str); - return NULL; - } else { - if (buff.str) { - _PyBytes_Resize(&buff.str, buff.cursor); - return buff.str; - } - Py_RETURN_NONE; - } -} - -static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t length) -{ - PyObject *read_method = PyObject_GetAttrString(py_file_obj, "read"); - PyObject *result = NULL; - char *buffer; - Py_ssize_t bufflen; - if (read_method) { - result = PyObject_CallFunction(read_method, (char *)"i", length); - if (result) { - if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { - if (bufflen == (Py_ssize_t)length) { - memcpy(data, buffer, length); - } else { - PyErr_SetString(PyExc_IOError, "read past end of file"); - } - } else { - PyErr_SetString(PyExc_IOError, "failed to copy buffer"); - } - } else { - PyErr_SetString(PyExc_IOError, "failed to read file"); - } - - - } - Py_XDECREF(read_method); - Py_XDECREF(result); -} - -static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length) -{ - PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); - _read_png_data(py_file_obj, data, length); - if (PyErr_Occurred()) { - png_error(png_ptr, "failed to read file"); - } - -} - -static PyObject *_read_png(PyObject *filein, bool float_result) -{ - png_byte header[8]; // 8 is the maximum size that can be checked - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - int num_dims; - std::vector row_pointers; - png_uint_32 width = 0; - png_uint_32 height = 0; - int bit_depth; - PyObject *result = NULL; - - // TODO: Remove direct calls to Numpy API here - PyErr_Clear(); - - PyObject *read_method = PyObject_GetAttrString(filein, "read"); - if (!(read_method && PyCallable_Check(read_method))) { - Py_XDECREF(read_method); - PyErr_SetString(PyExc_TypeError, - "Object does not appear to be a file-like object"); - goto exit; - } - Py_XDECREF(read_method); - _read_png_data(filein, header, 8); - if (PyErr_Occurred()) { - goto exit; - } - - if (png_sig_cmp(header, 0, 8)) { - PyErr_SetString(PyExc_ValueError, "invalid PNG header"); - goto exit; - } - - /* initialize stuff */ - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - - if (!png_ptr) { - PyErr_SetString(PyExc_RuntimeError, "png_create_read_struct failed"); - goto exit; - } - - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - PyErr_SetString(PyExc_RuntimeError, "png_create_info_struct failed"); - goto exit; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_RuntimeError, "error setting jump"); - } - goto exit; - } - - png_set_read_fn(png_ptr, (void *)filein, &read_png_data); - png_set_sig_bytes(png_ptr, 8); - png_read_info(png_ptr, info_ptr); - width = png_get_image_width(png_ptr, info_ptr); - height = png_get_image_height(png_ptr, info_ptr); - bit_depth = png_get_bit_depth(png_ptr, info_ptr); - - // Unpack 1, 2, and 4-bit images - if (bit_depth < 8) { - png_set_packing(png_ptr); - } - - // If sig bits are set, shift data - png_color_8p sig_bit; - if ((png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE) && - png_get_sBIT(png_ptr, info_ptr, &sig_bit)) { - png_set_shift(png_ptr, sig_bit); - } - -#if NPY_BYTE_ORDER == NPY_LITTLE_ENDIAN - // Convert big endian to little - if (bit_depth == 16) { - png_set_swap(png_ptr); - } -#endif - - // Convert palletes to full RGB - if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(png_ptr); - bit_depth = 8; - } - - // If there's an alpha channel convert gray to RGB - if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) { - png_set_gray_to_rgb(png_ptr); - } - - png_set_interlace_handling(png_ptr); - png_read_update_info(png_ptr, info_ptr); - - row_pointers.resize(height); - for (png_uint_32 row = 0; row < height; row++) { - row_pointers[row] = new png_byte[png_get_rowbytes(png_ptr, info_ptr)]; - } - - png_read_image(png_ptr, &row_pointers[0]); - - npy_intp dimensions[3]; - dimensions[0] = height; // numrows - dimensions[1] = width; // numcols - if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) { - dimensions[2] = 4; // RGBA images - } else if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) { - dimensions[2] = 3; // RGB images - } else { - dimensions[2] = 1; // Greyscale images - } - - if (float_result) { - double max_value = (1 << bit_depth) - 1; - - numpy::array_view A(dimensions); - - for (png_uint_32 y = 0; y < height; y++) { - png_byte *row = row_pointers[y]; - for (png_uint_32 x = 0; x < width; x++) { - if (bit_depth == 16) { - png_uint_16 *ptr = &reinterpret_cast(row)[x * dimensions[2]]; - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { - A(y, x, p) = (float)(ptr[p] / max_value); - } - } else { - png_byte *ptr = &(row[x * dimensions[2]]); - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { - A(y, x, p) = (float)(ptr[p] / max_value); - } - } - } - } - - result = A.pyobj(); - } else if (bit_depth == 16) { - numpy::array_view A(dimensions); - - for (png_uint_32 y = 0; y < height; y++) { - png_byte *row = row_pointers[y]; - for (png_uint_32 x = 0; x < width; x++) { - png_uint_16 *ptr = &reinterpret_cast(row)[x * dimensions[2]]; - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { - A(y, x, p) = ptr[p]; - } - } - } - - result = A.pyobj(); - } else if (bit_depth == 8) { - numpy::array_view A(dimensions); - - for (png_uint_32 y = 0; y < height; y++) { - png_byte *row = row_pointers[y]; - for (png_uint_32 x = 0; x < width; x++) { - png_byte *ptr = &(row[x * dimensions[2]]); - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { - A(y, x, p) = ptr[p]; - } - } - } - - result = A.pyobj(); - } else { - PyErr_SetString(PyExc_RuntimeError, "image has unknown bit depth"); - goto exit; - } - - // free the png memory - png_read_end(png_ptr, info_ptr); - - // For gray, return an x by y array, not an x by y by 1 - num_dims = (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) ? 3 : 2; - - if (num_dims == 2) { - PyArray_Dims dims = {dimensions, 2}; - PyObject *reshaped = PyArray_Newshape((PyArrayObject *)result, &dims, NPY_CORDER); - Py_DECREF(result); - result = reshaped; - } - -exit: - if (png_ptr && info_ptr) { -#ifndef png_infopp_NULL - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); -#else - png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); -#endif - } - - for (png_uint_32 row = 0; row < height; row++) { - delete[] row_pointers[row]; - } - - if (PyErr_Occurred()) { - Py_XDECREF(result); - return NULL; - } else { - return result; - } -} - -const char *Py_read_png_float__doc__ = - "read_png_float(file)\n" - "\n" - "Read in a PNG file, converting values to floating-point doubles\n" - "in the range (0, 1)\n" - "\n" - "Parameters\n" - "----------\n" - "file : binary-mode file-like object\n"; - -static PyObject *Py_read_png_float(PyObject *self, PyObject *args, PyObject *kwds) -{ - return _read_png(args, true); -} - -const char *Py_read_png_int__doc__ = - "read_png_int(file)\n" - "\n" - "Read in a PNG file with original integer values.\n" - "\n" - "Parameters\n" - "----------\n" - "file : binary-mode file-like object\n"; - -static PyObject *Py_read_png_int(PyObject *self, PyObject *args, PyObject *kwds) -{ - return _read_png(args, false); -} - -const char *Py_read_png__doc__ = - "read_png(file)\n" - "\n" - "Read in a PNG file, converting values to floating-point doubles\n" - "in the range (0, 1)\n" - "\n" - "Alias for read_png_float()\n" - "\n" - "Parameters\n" - "----------\n" - "file : binary-mode file-like object\n"; - -static PyMethodDef module_methods[] = { - {"write_png", (PyCFunction)Py_write_png, METH_VARARGS|METH_KEYWORDS, Py_write_png__doc__}, - {"read_png", (PyCFunction)Py_read_png_float, METH_O, Py_read_png__doc__}, - {"read_png_float", (PyCFunction)Py_read_png_float, METH_O, Py_read_png_float__doc__}, - {"read_png_int", (PyCFunction)Py_read_png_int, METH_O, Py_read_png_int__doc__}, - {NULL} -}; - -extern "C" { - - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_png", - NULL, - 0, - module_methods, - NULL, - NULL, - NULL, - NULL - }; - - PyMODINIT_FUNC PyInit__png(void) - { - PyObject *m; - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - import_array(); - - if (PyModule_AddIntConstant(m, "PNG_FILTER_NONE", PNG_FILTER_NONE) || - PyModule_AddIntConstant(m, "PNG_FILTER_SUB", PNG_FILTER_SUB) || - PyModule_AddIntConstant(m, "PNG_FILTER_UP", PNG_FILTER_UP) || - PyModule_AddIntConstant(m, "PNG_FILTER_AVG", PNG_FILTER_AVG) || - PyModule_AddIntConstant(m, "PNG_FILTER_PAETH", PNG_FILTER_PAETH)) { - return NULL; - } - - - return m; - } -} diff --git a/src/checkdep_libpng.c b/src/checkdep_libpng.c deleted file mode 100644 index fc8811b508dc..000000000000 --- a/src/checkdep_libpng.c +++ /dev/null @@ -1,17 +0,0 @@ -#ifdef __has_include - #if !__has_include() - #error "libpng version 1.2 or higher is required. \ -Consider installing it with e.g. 'conda install libpng', \ -'apt install libpng12-dev', 'dnf install libpng-devel', or \ -'brew install libpng'." - #endif -#endif - -#include -#pragma message("Compiling with libpng version " PNG_LIBPNG_VER_STRING ".") -#if PNG_LIBPNG_VER < 10200 - #error "libpng version 1.2 or higher is required. \ -Consider installing it with e.g. 'conda install libpng', \ -'apt install libpng12-dev', 'dnf install libpng-devel', or \ -'brew install libpng'." -#endif