features.py 11 KB

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