1"""
2Utility functions for handling images.
3
4Requires Pillow as you might imagine.
5"""
6
7import struct
8import zlib
9
10from django.core.files import File
11
12
13class ImageFile(File):
14 """
15 A mixin for use alongside django.core.files.base.File, which provides
16 additional features for dealing with images.
17 """
18
19 @property
20 def width(self):
21 return self._get_image_dimensions()[0]
22
23 @property
24 def height(self):
25 return self._get_image_dimensions()[1]
26
27 def _get_image_dimensions(self):
28 if not hasattr(self, "_dimensions_cache"):
29 close = self.closed
30 self.open()
31 self._dimensions_cache = get_image_dimensions(self, close=close)
32 return self._dimensions_cache
33
34
35def get_image_dimensions(file_or_path, close=False):
36 """
37 Return the (width, height) of an image, given an open file or a path. Set
38 'close' to True to close the file at the end if it is initially in an open
39 state.
40 """
41 from PIL import ImageFile as PillowImageFile
42
43 p = PillowImageFile.Parser()
44 if hasattr(file_or_path, "read"):
45 file = file_or_path
46 file_pos = file.tell()
47 file.seek(0)
48 else:
49 try:
50 file = open(file_or_path, "rb")
51 except OSError:
52 return (None, None)
53 close = True
54 try:
55 # Most of the time Pillow only needs a small chunk to parse the image
56 # and get the dimensions, but with some TIFF files Pillow needs to
57 # parse the whole file.
58 chunk_size = 1024
59 while 1:
60 data = file.read(chunk_size)
61 if not data:
62 break
63 try:
64 p.feed(data)
65 except zlib.error as e:
66 # ignore zlib complaining on truncated stream, just feed more
67 # data to parser (ticket #19457).
68 if e.args[0].startswith("Error -5"):
69 pass
70 else:
71 raise
72 except struct.error:
73 # Ignore PIL failing on a too short buffer when reads return
74 # less bytes than expected. Skip and feed more data to the
75 # parser (ticket #24544).
76 pass
77 except RuntimeError:
78 # e.g. "RuntimeError: could not create decoder object" for
79 # WebP files. A different chunk_size may work.
80 pass
81 if p.image:
82 return p.image.size
83 chunk_size *= 2
84 return (None, None)
85 finally:
86 if close:
87 file.close()
88 else:
89 file.seek(file_pos)