Skip to content

Commit

Permalink
Merge pull request #4700 from nulano/features-version
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Jun 21, 2020
2 parents 7b759e1 + 24672a2 commit 1bc67c9
Show file tree
Hide file tree
Showing 18 changed files with 223 additions and 47 deletions.
39 changes: 39 additions & 0 deletions Tests/test_features.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import re

import pytest
from PIL import features
Expand All @@ -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()
Expand All @@ -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]

Expand All @@ -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():
Expand All @@ -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():
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_icns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
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():
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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,
Expand Down Expand Up @@ -41,7 +41,7 @@ def gen_random_image(self, size, mode="RGB"):
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()
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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,
Expand Down Expand Up @@ -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()
Expand Down
6 changes: 5 additions & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
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,
Expand Down Expand Up @@ -47,6 +48,9 @@ def _assert_noerr(self, tmp_path, im):


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"""

Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from io import BytesIO

import pytest
from PIL import Image, ImageFile, PngImagePlugin
from PIL import Image, ImageFile, PngImagePlugin, features

from .helper import (
PillowLeakTestCase,
Expand Down Expand Up @@ -73,7 +73,7 @@ def get_chunks(self, filename):
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")

Expand Down
4 changes: 3 additions & 1 deletion Tests/test_file_webp.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -38,6 +39,7 @@ def setup_method(self):
def test_version(self):
_webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))

def test_read_rgb(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_imagecms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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

Expand Down Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
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,
Expand Down Expand Up @@ -41,7 +41,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():
Expand All @@ -68,7 +68,7 @@ def get_font(self):
)

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()
Expand Down Expand Up @@ -620,7 +620,7 @@ def test_complex_font_settings(self):
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()
Expand Down Expand Up @@ -692,7 +692,7 @@ def _check_text(self, font, path, epsilon):
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")
Expand All @@ -716,7 +716,7 @@ def test_variation_set_by_name(self):
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])
Expand Down
18 changes: 12 additions & 6 deletions docs/reference/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,45 +17,50 @@ 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
--------

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
4 changes: 2 additions & 2 deletions src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
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

Expand Down
Loading

0 comments on commit 1bc67c9

Please sign in to comment.