2008-08-09 04:59:02 +08:00
|
|
|
"""
|
|
|
|
Utility functions for handling images.
|
|
|
|
|
2014-03-21 22:54:53 +08:00
|
|
|
Requires Pillow as you might imagine.
|
2008-08-09 04:59:02 +08:00
|
|
|
"""
|
2015-03-28 03:54:14 +08:00
|
|
|
import struct
|
2012-12-14 00:26:34 +08:00
|
|
|
import zlib
|
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
from django.core.files import File
|
2013-01-01 20:20:36 +08:00
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
class ImageFile(File):
|
|
|
|
"""
|
|
|
|
A mixin for use alongside django.core.files.base.File, which provides
|
|
|
|
additional features for dealing with images.
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2016-08-26 08:06:22 +08:00
|
|
|
@property
|
|
|
|
def width(self):
|
2008-08-09 04:59:02 +08:00
|
|
|
return self._get_image_dimensions()[0]
|
|
|
|
|
2016-08-26 08:06:22 +08:00
|
|
|
@property
|
|
|
|
def height(self):
|
2008-08-09 04:59:02 +08:00
|
|
|
return self._get_image_dimensions()[1]
|
|
|
|
|
|
|
|
def _get_image_dimensions(self):
|
|
|
|
if not hasattr(self, "_dimensions_cache"):
|
2009-05-11 17:57:19 +08:00
|
|
|
close = self.closed
|
|
|
|
self.open()
|
2010-09-11 02:45:25 +08:00
|
|
|
self._dimensions_cache = get_image_dimensions(self, close=close)
|
2008-08-09 04:59:02 +08:00
|
|
|
return self._dimensions_cache
|
|
|
|
|
2013-01-01 20:20:36 +08:00
|
|
|
|
2010-09-11 02:45:25 +08:00
|
|
|
def get_image_dimensions(file_or_path, close=False):
|
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
Return the (width, height) of an image, given an open file or a path. Set
|
2010-09-11 02:45:25 +08:00
|
|
|
'close' to True to close the file at the end if it is initially in an open
|
|
|
|
state.
|
|
|
|
"""
|
2014-03-21 22:54:53 +08:00
|
|
|
from PIL import ImageFile as PillowImageFile
|
2010-09-11 02:45:25 +08:00
|
|
|
|
2014-03-21 22:54:53 +08:00
|
|
|
p = PillowImageFile.Parser()
|
2008-08-09 04:59:02 +08:00
|
|
|
if hasattr(file_or_path, "read"):
|
|
|
|
file = file_or_path
|
2010-09-11 02:45:25 +08:00
|
|
|
file_pos = file.tell()
|
|
|
|
file.seek(0)
|
2008-08-09 04:59:02 +08:00
|
|
|
else:
|
2021-05-24 23:10:00 +08:00
|
|
|
try:
|
|
|
|
file = open(file_or_path, "rb")
|
|
|
|
except OSError:
|
|
|
|
return (None, None)
|
2009-05-08 18:56:51 +08:00
|
|
|
close = True
|
|
|
|
try:
|
2014-03-21 22:54:53 +08:00
|
|
|
# Most of the time Pillow only needs a small chunk to parse the image
|
|
|
|
# and get the dimensions, but with some TIFF files Pillow needs to
|
|
|
|
# parse the whole file.
|
2012-07-31 03:54:29 +08:00
|
|
|
chunk_size = 1024
|
2009-05-08 18:56:51 +08:00
|
|
|
while 1:
|
2012-07-31 03:54:29 +08:00
|
|
|
data = file.read(chunk_size)
|
2009-05-08 18:56:51 +08:00
|
|
|
if not data:
|
|
|
|
break
|
2012-12-14 00:26:34 +08:00
|
|
|
try:
|
|
|
|
p.feed(data)
|
|
|
|
except zlib.error as e:
|
|
|
|
# ignore zlib complaining on truncated stream, just feed more
|
|
|
|
# data to parser (ticket #19457).
|
2013-01-01 20:13:31 +08:00
|
|
|
if e.args[0].startswith("Error -5"):
|
2012-12-14 00:26:34 +08:00
|
|
|
pass
|
|
|
|
else:
|
2013-01-01 20:20:36 +08:00
|
|
|
raise
|
2015-04-09 01:09:51 +08:00
|
|
|
except struct.error:
|
2015-03-28 03:54:14 +08:00
|
|
|
# Ignore PIL failing on a too short buffer when reads return
|
|
|
|
# less bytes than expected. Skip and feed more data to the
|
|
|
|
# parser (ticket #24544).
|
|
|
|
pass
|
2018-08-22 19:34:51 +08:00
|
|
|
except RuntimeError:
|
|
|
|
# e.g. "RuntimeError: could not create decoder object" for
|
|
|
|
# WebP files. A different chunk_size may work.
|
|
|
|
pass
|
2009-05-08 18:56:51 +08:00
|
|
|
if p.image:
|
|
|
|
return p.image.size
|
2013-11-04 02:08:55 +08:00
|
|
|
chunk_size *= 2
|
2015-03-22 21:46:38 +08:00
|
|
|
return (None, None)
|
2009-05-08 18:56:51 +08:00
|
|
|
finally:
|
|
|
|
if close:
|
|
|
|
file.close()
|
2010-09-11 02:45:25 +08:00
|
|
|
else:
|
|
|
|
file.seek(file_pos)
|