From 6c1ff252d60954bb6ae8af767dae858afa159562 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 05:35:43 +0200 Subject: [PATCH] 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