diff --git a/sat/tools/image.py b/sat/tools/image.py index eced46fc..01340b91 100644 --- a/sat/tools/image.py +++ b/sat/tools/image.py @@ -20,7 +20,7 @@ import tempfile import mimetypes -from PIL import Image +from PIL import Image, ImageOps from pathlib import Path from twisted.internet import threads from sat.core.i18n import _ @@ -69,10 +69,12 @@ def check(host, path, max_size=None): return report -def _resizeBlocking(image_path, new_size, dest): +def _resize_blocking(image_path, new_size, dest, fix_orientation): im_path = Path(image_path) im = Image.open(im_path) resized = im.resize(new_size, Image.LANCZOS) + if fix_orientation: + resized = ImageOps.fix_orientation(resized) if dest is None: dest = tempfile.NamedTemporaryFile(suffix=im_path.suffix, delete=False) @@ -85,7 +87,7 @@ def _resizeBlocking(image_path, new_size, dest): return Path(f.name) -def resize(image_path, new_size, dest=None): +def resize(image_path, new_size, dest=None, fix_orientation=True): """Resize an image to a new file, and return its path @param image_path(str, Path): path of the original image @@ -95,13 +97,15 @@ def resize(image_path, new_size, dest=None): file will be converted to PNG - Path: path to the file to create/overwrite - file: a file object which must be opened for writing in binary mode + @param fix_orientation: if True, use EXIF data to set orientation @return (Path): path of the resized file. The image at this path should be deleted after use """ - return threads.deferToThread(_resizeBlocking, image_path, new_size, dest) + return threads.deferToThread( + _resize_blocking, image_path, new_size, dest, fix_orientation) -def _convertBlocking(image_path, dest, extra): +def _convert_blocking(image_path, dest, extra): media_type = mimetypes.guess_type(str(image_path), strict=False)[0] if dest is None: @@ -173,7 +177,30 @@ def convert(image_path, dest=None, extra=None): raise ValueError(f"There is no file at {image_path}!") if extra is None: extra = {} - return threads.deferToThread(_convertBlocking, image_path, dest, extra) + return threads.deferToThread(_convert_blocking, image_path, dest, extra) + + +def __fix_orientation_blocking(image_path): + im = Image.open(image_path) + im_format = im.format + exif = im.getexif() + orientation = exif.get(0x0112) + if orientation is None or orientation<2: + # nothing to do + return False + im = ImageOps.exif_transpose(im) + im.save(image_path, im_format) + log.debug(f"image {image_path} orientation has been fixed") + return True + + +def fix_orientation(image_path: Path) -> bool: + """Apply orientation found in EXIF data if any + + @param image_path: image location, image will be modified in place + @return True if image has been modified + """ + return threads.deferToThread(__fix_orientation_blocking, image_path) def guess_type(source):