From a324f4a466ab12d4c8cf0b95abe4a5bd79b1c7aa Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 12 Oct 2019 14:29:10 +0100 Subject: [PATCH 1/4] add version to features info block --- Tests/test_file_icns.py | 4 +-- Tests/test_file_jpeg.py | 4 +-- Tests/test_file_jpeg2k.py | 4 +-- Tests/test_file_png.py | 4 +-- Tests/test_imagecms.py | 4 +-- Tests/test_imagefont.py | 12 +++---- src/PIL/IcnsImagePlugin.py | 4 +-- src/PIL/features.py | 64 ++++++++++++++++++++++++++++++++------ 8 files changed, 73 insertions(+), 27 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index aeb146f7e..7bf7b72ec 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -2,14 +2,14 @@ import io import sys import pytest -from PIL import IcnsImagePlugin, Image +from PIL import IcnsImagePlugin, Image, features from .helper import assert_image_equal, assert_image_similar # sample icon file TEST_FILE = "Tests/images/pillow.icns" -ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version") +ENABLE_JPEG2K = features.check_codec("jpg_2000") def test_sanity(): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ee0543027..be8e21d5a 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,7 +3,7 @@ import re from io import BytesIO import pytest -from PIL import ExifTags, Image, ImageFile, JpegImagePlugin +from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, features from .helper import ( assert_image, @@ -41,7 +41,7 @@ class TestFileJpeg: def test_sanity(self): # internal version number - assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version) + assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) with Image.open(TEST_FILE) as im: im.load() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 7b8b7a04a..07f8e8e05 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -2,7 +2,7 @@ import re from io import BytesIO import pytest -from PIL import Image, ImageFile, Jpeg2KImagePlugin +from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( assert_image_equal, @@ -35,7 +35,7 @@ def roundtrip(im, **options): def test_sanity(): # Internal version number - assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000")) with Image.open("Tests/images/test-card-lossless.jp2") as im: px = im.load() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a44bdecf8..9bd8507d9 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -3,7 +3,7 @@ import zlib from io import BytesIO import pytest -from PIL import Image, ImageFile, PngImagePlugin +from PIL import Image, ImageFile, PngImagePlugin, features from .helper import ( PillowLeakTestCase, @@ -73,7 +73,7 @@ class TestFilePng: def test_sanity(self, tmp_path): # internal version number - assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) + assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) test_file = str(tmp_path / "temp.png") diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 921fdc369..953731215 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -4,7 +4,7 @@ import re from io import BytesIO import pytest -from PIL import Image, ImageMode +from PIL import Image, ImageMode, features from .helper import assert_image, assert_image_equal, assert_image_similar, hopper @@ -46,7 +46,7 @@ def test_sanity(): assert list(map(type, v)) == [str, str, str, str] # internal version number - assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version) + assert re.search(r"\d+\.\d+$", features.version_module("littlecms2")) skip_missing() i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b62fc2e23..9602a3099 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,7 +7,7 @@ import sys from io import BytesIO import pytest -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageFont, features from .helper import ( assert_image_equal, @@ -40,7 +40,7 @@ class TestImageFont: @classmethod def setup_class(self): - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) self.metrics = self.METRICS["Default"] for conditions, metrics in self.METRICS.items(): @@ -67,7 +67,7 @@ class TestImageFont: ) def test_sanity(self): - assert re.search(r"\d+\.\d+\.\d+$", ImageFont.core.freetype2_version) + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) def test_font_properties(self): ttf = self.get_font() @@ -619,7 +619,7 @@ class TestImageFont: def test_variation_get(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.get_variation_names() @@ -691,7 +691,7 @@ class TestImageFont: def test_variation_set_by_name(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.set_variation_by_name("Bold") @@ -715,7 +715,7 @@ class TestImageFont: def test_variation_set_by_axes(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.set_variation_by_axes([100]) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index c00392615..9de7d8dfe 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -23,10 +23,10 @@ import subprocess import sys import tempfile -from PIL import Image, ImageFile, PngImagePlugin +from PIL import Image, ImageFile, PngImagePlugin, features from PIL._binary import i8 -enable_jpeg2k = hasattr(Image.core, "jp2klib_version") +enable_jpeg2k = features.check_codec("jpg_2000") if enable_jpeg2k: from PIL import Jpeg2KImagePlugin diff --git a/src/PIL/features.py b/src/PIL/features.py index 33e89cf24..e1823537e 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -8,11 +8,11 @@ import PIL from . import Image modules = { - "pil": "PIL._imaging", - "tkinter": "PIL._tkinter_finder", - "freetype2": "PIL._imagingft", - "littlecms2": "PIL._imagingcms", - "webp": "PIL._webp", + "pil": ("PIL._imaging", None), + "tkinter": ("PIL._tkinter_finder", None), + "freetype2": ("PIL._imagingft", "freetype2"), + "littlecms2": ("PIL._imagingcms", "littlecms"), + "webp": ("PIL._webp", None), } @@ -27,7 +27,7 @@ def check_module(feature): if not (feature in modules): raise ValueError("Unknown module %s" % feature) - module = modules[feature] + module, lib = modules[feature] try: __import__(module) @@ -36,6 +36,20 @@ def check_module(feature): return False +def version_module(feature): + if not check_module(feature): + return None + + module, lib = modules[feature] + + if lib is None: + return None + + attr = lib + "_version" + + return getattr(__import__(module, fromlist=[attr]), attr) + + def get_supported_modules(): """ :returns: A list of all supported modules. @@ -43,7 +57,12 @@ def get_supported_modules(): return [f for f in modules if check_module(f)] -codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"} +codecs = { + "jpg": ("jpeg", "jpeglib"), + "jpg_2000": ("jpeg2k", "jp2klib"), + "zlib": ("zip", "zlib"), + "libtiff": ("libtiff", "libtiff"), +} def check_codec(feature): @@ -57,11 +76,25 @@ def check_codec(feature): if feature not in codecs: raise ValueError("Unknown codec %s" % feature) - codec = codecs[feature] + codec, lib = codecs[feature] return codec + "_encoder" in dir(Image.core) +def version_codec(feature): + if not check_codec(feature): + return None + + codec, lib = codecs[feature] + + version = getattr(Image.core, lib + "_version") + + if feature == "libtiff": + return version.split("\n")[0].split("Version ")[1] + + return version + + def get_supported_codecs(): """ :returns: A list of all supported codecs. @@ -125,6 +158,14 @@ def check(feature): return False +def version(feature): + if feature in modules: + return version_module(feature) + if feature in codecs: + return version_codec(feature) + return None + + def get_supported(): """ :returns: A list of all supported modules, features, and codecs. @@ -187,7 +228,12 @@ def pilinfo(out=None, supported_formats=True): ("xcb", "XCB (X protocol)"), ]: if check(name): - print("---", feature, "support ok", file=out) + v = version(name) + if v is not None: + support = "ok (version {})".format(v) + else: + support = "ok" + print("---", feature, "support", support, file=out) else: print("***", feature, "support not installed", file=out) print("-" * 68, file=out) From 6c1ff252d60954bb6ae8af767dae858afa159562 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 05:35:43 +0200 Subject: [PATCH 2/4] check run-time version numbers where available, add docs --- docs/reference/features.rst | 18 ++++--- src/PIL/features.py | 89 ++++++++++++++++++++++++---------- src/_imaging.c | 9 ++++ src/_imagingcms.c | 4 +- src/_imagingft.c | 7 +++ src/_webp.c | 13 +++++ src/libImaging/QuantPngQuant.c | 9 ++++ src/libImaging/ZipEncode.c | 2 +- 8 files changed, 118 insertions(+), 33 deletions(-) diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 196f938ed..47e9a6d63 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -8,6 +8,7 @@ The :py:mod:`PIL.features` module can be used to detect which Pillow features ar .. autofunction:: PIL.features.pilinfo .. autofunction:: PIL.features.check +.. autofunction:: PIL.features.version .. autofunction:: PIL.features.get_supported Modules @@ -16,28 +17,31 @@ Modules Support for the following modules can be checked: * ``pil``: The Pillow core module, required for all functionality. -* ``tkinter``: Tkinter support. +* ``tkinter``: Tkinter support. Version number not available. * ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. * ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. * ``webp``: WebP image support. .. autofunction:: PIL.features.check_module +.. autofunction:: PIL.features.version_module .. autofunction:: PIL.features.get_supported_modules Codecs ------ -These are only checked during Pillow compilation. +Support for these is only checked during Pillow compilation. If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead. +Except for ``jpg``, the version number is checked at run-time. Support for the following codecs can be checked: -* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. +* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. Only compile time version number is available. * ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats. * ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG. * ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats. .. autofunction:: PIL.features.check_codec +.. autofunction:: PIL.features.version_codec .. autofunction:: PIL.features.get_supported_codecs Features @@ -45,16 +49,18 @@ Features Some of these are only checked during Pillow compilation. If the required library was uninstalled from the system, the relevant module may fail to load instead. +Feature version numbers are available only where stated. Support for the following features can be checked: -* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. +* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available. * ``transp_webp``: Support for transparency in WebP images. * ``webp_mux``: (compile time) Support for EXIF data in WebP images. * ``webp_anim``: (compile time) Support for animated WebP images. -* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. -* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. +* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. +* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. .. autofunction:: PIL.features.check_feature +.. autofunction:: PIL.features.version_feature .. autofunction:: PIL.features.get_supported_features diff --git a/src/PIL/features.py b/src/PIL/features.py index e1823537e..4f1bb0b8f 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -8,11 +8,11 @@ import PIL from . import Image modules = { - "pil": ("PIL._imaging", None), + "pil": ("PIL._imaging", "PILLOW_VERSION"), "tkinter": ("PIL._tkinter_finder", None), - "freetype2": ("PIL._imagingft", "freetype2"), - "littlecms2": ("PIL._imagingcms", "littlecms"), - "webp": ("PIL._webp", None), + "freetype2": ("PIL._imagingft", "freetype2_version"), + "littlecms2": ("PIL._imagingcms", "littlecms_version"), + "webp": ("PIL._webp", "webpdecoder_version"), } @@ -27,7 +27,7 @@ def check_module(feature): if not (feature in modules): raise ValueError("Unknown module %s" % feature) - module, lib = modules[feature] + module, ver = modules[feature] try: __import__(module) @@ -37,17 +37,21 @@ def check_module(feature): def version_module(feature): + """ + :param feature: The module to check for. + :returns: + The loaded version number as a string, or ``None`` if unknown or not available. + :raises ValueError: If the module is not defined in this version of Pillow. + """ if not check_module(feature): return None - module, lib = modules[feature] + module, ver = modules[feature] - if lib is None: + if ver is None: return None - attr = lib + "_version" - - return getattr(__import__(module, fromlist=[attr]), attr) + return getattr(__import__(module, fromlist=[ver]), ver) def get_supported_modules(): @@ -82,6 +86,13 @@ def check_codec(feature): def version_codec(feature): + """ + :param feature: The codec to check for. + :returns: + The version number as a string, or ``None`` if not available. + Checked at compile time for ``jpg``, run-time otherwise. + :raises ValueError: If the codec is not defined in this version of Pillow. + """ if not check_codec(feature): return None @@ -103,13 +114,13 @@ def get_supported_codecs(): features = { - "webp_anim": ("PIL._webp", "HAVE_WEBPANIM"), - "webp_mux": ("PIL._webp", "HAVE_WEBPMUX"), - "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), - "raqm": ("PIL._imagingft", "HAVE_RAQM"), - "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), - "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"), - "xcb": ("PIL._imaging", "HAVE_XCB"), + "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None), + "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), + "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), + "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), + "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), + "xcb": ("PIL._imaging", "HAVE_XCB", None), } @@ -124,7 +135,7 @@ def check_feature(feature): if feature not in features: raise ValueError("Unknown feature %s" % feature) - module, flag = features[feature] + module, flag, ver = features[feature] try: imported_module = __import__(module, fromlist=["PIL"]) @@ -133,6 +144,23 @@ def check_feature(feature): return None +def version_feature(feature): + """ + :param feature: The feature to check for. + :returns: The version number as a string, or ``None`` if not available. + :raises ValueError: If the feature is not defined in this version of Pillow. + """ + if not check_feature(feature): + return None + + module, flag, ver = features[feature] + + if ver is None: + return None + + return getattr(__import__(module, fromlist=[ver]), ver) + + def get_supported_features(): """ :returns: A list of all supported features. @@ -142,9 +170,9 @@ def get_supported_features(): def check(feature): """ - :param feature: A module, feature, or codec name. + :param feature: A module, codec, or feature name. :returns: - ``True`` if the module, feature, or codec is available, + ``True`` if the module, codec, or feature is available, ``False`` or ``None`` otherwise. """ @@ -159,10 +187,18 @@ def check(feature): def version(feature): + """ + :param feature: + The module, codec, or feature to check for. + :returns: + The version number as a string, or ``None`` if unknown or not available. + """ if feature in modules: return version_module(feature) if feature in codecs: return version_codec(feature) + if feature in features: + return version_feature(feature) return None @@ -228,12 +264,15 @@ def pilinfo(out=None, supported_formats=True): ("xcb", "XCB (X protocol)"), ]: if check(name): - v = version(name) - if v is not None: - support = "ok (version {})".format(v) + if name == "jpg" and check_feature("libjpeg_turbo"): + v = "libjpeg-turbo " + version_feature("libjpeg_turbo") else: - support = "ok" - print("---", feature, "support", support, file=out) + v = version(name) + if v is not None: + t = "compiled for" if name in ("pil", "jpg") else "loaded" + print("---", feature, "support ok,", t, "version", v, file=out) + else: + print("---", feature, "support ok", file=out) else: print("***", feature, "support not installed", file=out) print("-" * 68, file=out) diff --git a/src/_imaging.c b/src/_imaging.c index 40bfbf2fe..1ed5e8a42 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4168,12 +4168,21 @@ setup_module(PyObject* m) { #ifdef LIBJPEG_TURBO_VERSION PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); + #define tostr1(a) #a + #define tostr(a) tostr1(a) + PyDict_SetItemString(d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION))); + #undef tostr + #undef tostr1 #else PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False); #endif #ifdef HAVE_LIBIMAGEQUANT PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); + { + extern const char* ImagingImageQuantVersion(void); + PyDict_SetItemString(d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion())); + } #else PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False); #endif diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 60b6b7228..7f23d5964 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -1608,6 +1608,7 @@ static int setup_module(PyObject* m) { PyObject *d; PyObject *v; + int vn; d = PyModule_GetDict(m); @@ -1622,7 +1623,8 @@ setup_module(PyObject* m) { d = PyModule_GetDict(m); - v = PyUnicode_FromFormat("%d.%d", LCMS_VERSION / 100, LCMS_VERSION % 100); + vn = cmsGetEncodedCMMversion(); + v = PyUnicode_FromFormat("%d.%d", vn / 100, vn % 100); PyDict_SetItemString(d, "littlecms_version", v); return 0; diff --git a/src/_imagingft.c b/src/_imagingft.c index e0ff7521c..d7ff7ad28 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,6 +81,7 @@ typedef struct { static PyTypeObject Font_Type; +typedef const char* (*t_raqm_version_string) (void); typedef bool (*t_raqm_version_atleast)(unsigned int major, unsigned int minor, unsigned int micro); @@ -112,6 +113,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq); typedef struct { void* raqm; int version; + t_raqm_version_string version_string; t_raqm_version_atleast version_atleast; t_raqm_create create; t_raqm_set_text set_text; @@ -173,6 +175,7 @@ setraqm(void) } #ifndef _WIN32 + p_raqm.version_string = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_string"); p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); @@ -206,6 +209,7 @@ setraqm(void) return 2; } #else + p_raqm.version_string = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_string"); p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); @@ -1251,6 +1255,9 @@ setup_module(PyObject* m) { setraqm(); v = PyBool_FromLong(!!p_raqm.raqm); PyDict_SetItemString(d, "HAVE_RAQM", v); + if (p_raqm.version_string) { + PyDict_SetItemString(d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); + } return 0; } diff --git a/src/_webp.c b/src/_webp.c index c2b363cd0..468a9ff73 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -821,6 +821,16 @@ PyObject* WebPDecoderVersion_wrapper() { return Py_BuildValue("i", WebPGetDecoderVersion()); } +// Version as string +const char* +WebPDecoderVersion_str(void) +{ + static char version[20]; + int version_number = WebPGetDecoderVersion(); + sprintf(version, "%d.%d.%d", version_number >> 16, (version_number >> 8) % 0x100, version_number % 0x100); + return version; +} + /* * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well. * Files that are valid with 0.3 are reported as being invalid. @@ -872,10 +882,13 @@ void addTransparencyFlagToModule(PyObject* m) { } static int setup_module(PyObject* m) { + PyObject* d = PyModule_GetDict(m); addMuxFlagToModule(m); addAnimFlagToModule(m); addTransparencyFlagToModule(m); + PyDict_SetItemString(d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str())); + #ifdef HAVE_WEBPANIM /* Ready object types */ if (PyType_Ready(&WebPAnimDecoder_Type) < 0 || diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c index 753ceb02f..7a23ec8c5 100644 --- a/src/libImaging/QuantPngQuant.c +++ b/src/libImaging/QuantPngQuant.c @@ -113,4 +113,13 @@ err: return result; } +const char* +ImagingImageQuantVersion(void) +{ + static char version[20]; + int number = liq_version(); + sprintf(version, "%d.%d.%d", number / 10000, (number / 100) % 100, number % 100); + return version; +} + #endif diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c index 0b4435678..84ccb14ea 100644 --- a/src/libImaging/ZipEncode.c +++ b/src/libImaging/ZipEncode.c @@ -373,7 +373,7 @@ ImagingZipEncodeCleanup(ImagingCodecState state) { const char* ImagingZipVersion(void) { - return ZLIB_VERSION; + return zlibVersion(); } #endif From d5a6b2584e3e1a2dcc15c5adec291cd0390fb785 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 15 Jun 2020 15:32:30 +0200 Subject: [PATCH 3/4] add tests for version numbers --- Tests/test_features.py | 39 ++++++++++++++++++++++++++++++++++++++ Tests/test_file_libtiff.py | 6 +++++- Tests/test_file_webp.py | 4 +++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index 7cfa08071..1e7692204 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,4 +1,5 @@ import io +import re import pytest from PIL import features @@ -21,6 +22,27 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) +def test_version(): + # Check the correctness of the convenience function + # and the format of version numbers + + def test(name, function): + version = features.version(name) + if not features.check(name): + assert version is None + else: + assert function(name) == version + if name != "PIL": + assert version is None or re.search(r"\d+(\.\d+)*$", version) + + for module in features.modules: + test(module, features.version_module) + for codec in features.codecs: + test(codec, features.version_codec) + for feature in features.features: + test(feature, features.version_feature) + + @skip_unless_feature("webp") def test_webp_transparency(): assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() @@ -37,9 +59,22 @@ def test_webp_anim(): assert features.check("webp_anim") == _webp.HAVE_WEBPANIM +@skip_unless_feature("libjpeg_turbo") +def test_libjpeg_turbo_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo")) + + +@skip_unless_feature("libimagequant") +def test_libimagequant_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) + + def test_check_modules(): for feature in features.modules: assert features.check_module(feature) in [True, False] + + +def test_check_codecs(): for feature in features.codecs: assert features.check_codec(feature) in [True, False] @@ -64,6 +99,8 @@ def test_unsupported_codec(): # Act / Assert with pytest.raises(ValueError): features.check_codec(codec) + with pytest.raises(ValueError): + features.version_codec(codec) def test_unsupported_module(): @@ -72,6 +109,8 @@ def test_unsupported_module(): # Act / Assert with pytest.raises(ValueError): features.check_module(module) + with pytest.raises(ValueError): + features.version_module(module) def test_pilinfo(): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9d9e49289..c30eb54eb 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -3,11 +3,12 @@ import io import itertools import logging import os +import re from collections import namedtuple from ctypes import c_float import pytest -from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags +from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from .helper import ( assert_image_equal, @@ -47,6 +48,9 @@ class LibTiffTestCase: class TestFileLibTiff(LibTiffTestCase): + def test_version(self): + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) + def test_g4_tiff(self, tmp_path): """Test the ordinary file path load path""" diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index f538b0ecf..25a4bb8da 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,7 +1,8 @@ import io +import re import pytest -from PIL import Image, WebPImagePlugin +from PIL import Image, WebPImagePlugin, features from .helper import ( assert_image_similar, @@ -38,6 +39,7 @@ class TestFileWebp: def test_version(self): _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) def test_read_rgb(self): """ From 24672a2f7502e0ae0534bd0aadf17b2c3e200787 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 21 Jun 2020 18:07:10 +0100 Subject: [PATCH 4/4] simplify output Co-authored-by: Hugo van Kemenade --- src/PIL/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/features.py b/src/PIL/features.py index 4f1bb0b8f..66b093350 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -270,7 +270,7 @@ def pilinfo(out=None, supported_formats=True): v = version(name) if v is not None: t = "compiled for" if name in ("pil", "jpg") else "loaded" - print("---", feature, "support ok,", t, "version", v, file=out) + print("---", feature, "support ok,", t, v, file=out) else: print("---", feature, "support ok", file=out) else: