1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
"""Image utility functions for Sphinx."""
from __future__ import annotations
import base64
from os import path
from typing import TYPE_CHECKING, NamedTuple
import imagesize
if TYPE_CHECKING:
from os import PathLike
try:
from PIL import Image
except ImportError:
Image = None
mime_suffixes = {
'.gif': 'image/gif',
'.jpg': 'image/jpeg',
'.png': 'image/png',
'.pdf': 'application/pdf',
'.svg': 'image/svg+xml',
'.svgz': 'image/svg+xml',
'.ai': 'application/illustrator',
}
_suffix_from_mime = {v: k for k, v in reversed(mime_suffixes.items())}
class DataURI(NamedTuple):
mimetype: str
charset: str
data: bytes
def get_image_size(filename: str) -> tuple[int, int] | None:
try:
size = imagesize.get(filename)
if size[0] == -1:
size = None
elif isinstance(size[0], float) or isinstance(size[1], float):
size = (int(size[0]), int(size[1]))
if size is None and Image: # fallback to Pillow
with Image.open(filename) as im:
size = im.size
return size
except Exception:
return None
def guess_mimetype(
filename: PathLike[str] | str = '',
default: str | None = None,
) -> str | None:
ext = path.splitext(filename)[1].lower()
if ext in mime_suffixes:
return mime_suffixes[ext]
if path.exists(filename):
try:
imgtype = _image_type_from_file(filename)
except ValueError:
pass
else:
return 'image/' + imgtype
return default
def get_image_extension(mimetype: str) -> str | None:
return _suffix_from_mime.get(mimetype)
def parse_data_uri(uri: str) -> DataURI | None:
if not uri.startswith('data:'):
return None
# data:[<MIME-type>][;charset=<encoding>][;base64],<data>
mimetype = 'text/plain'
charset = 'US-ASCII'
properties, data = uri[5:].split(',', 1)
for prop in properties.split(';'):
if prop == 'base64':
pass # skip
elif prop.startswith('charset='):
charset = prop[8:]
elif prop:
mimetype = prop
image_data = base64.b64decode(data)
return DataURI(mimetype, charset, image_data)
def _image_type_from_file(filename: PathLike[str] | str) -> str:
with open(filename, 'rb') as f:
header = f.read(32) # 32 bytes
# Bitmap
# https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
if header.startswith(b'BM'):
return 'bmp'
# GIF
# https://en.wikipedia.org/wiki/GIF#File_format
if header.startswith((b'GIF87a', b'GIF89a')):
return 'gif'
# JPEG data
# https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
if header.startswith(b'\xFF\xD8'):
return 'jpeg'
# Portable Network Graphics
# https://en.wikipedia.org/wiki/PNG#File_header
if header.startswith(b'\x89PNG\r\n\x1A\n'):
return 'png'
# Scalable Vector Graphics
# https://svgwg.org/svg2-draft/struct.html
if b'<svg' in header.lower():
return 'svg+xml'
# TIFF
# https://en.wikipedia.org/wiki/TIFF#Byte_order
if header.startswith((b'MM', b'II')):
return 'tiff'
# WebP
# https://en.wikipedia.org/wiki/WebP#Technology
if header.startswith(b'RIFF') and header[8:12] == b'WEBP':
return 'webp'
raise ValueError('Could not detect image type!')
|