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