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