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 d97265215a4c..e8ba8c51be42 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png and b/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png differ
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