features.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. from __future__ import annotations
  2. import collections
  3. import os
  4. import sys
  5. import warnings
  6. from typing import IO
  7. import PIL
  8. from . import Image
  9. modules = {
  10. "pil": ("PIL._imaging", "PILLOW_VERSION"),
  11. "tkinter": ("PIL._tkinter_finder", "tk_version"),
  12. "freetype2": ("PIL._imagingft", "freetype2_version"),
  13. "littlecms2": ("PIL._imagingcms", "littlecms_version"),
  14. "webp": ("PIL._webp", "webpdecoder_version"),
  15. }
  16. def check_module(feature: str) -> bool:
  17. """
  18. Checks if a module is available.
  19. :param feature: The module to check for.
  20. :returns: ``True`` if available, ``False`` otherwise.
  21. :raises ValueError: If the module is not defined in this version of Pillow.
  22. """
  23. if feature not in modules:
  24. msg = f"Unknown module {feature}"
  25. raise ValueError(msg)
  26. module, ver = modules[feature]
  27. try:
  28. __import__(module)
  29. return True
  30. except ModuleNotFoundError:
  31. return False
  32. except ImportError as ex:
  33. warnings.warn(str(ex))
  34. return False
  35. def version_module(feature: str) -> str | None:
  36. """
  37. :param feature: The module to check for.
  38. :returns:
  39. The loaded version number as a string, or ``None`` if unknown or not available.
  40. :raises ValueError: If the module is not defined in this version of Pillow.
  41. """
  42. if not check_module(feature):
  43. return None
  44. module, ver = modules[feature]
  45. return getattr(__import__(module, fromlist=[ver]), ver)
  46. def get_supported_modules() -> list[str]:
  47. """
  48. :returns: A list of all supported modules.
  49. """
  50. return [f for f in modules if check_module(f)]
  51. codecs = {
  52. "jpg": ("jpeg", "jpeglib"),
  53. "jpg_2000": ("jpeg2k", "jp2klib"),
  54. "zlib": ("zip", "zlib"),
  55. "libtiff": ("libtiff", "libtiff"),
  56. }
  57. def check_codec(feature: str) -> bool:
  58. """
  59. Checks if a codec is available.
  60. :param feature: The codec to check for.
  61. :returns: ``True`` if available, ``False`` otherwise.
  62. :raises ValueError: If the codec is not defined in this version of Pillow.
  63. """
  64. if feature not in codecs:
  65. msg = f"Unknown codec {feature}"
  66. raise ValueError(msg)
  67. codec, lib = codecs[feature]
  68. return f"{codec}_encoder" in dir(Image.core)
  69. def version_codec(feature: str) -> str | None:
  70. """
  71. :param feature: The codec to check for.
  72. :returns:
  73. The version number as a string, or ``None`` if not available.
  74. Checked at compile time for ``jpg``, run-time otherwise.
  75. :raises ValueError: If the codec is not defined in this version of Pillow.
  76. """
  77. if not check_codec(feature):
  78. return None
  79. codec, lib = codecs[feature]
  80. version = getattr(Image.core, f"{lib}_version")
  81. if feature == "libtiff":
  82. return version.split("\n")[0].split("Version ")[1]
  83. return version
  84. def get_supported_codecs() -> list[str]:
  85. """
  86. :returns: A list of all supported codecs.
  87. """
  88. return [f for f in codecs if check_codec(f)]
  89. features = {
  90. "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
  91. "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
  92. "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
  93. "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
  94. "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
  95. "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
  96. "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
  97. "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
  98. "xcb": ("PIL._imaging", "HAVE_XCB", None),
  99. }
  100. def check_feature(feature: str) -> bool | None:
  101. """
  102. Checks if a feature is available.
  103. :param feature: The feature to check for.
  104. :returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown.
  105. :raises ValueError: If the feature is not defined in this version of Pillow.
  106. """
  107. if feature not in features:
  108. msg = f"Unknown feature {feature}"
  109. raise ValueError(msg)
  110. module, flag, ver = features[feature]
  111. try:
  112. imported_module = __import__(module, fromlist=["PIL"])
  113. return getattr(imported_module, flag)
  114. except ModuleNotFoundError:
  115. return None
  116. except ImportError as ex:
  117. warnings.warn(str(ex))
  118. return None
  119. def version_feature(feature: str) -> str | None:
  120. """
  121. :param feature: The feature to check for.
  122. :returns: The version number as a string, or ``None`` if not available.
  123. :raises ValueError: If the feature is not defined in this version of Pillow.
  124. """
  125. if not check_feature(feature):
  126. return None
  127. module, flag, ver = features[feature]
  128. if ver is None:
  129. return None
  130. return getattr(__import__(module, fromlist=[ver]), ver)
  131. def get_supported_features() -> list[str]:
  132. """
  133. :returns: A list of all supported features.
  134. """
  135. return [f for f in features if check_feature(f)]
  136. def check(feature: str) -> bool | None:
  137. """
  138. :param feature: A module, codec, or feature name.
  139. :returns:
  140. ``True`` if the module, codec, or feature is available,
  141. ``False`` or ``None`` otherwise.
  142. """
  143. if feature in modules:
  144. return check_module(feature)
  145. if feature in codecs:
  146. return check_codec(feature)
  147. if feature in features:
  148. return check_feature(feature)
  149. warnings.warn(f"Unknown feature '{feature}'.", stacklevel=2)
  150. return False
  151. def version(feature: str) -> str | None:
  152. """
  153. :param feature:
  154. The module, codec, or feature to check for.
  155. :returns:
  156. The version number as a string, or ``None`` if unknown or not available.
  157. """
  158. if feature in modules:
  159. return version_module(feature)
  160. if feature in codecs:
  161. return version_codec(feature)
  162. if feature in features:
  163. return version_feature(feature)
  164. return None
  165. def get_supported() -> list[str]:
  166. """
  167. :returns: A list of all supported modules, features, and codecs.
  168. """
  169. ret = get_supported_modules()
  170. ret.extend(get_supported_features())
  171. ret.extend(get_supported_codecs())
  172. return ret
  173. def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
  174. """
  175. Prints information about this installation of Pillow.
  176. This function can be called with ``python3 -m PIL``.
  177. It can also be called with ``python3 -m PIL.report`` or ``python3 -m PIL --report``
  178. to have "supported_formats" set to ``False``, omitting the list of all supported
  179. image file formats.
  180. :param out:
  181. The output stream to print to. Defaults to ``sys.stdout`` if ``None``.
  182. :param supported_formats:
  183. If ``True``, a list of all supported image file formats will be printed.
  184. """
  185. if out is None:
  186. out = sys.stdout
  187. Image.init()
  188. print("-" * 68, file=out)
  189. print(f"Pillow {PIL.__version__}", file=out)
  190. py_version_lines = sys.version.splitlines()
  191. print(f"Python {py_version_lines[0].strip()}", file=out)
  192. for py_version in py_version_lines[1:]:
  193. print(f" {py_version.strip()}", file=out)
  194. print("-" * 68, file=out)
  195. print(f"Python executable is {sys.executable or 'unknown'}", file=out)
  196. if sys.prefix != sys.base_prefix:
  197. print(f"Environment Python files loaded from {sys.prefix}", file=out)
  198. print(f"System Python files loaded from {sys.base_prefix}", file=out)
  199. print("-" * 68, file=out)
  200. print(
  201. f"Python Pillow modules loaded from {os.path.dirname(Image.__file__)}",
  202. file=out,
  203. )
  204. print(
  205. f"Binary Pillow modules loaded from {os.path.dirname(Image.core.__file__)}",
  206. file=out,
  207. )
  208. print("-" * 68, file=out)
  209. for name, feature in [
  210. ("pil", "PIL CORE"),
  211. ("tkinter", "TKINTER"),
  212. ("freetype2", "FREETYPE2"),
  213. ("littlecms2", "LITTLECMS2"),
  214. ("webp", "WEBP"),
  215. ("transp_webp", "WEBP Transparency"),
  216. ("webp_mux", "WEBPMUX"),
  217. ("webp_anim", "WEBP Animation"),
  218. ("jpg", "JPEG"),
  219. ("jpg_2000", "OPENJPEG (JPEG2000)"),
  220. ("zlib", "ZLIB (PNG/ZIP)"),
  221. ("libtiff", "LIBTIFF"),
  222. ("raqm", "RAQM (Bidirectional Text)"),
  223. ("libimagequant", "LIBIMAGEQUANT (Quantization method)"),
  224. ("xcb", "XCB (X protocol)"),
  225. ]:
  226. if check(name):
  227. v: str | None = None
  228. if name == "jpg":
  229. libjpeg_turbo_version = version_feature("libjpeg_turbo")
  230. if libjpeg_turbo_version is not None:
  231. v = "libjpeg-turbo " + libjpeg_turbo_version
  232. if v is None:
  233. v = version(name)
  234. if v is not None:
  235. version_static = name in ("pil", "jpg")
  236. if name == "littlecms2":
  237. # this check is also in src/_imagingcms.c:setup_module()
  238. version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
  239. t = "compiled for" if version_static else "loaded"
  240. if name == "raqm":
  241. for f in ("fribidi", "harfbuzz"):
  242. v2 = version_feature(f)
  243. if v2 is not None:
  244. v += f", {f} {v2}"
  245. print("---", feature, "support ok,", t, v, file=out)
  246. else:
  247. print("---", feature, "support ok", file=out)
  248. else:
  249. print("***", feature, "support not installed", file=out)
  250. print("-" * 68, file=out)
  251. if supported_formats:
  252. extensions = collections.defaultdict(list)
  253. for ext, i in Image.EXTENSION.items():
  254. extensions[i].append(ext)
  255. for i in sorted(Image.ID):
  256. line = f"{i}"
  257. if i in Image.MIME:
  258. line = f"{line} {Image.MIME[i]}"
  259. print(line, file=out)
  260. if i in extensions:
  261. print(
  262. "Extensions: {}".format(", ".join(sorted(extensions[i]))), file=out
  263. )
  264. features = []
  265. if i in Image.OPEN:
  266. features.append("open")
  267. if i in Image.SAVE:
  268. features.append("save")
  269. if i in Image.SAVE_ALL:
  270. features.append("save_all")
  271. if i in Image.DECODERS:
  272. features.append("decode")
  273. if i in Image.ENCODERS:
  274. features.append("encode")
  275. print("Features: {}".format(", ".join(features)), file=out)
  276. print("-" * 68, file=out)