NewsBlur/utils/image_functions.py

92 lines
2.9 KiB
Python
Raw Normal View History

2013-01-08 14:11:59 -08:00
"""Operations for images through the PIL."""
import urllib.request
2024-04-24 09:50:42 -04:00
from io import BytesIO
from PIL import Image, ImageFile
2013-06-21 13:15:04 -07:00
from PIL import ImageOps as PILOps
from PIL.ExifTags import TAGS
2013-01-08 14:11:59 -08:00
2024-04-24 09:43:56 -04:00
PROFILE_PICTURE_SIZES = {"fullsize": (256, 256), "thumbnail": (64, 64)}
2013-01-08 14:11:59 -08:00
class ImageOps:
2024-04-24 09:43:56 -04:00
"""Module that holds all image operations. Since there's no state,
2013-01-08 14:11:59 -08:00
everything is a classmethod."""
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
@classmethod
def resize_image(cls, image_body, size, fit_to_size=False):
"""Takes a raw image (in image_body) and resizes it to fit given
2024-04-24 09:43:56 -04:00
dimensions. Returns a file-like object in the form of a StringIO.
This must happen in this function because PIL is transforming the
2013-01-08 14:11:59 -08:00
original as it works."""
2024-04-24 09:43:56 -04:00
2020-07-01 11:57:51 -04:00
image_file = BytesIO(image_body)
2013-01-08 14:11:59 -08:00
try:
image = Image.open(image_file)
except IOError:
# Invalid image file
return False
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
# Get the image format early, as we lose it after perform a `thumbnail` or `fit`.
format = image.format
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
# Check for rotation
image = cls.adjust_image_orientation(image)
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
if not fit_to_size:
image.thumbnail(PROFILE_PICTURE_SIZES[size], Image.ANTIALIAS)
else:
2024-04-24 09:43:56 -04:00
image = PILOps.fit(
image, PROFILE_PICTURE_SIZES[size], method=Image.ANTIALIAS, centering=(0.5, 0.5)
)
2020-07-01 11:57:51 -04:00
output = BytesIO()
2024-04-24 09:43:56 -04:00
if format.lower() == "jpg":
format = "jpeg"
2013-01-08 14:11:59 -08:00
image.save(output, format=format, quality=95)
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
return output
2024-04-24 09:43:56 -04:00
2013-01-08 14:11:59 -08:00
@classmethod
def adjust_image_orientation(cls, image):
"""Since the iPhone will store an image on its side but with EXIF
data stating that it should be rotated, we need to find that
EXIF data and correctly rotate the image before storage."""
2024-04-24 09:43:56 -04:00
if hasattr(image, "_getexif"):
2013-01-08 14:11:59 -08:00
exif = image._getexif()
if exif:
2020-06-19 02:27:48 -04:00
for tag, value in list(exif.items()):
2013-01-08 14:11:59 -08:00
decoded = TAGS.get(tag, tag)
2024-04-24 09:43:56 -04:00
if decoded == "Orientation":
2013-01-08 14:11:59 -08:00
if value == 6:
image = image.rotate(-90)
if value == 8:
image = image.rotate(90)
if value == 3:
image = image.rotate(180)
break
return image
2024-04-24 09:43:56 -04:00
@classmethod
def image_size(cls, url, headers=None):
2024-04-24 09:43:56 -04:00
if not headers:
headers = {}
req = urllib.request.Request(url, data=None, headers=headers)
file = urllib.request.urlopen(req)
size = file.headers.get("content-length")
2024-04-24 09:43:56 -04:00
if size:
size = int(size)
p = ImageFile.Parser()
while True:
data = file.read(1024)
if not data:
break
p.feed(data)
if p.image:
return p.image.size
break
file.close()
return None, None